mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-24 15:25:43 +00:00
feat: manual pullet cost
This commit is contained in:
@@ -15,6 +15,8 @@ const (
|
||||
hppV2ComponentDirectPulletPurchase = "DIRECT_PULLET_PURCHASE"
|
||||
hppV2ComponentBopRegular = "BOP_REGULAR"
|
||||
hppV2ComponentBopEksp = "BOP_EKSPEDISI"
|
||||
hppV2ComponentManualPulletCost = "MANUAL_PULLET_COST"
|
||||
hppV2ComponentDepreciation = "DEPRECIATION"
|
||||
hppV2PartGrowingNormal = "growing_normal"
|
||||
hppV2PartGrowingCutover = "growing_cutover"
|
||||
hppV2PartLayingNormal = "laying_normal"
|
||||
@@ -23,6 +25,9 @@ const (
|
||||
hppV2PartGrowingFarm = "growing_farm"
|
||||
hppV2PartLayingDirect = "laying_direct"
|
||||
hppV2PartLayingFarm = "laying_farm"
|
||||
hppV2PartManualCutover = "manual_cutover"
|
||||
hppV2PartDepreciationNormal = "normal_transfer"
|
||||
hppV2PartDepreciationCutover = "manual_cutover"
|
||||
hppV2ProrationPopulation = "growing_population_share"
|
||||
hppV2ProrationEggWeight = "laying_egg_weight_share"
|
||||
hppV2ProrationEggPiece = "laying_egg_piece_share"
|
||||
@@ -109,18 +114,14 @@ func (s *hppV2Service) CalculateHppBreakdown(projectFlockKandangId uint, date *t
|
||||
|
||||
totalPulletCost := 0.0
|
||||
totalProductionCost := 0.0
|
||||
components := make([]HppV2Component, 0, 6)
|
||||
components := make([]HppV2Component, 0, 8)
|
||||
appendComponent := func(component *HppV2Component) {
|
||||
if component == nil || (component.Total == 0 && len(component.Parts) == 0) {
|
||||
return
|
||||
}
|
||||
components = append(components, *component)
|
||||
if componentHasScope(component, hppV2ScopePulletCost) {
|
||||
totalPulletCost += component.Total
|
||||
}
|
||||
if componentHasScope(component, hppV2ScopeProductionCost) {
|
||||
totalProductionCost += component.Total
|
||||
}
|
||||
totalPulletCost += componentScopeTotal(component, hppV2ScopePulletCost)
|
||||
totalProductionCost += componentScopeTotal(component, hppV2ScopeProductionCost)
|
||||
}
|
||||
appendComponent(pakanComponent)
|
||||
|
||||
@@ -154,6 +155,18 @@ func (s *hppV2Service) CalculateHppBreakdown(projectFlockKandangId uint, date *t
|
||||
}
|
||||
appendComponent(bopEkspedisiComponent)
|
||||
|
||||
manualPulletComponent, err := s.getManualPulletCostComponent(projectFlockKandangId, contextRow, startOfDay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appendComponent(manualPulletComponent)
|
||||
|
||||
depreciationComponent, err := s.getDepreciationComponent(projectFlockKandangId, contextRow, startOfDay, totalPulletCost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appendComponent(depreciationComponent)
|
||||
|
||||
hppCost, err := s.GetHppEstimationDanRealisasi(totalProductionCost, projectFlockKandangId, &startOfDay, &endOfDay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -527,6 +540,7 @@ func (s *hppV2Service) buildGrowingChickinPart(
|
||||
rows,
|
||||
partCode,
|
||||
partTitle,
|
||||
[]string{hppV2ScopePulletCost},
|
||||
&HppV2Proration{
|
||||
Basis: hppV2ProrationPopulation,
|
||||
Numerator: transferTotalQty,
|
||||
@@ -550,7 +564,7 @@ func (s *hppV2Service) buildLayingChickinPart(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buildChickinPartFromRows(rows, partCode, partTitle, nil, 1), nil
|
||||
return buildChickinPartFromRows(rows, partCode, partTitle, []string{hppV2ScopeProductionCost}, nil, 1), nil
|
||||
}
|
||||
|
||||
func (s *hppV2Service) buildGrowingUsagePart(
|
||||
@@ -653,9 +667,10 @@ func (s *hppV2Service) buildGrowingUsagePart(
|
||||
}
|
||||
|
||||
return &HppV2ComponentPart{
|
||||
Code: partCode,
|
||||
Title: partTitle,
|
||||
Total: baseTotal * ratio,
|
||||
Code: partCode,
|
||||
Title: partTitle,
|
||||
Scopes: []string{hppV2ScopePulletCost},
|
||||
Total: baseTotal * ratio,
|
||||
Proration: &HppV2Proration{
|
||||
Basis: hppV2ProrationPopulation,
|
||||
Numerator: transferTotalQty,
|
||||
@@ -703,6 +718,7 @@ func (s *hppV2Service) buildLayingUsagePart(
|
||||
return &HppV2ComponentPart{
|
||||
Code: hppV2PartLayingCutover,
|
||||
Title: "Laying Cut-over",
|
||||
Scopes: []string{hppV2ScopeProductionCost},
|
||||
Total: total,
|
||||
References: references,
|
||||
}, nil
|
||||
@@ -741,6 +757,7 @@ func (s *hppV2Service) buildLayingUsagePart(
|
||||
return &HppV2ComponentPart{
|
||||
Code: hppV2PartLayingNormal,
|
||||
Title: "Laying",
|
||||
Scopes: []string{hppV2ScopeProductionCost},
|
||||
Total: total,
|
||||
References: references,
|
||||
}, nil
|
||||
@@ -818,6 +835,7 @@ func (s *hppV2Service) buildGrowingExpensePart(
|
||||
rows,
|
||||
map[bool]string{false: hppV2PartGrowingDirect, true: hppV2PartGrowingFarm}[farmLevel],
|
||||
map[bool]string{false: "Growing Direct", true: "Growing Farm"}[farmLevel],
|
||||
[]string{hppV2ScopePulletCost},
|
||||
&HppV2Proration{
|
||||
Basis: hppV2ProrationPopulation,
|
||||
Numerator: transferTotalQty,
|
||||
@@ -838,7 +856,7 @@ func (s *hppV2Service) buildLayingExpenseDirectPart(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buildExpensePartFromRows(rows, hppV2PartLayingDirect, "Laying Direct", nil, 1), nil
|
||||
return buildExpensePartFromRows(rows, hppV2PartLayingDirect, "Laying Direct", []string{hppV2ScopeProductionCost}, nil, 1), nil
|
||||
}
|
||||
|
||||
func (s *hppV2Service) buildLayingExpenseFarmPart(
|
||||
@@ -893,6 +911,7 @@ func (s *hppV2Service) buildLayingExpenseFarmPart(
|
||||
rows,
|
||||
hppV2PartLayingFarm,
|
||||
"Laying Farm",
|
||||
[]string{hppV2ScopeProductionCost},
|
||||
&HppV2Proration{
|
||||
Basis: basis,
|
||||
Numerator: numerator,
|
||||
@@ -903,6 +922,294 @@ func (s *hppV2Service) buildLayingExpenseFarmPart(
|
||||
), nil
|
||||
}
|
||||
|
||||
func (s *hppV2Service) getManualPulletCostComponent(
|
||||
projectFlockKandangId uint,
|
||||
contextRow *commonRepo.HppV2ProjectFlockKandangContext,
|
||||
periodDate time.Time,
|
||||
) (*HppV2Component, error) {
|
||||
if s.hppRepo == nil || contextRow == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sourceProjectFlockID, transferTotalQty, err := s.hppRepo.GetTransferSourceSummary(context.Background(), projectFlockKandangId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sourceProjectFlockID != 0 && transferTotalQty > 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
manualInput, err := s.hppRepo.GetManualDepreciationInputByProjectFlockID(context.Background(), contextRow.ProjectFlockID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if manualInput == nil || manualInput.TotalCost <= 0 || manualInput.CutoverDate.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
if dateOnly(periodDate).Before(dateOnly(manualInput.CutoverDate)) {
|
||||
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
|
||||
}
|
||||
|
||||
totalPopulation, err := s.hppRepo.GetTotalPopulation(context.Background(), farmPFKIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if totalPopulation <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
targetPopulation, err := s.hppRepo.GetTotalPopulation(context.Background(), []uint{projectFlockKandangId})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if targetPopulation <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ratio := targetPopulation / totalPopulation
|
||||
if ratio <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
appliedTotal := manualInput.TotalCost * ratio
|
||||
part := HppV2ComponentPart{
|
||||
Code: hppV2PartManualCutover,
|
||||
Title: "Manual Cut-over",
|
||||
Scopes: []string{hppV2ScopePulletCost},
|
||||
Total: appliedTotal,
|
||||
Proration: &HppV2Proration{
|
||||
Basis: hppV2ProrationPopulation,
|
||||
Numerator: targetPopulation,
|
||||
Denominator: totalPopulation,
|
||||
Ratio: ratio,
|
||||
},
|
||||
Details: map[string]any{
|
||||
"cutover_date": formatDateOnly(manualInput.CutoverDate),
|
||||
"farm_total_cost": manualInput.TotalCost,
|
||||
"target_population": targetPopulation,
|
||||
"farm_population": totalPopulation,
|
||||
},
|
||||
References: []HppV2Reference{
|
||||
{
|
||||
Type: "farm_depreciation_manual_input",
|
||||
ID: manualInput.ID,
|
||||
Date: formatDateOnly(manualInput.CutoverDate),
|
||||
Qty: 1,
|
||||
Total: manualInput.TotalCost,
|
||||
AppliedTotal: appliedTotal,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return &HppV2Component{
|
||||
Code: hppV2ComponentManualPulletCost,
|
||||
Title: "Manual Pullet Cost",
|
||||
Scopes: []string{hppV2ScopePulletCost},
|
||||
Total: appliedTotal,
|
||||
Parts: []HppV2ComponentPart{part},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *hppV2Service) getDepreciationComponent(
|
||||
projectFlockKandangId uint,
|
||||
contextRow *commonRepo.HppV2ProjectFlockKandangContext,
|
||||
periodDate time.Time,
|
||||
totalPulletCost float64,
|
||||
) (*HppV2Component, error) {
|
||||
if s.hppRepo == nil || contextRow == nil || totalPulletCost <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
transferInput, err := s.hppRepo.GetLatestTransferInputByProjectFlockKandangID(context.Background(), projectFlockKandangId, periodDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var part *HppV2ComponentPart
|
||||
if transferInput != nil && transferInput.SourceProjectFlockID > 0 {
|
||||
part, err = s.buildNormalTransferDepreciationPart(contextRow, transferInput, periodDate, totalPulletCost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
part, err = s.buildManualCutoverDepreciationPart(projectFlockKandangId, contextRow, periodDate, totalPulletCost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if part == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &HppV2Component{
|
||||
Code: hppV2ComponentDepreciation,
|
||||
Title: "Depreciation",
|
||||
Scopes: []string{hppV2ScopeProductionCost},
|
||||
Total: part.Total,
|
||||
Parts: []HppV2ComponentPart{*part},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *hppV2Service) buildNormalTransferDepreciationPart(
|
||||
contextRow *commonRepo.HppV2ProjectFlockKandangContext,
|
||||
transferInput *commonRepo.HppV2LatestTransferInputRow,
|
||||
periodDate time.Time,
|
||||
totalPulletCost float64,
|
||||
) (*HppV2ComponentPart, error) {
|
||||
if contextRow == nil || transferInput == nil || totalPulletCost <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
originDate, err := s.hppRepo.GetEarliestChickInDateByProjectFlockID(context.Background(), transferInput.SourceProjectFlockID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if originDate == nil || originDate.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
scheduleDay := DepreciationScheduleDay(*originDate, periodDate, contextRow.HouseType)
|
||||
if scheduleDay <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
houseType := NormalizeDepreciationHouseType(contextRow.HouseType)
|
||||
percentByHouseType, err := s.hppRepo.GetDepreciationPercents(context.Background(), []string{houseType}, scheduleDay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pulletCostDayN, depreciationValue, depreciationPercent := CalculateDepreciationAtDayN(
|
||||
totalPulletCost,
|
||||
scheduleDay,
|
||||
contextRow.HouseType,
|
||||
percentByHouseType,
|
||||
)
|
||||
if depreciationValue <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &HppV2ComponentPart{
|
||||
Code: hppV2PartDepreciationNormal,
|
||||
Title: "Normal Transfer",
|
||||
Scopes: []string{hppV2ScopeProductionCost},
|
||||
Total: depreciationValue,
|
||||
Details: map[string]any{
|
||||
"basis_total": totalPulletCost,
|
||||
"pullet_cost_day_n": pulletCostDayN,
|
||||
"depreciation_percent": depreciationPercent,
|
||||
"schedule_day": scheduleDay,
|
||||
"origin_date": formatDateOnly(*originDate),
|
||||
"transfer_date": formatDateOnly(transferInput.TransferDate),
|
||||
"source_project_flock_id": transferInput.SourceProjectFlockID,
|
||||
},
|
||||
References: []HppV2Reference{
|
||||
{
|
||||
Type: "laying_transfer",
|
||||
ID: transferInput.TransferID,
|
||||
Date: formatDateOnly(transferInput.TransferDate),
|
||||
Qty: transferInput.TransferQty,
|
||||
Total: totalPulletCost,
|
||||
AppliedTotal: depreciationValue,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *hppV2Service) buildManualCutoverDepreciationPart(
|
||||
projectFlockKandangId uint,
|
||||
contextRow *commonRepo.HppV2ProjectFlockKandangContext,
|
||||
periodDate time.Time,
|
||||
totalPulletCost float64,
|
||||
) (*HppV2ComponentPart, error) {
|
||||
if contextRow == nil || totalPulletCost <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
manualInput, err := s.hppRepo.GetManualDepreciationInputByProjectFlockID(context.Background(), contextRow.ProjectFlockID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if manualInput == nil || manualInput.TotalCost <= 0 || manualInput.CutoverDate.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
if dateOnly(periodDate).Before(dateOnly(manualInput.CutoverDate)) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
originDate, err := s.hppRepo.GetEarliestChickInDateByProjectFlockID(context.Background(), contextRow.ProjectFlockID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if originDate == nil || originDate.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
reportScheduleDay := DepreciationScheduleDay(*originDate, periodDate, contextRow.HouseType)
|
||||
if reportScheduleDay <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cutoverScheduleDay := DepreciationScheduleDay(*originDate, manualInput.CutoverDate, contextRow.HouseType)
|
||||
startDay := 1
|
||||
if cutoverScheduleDay > 0 {
|
||||
startDay = cutoverScheduleDay
|
||||
}
|
||||
|
||||
houseType := NormalizeDepreciationHouseType(contextRow.HouseType)
|
||||
percentByHouseType, err := s.hppRepo.GetDepreciationPercents(context.Background(), []string{houseType}, reportScheduleDay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pulletCostDayN, depreciationValue, depreciationPercent := CalculateDepreciationFromDayRange(
|
||||
totalPulletCost,
|
||||
startDay,
|
||||
reportScheduleDay,
|
||||
contextRow.HouseType,
|
||||
percentByHouseType,
|
||||
)
|
||||
if depreciationValue <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &HppV2ComponentPart{
|
||||
Code: hppV2PartDepreciationCutover,
|
||||
Title: "Manual Cut-over",
|
||||
Scopes: []string{hppV2ScopeProductionCost},
|
||||
Total: depreciationValue,
|
||||
Details: map[string]any{
|
||||
"basis_total": totalPulletCost,
|
||||
"pullet_cost_day_n": pulletCostDayN,
|
||||
"depreciation_percent": depreciationPercent,
|
||||
"schedule_day": reportScheduleDay,
|
||||
"start_schedule_day": startDay,
|
||||
"origin_date": formatDateOnly(*originDate),
|
||||
"cutover_date": formatDateOnly(manualInput.CutoverDate),
|
||||
"manual_input_id": manualInput.ID,
|
||||
"project_flock_kandang": projectFlockKandangId,
|
||||
},
|
||||
References: []HppV2Reference{
|
||||
{
|
||||
Type: "farm_depreciation_manual_input",
|
||||
ID: manualInput.ID,
|
||||
Date: formatDateOnly(manualInput.CutoverDate),
|
||||
Qty: 1,
|
||||
Total: totalPulletCost,
|
||||
AppliedTotal: depreciationValue,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *hppV2Service) GetHppEstimationDanRealisasi(totalProductionCost float64, projectFlockKandangId uint, startDate *time.Time, endDate *time.Time) (*HppCostResponse, error) {
|
||||
if s.hppRepo == nil {
|
||||
return &HppCostResponse{}, nil
|
||||
@@ -975,6 +1282,7 @@ func buildExpensePartFromRows(
|
||||
rows []commonRepo.HppV2ExpenseCostRow,
|
||||
code string,
|
||||
title string,
|
||||
scopes []string,
|
||||
proration *HppV2Proration,
|
||||
ratio float64,
|
||||
) *HppV2ComponentPart {
|
||||
@@ -1005,6 +1313,7 @@ func buildExpensePartFromRows(
|
||||
return &HppV2ComponentPart{
|
||||
Code: code,
|
||||
Title: title,
|
||||
Scopes: append([]string{}, scopes...),
|
||||
Total: total,
|
||||
Proration: proration,
|
||||
References: references,
|
||||
@@ -1015,6 +1324,7 @@ func buildChickinPartFromRows(
|
||||
rows []commonRepo.HppV2ChickinCostRow,
|
||||
code string,
|
||||
title string,
|
||||
scopes []string,
|
||||
proration *HppV2Proration,
|
||||
ratio float64,
|
||||
) *HppV2ComponentPart {
|
||||
@@ -1048,6 +1358,7 @@ func buildChickinPartFromRows(
|
||||
return &HppV2ComponentPart{
|
||||
Code: code,
|
||||
Title: title,
|
||||
Scopes: append([]string{}, scopes...),
|
||||
Total: total,
|
||||
Proration: proration,
|
||||
References: references,
|
||||
@@ -1065,3 +1376,48 @@ func componentHasScope(component *HppV2Component, scope string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func componentScopeTotal(component *HppV2Component, scope string) float64 {
|
||||
if component == nil || scope == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
total := 0.0
|
||||
hasPartScopes := false
|
||||
for _, part := range component.Parts {
|
||||
if len(part.Scopes) == 0 {
|
||||
continue
|
||||
}
|
||||
hasPartScopes = true
|
||||
if partHasScope(&part, scope) {
|
||||
total += part.Total
|
||||
}
|
||||
}
|
||||
if hasPartScopes {
|
||||
return total
|
||||
}
|
||||
if componentHasScope(component, scope) {
|
||||
return component.Total
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func partHasScope(part *HppV2ComponentPart, scope string) bool {
|
||||
if part == nil || scope == "" {
|
||||
return false
|
||||
}
|
||||
for _, candidate := range part.Scopes {
|
||||
if candidate == scope {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func dateOnly(value time.Time) time.Time {
|
||||
return time.Date(value.Year(), value.Month(), value.Day(), 0, 0, 0, 0, value.Location())
|
||||
}
|
||||
|
||||
func formatDateOnly(value time.Time) string {
|
||||
return dateOnly(value).Format("2006-01-02")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user