mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-06-09 15:07:49 +00:00
initial refactori trasnfer to laying, and depretitation to 25 week
This commit is contained in:
@@ -312,10 +312,10 @@ func (u *ProjectflockController) LookupProjectFlockKandang(c *fiber.Ctx) error {
|
||||
mapped := warehouseDTO.ToWarehouseRelationDTO(*warehouse)
|
||||
dtoResult.Warehouse = &mapped
|
||||
}
|
||||
if _, isLaying, serr := u.ProjectflockService.GetProjectFlockKandangTransferStateAtDate(c, result.Id, recordDate); serr != nil {
|
||||
if isTransition, isLaying, serr := u.ProjectflockService.GetProjectFlockKandangTransferStateAtDate(c, result.Id, recordDate); serr != nil {
|
||||
return serr
|
||||
} else {
|
||||
dtoResult.IsTransition = false
|
||||
dtoResult.IsTransition = isTransition
|
||||
dtoResult.IsLaying = isLaying
|
||||
}
|
||||
applyCutOverLayingLookupOverride(&dtoResult)
|
||||
@@ -346,7 +346,7 @@ func (u *ProjectflockController) LookupProjectFlockKandang(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
func applyCutOverLayingLookupOverride(result *dto.ProjectFlockKandangDTO) {
|
||||
if result == nil || result.ProjectFlock == nil || result.IsLaying || result.ChickInDate == nil {
|
||||
if result == nil || result.ProjectFlock == nil || result.IsLaying || result.IsTransition || result.ChickInDate == nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -588,17 +588,29 @@ func (s projectflockService) GetProjectFlockKandangTransferStateAtDate(ctx *fibe
|
||||
switch category {
|
||||
case strings.ToUpper(string(utils.ProjectFlockCategoryGrowing)):
|
||||
transfer, err = s.TransferLayingRepo.GetLatestApprovedBySourceKandang(ctx.Context(), projectFlockKandangID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return false, false, nil
|
||||
}
|
||||
s.Log.Errorf("Failed to resolve transfer state for project flock kandang %d: %+v", projectFlockKandangID, err)
|
||||
return false, false, fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve transfer state")
|
||||
}
|
||||
case strings.ToUpper(string(utils.ProjectFlockCategoryLaying)):
|
||||
transfer, err = s.TransferLayingRepo.GetLatestApprovedByTargetKandang(ctx.Context(), projectFlockKandangID)
|
||||
default:
|
||||
return false, false, nil
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// Multi-source: target kandang bisa menerima dari multiple transfer terpisah. Pakai
|
||||
// EARLIEST transfer (transfer_date ASC) sebagai anchor — kandang masuk transition/laying
|
||||
// mengikuti batch pertama yang sampai.
|
||||
allTransfers, allErr := s.TransferLayingRepo.GetAllApprovedByTargetKandang(ctx.Context(), projectFlockKandangID)
|
||||
if allErr != nil {
|
||||
s.Log.Errorf("Failed to resolve transfers for project flock kandang %d: %+v", projectFlockKandangID, allErr)
|
||||
return false, false, fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve transfer state")
|
||||
}
|
||||
if len(allTransfers) == 0 {
|
||||
return false, false, nil
|
||||
}
|
||||
s.Log.Errorf("Failed to resolve transfer state for project flock kandang %d: %+v", projectFlockKandangID, err)
|
||||
return false, false, fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve transfer state")
|
||||
// Repository ORDER BY transfer_date ASC, id ASC → [0] = earliest
|
||||
transfer = &allTransfers[0]
|
||||
default:
|
||||
return false, false, nil
|
||||
}
|
||||
if transfer == nil {
|
||||
return false, false, nil
|
||||
|
||||
@@ -198,10 +198,22 @@ func (s recordingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
targetTransferByPFK, err := s.TransferLayingRepo.GetLatestApprovedByTargetKandangs(c.Context(), layingPFKIDs)
|
||||
// Multi-source support: 1 target kandang bisa menerima dari multiple transfer terpisah.
|
||||
// Untuk state evaluation (IsTransition/IsLaying), kita pakai EARLIEST transfer sebagai anchor
|
||||
// (sesuai dengan rule "kandang masuk fase laying mengikuti batch pertama yang sampai").
|
||||
allTransfersByTarget, err := s.TransferLayingRepo.GetAllApprovedByTargetKandangs(c.Context(), layingPFKIDs)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
targetTransferByPFK := make(map[uint]*entity.LayingTransfer, len(allTransfersByTarget))
|
||||
for pfkID, list := range allTransfersByTarget {
|
||||
if len(list) == 0 {
|
||||
continue
|
||||
}
|
||||
// list sudah ORDER BY transfer_date ASC, id ASC → element [0] adalah earliest
|
||||
earliest := list[0]
|
||||
targetTransferByPFK[pfkID] = &earliest
|
||||
}
|
||||
hasTargetRecordingCache := make(map[uint]bool)
|
||||
|
||||
cutOverChickinAvailability := make(map[uint]bool)
|
||||
@@ -1292,17 +1304,29 @@ func (s *recordingService) evaluatePopulationMutationState(ctx context.Context,
|
||||
switch category {
|
||||
case strings.ToUpper(string(utils.ProjectFlockCategoryGrowing)):
|
||||
transfer, err = s.TransferLayingRepo.GetLatestApprovedBySourceKandang(ctx, recording.ProjectFlockKandangId)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return true, false, false, false, nil, time.Time{}, nil
|
||||
}
|
||||
s.Log.Errorf("Failed to resolve approved transfer for recording %d: %+v", recording.Id, err)
|
||||
return true, false, false, false, nil, time.Time{}, fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi perubahan populasi recording")
|
||||
}
|
||||
case strings.ToUpper(string(utils.ProjectFlockCategoryLaying)):
|
||||
transfer, err = s.TransferLayingRepo.GetLatestApprovedByTargetKandang(ctx, recording.ProjectFlockKandangId)
|
||||
default:
|
||||
return true, false, false, false, nil, time.Time{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// Multi-source: target kandang bisa menerima dari multiple transfer terpisah.
|
||||
// Pakai EARLIEST transfer (transfer_date ASC) sebagai anchor untuk state evaluation —
|
||||
// kandang dianggap masuk transition/laying berdasarkan batch pertama yang masuk.
|
||||
allTransfers, allErr := s.TransferLayingRepo.GetAllApprovedByTargetKandang(ctx, recording.ProjectFlockKandangId)
|
||||
if allErr != nil {
|
||||
s.Log.Errorf("Failed to resolve approved transfers for recording %d: %+v", recording.Id, allErr)
|
||||
return true, false, false, false, nil, time.Time{}, fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi perubahan populasi recording")
|
||||
}
|
||||
if len(allTransfers) == 0 {
|
||||
return true, false, false, false, nil, time.Time{}, nil
|
||||
}
|
||||
s.Log.Errorf("Failed to resolve approved transfer for recording %d: %+v", recording.Id, err)
|
||||
return true, false, false, false, nil, time.Time{}, fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi perubahan populasi recording")
|
||||
// Repository sudah ORDER BY transfer_date ASC, id ASC → element [0] adalah earliest.
|
||||
transfer = &allTransfers[0]
|
||||
default:
|
||||
return true, false, false, false, nil, time.Time{}, nil
|
||||
}
|
||||
if transfer == nil {
|
||||
return true, false, false, false, nil, time.Time{}, nil
|
||||
|
||||
+91
@@ -19,6 +19,11 @@ type TransferLayingRepository interface {
|
||||
GetLatestApprovedByTargetKandang(ctx context.Context, targetProjectFlockKandangID uint) (*entity.LayingTransfer, error)
|
||||
GetLatestApprovedBySourceKandangs(ctx context.Context, pfkIDs []uint) (map[uint]*entity.LayingTransfer, error)
|
||||
GetLatestApprovedByTargetKandangs(ctx context.Context, pfkIDs []uint) (map[uint]*entity.LayingTransfer, error)
|
||||
// GetAllApprovedByTargetKandang return semua approved transfer yang menuju ke target kandang itu.
|
||||
// Dipakai untuk multi-source case di mana 1 target kandang bisa menerima dari multiple transfer
|
||||
// terpisah (tiap transfer = 1 source). Order: transfer_date ASC, id ASC (kronologis).
|
||||
GetAllApprovedByTargetKandang(ctx context.Context, targetProjectFlockKandangID uint) ([]entity.LayingTransfer, error)
|
||||
GetAllApprovedByTargetKandangs(ctx context.Context, pfkIDs []uint) (map[uint][]entity.LayingTransfer, error)
|
||||
|
||||
// Tambah method baru untuk query dengan filter lengkap
|
||||
GetAllWithFilters(ctx context.Context, offset int, limit int, params *GetAllFilterParams) ([]entity.LayingTransfer, int64, error)
|
||||
@@ -362,3 +367,89 @@ func (r *TransferLayingRepositoryImpl) GetLatestApprovedByTargetKandangs(ctx con
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetAllApprovedByTargetKandang return SEMUA approved transfer ke target kandang itu (bukan hanya yang
|
||||
// terbaru). Dipakai untuk skenario multi-source di mana 1 target kandang menerima dari multiple transfer
|
||||
// terpisah, sehingga depresiasi/HPP/recording state perlu aggregate dari semua transfer.
|
||||
func (r *TransferLayingRepositoryImpl) GetAllApprovedByTargetKandang(ctx context.Context, targetProjectFlockKandangID uint) ([]entity.LayingTransfer, error) {
|
||||
if targetProjectFlockKandangID == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var transfers []entity.LayingTransfer
|
||||
err := r.db.WithContext(ctx).
|
||||
Model(&entity.LayingTransfer{}).
|
||||
Joins("JOIN laying_transfer_targets ltt ON ltt.laying_transfer_id = laying_transfers.id AND ltt.deleted_at IS NULL").
|
||||
Where("ltt.target_project_flock_kandang_id = ?", targetProjectFlockKandangID).
|
||||
Where("laying_transfers.deleted_at IS NULL").
|
||||
Where(`(
|
||||
SELECT a.action
|
||||
FROM approvals a
|
||||
WHERE a.approvable_type = ?
|
||||
AND a.approvable_id = laying_transfers.id
|
||||
ORDER BY a.id DESC
|
||||
LIMIT 1
|
||||
) = ?`, string(utils.ApprovalWorkflowTransferToLaying), entity.ApprovalActionApproved).
|
||||
Order("laying_transfers.transfer_date ASC, laying_transfers.id ASC").
|
||||
Distinct("laying_transfers.*").
|
||||
Find(&transfers).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transfers, nil
|
||||
}
|
||||
|
||||
// GetAllApprovedByTargetKandangs batch version: return map dari target_pfk_id ke list of approved transfers.
|
||||
// Order per target: transfer_date ASC, id ASC.
|
||||
func (r *TransferLayingRepositoryImpl) GetAllApprovedByTargetKandangs(ctx context.Context, pfkIDs []uint) (map[uint][]entity.LayingTransfer, error) {
|
||||
result := make(map[uint][]entity.LayingTransfer)
|
||||
if len(pfkIDs) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type targetTransferRow struct {
|
||||
TargetPFKID uint `gorm:"column:target_pfk_id"`
|
||||
TransferID uint `gorm:"column:transfer_id"`
|
||||
}
|
||||
|
||||
var rows []targetTransferRow
|
||||
err := r.db.WithContext(ctx).Raw(`
|
||||
SELECT ltt.target_project_flock_kandang_id AS target_pfk_id, ltt.laying_transfer_id AS transfer_id
|
||||
FROM laying_transfer_targets ltt
|
||||
JOIN laying_transfers t ON t.id = ltt.laying_transfer_id AND t.deleted_at IS NULL
|
||||
WHERE ltt.target_project_flock_kandang_id IN ?
|
||||
AND ltt.deleted_at IS NULL
|
||||
AND (
|
||||
SELECT a.action FROM approvals a
|
||||
WHERE a.approvable_type = ? AND a.approvable_id = t.id
|
||||
ORDER BY a.id DESC LIMIT 1
|
||||
) = ?
|
||||
ORDER BY t.transfer_date ASC, t.id ASC
|
||||
`,
|
||||
pfkIDs, string(utils.ApprovalWorkflowTransferToLaying), string(entity.ApprovalActionApproved),
|
||||
).Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
transferIDs := make([]uint, 0, len(rows))
|
||||
targetsByTransfer := make(map[uint][]uint, len(rows))
|
||||
for _, row := range rows {
|
||||
transferIDs = append(transferIDs, row.TransferID)
|
||||
targetsByTransfer[row.TransferID] = append(targetsByTransfer[row.TransferID], row.TargetPFKID)
|
||||
}
|
||||
|
||||
var transfers []entity.LayingTransfer
|
||||
if err := r.db.WithContext(ctx).Where("id IN ? AND deleted_at IS NULL", transferIDs).Order("transfer_date ASC, id ASC").Find(&transfers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range transfers {
|
||||
for _, targetID := range targetsByTransfer[transfers[i].Id] {
|
||||
result[targetID] = append(result[targetID], transfers[i])
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -1617,6 +1617,13 @@ func (s *transferLayingService) validateKandangOwnership(
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateTargetSourceLineage memvalidasi bahwa source kandang yang sama TIDAK boleh ditransfer 2x ke
|
||||
// target kandang yang sama (anti-duplicate pair). Aturan lama "satu target hanya boleh punya satu
|
||||
// source" sudah dihapus — sekarang 1 target boleh menerima dari multiple source kandang via transfer
|
||||
// terpisah (multi-source via N-call approach).
|
||||
//
|
||||
// Yang ditolak: kalau ada approved transfer lain (id != excludeTransferID) yang punya pair
|
||||
// (source = sourceProjectFlockKandangID, target ∈ targetKandangIDs) yang sama.
|
||||
func (s *transferLayingService) validateTargetSourceLineage(
|
||||
ctx context.Context,
|
||||
sourceProjectFlockKandangID uint,
|
||||
@@ -1637,7 +1644,7 @@ func (s *transferLayingService) validateTargetSourceLineage(
|
||||
}
|
||||
seen[targetKandangID] = struct{}{}
|
||||
|
||||
existingTransfer, err := s.Repository.GetLatestApprovedByTargetKandang(ctx, targetKandangID)
|
||||
existingTransfers, err := s.Repository.GetAllApprovedByTargetKandang(ctx, targetKandangID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
continue
|
||||
@@ -1645,47 +1652,49 @@ func (s *transferLayingService) validateTargetSourceLineage(
|
||||
s.Log.Errorf("Failed to validate transfer lineage for target kandang %d: %+v", targetKandangID, err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi relasi sumber transfer ke laying")
|
||||
}
|
||||
if existingTransfer == nil {
|
||||
continue
|
||||
}
|
||||
if excludeTransferID != 0 && existingTransfer.Id == excludeTransferID {
|
||||
continue
|
||||
}
|
||||
|
||||
existingSourceID := uint(0)
|
||||
if existingTransfer.SourceProjectFlockKandangId != nil && *existingTransfer.SourceProjectFlockKandangId != 0 {
|
||||
existingSourceID = *existingTransfer.SourceProjectFlockKandangId
|
||||
}
|
||||
if existingSourceID == 0 && s.LayingTransferSourceRepo != nil {
|
||||
sources, sourceErr := s.LayingTransferSourceRepo.GetByLayingTransferId(ctx, existingTransfer.Id)
|
||||
if sourceErr != nil {
|
||||
s.Log.Errorf("Failed to resolve transfer sources for lineage validation transfer=%d: %+v", existingTransfer.Id, sourceErr)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi relasi sumber transfer ke laying")
|
||||
for i := range existingTransfers {
|
||||
existingTransfer := &existingTransfers[i]
|
||||
if excludeTransferID != 0 && existingTransfer.Id == excludeTransferID {
|
||||
continue
|
||||
}
|
||||
for _, source := range sources {
|
||||
if source.SourceProjectFlockKandangId != 0 {
|
||||
existingSourceID = source.SourceProjectFlockKandangId
|
||||
break
|
||||
|
||||
// Source di header (single source of truth per migration 20260307130342).
|
||||
existingSourceID := uint(0)
|
||||
if existingTransfer.SourceProjectFlockKandangId != nil && *existingTransfer.SourceProjectFlockKandangId != 0 {
|
||||
existingSourceID = *existingTransfer.SourceProjectFlockKandangId
|
||||
}
|
||||
|
||||
// Fallback ke laying_transfer_sources untuk transfer yang belum punya source di header
|
||||
// (historis pre-migration 20260307130342).
|
||||
if existingSourceID == 0 && s.LayingTransferSourceRepo != nil {
|
||||
sources, sourceErr := s.LayingTransferSourceRepo.GetByLayingTransferId(ctx, existingTransfer.Id)
|
||||
if sourceErr != nil {
|
||||
s.Log.Errorf("Failed to resolve transfer sources for lineage validation transfer=%d: %+v", existingTransfer.Id, sourceErr)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi relasi sumber transfer ke laying")
|
||||
}
|
||||
for _, source := range sources {
|
||||
if source.SourceProjectFlockKandangId == sourceProjectFlockKandangID {
|
||||
existingSourceID = source.SourceProjectFlockKandangId
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if existingSourceID == 0 {
|
||||
continue
|
||||
}
|
||||
if existingSourceID == sourceProjectFlockKandangID {
|
||||
continue
|
||||
}
|
||||
|
||||
return fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf(
|
||||
"Kandang tujuan %d sudah memiliki lineage sumber kandang %d dari transfer %s. Tidak boleh ganti ke sumber kandang %d.",
|
||||
targetKandangID,
|
||||
existingSourceID,
|
||||
existingTransfer.TransferNumber,
|
||||
sourceProjectFlockKandangID,
|
||||
),
|
||||
)
|
||||
if existingSourceID != sourceProjectFlockKandangID {
|
||||
continue
|
||||
}
|
||||
|
||||
return fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf(
|
||||
"Source kandang %d sudah pernah ditransfer ke target kandang %d via transfer %s. Tidak boleh duplikat (source, target) pair yang sama.",
|
||||
sourceProjectFlockKandangID,
|
||||
targetKandangID,
|
||||
existingTransfer.TransferNumber,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user