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" sequenceservice "github.com/Caknoooo/go-gin-clean-starter/modules/sequence/service" 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" "github.com/sirupsen/logrus" "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) GetLineById(ctx context.Context, lineId 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 sequenceService sequenceservice.SequenceService log *logrus.Logger } // GetLineById implements InventoryReceiptService. func (s *inventoryReceiptService) GetLineById(ctx context.Context, lineId string) (dtodomain.InventoryReceiptLineResponse, error) { line, err := s.receiptLineRepo.GetById(ctx, nil, lineId) if err != nil { return dtodomain.InventoryReceiptLineResponse{}, err } return dtodomain.ToInventoryReceiptLineResponse(line), nil } 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 { line, err := s.receiptLineRepo.GetById(ctx, nil, lineId) if err != nil { return err } line.FullAuditTrail = utils.FillAuditTrail(ctx, constants.DELETE) _, err = s.receiptLineRepo.Update(ctx, nil, line) if err != nil { return err } result := s.receiptLineRepo.Delete(ctx, nil, lineId) if result != nil { return result } s.log.WithFields(logrus.Fields{ "user_id": utils.GetUserID(ctx), "action": "delete", "entity": "inventory_receipt_line", "entity_id": lineId, }).Info("Inventory Receipt Line deleted") return nil } // 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 } before := line 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 } } line.FullAuditTrail = utils.FillAuditTrail(ctx, constants.UPDATE) updated, err := s.receiptLineRepo.Update(ctx, nil, line) if err != nil { return dtodomain.InventoryReceiptLineResponse{}, err } changes := utils.GetChangedFields(before, updated) s.log.WithFields(logrus.Fields{ "user_id": utils.GetUserID(ctx), "action": "update", "entity": "inventory_receipt_line", "entity_id": lineId, "changes": changes, }).Info("Inventory Receipt Line updated") 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 := s.sequenceService.GenerateDocumentNumber(ctx, req.ClientID, "RCPT", pkgdto.SequenceConfig{ Prefix: "RCPT", EntityType: "INV_RECEIPT", Period: "", }) 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, FullAuditTrail: utils.FillAuditTrail(ctx, constants.CREATE), } 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() s.log.WithFields(logrus.Fields{ "user_id": utils.GetUserID(ctx), "action": "create", "entity": "inventory_receipt", "entity_id": created.ID.String(), }).Info("Inventory Receipt created") 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 } before := receipt 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 receipt.FullAuditTrail = utils.FillAuditTrail(ctx, constants.UPDATE) 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 } changes := utils.GetChangedFields(before, result) s.log.WithFields(logrus.Fields{ "user_id": utils.GetUserID(ctx), "action": "update", "entity": "inventory_receipt", "entity_id": id, "changes": changes, }).Info("Inventory Receipt updated") 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() } }() receipt, err := s.receiptRepo.GetById(ctx, tx, id) if err != nil { tx.Rollback() return err } receipt.FullAuditTrail = utils.FillAuditTrail(ctx, constants.DELETE) _, err = s.receiptRepo.Update(ctx, tx, receipt) if err != nil { tx.Rollback() return err } if err := s.receiptRepo.Delete(ctx, tx, id); err != nil { tx.Rollback() return err } tx.Commit() s.log.WithFields(logrus.Fields{ "user_id": utils.GetUserID(ctx), "action": "delete", "entity": "inventory_receipt", "entity_id": id, }).Info("Inventory Receipt deleted") 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, FullAuditTrail: utils.FillAuditTrail(ctx, constants.CREATE), } created, err := s.receiptLineRepo.Create(ctx, nil, line) if err != nil { return dtodomain.InventoryReceiptLineResponse{}, err } s.log.WithFields(logrus.Fields{ "user_id": utils.GetUserID(ctx), "action": "create", "entity": "inventory_receipt_line", "entity_id": created.ID.String(), }).Info("Inventory Receipt Line created") 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, sequenceService sequenceservice.SequenceService, log *logrus.Logger) InventoryReceiptService { return &inventoryReceiptService{ db: db, receiptRepo: receiptRepo, receiptLineRepo: receiptLineRepo, productRepo: productRepo, uomRepo: uomRepo, invStorageRepository: invStorageRepository, sequenceService: sequenceService, log: log, } }