adjust common hpp v2

This commit is contained in:
giovanni
2026-04-19 17:27:42 +07:00
parent 69d6fc165a
commit 04aad18a4c
7 changed files with 1020 additions and 259 deletions
+211 -14
View File
@@ -28,13 +28,14 @@ const (
hppV2PartManualCutover = "manual_cutover"
hppV2PartDepreciationNormal = "normal_transfer"
hppV2PartDepreciationCutover = "manual_cutover"
hppV2PartDepreciationFarmSnapshot = "farm_snapshot"
hppV2ProrationPopulation = "growing_population_share"
hppV2ProrationEggWeight = "laying_egg_weight_share"
hppV2ProrationEggPiece = "laying_egg_piece_share"
hppV2ScopePulletCost = "pullet_cost"
hppV2ScopeProductionCost = "production_cost"
hppV2CutoverFlagPakan = "PAKAN-CUTOVER"
hppV2CutoverFlagOvk = "OVK-CUTOVER"
hppV2CutoverFlagOvk = "OVK"
)
type HppV2Service interface {
@@ -115,57 +116,101 @@ func (s *hppV2Service) CalculateHppBreakdown(projectFlockKandangId uint, date *t
totalPulletCost := 0.0
totalProductionCost := 0.0
components := make([]HppV2Component, 0, 8)
appendComponent := func(component *HppV2Component) {
appendComponent := func(requestedCode string, component *HppV2Component) {
pulletBefore := totalPulletCost
productionBefore := totalProductionCost
if component == nil || (component.Total == 0 && len(component.Parts) == 0) {
utils.Log.Infof(
"HPP v2 component skipped: project_flock_kandang_id=%d period_date=%s component=%s reason=empty_or_nil total_pullet_cost=%.2f total_production_cost=%.2f",
projectFlockKandangId,
startOfDay.Format("2006-01-02"),
requestedCode,
totalPulletCost,
totalProductionCost,
)
return
}
pulletAdded := componentScopeTotal(component, hppV2ScopePulletCost)
productionAdded := componentScopeTotal(component, hppV2ScopeProductionCost)
components = append(components, *component)
totalPulletCost += componentScopeTotal(component, hppV2ScopePulletCost)
totalProductionCost += componentScopeTotal(component, hppV2ScopeProductionCost)
totalPulletCost += pulletAdded
totalProductionCost += productionAdded
utils.Log.Infof(
"HPP v2 component applied: project_flock_kandang_id=%d period_date=%s component=%s component_total=%.2f pullet_added=%.2f production_added=%.2f total_pullet_before=%.2f total_pullet_after=%.2f total_production_before=%.2f total_production_after=%.2f parts_count=%d",
projectFlockKandangId,
startOfDay.Format("2006-01-02"),
component.Code,
component.Total,
pulletAdded,
productionAdded,
pulletBefore,
totalPulletCost,
productionBefore,
totalProductionCost,
len(component.Parts),
)
}
appendComponent(pakanComponent)
appendComponent(hppV2ComponentPakan, pakanComponent)
ovkComponent, err := s.GetOvkBreakdown(projectFlockKandangId, &endOfDay)
if err != nil {
return nil, err
}
appendComponent(ovkComponent)
appendComponent(hppV2ComponentOvk, ovkComponent)
docComponent, err := s.GetDocChickinBreakdown(projectFlockKandangId, &endOfDay)
if err != nil {
return nil, err
}
appendComponent(docComponent)
appendComponent(hppV2ComponentDocChickin, docComponent)
directPulletComponent, err := s.GetDirectPulletPurchaseBreakdown(projectFlockKandangId, &endOfDay)
if err != nil {
return nil, err
}
appendComponent(directPulletComponent)
appendComponent(hppV2ComponentDirectPulletPurchase, directPulletComponent)
bopRegularComponent, err := s.GetBopRegularBreakdown(projectFlockKandangId, &endOfDay)
if err != nil {
return nil, err
}
appendComponent(bopRegularComponent)
appendComponent(hppV2ComponentBopRegular, bopRegularComponent)
bopEkspedisiComponent, err := s.GetBopEkspedisiBreakdown(projectFlockKandangId, &endOfDay)
if err != nil {
return nil, err
}
appendComponent(bopEkspedisiComponent)
appendComponent(hppV2ComponentBopEksp, bopEkspedisiComponent)
manualPulletComponent, err := s.getManualPulletCostComponent(projectFlockKandangId, contextRow, startOfDay)
if err != nil {
return nil, err
}
appendComponent(manualPulletComponent)
appendComponent(hppV2ComponentManualPulletCost, manualPulletComponent)
depreciationComponent, err := s.getDepreciationComponent(projectFlockKandangId, contextRow, startOfDay, totalPulletCost)
depreciationComponent, err := s.getDepreciationComponent(projectFlockKandangId, contextRow, startOfDay, endOfDay, totalPulletCost)
if err != nil {
return nil, err
}
appendComponent(depreciationComponent)
depreciationCostToProduction := componentScopeTotal(depreciationComponent, hppV2ScopeProductionCost)
depreciationSource := ""
if depreciationComponent != nil && len(depreciationComponent.Parts) > 0 {
depreciationSource = depreciationComponent.Parts[0].Code
}
productionCostBeforeDepreciation := totalProductionCost
appendComponent(hppV2ComponentDepreciation, depreciationComponent)
utils.Log.Infof(
"HPP v2 depreciation cost applied: project_flock_kandang_id=%d period_date=%s depreciation_source=%s depreciation_cost=%.2f production_cost_before=%.2f production_cost_after=%.2f",
projectFlockKandangId,
startOfDay.Format("2006-01-02"),
depreciationSource,
depreciationCostToProduction,
productionCostBeforeDepreciation,
totalProductionCost,
)
hppCost, err := s.GetHppEstimationDanRealisasi(totalProductionCost, projectFlockKandangId, &startOfDay, &endOfDay)
if err != nil {
@@ -179,6 +224,7 @@ func (s *hppV2Service) CalculateHppBreakdown(projectFlockKandangId uint, date *t
ProjectFlockKandangID: projectFlockKandangId,
ProjectFlockID: contextRow.ProjectFlockID,
ProjectFlockCategory: contextRow.ProjectFlockCategory,
HouseType: contextRow.HouseType,
KandangID: contextRow.KandangID,
KandangName: contextRow.KandangName,
LocationID: contextRow.LocationID,
@@ -1022,9 +1068,28 @@ func (s *hppV2Service) getDepreciationComponent(
projectFlockKandangId uint,
contextRow *commonRepo.HppV2ProjectFlockKandangContext,
periodDate time.Time,
endDate time.Time,
totalPulletCost float64,
) (*HppV2Component, error) {
if s.hppRepo == nil || contextRow == nil || totalPulletCost <= 0 {
if s.hppRepo == nil || contextRow == nil {
return nil, nil
}
snapshotPart, err := s.buildFarmSnapshotDepreciationPart(projectFlockKandangId, contextRow, periodDate, endDate)
if err != nil {
return nil, err
}
if snapshotPart != nil {
return &HppV2Component{
Code: hppV2ComponentDepreciation,
Title: "Depreciation",
Scopes: []string{hppV2ScopeProductionCost},
Total: snapshotPart.Total,
Parts: []HppV2ComponentPart{*snapshotPart},
}, nil
}
if totalPulletCost <= 0 {
return nil, nil
}
@@ -1058,6 +1123,101 @@ func (s *hppV2Service) getDepreciationComponent(
}, nil
}
func (s *hppV2Service) buildFarmSnapshotDepreciationPart(
projectFlockKandangId uint,
contextRow *commonRepo.HppV2ProjectFlockKandangContext,
periodDate time.Time,
endDate time.Time,
) (*HppV2ComponentPart, error) {
if contextRow == nil {
return nil, nil
}
snapshot, err := s.hppRepo.GetFarmDepreciationSnapshotByProjectFlockIDAndPeriod(context.Background(), contextRow.ProjectFlockID, periodDate)
if err != nil {
return nil, err
}
if snapshot == nil || snapshot.DepreciationValue <= 0 {
return nil, nil
}
farmPFKIDs, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), contextRow.ProjectFlockID)
if err != nil {
return nil, err
}
if len(farmPFKIDs) == 0 {
return nil, nil
}
end := endDate
targetPieces, targetWeight, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, &end)
if err != nil {
return nil, err
}
farmPieces, farmWeight, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), farmPFKIDs, &end)
if err != nil {
return nil, err
}
basis := hppV2ProrationEggWeight
numerator := targetWeight
denominator := farmWeight
if denominator <= 0 {
basis = hppV2ProrationEggPiece
numerator = targetPieces
denominator = farmPieces
}
if denominator <= 0 {
return nil, nil
}
ratio := numerator / denominator
if ratio <= 0 {
return nil, nil
}
appliedDepreciation := snapshot.DepreciationValue * ratio
if appliedDepreciation <= 0 {
return nil, nil
}
appliedPulletCostDayN := snapshot.PulletCostDayNTotal * ratio
depreciationPercent := snapshot.DepreciationPercentEffective
if appliedPulletCostDayN > 0 {
depreciationPercent = (appliedDepreciation / appliedPulletCostDayN) * 100
}
return &HppV2ComponentPart{
Code: hppV2PartDepreciationFarmSnapshot,
Title: "Farm Snapshot",
Scopes: []string{hppV2ScopeProductionCost},
Total: appliedDepreciation,
Proration: &HppV2Proration{
Basis: basis,
Numerator: numerator,
Denominator: denominator,
Ratio: ratio,
},
Details: map[string]any{
"basis_total": snapshot.DepreciationValue,
"pullet_cost_day_n": appliedPulletCostDayN,
"depreciation_percent": depreciationPercent,
"snapshot_id": snapshot.ID,
"snapshot_period_date": formatDateOnly(snapshot.PeriodDate),
"snapshot_project_flock": snapshot.ProjectFlockID,
},
References: []HppV2Reference{
{
Type: "farm_depreciation_snapshot",
ID: snapshot.ID,
Date: formatDateOnly(snapshot.PeriodDate),
Qty: 1,
Total: snapshot.DepreciationValue,
AppliedTotal: appliedDepreciation,
},
},
}, nil
}
func (s *hppV2Service) buildNormalTransferDepreciationPart(
contextRow *commonRepo.HppV2ProjectFlockKandangContext,
transferInput *commonRepo.HppV2LatestTransferInputRow,
@@ -1211,17 +1371,40 @@ func (s *hppV2Service) buildManualCutoverDepreciationPart(
}
func (s *hppV2Service) GetHppEstimationDanRealisasi(totalProductionCost float64, projectFlockKandangId uint, startDate *time.Time, endDate *time.Time) (*HppCostResponse, error) {
utils.Log.Infof(
"GetHppEstimationDanRealisasi started: project_flock_kandang_id=%d total_production_cost=%.2f start_date=%s end_date=%s",
projectFlockKandangId,
totalProductionCost,
formatTimePtr(startDate),
formatTimePtr(endDate),
)
if s.hppRepo == nil {
utils.Log.Warnf(
"GetHppEstimationDanRealisasi skipped: hpp repository is nil (project_flock_kandang_id=%d)",
projectFlockKandangId,
)
return &HppCostResponse{}, nil
}
estimPieces, estimWeightKg, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, endDate)
if err != nil {
utils.Log.WithError(err).Errorf(
"GetHppEstimationDanRealisasi failed to get estimation egg production: project_flock_kandang_id=%d end_date=%s",
projectFlockKandangId,
formatTimePtr(endDate),
)
return nil, err
}
realPieces, realWeightKg, err := s.hppRepo.GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, startDate, endDate)
if err != nil {
utils.Log.WithError(err).Errorf(
"GetHppEstimationDanRealisasi failed to get realization egg sales: project_flock_kandang_id=%d start_date=%s end_date=%s",
projectFlockKandangId,
formatTimePtr(startDate),
formatTimePtr(endDate),
)
return nil, err
}
@@ -1249,6 +1432,20 @@ func (s *hppV2Service) GetHppEstimationDanRealisasi(totalProductionCost float64,
real.HargaButir = roundToTwoDecimals(totalProductionCost / realPieces)
}
utils.Log.Infof(
"GetHppEstimationDanRealisasi success: project_flock_kandang_id=%d estimation_butir=%.2f estimation_kg=%.2f estimation_harga_butir=%.2f estimation_harga_kg=%.2f real_butir=%.2f real_kg=%.2f real_harga_butir=%.2f real_harga_kg=%.2f totalProductionCost=%.2f",
projectFlockKandangId,
estimation.Butir,
estimation.Kg,
estimation.HargaButir,
estimation.HargaKg,
real.Butir,
real.Kg,
real.HargaButir,
real.HargaKg,
totalProductionCost,
)
return &HppCostResponse{
Estimation: estimation,
Real: real,