FIX[BE]: add laying transfer source and target repositories to transfer laying service

This commit is contained in:
aguhh18
2026-01-11 13:30:19 +07:00
parent 272367d8ef
commit c1e9b5a975
2 changed files with 51 additions and 42 deletions
@@ -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,
@@ -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{}{
if err := sourceRepoTx.PatchOne(c.Context(), source.Id, map[string]interface{}{
"usage_qty": source.UsageQty + consumeResult.UsageQuantity,
"pending_usage_qty": consumeResult.PendingQuantity,
}).Error; err != nil {
}, 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) {