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) { func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
transferLayingRepo := rTransferLaying.NewTransferLayingRepository(db) transferLayingRepo := rTransferLaying.NewTransferLayingRepository(db)
layingTransferSourceRepo := rTransferLaying.NewLayingTransferSourceRepository(db)
layingTransferTargetRepo := rTransferLaying.NewLayingTransferTargetRepository(db)
userRepo := rUser.NewUserRepository(db) userRepo := rUser.NewUserRepository(db)
projectFlockRepo := rProjectFlock.NewProjectflockRepository(db) projectFlockRepo := rProjectFlock.NewProjectflockRepository(db)
projectFlockKandangRepo := rProjectFlock.NewProjectFlockKandangRepository(db) projectFlockKandangRepo := rProjectFlock.NewProjectFlockKandangRepository(db)
@@ -80,6 +82,8 @@ func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, val
transferLayingService := sTransferLaying.NewTransferLayingService( transferLayingService := sTransferLaying.NewTransferLayingService(
transferLayingRepo, transferLayingRepo,
layingTransferSourceRepo,
layingTransferTargetRepo,
projectFlockRepo, projectFlockRepo,
projectFlockKandangRepo, projectFlockKandangRepo,
projectFlockPopulationRepo, projectFlockPopulationRepo,
@@ -41,6 +41,8 @@ type transferLayingService struct {
Log *logrus.Logger Log *logrus.Logger
Validate *validator.Validate Validate *validator.Validate
Repository repository.TransferLayingRepository Repository repository.TransferLayingRepository
LayingTransferSourceRepo repository.LayingTransferSourceRepository
LayingTransferTargetRepo repository.LayingTransferTargetRepository
ProjectFlockRepo ProjectFlockRepository.ProjectflockRepository ProjectFlockRepo ProjectFlockRepository.ProjectflockRepository
ProjectFlockKandangRepo ProjectFlockRepository.ProjectFlockKandangRepository ProjectFlockKandangRepo ProjectFlockRepository.ProjectFlockKandangRepository
ProjectFlockPopulationRepo ProjectFlockRepository.ProjectFlockPopulationRepository ProjectFlockPopulationRepo ProjectFlockRepository.ProjectFlockPopulationRepository
@@ -53,6 +55,8 @@ type transferLayingService struct {
func NewTransferLayingService( func NewTransferLayingService(
repo repository.TransferLayingRepository, repo repository.TransferLayingRepository,
layingTransferSourceRepo repository.LayingTransferSourceRepository,
layingTransferTargetRepo repository.LayingTransferTargetRepository,
projectFlockRepo ProjectFlockRepository.ProjectflockRepository, projectFlockRepo ProjectFlockRepository.ProjectflockRepository,
projectFlockKandangRepo ProjectFlockRepository.ProjectFlockKandangRepository, projectFlockKandangRepo ProjectFlockRepository.ProjectFlockKandangRepository,
projectFlockPopulationRepo ProjectFlockRepository.ProjectFlockPopulationRepository, projectFlockPopulationRepo ProjectFlockRepository.ProjectFlockPopulationRepository,
@@ -66,6 +70,8 @@ func NewTransferLayingService(
Log: utils.Log, Log: utils.Log,
Validate: validate, Validate: validate,
Repository: repo, Repository: repo,
LayingTransferSourceRepo: layingTransferSourceRepo,
LayingTransferTargetRepo: layingTransferTargetRepo,
ProjectFlockRepo: projectFlockRepo, ProjectFlockRepo: projectFlockRepo,
ProjectFlockKandangRepo: projectFlockKandangRepo, ProjectFlockKandangRepo: projectFlockKandangRepo,
ProjectFlockPopulationRepo: projectFlockPopulationRepo, 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 { err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
repoTx := s.Repository.WithTx(dbTransaction) 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 { if err := repoTx.CreateOne(c.Context(), createBody, nil); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat record transfer laying") 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 PendingUsageQty: 0, // Di-set 0, biarkan FIFO Consume yang handle saat Approval
ProductWarehouseId: &productWarehouseId, 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") 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 { for _, sourceDetail := range req.SourceKandangs {
if pwID, ok := sourceWarehouseMap[sourceDetail.ProjectFlockKandangId]; ok { if pwID, ok := sourceWarehouseMap[sourceDetail.ProjectFlockKandangId]; ok {
// Get product warehouse untuk ambil product ID // Get product warehouse untuk ambil product ID
var sourcePW entity.ProductWarehouse sourcePW, err := pwRepoTx.GetByID(c.Context(), pwID, nil)
if err := dbTransaction.First(&sourcePW, pwID).Error; err == nil { if err == nil {
sourceProductID = sourcePW.ProductId sourceProductID = sourcePW.ProductId
break 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 // Cari product warehouse di target berdasarkan: warehouse + project_flock_kandang + PRODUCT
var targetPW entity.ProductWarehouse targetPW, err := pwRepoTx.FindByProductWarehouseAndPfk(c.Context(), sourceProductID, targetWarehouse.Id, &targetDetail.ProjectFlockKandangId)
err = dbTransaction.Where("warehouse_id = ? AND project_flock_kandang_id = ? AND product_id = ?",
targetWarehouse.Id, targetDetail.ProjectFlockKandangId, sourceProductID).
First(&targetPW).Error
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
// Create baru dengan product yang sama dengan source newTargetPW := entity.ProductWarehouse{
targetPW = entity.ProductWarehouse{
ProductId: sourceProductID, ProductId: sourceProductID,
WarehouseId: targetWarehouse.Id, WarehouseId: targetWarehouse.Id,
ProjectFlockKandangId: &targetDetail.ProjectFlockKandangId, ProjectFlockKandangId: &targetDetail.ProjectFlockKandangId,
Quantity: 0, 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)) return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal membuat product warehouse untuk kandang tujuan %d: %v", targetDetail.ProjectFlockKandangId, err))
} }
targetPW = &newTargetPW
} else { } else {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mendapatkan product warehouse untuk kandang tujuan %d: %v", targetDetail.ProjectFlockKandangId, err)) 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, TotalUsed: 0,
ProductWarehouseId: &targetPW.Id, 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") 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 { err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
repoTx := s.Repository.WithTx(dbTransaction) repoTx := s.Repository.WithTx(dbTransaction)
sourceRepo := s.LayingTransferSourceRepo.WithTx(dbTransaction)
targetRepo := s.LayingTransferTargetRepo.WithTx(dbTransaction)
// Hapus old sources dan targets // Hapus old sources dan targets
for _, oldSource := range existingTransfer.Sources { 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") return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete old source")
} }
} }
for _, oldTarget := range existingTransfer.Targets { 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") 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, PendingUsageQty: sourceDetail.Quantity,
ProductWarehouseId: &productWarehouseId, 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") return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer source")
} }
} }
pwRepo := rInventory.NewProductWarehouseRepository(dbTransaction)
for _, targetDetail := range req.TargetKandangs { for _, targetDetail := range req.TargetKandangs {
targetprojectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetDetail.ProjectFlockKandangId) targetprojectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetDetail.ProjectFlockKandangId)
if err != nil { if err != nil {
@@ -483,8 +494,8 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update,
firstSourceKandangID := req.SourceKandangs[0].ProjectFlockKandangId firstSourceKandangID := req.SourceKandangs[0].ProjectFlockKandangId
populations, err := s.ProjectFlockPopulationRepo.GetByProjectFlockKandangID(c.Context(), firstSourceKandangID) populations, err := s.ProjectFlockPopulationRepo.GetByProjectFlockKandangID(c.Context(), firstSourceKandangID)
if err == nil && len(populations) > 0 && populations[0].ProductWarehouseId > 0 { if err == nil && len(populations) > 0 && populations[0].ProductWarehouseId > 0 {
var sourcePW entity.ProductWarehouse sourcePW, err := pwRepo.GetByID(c.Context(), populations[0].ProductWarehouseId, nil)
if err := dbTransaction.First(&sourcePW, populations[0].ProductWarehouseId).Error; err == nil { if err == nil {
sourceProductID = sourcePW.ProductId 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") return fiber.NewError(fiber.StatusInternalServerError, "Failed to get product from source warehouse")
} }
// Cari product warehouse di target berdasarkan: warehouse + project_flock_kandang + PRODUCT targetPW, err := pwRepo.FindByProductWarehouseAndPfk(c.Context(), sourceProductID, targetWarehouse.Id, &targetDetail.ProjectFlockKandangId)
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
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
// Create baru dengan product yang sama dengan source
targetPW = entity.ProductWarehouse{ newTargetPW := entity.ProductWarehouse{
ProductId: sourceProductID, ProductId: sourceProductID,
WarehouseId: targetWarehouse.Id, WarehouseId: targetWarehouse.Id,
ProjectFlockKandangId: &targetDetail.ProjectFlockKandangId, ProjectFlockKandangId: &targetDetail.ProjectFlockKandangId,
Quantity: 0, 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)) return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to create product warehouse for target kandang %d: %v", targetDetail.ProjectFlockKandangId, err))
} }
targetPW = &newTargetPW
} else { } else {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to get product warehouse for target kandang %d: %v", targetDetail.ProjectFlockKandangId, err)) 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, TotalUsed: 0,
ProductWarehouseId: &targetPW.Id, 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") 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()) approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB())
latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), id, nil) latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), id, nil)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status") 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 { err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
repoTx := s.Repository.WithTx(dbTransaction) 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 { if err := repoTx.DeleteOne(c.Context(), id); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete transfer laying") 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 { err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
repoTx := s.Repository.WithTx(dbTransaction) repoTx := s.Repository.WithTx(dbTransaction)
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(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) targetRepoTx := repository.NewLayingTransferTargetRepository(dbTransaction)
for _, approvableID := range approvableIDs { for _, approvableID := range approvableIDs {
@@ -643,7 +653,7 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
if action == entity.ApprovalActionApproved { if action == entity.ApprovalActionApproved {
sources, err := repository.NewLayingTransferSourceRepository(dbTransaction).GetByLayingTransferId(c.Context(), approvableID) sources, err := sourceRepoTx.GetByLayingTransferId(c.Context(), approvableID)
if err != nil { if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil sources transfer") 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)) return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal consume FIFO stock: %v", err))
} }
// Update source usage tracking if err := sourceRepoTx.PatchOne(c.Context(), source.Id, map[string]interface{}{
if err := dbTransaction.Model(&entity.LayingTransferSource{}). "usage_qty": source.UsageQty + consumeResult.UsageQuantity,
Where("id = ?", source.Id). "pending_usage_qty": consumeResult.PendingQuantity,
Updates(map[string]interface{}{ }, nil); err != nil {
"usage_qty": source.UsageQty + consumeResult.UsageQuantity,
"pending_usage_qty": consumeResult.PendingQuantity,
}).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Gagal update source usage qty") return fiber.NewError(fiber.StatusInternalServerError, "Gagal update source usage qty")
} }
} }
// Replenish ke target warehouse
for _, target := range targets { for _, target := range targets {
if target.ProductWarehouseId == nil { if target.ProductWarehouseId == nil {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Target product warehouse tidak ditemukan untuk transfer %d", approvableID)) 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)) return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal replenish stock ke target warehouse: %v", err))
} }
if err := dbTransaction.Model(&entity.LayingTransferTarget{}). if err := targetRepoTx.PatchOne(c.Context(), target.Id, map[string]interface{}{
Where("id = ?", target.Id). "total_qty": replenishResult.AddedQuantity,
Update("total_qty", replenishResult.AddedQuantity).Error; err != nil { }, nil); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Gagal update target total qty") return fiber.NewError(fiber.StatusInternalServerError, "Gagal update target total qty")
} }
} }
@@ -777,9 +783,8 @@ func (s *transferLayingService) getOrCreateProductWarehouse(ctx context.Context,
newWarehouse := &entity.ProductWarehouse{ newWarehouse := &entity.ProductWarehouse{
ProductId: productID, ProductId: productID,
WarehouseId: warehouseID, WarehouseId: warehouseID,
ProjectFlockKandangId: projectFlockKandangId, // Set flock ID agar bisa di-chickin di target flock ProjectFlockKandangId: projectFlockKandangId,
Quantity: quantity, Quantity: quantity,
// CreatedBy: actorID,
} }
if err := productWarehouseRepoTx.CreateOne(ctx, newWarehouse, nil); err != nil { if err := productWarehouseRepoTx.CreateOne(ctx, newWarehouse, nil); err != nil {
@@ -830,7 +835,7 @@ func (s *transferLayingService) validateKandangOwnership(
) error { ) error {
for _, kandangID := range kandangIDs { for _, kandangID := range kandangIDs {
// validasi terlebih dahulu apakah kandangnya itu ada atau gak
projectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(ctx, kandangID) projectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(ctx, kandangID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {