add command for normalize data recording population not match; adjust closing overhead and keuangan

This commit is contained in:
giovanni
2026-06-07 16:34:22 +07:00
parent 98bfdac3c5
commit edfd6ac95c
3 changed files with 318 additions and 2 deletions
@@ -789,11 +789,56 @@ func (s closingService) GetOverhead(c *fiber.Ctx, projectFlockID uint, projectFl
totalActualPopulation := totalChickinQty - totalDepletion
// Prefer recording-based population (recordings.total_chick_qty) so closing stays
// consistent with normalized cut-over flocks. For normal flocks this equals
// chickin - depletion (no-op); it only differs when the recording population was
// normalized separately from recording_depletions. Falls back if any kandang in
// scope lacks a recording.
scopeKandangs := projectFlockKandangs
if projectFlockKandangID != nil {
scopeKandangs = nil
for _, k := range projectFlockKandangs {
if k.Id == *projectFlockKandangID {
scopeKandangs = append(scopeKandangs, k)
break
}
}
}
if recPop, ok := s.actualPopulationFromRecordings(c.Context(), scopeKandangs); ok {
totalActualPopulation = recPop
}
result := dto.ToOverheadListDTOs(budgets, realizations, totalChickinQty, totalActualPopulation, projectFlockKandangID != nil, totalKandangCount)
return &result, nil
}
// actualPopulationFromRecordings sums the latest recordings.total_chick_qty across the
// given kandangs (the production population source of truth). Returns ok=false if any
// kandang lacks a recording, so the caller falls back to chickin-minus-depletion.
// For normal flocks this equals chickin - depletion; it only differs for cut-over flocks
// whose recording population was normalized separately from recording_depletions.
func (s closingService) actualPopulationFromRecordings(ctx context.Context, kandangs []entity.ProjectFlockKandang) (float64, bool) {
if s.RecordingRepo == nil || len(kandangs) == 0 {
return 0, false
}
total := 0.0
for _, k := range kandangs {
latest, err := s.RecordingRepo.GetLatestByProjectFlockKandangID(ctx, k.Id)
if err != nil {
s.Log.Warnf("actualPopulationFromRecordings: latest recording pfk=%d: %v", k.Id, err)
return 0, false
}
if latest == nil || latest.TotalChickQty == nil {
return 0, false
}
if *latest.TotalChickQty > 0 {
total += *latest.TotalChickQty
}
}
return total, true
}
type activeKandangMetricRow struct {
ProjectFlockKandangID uint `gorm:"column:project_flock_kandang_id"`
ProjectFlockID uint `gorm:"column:project_flock_id"`
@@ -156,7 +156,7 @@ func (s closingKeuanganService) calculateClosingKeuangan(c *fiber.Ctx, projectFl
hppSection := s.buildHPPSection(c, projectFlock, projectFlockKandangs, costs, productionData)
profitLossSection := s.buildProfitLossSection(projectFlock, costs, productionData)
profitLossSection := s.buildProfitLossSection(c, projectFlock, projectFlockKandangs, costs, productionData)
data := dto.ToClosingKeuanganData(hppSection, profitLossSection)
return &data, nil
@@ -386,7 +386,7 @@ func (s closingKeuanganService) buildHPPSection(c *fiber.Ctx, projectFlock *enti
return dto.ToHPPSection(hppItems, hppSummary)
}
func (s closingKeuanganService) buildProfitLossSection(projectFlock *entity.ProjectFlock, costs *CostData, production *ProductionData) dto.ProfitLossSection {
func (s closingKeuanganService) buildProfitLossSection(c *fiber.Ctx, projectFlock *entity.ProjectFlock, projectFlockKandangs []entity.ProjectFlockKandang, costs *CostData, production *ProductionData) dto.ProfitLossSection {
totalWeightProduced := production.TotalWeightProduced
totalEggWeightKg := production.TotalEggWeightKg
@@ -394,6 +394,11 @@ func (s closingKeuanganService) buildProfitLossSection(projectFlock *entity.Proj
totalWeightSold := production.TotalWeightSold
totalBirdSold := production.TotalBirdSold
actualPopulation := production.TotalPopulationIn - production.TotalDepletion
// Prefer recording-based population (consistent with buildHPPSection) so per-ekor
// P&L matches the normalized recording population for cut-over flocks.
if lastPopulation, ok := s.getLastPopulationFromRecordings(c, projectFlockKandangs); ok {
actualPopulation = lastPopulation
}
isLaying := projectFlock.Category == string(utils.ProjectFlockCategoryLaying)