fix: resolve dashboard OpenAPI integration issues

- FCRs & Transfer to Laying: add ExampleResponse field to routeMeta and
  inject example payloads into OpenAPI 200 responses for list and detail
  endpoints so dashboard consumers have concrete response shapes to work with

- Chick In: enable GET /api/production/chickins/ list endpoint (was
  commented out); add P_ChickinsGetAll permission constant and wire it
  into the route; add OpenAPI spec entry with query params and example

- Recording GET all: fix N+1 query bottleneck (2-3s response time) by
  pre-fetching approved transfer maps per PFK ID in two batch queries
  before the per-recording loop; add evaluatePopulationMutationStateFromCaches
  that uses the pre-fetched maps and caches hasAnyRecordingOnTransferTargets
  results by transfer ID — reducing per-page query count from ~20-40 to ~10-12

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Adnan Zahir
2026-05-02 10:57:45 +07:00
parent c804c59f05
commit 3768892a17
6 changed files with 370 additions and 38 deletions
@@ -17,6 +17,8 @@ type TransferLayingRepository interface {
IdExists(ctx context.Context, id uint) (bool, error)
GetLatestApprovedBySourceKandang(ctx context.Context, sourceProjectFlockKandangID uint) (*entity.LayingTransfer, error)
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)
// Tambah method baru untuk query dengan filter lengkap
GetAllWithFilters(ctx context.Context, offset int, limit int, params *GetAllFilterParams) ([]entity.LayingTransfer, int64, error)
@@ -242,3 +244,121 @@ func (r *TransferLayingRepositoryImpl) GetLatestApprovedByTargetKandang(ctx cont
}
return &transfer, nil
}
type pfkTransferIDRow struct {
SourcePFKID uint `gorm:"column:source_pfk_id"`
TransferID uint `gorm:"column:transfer_id"`
}
func (r *TransferLayingRepositoryImpl) GetLatestApprovedBySourceKandangs(ctx context.Context, pfkIDs []uint) (map[uint]*entity.LayingTransfer, error) {
result := make(map[uint]*entity.LayingTransfer)
if len(pfkIDs) == 0 {
return result, nil
}
var rows []pfkTransferIDRow
err := r.db.WithContext(ctx).Raw(`
SELECT DISTINCT ON (source_pfk_id) source_pfk_id, transfer_id
FROM (
SELECT id AS transfer_id, source_project_flock_kandang_id AS source_pfk_id
FROM laying_transfers
WHERE source_project_flock_kandang_id IN ?
AND deleted_at IS NULL
AND (
SELECT a.action FROM approvals a
WHERE a.approvable_type = ? AND a.approvable_id = id
ORDER BY a.id DESC LIMIT 1
) = ?
UNION ALL
SELECT lts.laying_transfer_id AS transfer_id, lts.source_project_flock_kandang_id AS source_pfk_id
FROM laying_transfer_sources lts
JOIN laying_transfers t ON t.id = lts.laying_transfer_id AND t.deleted_at IS NULL
WHERE lts.source_project_flock_kandang_id IN ?
AND lts.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
) = ?
) combined
ORDER BY source_pfk_id, transfer_id DESC
`,
pfkIDs, string(utils.ApprovalWorkflowTransferToLaying), string(entity.ApprovalActionApproved),
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))
pfkByTransfer := make(map[uint]uint, len(rows))
for _, row := range rows {
transferIDs = append(transferIDs, row.TransferID)
pfkByTransfer[row.TransferID] = row.SourcePFKID
}
var transfers []entity.LayingTransfer
if err := r.db.WithContext(ctx).Where("id IN ? AND deleted_at IS NULL", transferIDs).Find(&transfers).Error; err != nil {
return nil, err
}
for i := range transfers {
if pfkID := pfkByTransfer[transfers[i].Id]; pfkID != 0 {
result[pfkID] = &transfers[i]
}
}
return result, nil
}
func (r *TransferLayingRepositoryImpl) GetLatestApprovedByTargetKandangs(ctx context.Context, pfkIDs []uint) (map[uint]*entity.LayingTransfer, error) {
result := make(map[uint]*entity.LayingTransfer)
if len(pfkIDs) == 0 {
return result, nil
}
var rows []pfkTransferIDRow
err := r.db.WithContext(ctx).Raw(`
SELECT DISTINCT ON (source_pfk_id) source_pfk_id, transfer_id
FROM (
SELECT ltt.laying_transfer_id AS transfer_id, ltt.target_project_flock_kandang_id AS source_pfk_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
) = ?
) combined
ORDER BY source_pfk_id, transfer_id DESC
`,
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))
pfkByTransfer := make(map[uint]uint, len(rows))
for _, row := range rows {
transferIDs = append(transferIDs, row.TransferID)
pfkByTransfer[row.TransferID] = row.SourcePFKID
}
var transfers []entity.LayingTransfer
if err := r.db.WithContext(ctx).Where("id IN ? AND deleted_at IS NULL", transferIDs).Find(&transfers).Error; err != nil {
return nil, err
}
for i := range transfers {
if pfkID := pfkByTransfer[transfers[i].Id]; pfkID != 0 {
result[pfkID] = &transfers[i]
}
}
return result, nil
}