package service import ( "context" "errors" "github.com/Caknoooo/go-gin-clean-starter/database/entities" dtodomain "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_receipt/dto" "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_receipt/query" "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_receipt/repository" invstoragerepository "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_storage/repository" productrepository "github.com/Caknoooo/go-gin-clean-starter/modules/product/repository" uomrepository "github.com/Caknoooo/go-gin-clean-starter/modules/uom/repository" "github.com/Caknoooo/go-gin-clean-starter/pkg/constants" pkgdto "github.com/Caknoooo/go-gin-clean-starter/pkg/dto" "github.com/Caknoooo/go-gin-clean-starter/pkg/utils" "github.com/google/uuid" "gorm.io/gorm" ) type InventoryReceiptService interface { Create(ctx context.Context, req dtodomain.InventoryReceiptCreateRequest) (dtodomain.InventoryReceiptResponse, error) GetById(ctx context.Context, id string) (dtodomain.InventoryReceiptResponse, error) GetAll(ctx context.Context, filter query.InventoryReceiptFilter) ([]dtodomain.InventoryReceiptResponse, int64, error) Update(ctx context.Context, req dtodomain.InventoryReceiptUpdateRequest, id string) (dtodomain.InventoryReceiptResponse, error) Delete(ctx context.Context, id string) error GetLinesByReceiptId(ctx context.Context, id string) ([]dtodomain.InventoryReceiptLineResponse, error) CreateLine(ctx context.Context, receiptId string, req dtodomain.InventoryReceiptLineCreateRequest) (dtodomain.InventoryReceiptLineResponse, error) UpdateLine(ctx context.Context, lineId string, req dtodomain.InventoryReceiptLineUpdateRequest) (dtodomain.InventoryReceiptLineResponse, error) DeleteLine(ctx context.Context, lineId string) error OnComplete(ctx context.Context, id string) (dtodomain.InventoryReceiptResponse, error) } type inventoryReceiptService struct { db *gorm.DB receiptRepo repository.InventoryReceiptRepository receiptLineRepo repository.InventoryReceiptLineRepository productRepo productrepository.ProductRepository uomRepo uomrepository.UomRepository invStorageRepository invstoragerepository.InventoryStorageRepository } func (s *inventoryReceiptService) GetLinesByReceiptId(ctx context.Context, id string) ([]dtodomain.InventoryReceiptLineResponse, error) { lines, err := s.receiptLineRepo.GetAllByReceiptId(ctx, id) if err != nil { return nil, err } var responses []dtodomain.InventoryReceiptLineResponse for _, line := range lines { responses = append(responses, dtodomain.ToInventoryReceiptLineResponse(line)) } return responses, nil } func (s *inventoryReceiptService) OnComplete(ctx context.Context, id string) (dtodomain.InventoryReceiptResponse, error) { tx := s.db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() receipt, err := s.receiptRepo.GetById(ctx, tx, id) if err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } receipt.Status = constants.COMPLETED for _, line := range receipt.ReceiptLines { product, err := s.productRepo.GetById(ctx, tx, line.ProductID.String()) if err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } existing, err := s.invStorageRepository.GetLatestByProductAndClient(ctx, tx, product.ID.String(), receipt.ClientID.String()) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } if existing.ID != uuid.Nil { existing.OnHandQuantity += line.Quantity existing.AvailableQuantity += line.Quantity _, err = s.invStorageRepository.Update(ctx, tx, existing) if err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } } else { newStorage := entities.InventoryStorageEntity{ ProductID: line.ProductID, UomID: *product.UomID, OnHandQuantity: line.Quantity, AvailableQuantity: line.Quantity, InvReceiptID: receipt.ID, ClientID: receipt.ClientID, } _, err = s.invStorageRepository.Create(ctx, tx, newStorage) if err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } } } if _, err := s.receiptRepo.Update(ctx, tx, receipt); err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } tx.Commit() return toInventoryReceiptResponse(receipt), nil } // DeleteLine implements InventoryReceiptService. func (s *inventoryReceiptService) DeleteLine(ctx context.Context, lineId string) error { return s.receiptLineRepo.Delete(ctx, nil, lineId) } // UpdateLine implements InventoryReceiptService. func (s *inventoryReceiptService) UpdateLine(ctx context.Context, lineId string, req dtodomain.InventoryReceiptLineUpdateRequest) (dtodomain.InventoryReceiptLineResponse, error) { line, err := s.receiptLineRepo.GetById(ctx, nil, lineId) if err != nil { return dtodomain.InventoryReceiptLineResponse{}, err } if req.Quantity != nil { line.Quantity = *req.Quantity } if req.BatchNumber != nil { line.BatchNumber = *req.BatchNumber } if req.RepackingSuggestion != nil { line.RepackingSuggestion = *req.RepackingSuggestion } if req.RepackUomID != nil { if *req.RepackUomID != "" { tmp, err := uuid.Parse(*req.RepackUomID) if err != nil { return dtodomain.InventoryReceiptLineResponse{}, err } line.RepackUomID = &tmp } else { line.RepackUomID = nil } } if req.ProductID != nil { if *req.ProductID != "" { tmp, err := uuid.Parse(*req.ProductID) if err != nil { return dtodomain.InventoryReceiptLineResponse{}, err } line.ProductID = tmp } else { line.ProductID = uuid.Nil } } updated, err := s.receiptLineRepo.Update(ctx, nil, line) if err != nil { return dtodomain.InventoryReceiptLineResponse{}, err } var repackUomID *string if updated.RepackUomID != nil { tmp := updated.RepackUomID.String() repackUomID = &tmp } product := dtodomain.InventoryReceiptLineProductResponse{} if updated.Product.ID != uuid.Nil { product = dtodomain.InventoryReceiptLineProductResponse{ ID: updated.Product.ID.String(), Name: updated.Product.Name, } } return dtodomain.InventoryReceiptLineResponse{ ID: updated.ID.String(), Quantity: updated.Quantity, BatchNumber: updated.BatchNumber, RepackingSuggestion: updated.RepackingSuggestion, RepackUomID: repackUomID, Product: product, ClientID: updated.ClientID.String(), }, nil } func toAssignmentResponse(e entities.TAssignmentEntity) dtodomain.AssignmentResponse { // client := pkgdto.IdNameResponse{} // if e.Client.ID != uuid.Nil { // client = pkgdto.IdNameResponse{ // ID: e.Client.ID.String(), // Name: e.Client.Name, // } // } users := make([]dtodomain.AssignmentUserResponse, 0) for _, user := range e.AssignmentUsers { userResp := dtodomain.AssignmentUserResponse{ ID: user.ID.String(), TaskType: user.TaskType, User: pkgdto.IdNameResponse{ID: user.User.ID.String(), Name: user.User.Name}, Role: pkgdto.IdNameResponse{ID: user.Role.ID.String(), Name: user.Role.Name}, // Client: pkgdto.IdNameResponse{ID: user.Client.ID.String(), Name: user.Client.Name}, } users = append(users, userResp) } return dtodomain.AssignmentResponse{ ID: e.ID.String(), DocumentType: e.DocumentType, DocumentID: e.DocumentID.String(), // Client: client, AssignmentUsers: users, } } func toInventoryReceiptResponse(e entities.TInventoryReceiptEntity) dtodomain.InventoryReceiptResponse { client := pkgdto.IdNameResponse{} if e.Client.ID != uuid.Nil { client = pkgdto.IdNameResponse{ ID: e.Client.ID.String(), Name: e.Client.Name, } } // lines := make([]dtodomain.InventoryReceiptLineResponse, 0) // for _, line := range e.ReceiptLines { // product := dtodomain.InventoryReceiptLineProductResponse{} // if line.Product.ID != uuid.Nil { // product = dtodomain.InventoryReceiptLineProductResponse{ // ID: line.Product.ID.String(), // Name: line.Product.Name, // RefNumber: line.Product.RefNumber, // Uom: pkgdto.IdNameResponse{ // ID: line.Product.Uom.ID.String(), // Name: line.Product.Uom.Name, // }, // DimLength: line.Product.DimLength, // DimWidth: line.Product.DimWidth, // DimHeight: line.Product.DimHeight, // DimUom: pkgdto.IdNameResponse{ // ID: line.Product.DimUom.ID.String(), // Name: line.Product.DimUom.Name, // }, // } // } // var repackUomID *string // if line.RepackUomID != nil { // tmp := line.RepackUomID.String() // repackUomID = &tmp // } else { // repackUomID = nil // } // lines = append(lines, dtodomain.InventoryReceiptLineResponse{ // ID: line.ID.String(), // Quantity: line.Quantity, // BatchNumber: line.BatchNumber, // RepackingSuggestion: line.RepackingSuggestion, // RepackUomID: repackUomID, // Product: product, // ClientID: line.ClientID.String(), // }) // } var assignment *dtodomain.AssignmentResponse if e.Assignment.ID != uuid.Nil { assignmentObj := toAssignmentResponse(e.Assignment) assignment = &assignmentObj } else { assignment = nil } return dtodomain.InventoryReceiptResponse{ ID: e.ID.String(), ReferenceNumber: e.ReferenceNumber, DocumentNumber: e.DocumentNumber, Status: e.Status, DocumentDate: utils.DateTimeToString(e.DocumentDate), Source: e.Source, QrCodeFile: e.QrCodeFile, // ClientID: e.ClientID.String(), Client: client, // LineCount: len(lines), // ReceiptLines: lines, Assignment: assignment, } } func (s *inventoryReceiptService) Create(ctx context.Context, req dtodomain.InventoryReceiptCreateRequest) (dtodomain.InventoryReceiptResponse, error) { tx := s.db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() clientUUID, err := uuid.Parse(req.ClientID) if err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } docNum, err := entities.GenerateDocumentNumber(s.db, req.ClientID) if err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } receipt := entities.TInventoryReceiptEntity{ ReferenceNumber: req.ReferenceNumber, DocumentNumber: docNum, DocumentDate: utils.StringToDateTime(req.DocumentDate), Source: req.Source, QrCodeFile: req.QrCodeFile, ClientID: clientUUID, Status: req.Status, } created, err := s.receiptRepo.Create(ctx, tx, receipt) if err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } // Bulk create lines var lines []entities.TInventoryReceiptLineEntity // var invStorages []entities.InventoryStorageEntity for _, lineReq := range req.ReceiptLines { var productUUID uuid.UUID if lineReq.ProductID != "" { productUUID, err = uuid.Parse(lineReq.ProductID) if err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } } else if lineReq.ProductCode != "" { product, err := s.productRepo.GetByCode(ctx, tx, lineReq.ProductCode, req.ClientID) if err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } productUUID = product.ID } else { productUUID = uuid.Nil } var repackUomUUID *uuid.UUID if lineReq.RepackUomID != "" { tmp, err := uuid.Parse(lineReq.RepackUomID) if err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } repackUomUUID = &tmp } else if lineReq.RepackUomCode != "" { uom, err := s.uomRepo.GetByCode(ctx, tx, lineReq.RepackUomCode, req.ClientID) if err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } repackUomUUID = &uom.ID } else { repackUomUUID = nil } clientLineUUID, err := uuid.Parse(lineReq.ClientID) if err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } lines = append(lines, entities.TInventoryReceiptLineEntity{ Quantity: lineReq.Quantity, BatchNumber: lineReq.BatchNumber, RepackingSuggestion: lineReq.RepackingSuggestion, RepackUomID: repackUomUUID, InvReceiptID: created.ID, ProductID: productUUID, ClientID: clientLineUUID, }) // // Prepare inventory storage entity // product, err := s.productRepo.GetById(ctx, tx, productUUID.String()) // if err != nil { // tx.Rollback() // return dtodomain.InventoryReceiptResponse{}, err // } // invStorages = append(invStorages, entities.InventoryStorageEntity{ // ProductID: productUUID, // UomID: *product.UomID, // OnHandQuantity: 0, // AvailableQuantity: 0, // InvReceiptID: created.ID, // ClientID: clientUUID, // }) } if len(lines) > 0 { err = s.receiptLineRepo.BulkCreate(ctx, tx, lines) if err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } } // if len(invStorages) > 0 { // err = s.invStorageRepository.BulkCreate(ctx, tx, invStorages) // if err != nil { // tx.Rollback() // return dtodomain.InventoryReceiptResponse{}, err // } // } tx.Commit() result, err := s.receiptRepo.GetById(ctx, nil, created.ID.String()) if err != nil { return dtodomain.InventoryReceiptResponse{}, err } return toInventoryReceiptResponse(result), nil } func (s *inventoryReceiptService) GetById(ctx context.Context, id string) (dtodomain.InventoryReceiptResponse, error) { receipt, err := s.receiptRepo.GetById(ctx, nil, id) if err != nil { return dtodomain.InventoryReceiptResponse{}, err } return toInventoryReceiptResponse(receipt), nil } func (s *inventoryReceiptService) GetAll(ctx context.Context, filter query.InventoryReceiptFilter) ([]dtodomain.InventoryReceiptResponse, int64, error) { receipts, total, err := s.receiptRepo.GetAll(ctx, filter) if err != nil { return nil, 0, err } var responses []dtodomain.InventoryReceiptResponse for _, e := range receipts { responses = append(responses, toInventoryReceiptResponse(e)) } if responses == nil { responses = make([]dtodomain.InventoryReceiptResponse, 0) } return responses, total, nil } func (s *inventoryReceiptService) Update(ctx context.Context, req dtodomain.InventoryReceiptUpdateRequest, id string) (dtodomain.InventoryReceiptResponse, error) { tx := s.db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() receipt, err := s.receiptRepo.GetById(ctx, tx, id) if err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } if req.ReferenceNumber != "" { receipt.ReferenceNumber = req.ReferenceNumber } receipt.DocumentDate = utils.StringToDateTime(req.DocumentDate) receipt.Source = req.Source receipt.QrCodeFile = req.QrCodeFile receipt.Status = req.Status updated, err := s.receiptRepo.Update(ctx, tx, receipt) if err != nil { tx.Rollback() return dtodomain.InventoryReceiptResponse{}, err } tx.Commit() result, err := s.receiptRepo.GetById(ctx, nil, updated.ID.String()) if err != nil { return dtodomain.InventoryReceiptResponse{}, err } return toInventoryReceiptResponse(result), nil } func (s *inventoryReceiptService) Delete(ctx context.Context, id string) error { tx := s.db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() if err := s.receiptRepo.Delete(ctx, tx, id); err != nil { tx.Rollback() return err } tx.Commit() return nil } func (s *inventoryReceiptService) CreateLine(ctx context.Context, receiptId string, req dtodomain.InventoryReceiptLineCreateRequest) (dtodomain.InventoryReceiptLineResponse, error) { receiptUUID, err := uuid.Parse(receiptId) if err != nil { return dtodomain.InventoryReceiptLineResponse{}, err } var productUUID uuid.UUID if req.ProductID != "" { productUUID, err = uuid.Parse(req.ProductID) if err != nil { return dtodomain.InventoryReceiptLineResponse{}, err } } else if req.ProductCode != "" { product, err := s.productRepo.GetByCode(ctx, nil, req.ProductCode, req.ClientID) if err != nil { return dtodomain.InventoryReceiptLineResponse{}, err } productUUID = product.ID } else { productUUID = uuid.Nil } var repackUomUUID *uuid.UUID if req.RepackUomID != "" { tmp, err := uuid.Parse(req.RepackUomID) if err != nil { return dtodomain.InventoryReceiptLineResponse{}, err } repackUomUUID = &tmp } else if req.RepackUomCode != "" { uom, err := s.uomRepo.GetByCode(ctx, nil, req.RepackUomCode, req.ClientID) if err != nil { return dtodomain.InventoryReceiptLineResponse{}, err } repackUomUUID = &uom.ID } else { repackUomUUID = nil } clientLineUUID, err := uuid.Parse(req.ClientID) if err != nil { return dtodomain.InventoryReceiptLineResponse{}, err } line := entities.TInventoryReceiptLineEntity{ Quantity: req.Quantity, BatchNumber: req.BatchNumber, RepackingSuggestion: req.RepackingSuggestion, RepackUomID: repackUomUUID, InvReceiptID: receiptUUID, ProductID: productUUID, ClientID: clientLineUUID, } created, err := s.receiptLineRepo.Create(ctx, nil, line) if err != nil { return dtodomain.InventoryReceiptLineResponse{}, err } var repackUomID *string if created.RepackUomID != nil { tmp := created.RepackUomID.String() repackUomID = &tmp } product := dtodomain.InventoryReceiptLineProductResponse{} if created.Product.ID != uuid.Nil { product = dtodomain.InventoryReceiptLineProductResponse{ ID: created.Product.ID.String(), Name: created.Product.Name, } } return dtodomain.InventoryReceiptLineResponse{ ID: created.ID.String(), Quantity: created.Quantity, BatchNumber: created.BatchNumber, RepackingSuggestion: created.RepackingSuggestion, RepackUomID: repackUomID, Product: product, ClientID: created.ClientID.String(), }, nil } func NewInventoryReceiptService(db *gorm.DB, receiptRepo repository.InventoryReceiptRepository, receiptLineRepo repository.InventoryReceiptLineRepository, productRepo productrepository.ProductRepository, uomRepo uomrepository.UomRepository, invStorageRepository invstoragerepository.InventoryStorageRepository) InventoryReceiptService { return &inventoryReceiptService{ db: db, receiptRepo: receiptRepo, receiptLineRepo: receiptLineRepo, productRepo: productRepo, uomRepo: uomRepo, invStorageRepository: invStorageRepository, } }