FIX[BE]: fixing bug transfer to laying, delet biaya, nominal expesen e, chickin

This commit is contained in:
aguhh18
2026-01-07 09:27:39 +07:00
parent a08466a28e
commit 0a84e427c1
21 changed files with 432 additions and 126 deletions
@@ -2,6 +2,7 @@ package transfer_layings
import (
"fmt"
"strings"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
@@ -13,6 +14,7 @@ import (
rTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/repositories"
sTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/services"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
rInventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
@@ -31,6 +33,44 @@ func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, val
productWarehouseRepo := rInventory.NewProductWarehouseRepository(db)
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
if err := fifoService.RegisterUsable(fifo.UsableConfig{
Key: fifo.UsableKeyTransferToLaying,
Table: "laying_transfers",
Columns: fifo.UsableColumns{
ID: "id",
ProductWarehouseID: "product_warehouse_id",
UsageQuantity: "usage_qty",
PendingQuantity: "pending_usage_qty",
CreatedAt: "created_at",
},
}); err != nil {
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
panic(fmt.Sprintf("failed to register transfer to laying usable workflow: %v", err))
}
}
if err := fifoService.RegisterStockable(fifo.StockableConfig{
Key: fifo.StockableKeyTransferToLaying,
Table: "laying_transfers",
Columns: fifo.StockableColumns{
ID: "id",
ProductWarehouseID: "dest_product_warehouse_id",
TotalQuantity: "total_qty",
TotalUsedQuantity: "total_used",
CreatedAt: "created_at",
},
OrderBy: []string{"created_at ASC", "id ASC"},
}); err != nil {
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
panic(fmt.Sprintf("failed to register transfer to laying stockable workflow: %v", err))
}
}
approvalRepo := commonRepo.NewApprovalRepository(db)
approvalService := commonSvc.NewApprovalService(approvalRepo)
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowTransferToLaying, utils.TransferToLayingApprovalSteps); err != nil {
@@ -45,6 +85,7 @@ func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, val
productWarehouseRepo,
warehouseRepo,
approvalService,
fifoService,
validate,
)
userService := sUser.NewUserService(userRepo, validate)
@@ -17,6 +17,7 @@ import (
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/repositories"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/validations"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
@@ -45,6 +46,7 @@ type transferLayingService struct {
ProductWarehouseRepo rInventory.ProductWarehouseRepository
WarehouseRepo rWarehouse.WarehouseRepository
ApprovalService commonSvc.ApprovalService
FifoSvc commonSvc.FifoService
}
func NewTransferLayingService(
@@ -55,6 +57,7 @@ func NewTransferLayingService(
productWarehouseRepo rInventory.ProductWarehouseRepository,
warehouseRepo rWarehouse.WarehouseRepository,
approvalService commonSvc.ApprovalService,
fifoSvc commonSvc.FifoService,
validate *validator.Validate,
) TransferLayingService {
return &transferLayingService{
@@ -67,6 +70,7 @@ func NewTransferLayingService(
ProductWarehouseRepo: productWarehouseRepo,
WarehouseRepo: warehouseRepo,
ApprovalService: approvalService,
FifoSvc: fifoSvc,
}
}
@@ -268,15 +272,20 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
CreatedBy: actorID,
}
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
if len(sourceWarehouseMap) > 0 {
for _, pwID := range sourceWarehouseMap {
createBody.ProductWarehouseId = &pwID
break
}
}
if err := s.Repository.WithTx(dbTransaction).CreateOne(c.Context(), createBody, nil); err != nil {
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
repoTx := s.Repository.WithTx(dbTransaction)
if err := repoTx.CreateOne(c.Context(), createBody, nil); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer laying record")
}
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
projectFlockPopulationRepoTx := s.ProjectFlockPopulationRepo.WithTx(dbTransaction)
for _, sourceDetail := range req.SourceKandangs {
productWarehouseId := sourceWarehouseMap[sourceDetail.ProjectFlockKandangId]
@@ -290,13 +299,6 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer source")
}
if err := s.reduceProjectFlockPopulation(c.Context(), projectFlockPopulationRepoTx, sourceDetail.ProjectFlockKandangId, sourceDetail.Quantity); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to reduce project flock population")
}
if err := productWarehouseRepoTx.PatchOne(c.Context(), productWarehouseId, map[string]any{"qty": gorm.Expr("qty - ?", sourceDetail.Quantity)}, nil); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update source warehouse quantity")
}
}
for _, targetDetail := range req.TargetKandangs {
@@ -325,6 +327,22 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
}
}
// Set DestProductWarehouseID untuk STOCKABLE role (ambil dari target pertama)
if len(req.TargetKandangs) > 0 {
firstTargetPWID := req.TargetKandangs[0].ProjectFlockKandangId
// Cari ProductWarehouse untuk target kandang
targetWarehouse, _ := s.WarehouseRepo.GetLatestByKandangID(c.Context(), firstTargetPWID)
if targetWarehouse != nil {
// Query ProductWarehouse by warehouse and kandang
var targetPW entity.ProductWarehouse
err := dbTransaction.Where("warehouse_id = ? AND project_flock_kandang_id = ?", targetWarehouse.Id, firstTargetPWID).
First(&targetPW).Error
if err == nil {
createBody.DestProductWarehouseID = &targetPW.Id
}
}
}
if err := createApprovalTransferLaying(c.Context(), dbTransaction, createBody.Id, createBody.CreatedBy); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer approval")
}
@@ -339,7 +357,7 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
return s.GetOne(c, createBody.Id)
}
func (s transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.LayingTransfer, error) {
func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.LayingTransfer, error) {
if err := s.Validate.Struct(req); err != nil {
return nil, err
}
@@ -381,6 +399,7 @@ func (s transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, i
}
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
repoTx := s.Repository.WithTx(dbTransaction)
projectFlockPopulationRepoTx := s.ProjectFlockPopulationRepo.WithTx(dbTransaction)
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
@@ -416,7 +435,7 @@ func (s transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, i
totalSourceQty += source.Quantity
}
if err := s.Repository.WithTx(dbTransaction).PatchOne(c.Context(), id, map[string]any{
if err := repoTx.PatchOne(c.Context(), id, map[string]any{
"transfer_date": transferDate,
"notes": req.Reason,
"pending_usage_qty": &totalSourceQty,
@@ -531,8 +550,9 @@ 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)
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
projectFlockPopulationRepoTx := s.ProjectFlockPopulationRepo.WithTx(dbTransaction)
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
sources, err := sourceRepoTx.GetByLayingTransferId(c.Context(), id)
@@ -551,7 +571,6 @@ func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error {
}
}
projectFlockPopulationRepoTx := s.ProjectFlockPopulationRepo.WithTx(dbTransaction)
for _, source := range sources {
populations, err := projectFlockPopulationRepoTx.GetByProjectFlockKandangID(c.Context(), source.SourceProjectFlockKandangId)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
@@ -575,7 +594,7 @@ func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error {
}
}
if err := s.Repository.WithTx(dbTransaction).DeleteOne(c.Context(), id); err != nil {
if err := repoTx.DeleteOne(c.Context(), id); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete transfer laying")
}
@@ -624,14 +643,13 @@ 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))
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
targetRepoTx := repository.NewLayingTransferTargetRepository(dbTransaction)
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
for _, approvableID := range approvableIDs {
transfer, err := s.Repository.GetByID(c.Context(), approvableID, nil)
transfer, err := repoTx.GetByID(c.Context(), approvableID, nil)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("TransferLaying %d not found", approvableID))
@@ -664,44 +682,45 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
}
if len(sources) > 0 && len(targets) > 0 {
firstSource := sources[0]
if firstSource.ProductWarehouseId == nil {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Source product warehouse not found for transfer %d", approvableID))
}
sourceWarehouse, err := productWarehouseRepoTx.GetByID(c.Context(), *firstSource.ProductWarehouseId, nil)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get source warehouse")
}
for _, target := range targets {
targetPFK, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), target.TargetProjectFlockKandangId)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
continue
}
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target project flock kandang")
for _, source := range sources {
if source.ProductWarehouseId == nil {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Source product warehouse not found for transfer %d", approvableID))
}
targetWarehouse, err := s.WarehouseRepo.GetLatestByKandangID(c.Context(), targetPFK.KandangId)
_, err := s.FifoSvc.Consume(c.Context(), commonSvc.StockConsumeRequest{
UsableKey: fifo.UsableKeyTransferToLaying,
UsableID: approvableID,
ProductWarehouseID: *source.ProductWarehouseId,
Quantity: source.Qty,
AllowPending: false,
Tx: dbTransaction,
})
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
continue
}
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target warehouse")
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to consume FIFO stock for source %d: %v", source.ProductWarehouseId, err))
}
}
if transfer.DestProductWarehouseID != nil {
note := fmt.Sprintf("Transfer to Laying #%s", transfer.TransferNumber)
replenishResult, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
StockableKey: fifo.StockableKeyTransferToLaying,
StockableID: approvableID,
ProductWarehouseID: *transfer.DestProductWarehouseID,
Quantity: *transfer.PendingUsageQty,
Note: &note,
Tx: dbTransaction,
})
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to replenish stock to destination warehouse: %v", err))
}
if _, err := s.getOrCreateProductWarehouse(
c.Context(),
dbTransaction,
sourceWarehouse.ProductId,
targetWarehouse.Id,
target.Qty,
actorID,
&target.TargetProjectFlockKandangId, // Set flock ID agar bisa di-chickin di target flock
); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create or update product warehouse")
if err := dbTransaction.Model(&entity.LayingTransfer{}).
Where("id = ?", approvableID).
Updates(map[string]interface{}{
"total_qty": replenishResult.AddedQuantity,
}).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update total quantity for transfer")
}
}
}
@@ -709,9 +728,10 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
usageQty := *transfer.PendingUsageQty
updateData := map[string]any{
"usage_qty": usageQty,
"total_qty": usageQty, // Same as usage_qty for initial transfer
"pending_usage_qty": nil,
}
if err := s.Repository.WithTx(dbTransaction).PatchOne(c.Context(), approvableID, updateData, nil); err != nil {
if err := repoTx.PatchOne(c.Context(), approvableID, updateData, nil); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transfer laying status")
}
}