From 1c99093ff8f7ecd29a501e449c64933ffd3d736f Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Thu, 6 Nov 2025 10:31:35 +0700 Subject: [PATCH] feat[BE-127]; creating correct logic update and delete transfer laying --- .../services/transfer_laying.service.go | 172 ++++++++++++++++-- .../validations/transfer_laying.validation.go | 8 +- 2 files changed, 161 insertions(+), 19 deletions(-) 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 92965de0..06c297bc 100644 --- a/internal/modules/production/transfer_layings/services/transfer_laying.service.go +++ b/internal/modules/production/transfer_layings/services/transfer_laying.service.go @@ -361,7 +361,9 @@ func (s transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, i return nil, err } - _, err := s.Repository.GetByID(c.Context(), id, nil) + existingTransfer, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB { + return db.Preload("Sources.ProductWarehouse").Preload("Targets") + }) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "TransferLaying not found") @@ -382,26 +384,140 @@ func (s transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, i } } - updateBody := make(map[string]any) - - if req.TransferDate != nil { - updateBody["transfer_date"] = *req.TransferDate + if _, err := s.ProjectFlockRepo.GetByID(c.Context(), req.SourceProjectFlockId, nil); err != nil { + return nil, fiber.NewError(fiber.StatusBadRequest, "Source project flock not found") } - if req.Reason != nil { - updateBody["notes"] = *req.Reason + if _, err := s.ProjectFlockRepo.GetByID(c.Context(), req.TargetProjectFlockId, nil); err != nil { + return nil, fiber.NewError(fiber.StatusBadRequest, "Target project flock not found") } - if len(updateBody) == 0 { - return s.GetOne(c, id) + transferDate, err := time.Parse("2006-01-02", req.TransferDate) + if err != nil { + return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid transfer date format") } - if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, fiber.NewError(fiber.StatusNotFound, "TransferLaying not found") + err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { + projectFlockPopulationRepoTx := s.ProjectFlockPopulationRepo.WithTx(dbTransaction) + productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(dbTransaction) + + for _, oldSource := range existingTransfer.Sources { + if oldSource.ProductWarehouseId != nil && oldSource.Qty > 0 { + + if err := productWarehouseRepoTx.PatchOne(c.Context(), *oldSource.ProductWarehouseId, map[string]any{ + "quantity": gorm.Expr("quantity + ?", oldSource.Qty), + }, nil); err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to restore warehouse quantity") + } + + if err := s.restoreProjectFlockPopulation(c.Context(), projectFlockPopulationRepoTx, oldSource.SourceProjectFlockKandangId, oldSource.Qty); err != nil { + return err + } + } } - s.Log.Errorf("Failed to update transferLaying: %+v", err) - return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update transfer laying") + + for _, oldSource := range existingTransfer.Sources { + if err := dbTransaction.Delete(&oldSource).Error; 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 { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete old target") + } + } + + totalSourceQty := 0.0 + for _, source := range req.SourceKandangs { + totalSourceQty += source.Quantity + } + + if err := s.Repository.WithTx(dbTransaction).PatchOne(c.Context(), id, map[string]any{ + "transfer_date": transferDate, + "notes": req.Reason, + "pending_usage_qty": &totalSourceQty, + }, nil); err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transfer header") + } + + sourceWarehouseMap := make(map[uint]uint) + for _, sourceDetail := range req.SourceKandangs { + + populations, err := projectFlockPopulationRepoTx.GetByProjectFlockKandangID(c.Context(), sourceDetail.ProjectFlockKandangId) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get populations") + } + + if len(populations) == 0 { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d has no population available", sourceDetail.ProjectFlockKandangId)) + } + + var totalPopulation float64 + var productWarehouseId uint + + for _, pop := range populations { + totalPopulation += pop.TotalQty + if pop.ProductWarehouseId > 0 { + productWarehouseId = pop.ProductWarehouseId + } + } + + if totalPopulation < sourceDetail.Quantity { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d has insufficient quantity. Available: %.0f, Requested: %.0f", sourceDetail.ProjectFlockKandangId, totalPopulation, sourceDetail.Quantity)) + } + + sourceWarehouseMap[sourceDetail.ProjectFlockKandangId] = productWarehouseId + + source := entity.LayingTransferSource{ + LayingTransferId: id, + SourceProjectFlockKandangId: sourceDetail.ProjectFlockKandangId, + Qty: sourceDetail.Quantity, + ProductWarehouseId: &productWarehouseId, + } + if err := dbTransaction.Create(&source).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer source") + } + + if err := s.reduceProjectFlockPopulation(c.Context(), projectFlockPopulationRepoTx, sourceDetail.ProjectFlockKandangId, sourceDetail.Quantity); err != nil { + return err + } + + if err := productWarehouseRepoTx.PatchOne(c.Context(), productWarehouseId, map[string]any{"quantity": gorm.Expr("quantity - ?", sourceDetail.Quantity)}, nil); err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to update source warehouse quantity") + } + } + + for _, targetDetail := range req.TargetKandangs { + targetPFK, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetDetail.ProjectFlockKandangId) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target project flock kandang") + } + + targetWarehouse, err := s.WarehouseRepo.GetLatestByKandangID(c.Context(), targetPFK.KandangId) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No warehouse found for target kandang %d", targetDetail.ProjectFlockKandangId)) + } + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target warehouse") + } + + target := entity.LayingTransferTarget{ + LayingTransferId: id, + TargetProjectFlockKandangId: targetDetail.ProjectFlockKandangId, + Qty: targetDetail.Quantity, + ProductWarehouseId: &targetWarehouse.Id, + } + if err := dbTransaction.Create(&target).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer target") + } + } + + return nil + }) + + if err != nil { + return nil, err } return s.GetOne(c, id) @@ -464,9 +580,8 @@ func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error { for i := len(populations) - 1; i >= 0 && remainingToRestore > 0; i-- { pop := populations[i] restoreAmount := remainingToRestore - if remainingToRestore < pop.TotalQty { - - restoreAmount = remainingToRestore + if pop.TotalQty < remainingToRestore { + restoreAmount = pop.TotalQty } newQty := pop.TotalQty + restoreAmount @@ -725,3 +840,26 @@ func (s *transferLayingService) reduceProjectFlockPopulation(ctx context.Context return nil } + +func (s *transferLayingService) restoreProjectFlockPopulation(ctx context.Context, populationRepo ProjectFlockRepository.ProjectFlockPopulationRepository, projectFlockKandangID uint, quantityToRestore float64) error { + populations, err := populationRepo.GetByProjectFlockKandangID(ctx, projectFlockKandangID) + if err != nil { + return err + } + + if len(populations) == 0 { + return fiber.NewError(fiber.StatusBadRequest, "No populations found for restoration") + } + + // Restore in LIFO order (from newest to oldest) + // Add all quantity back to the last (newest) population + if len(populations) > 0 { + lastPop := populations[len(populations)-1] + newQty := lastPop.TotalQty + quantityToRestore + if err := populationRepo.PatchOne(ctx, lastPop.Id, map[string]any{"total_qty": newQty}, nil); err != nil { + return err + } + } + + return nil +} diff --git a/internal/modules/production/transfer_layings/validations/transfer_laying.validation.go b/internal/modules/production/transfer_layings/validations/transfer_laying.validation.go index e35ba4f3..bd117bd9 100644 --- a/internal/modules/production/transfer_layings/validations/transfer_laying.validation.go +++ b/internal/modules/production/transfer_layings/validations/transfer_laying.validation.go @@ -20,8 +20,12 @@ type Create struct { } type Update struct { - TransferDate *string `json:"transfer_date,omitempty" validate:"omitempty,datetime=2006-01-02"` - Reason *string `json:"reason,omitempty" validate:"omitempty,max=1000"` + TransferDate string `json:"transfer_date" validate:"required,datetime=2006-01-02"` + SourceProjectFlockId uint `json:"source_project_flock_id" validate:"required"` + TargetProjectFlockId uint `json:"target_project_flock_id" validate:"required"` + SourceKandangs []SourceKandangDetail `json:"source_kandangs" validate:"required,min=1,dive,required"` + TargetKandangs []TargetKandangDetail `json:"target_kandangs" validate:"required,min=1,dive,required"` + Reason string `json:"reason" validate:"omitempty,max=1000"` } type Query struct {