diff --git a/internal/modules/production/transfer_layings/module.go b/internal/modules/production/transfer_layings/module.go index 2068ccd7..dfe2ad44 100644 --- a/internal/modules/production/transfer_layings/module.go +++ b/internal/modules/production/transfer_layings/module.go @@ -26,6 +26,8 @@ type TransferLayingModule struct{} func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { transferLayingRepo := rTransferLaying.NewTransferLayingRepository(db) + layingTransferSourceRepo := rTransferLaying.NewLayingTransferSourceRepository(db) + layingTransferTargetRepo := rTransferLaying.NewLayingTransferTargetRepository(db) userRepo := rUser.NewUserRepository(db) projectFlockRepo := rProjectFlock.NewProjectflockRepository(db) projectFlockKandangRepo := rProjectFlock.NewProjectFlockKandangRepository(db) @@ -80,6 +82,8 @@ func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, val transferLayingService := sTransferLaying.NewTransferLayingService( transferLayingRepo, + layingTransferSourceRepo, + layingTransferTargetRepo, projectFlockRepo, projectFlockKandangRepo, projectFlockPopulationRepo, diff --git a/internal/modules/production/transfer_layings/services/transfer_laying.service.go b/internal/modules/production/transfer_layings/services/transfer_laying.service.go index e62d4722..9732ad75 100644 --- a/internal/modules/production/transfer_layings/services/transfer_laying.service.go +++ b/internal/modules/production/transfer_layings/services/transfer_laying.service.go @@ -41,6 +41,8 @@ type transferLayingService struct { Log *logrus.Logger Validate *validator.Validate Repository repository.TransferLayingRepository + LayingTransferSourceRepo repository.LayingTransferSourceRepository + LayingTransferTargetRepo repository.LayingTransferTargetRepository ProjectFlockRepo ProjectFlockRepository.ProjectflockRepository ProjectFlockKandangRepo ProjectFlockRepository.ProjectFlockKandangRepository ProjectFlockPopulationRepo ProjectFlockRepository.ProjectFlockPopulationRepository @@ -53,6 +55,8 @@ type transferLayingService struct { func NewTransferLayingService( repo repository.TransferLayingRepository, + layingTransferSourceRepo repository.LayingTransferSourceRepository, + layingTransferTargetRepo repository.LayingTransferTargetRepository, projectFlockRepo ProjectFlockRepository.ProjectflockRepository, projectFlockKandangRepo ProjectFlockRepository.ProjectFlockKandangRepository, projectFlockPopulationRepo ProjectFlockRepository.ProjectFlockPopulationRepository, @@ -66,6 +70,8 @@ func NewTransferLayingService( Log: utils.Log, Validate: validate, Repository: repo, + LayingTransferSourceRepo: layingTransferSourceRepo, + LayingTransferTargetRepo: layingTransferTargetRepo, ProjectFlockRepo: projectFlockRepo, ProjectFlockKandangRepo: projectFlockKandangRepo, ProjectFlockPopulationRepo: projectFlockPopulationRepo, @@ -262,7 +268,11 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create) } err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { + repoTx := s.Repository.WithTx(dbTransaction) + sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction) + targetRepoTx := repository.NewLayingTransferTargetRepository(dbTransaction) + pwRepoTx := rInventory.NewProductWarehouseRepository(dbTransaction) if err := repoTx.CreateOne(c.Context(), createBody, nil); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat record transfer laying") @@ -278,7 +288,7 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create) PendingUsageQty: 0, // Di-set 0, biarkan FIFO Consume yang handle saat Approval ProductWarehouseId: &productWarehouseId, } - if err := dbTransaction.Create(&source).Error; err != nil { + if err := sourceRepoTx.CreateOne(c.Context(), &source, nil); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat sumber transfer") } @@ -304,8 +314,8 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create) for _, sourceDetail := range req.SourceKandangs { if pwID, ok := sourceWarehouseMap[sourceDetail.ProjectFlockKandangId]; ok { // Get product warehouse untuk ambil product ID - var sourcePW entity.ProductWarehouse - if err := dbTransaction.First(&sourcePW, pwID).Error; err == nil { + sourcePW, err := pwRepoTx.GetByID(c.Context(), pwID, nil) + if err == nil { sourceProductID = sourcePW.ProductId break } @@ -317,22 +327,19 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create) } // Cari product warehouse di target berdasarkan: warehouse + project_flock_kandang + PRODUCT - var targetPW entity.ProductWarehouse - err = dbTransaction.Where("warehouse_id = ? AND project_flock_kandang_id = ? AND product_id = ?", - targetWarehouse.Id, targetDetail.ProjectFlockKandangId, sourceProductID). - First(&targetPW).Error + targetPW, err := pwRepoTx.FindByProductWarehouseAndPfk(c.Context(), sourceProductID, targetWarehouse.Id, &targetDetail.ProjectFlockKandangId) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - // Create baru dengan product yang sama dengan source - targetPW = entity.ProductWarehouse{ + newTargetPW := entity.ProductWarehouse{ ProductId: sourceProductID, WarehouseId: targetWarehouse.Id, ProjectFlockKandangId: &targetDetail.ProjectFlockKandangId, Quantity: 0, } - if err := dbTransaction.Create(&targetPW).Error; err != nil { + if err := pwRepoTx.CreateOne(c.Context(), &newTargetPW, nil); err != nil { return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal membuat product warehouse untuk kandang tujuan %d: %v", targetDetail.ProjectFlockKandangId, err)) } + targetPW = &newTargetPW } else { return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mendapatkan product warehouse untuk kandang tujuan %d: %v", targetDetail.ProjectFlockKandangId, err)) } @@ -345,7 +352,7 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create) TotalUsed: 0, ProductWarehouseId: &targetPW.Id, } - if err := dbTransaction.Create(&target).Error; err != nil { + if err := targetRepoTx.CreateOne(c.Context(), &target, nil); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat target transfer") } } @@ -407,16 +414,18 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { repoTx := s.Repository.WithTx(dbTransaction) + sourceRepo := s.LayingTransferSourceRepo.WithTx(dbTransaction) + targetRepo := s.LayingTransferTargetRepo.WithTx(dbTransaction) // Hapus old sources dan targets for _, oldSource := range existingTransfer.Sources { - if err := dbTransaction.Delete(&oldSource).Error; err != nil { + if err := sourceRepo.DeleteOne(c.Context(), oldSource.Id); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete old source") } } for _, oldTarget := range existingTransfer.Targets { - if err := dbTransaction.Delete(&oldTarget).Error; err != nil { + if err := targetRepo.DeleteOne(c.Context(), oldTarget.Id); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete old target") } } @@ -458,11 +467,13 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, PendingUsageQty: sourceDetail.Quantity, ProductWarehouseId: &productWarehouseId, } - if err := dbTransaction.Create(&source).Error; err != nil { + if err := sourceRepo.CreateOne(c.Context(), &source, nil); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer source") } } + pwRepo := rInventory.NewProductWarehouseRepository(dbTransaction) + for _, targetDetail := range req.TargetKandangs { targetprojectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetDetail.ProjectFlockKandangId) if err != nil { @@ -483,8 +494,8 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, firstSourceKandangID := req.SourceKandangs[0].ProjectFlockKandangId populations, err := s.ProjectFlockPopulationRepo.GetByProjectFlockKandangID(c.Context(), firstSourceKandangID) if err == nil && len(populations) > 0 && populations[0].ProductWarehouseId > 0 { - var sourcePW entity.ProductWarehouse - if err := dbTransaction.First(&sourcePW, populations[0].ProductWarehouseId).Error; err == nil { + sourcePW, err := pwRepo.GetByID(c.Context(), populations[0].ProductWarehouseId, nil) + if err == nil { sourceProductID = sourcePW.ProductId } } @@ -494,23 +505,20 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, return fiber.NewError(fiber.StatusInternalServerError, "Failed to get product from source warehouse") } - // Cari product warehouse di target berdasarkan: warehouse + project_flock_kandang + PRODUCT - var targetPW entity.ProductWarehouse - err = dbTransaction.Where("warehouse_id = ? AND project_flock_kandang_id = ? AND product_id = ?", - targetWarehouse.Id, targetDetail.ProjectFlockKandangId, sourceProductID). - First(&targetPW).Error + targetPW, err := pwRepo.FindByProductWarehouseAndPfk(c.Context(), sourceProductID, targetWarehouse.Id, &targetDetail.ProjectFlockKandangId) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - // Create baru dengan product yang sama dengan source - targetPW = entity.ProductWarehouse{ + + newTargetPW := entity.ProductWarehouse{ ProductId: sourceProductID, WarehouseId: targetWarehouse.Id, ProjectFlockKandangId: &targetDetail.ProjectFlockKandangId, Quantity: 0, } - if err := dbTransaction.Create(&targetPW).Error; err != nil { + if err := pwRepo.CreateOne(c.Context(), &newTargetPW, nil); err != nil { return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to create product warehouse for target kandang %d: %v", targetDetail.ProjectFlockKandangId, err)) } + targetPW = &newTargetPW } else { return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to get product warehouse for target kandang %d: %v", targetDetail.ProjectFlockKandangId, err)) } @@ -523,7 +531,7 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, TotalUsed: 0, ProductWarehouseId: &targetPW.Id, } - if err := dbTransaction.Create(&target).Error; err != nil { + if err := targetRepo.CreateOne(c.Context(), &target, nil); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer target") } } @@ -551,6 +559,7 @@ func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error { } approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB()) + latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), id, nil) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status") @@ -565,8 +574,6 @@ func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error { err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { repoTx := s.Repository.WithTx(dbTransaction) - // Delete transfer - cascade akan menghapus sources dan targets - // FIFO akan menangani stock allocation cleanup via foreign key constraints if err := repoTx.DeleteOne(c.Context(), id); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete transfer laying") } @@ -618,6 +625,9 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) ( err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { repoTx := s.Repository.WithTx(dbTransaction) approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction)) + + // Gunakan repo baru untuk transaction scope agar bisa akses method custom + sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction) targetRepoTx := repository.NewLayingTransferTargetRepository(dbTransaction) for _, approvableID := range approvableIDs { @@ -643,7 +653,7 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) ( if action == entity.ApprovalActionApproved { - sources, err := repository.NewLayingTransferSourceRepository(dbTransaction).GetByLayingTransferId(c.Context(), approvableID) + sources, err := sourceRepoTx.GetByLayingTransferId(c.Context(), approvableID) if err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil sources transfer") } @@ -677,18 +687,14 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) ( return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal consume FIFO stock: %v", err)) } - // Update source usage tracking - if err := dbTransaction.Model(&entity.LayingTransferSource{}). - Where("id = ?", source.Id). - Updates(map[string]interface{}{ - "usage_qty": source.UsageQty + consumeResult.UsageQuantity, - "pending_usage_qty": consumeResult.PendingQuantity, - }).Error; err != nil { + if err := sourceRepoTx.PatchOne(c.Context(), source.Id, map[string]interface{}{ + "usage_qty": source.UsageQty + consumeResult.UsageQuantity, + "pending_usage_qty": consumeResult.PendingQuantity, + }, nil); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Gagal update source usage qty") } } - // Replenish ke target warehouse for _, target := range targets { if target.ProductWarehouseId == nil { return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Target product warehouse tidak ditemukan untuk transfer %d", approvableID)) @@ -707,9 +713,9 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) ( return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal replenish stock ke target warehouse: %v", err)) } - if err := dbTransaction.Model(&entity.LayingTransferTarget{}). - Where("id = ?", target.Id). - Update("total_qty", replenishResult.AddedQuantity).Error; err != nil { + if err := targetRepoTx.PatchOne(c.Context(), target.Id, map[string]interface{}{ + "total_qty": replenishResult.AddedQuantity, + }, nil); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Gagal update target total qty") } } @@ -777,9 +783,8 @@ func (s *transferLayingService) getOrCreateProductWarehouse(ctx context.Context, newWarehouse := &entity.ProductWarehouse{ ProductId: productID, WarehouseId: warehouseID, - ProjectFlockKandangId: projectFlockKandangId, // Set flock ID agar bisa di-chickin di target flock + ProjectFlockKandangId: projectFlockKandangId, Quantity: quantity, - // CreatedBy: actorID, } if err := productWarehouseRepoTx.CreateOne(ctx, newWarehouse, nil); err != nil { @@ -830,7 +835,7 @@ func (s *transferLayingService) validateKandangOwnership( ) error { for _, kandangID := range kandangIDs { - // validasi terlebih dahulu apakah kandangnya itu ada atau gak + projectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(ctx, kandangID) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) {