mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-24 23:35:43 +00:00
feat: manual pullet cost
This commit is contained in:
@@ -15,6 +15,10 @@ import (
|
||||
type hppV2RepoStub struct {
|
||||
contextByPFK map[uint]*commonRepo.HppV2ProjectFlockKandangContext
|
||||
pfkIDsByProject map[uint][]uint
|
||||
latestTransferByPFK map[uint]*commonRepo.HppV2LatestTransferInputRow
|
||||
manualInputByProject map[uint]*commonRepo.HppV2ManualDepreciationInputRow
|
||||
chickInDateByProject map[uint]*time.Time
|
||||
depreciationByHouse map[string]map[int]float64
|
||||
usageRowsByKey map[string][]commonRepo.HppV2UsageCostRow
|
||||
adjustRowsByKey map[string][]commonRepo.HppV2AdjustmentCostRow
|
||||
chickinRowsByKey map[string][]commonRepo.HppV2ChickinCostRow
|
||||
@@ -47,6 +51,35 @@ func (s *hppV2RepoStub) GetProjectFlockKandangIDs(_ context.Context, projectFloc
|
||||
return append([]uint{}, s.pfkIDsByProject[projectFlockId]...), nil
|
||||
}
|
||||
|
||||
func (s *hppV2RepoStub) GetLatestTransferInputByProjectFlockKandangID(_ context.Context, projectFlockKandangId uint, _ time.Time) (*commonRepo.HppV2LatestTransferInputRow, error) {
|
||||
return s.latestTransferByPFK[projectFlockKandangId], nil
|
||||
}
|
||||
|
||||
func (s *hppV2RepoStub) GetManualDepreciationInputByProjectFlockID(_ context.Context, projectFlockID uint) (*commonRepo.HppV2ManualDepreciationInputRow, error) {
|
||||
return s.manualInputByProject[projectFlockID], nil
|
||||
}
|
||||
|
||||
func (s *hppV2RepoStub) GetEarliestChickInDateByProjectFlockID(_ context.Context, projectFlockID uint) (*time.Time, error) {
|
||||
return s.chickInDateByProject[projectFlockID], nil
|
||||
}
|
||||
|
||||
func (s *hppV2RepoStub) GetDepreciationPercents(_ context.Context, houseTypes []string, maxDay int) (map[string]map[int]float64, error) {
|
||||
result := make(map[string]map[int]float64)
|
||||
for _, houseType := range houseTypes {
|
||||
source := s.depreciationByHouse[houseType]
|
||||
if len(source) == 0 {
|
||||
continue
|
||||
}
|
||||
result[houseType] = make(map[int]float64)
|
||||
for day, pct := range source {
|
||||
if day <= maxDay {
|
||||
result[houseType][day] = pct
|
||||
}
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *hppV2RepoStub) ListUsageCostRowsByProductFlags(_ context.Context, projectFlockKandangIDs []uint, flagNames []string, _ *time.Time) ([]commonRepo.HppV2UsageCostRow, error) {
|
||||
return append([]commonRepo.HppV2UsageCostRow{}, s.usageRowsByKey[stubKey(projectFlockKandangIDs, flagNames)]...), nil
|
||||
}
|
||||
@@ -161,8 +194,11 @@ func TestHppV2CalculateHppBreakdown_ComposesPakanSlices(t *testing.T) {
|
||||
if result == nil {
|
||||
t.Fatal("expected breakdown result")
|
||||
}
|
||||
if got := result.TotalProductionCost; got != 2950 {
|
||||
t.Fatalf("expected total production cost 2950, got %v", got)
|
||||
if got := result.TotalPulletCost; got != 1150 {
|
||||
t.Fatalf("expected total pullet cost 1150, got %v", got)
|
||||
}
|
||||
if got := result.TotalProductionCost; got != 1800 {
|
||||
t.Fatalf("expected total production cost 1800, got %v", got)
|
||||
}
|
||||
if len(result.Components) != 1 {
|
||||
t.Fatalf("expected 1 component, got %d", len(result.Components))
|
||||
@@ -190,11 +226,11 @@ func TestHppV2CalculateHppBreakdown_ComposesPakanSlices(t *testing.T) {
|
||||
if component.Parts[0].Proration == nil || component.Parts[0].Proration.Ratio != 0.25 {
|
||||
t.Fatalf("expected growing proration ratio 0.25, got %+v", component.Parts[0].Proration)
|
||||
}
|
||||
if result.Hpp.Estimation.HargaKg != 295 {
|
||||
t.Fatalf("expected estimation harga/kg 295, got %v", result.Hpp.Estimation.HargaKg)
|
||||
if result.Hpp.Estimation.HargaKg != 180 {
|
||||
t.Fatalf("expected estimation harga/kg 180, got %v", result.Hpp.Estimation.HargaKg)
|
||||
}
|
||||
if result.Hpp.Real.HargaKg != 737.5 {
|
||||
t.Fatalf("expected real harga/kg 737.5, got %v", result.Hpp.Real.HargaKg)
|
||||
if result.Hpp.Real.HargaKg != 450 {
|
||||
t.Fatalf("expected real harga/kg 450, got %v", result.Hpp.Real.HargaKg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,11 +372,14 @@ func TestHppV2CalculateHppBreakdown_IncludesOvkComponent(t *testing.T) {
|
||||
if componentTotals[hppV2ComponentOvk] != 450 {
|
||||
t.Fatalf("expected ovk total 450, got %v", componentTotals[hppV2ComponentOvk])
|
||||
}
|
||||
if result.TotalProductionCost != 950 {
|
||||
t.Fatalf("expected total production cost 950, got %v", result.TotalProductionCost)
|
||||
if result.TotalPulletCost != 250 {
|
||||
t.Fatalf("expected total pullet cost 250, got %v", result.TotalPulletCost)
|
||||
}
|
||||
if result.Hpp.Estimation.HargaKg != 79.17 {
|
||||
t.Fatalf("expected estimation harga/kg 79.17, got %v", result.Hpp.Estimation.HargaKg)
|
||||
if result.TotalProductionCost != 700 {
|
||||
t.Fatalf("expected total production cost 700, got %v", result.TotalProductionCost)
|
||||
}
|
||||
if result.Hpp.Estimation.HargaKg != 58.33 {
|
||||
t.Fatalf("expected estimation harga/kg 58.33, got %v", result.Hpp.Estimation.HargaKg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,11 +542,204 @@ func TestHppV2CalculateHppBreakdown_IncludesBopRegularAndEkspedisi(t *testing.T)
|
||||
if componentTotals[hppV2ComponentBopEksp] != 88 {
|
||||
t.Fatalf("expected expedition BOP total 88, got %v", componentTotals[hppV2ComponentBopEksp])
|
||||
}
|
||||
if result.TotalProductionCost != 358 {
|
||||
t.Fatalf("expected total production cost 358, got %v", result.TotalProductionCost)
|
||||
if result.TotalPulletCost != 190 {
|
||||
t.Fatalf("expected total pullet cost 190, got %v", result.TotalPulletCost)
|
||||
}
|
||||
if result.Hpp.Estimation.HargaKg != 119.33 {
|
||||
t.Fatalf("expected estimation harga/kg 119.33, got %v", result.Hpp.Estimation.HargaKg)
|
||||
if result.TotalProductionCost != 168 {
|
||||
t.Fatalf("expected total production cost 168, got %v", result.TotalProductionCost)
|
||||
}
|
||||
if result.Hpp.Estimation.HargaKg != 56 {
|
||||
t.Fatalf("expected estimation harga/kg 56, got %v", result.Hpp.Estimation.HargaKg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHppV2CalculateHppBreakdown_AddsDepreciationForNormalTransfer(t *testing.T) {
|
||||
sourceChickIn := mustTime(t, "2026-01-01")
|
||||
reportDate := sourceChickIn.AddDate(0, 0, 154)
|
||||
|
||||
repo := &hppV2RepoStub{
|
||||
contextByPFK: map[uint]*commonRepo.HppV2ProjectFlockKandangContext{
|
||||
50: {
|
||||
ProjectFlockKandangID: 50,
|
||||
ProjectFlockID: 10,
|
||||
ProjectFlockCategory: "LAYING",
|
||||
KandangID: 500,
|
||||
KandangName: "Kandang F",
|
||||
LocationID: 21,
|
||||
HouseType: "close_house",
|
||||
},
|
||||
},
|
||||
pfkIDsByProject: map[uint][]uint{
|
||||
11: {501},
|
||||
},
|
||||
latestTransferByPFK: map[uint]*commonRepo.HppV2LatestTransferInputRow{
|
||||
50: {
|
||||
ProjectFlockKandangID: 50,
|
||||
SourceProjectFlockID: 11,
|
||||
TransferDate: mustTime(t, "2026-05-20"),
|
||||
TransferQty: 100,
|
||||
TransferID: 701,
|
||||
},
|
||||
},
|
||||
chickInDateByProject: map[uint]*time.Time{
|
||||
11: &sourceChickIn,
|
||||
},
|
||||
depreciationByHouse: map[string]map[int]float64{
|
||||
"close_house": {
|
||||
1: 10,
|
||||
},
|
||||
},
|
||||
usageRowsByKey: map[string][]commonRepo.HppV2UsageCostRow{
|
||||
stubKey([]uint{501}, []string{"PAKAN"}): {
|
||||
{StockableType: "purchase_items", StockableID: 9301, SourceProductID: 41, SourceProductName: "Pakan Growing", Qty: 25, UnitPrice: 40, TotalCost: 1000},
|
||||
},
|
||||
},
|
||||
totalPopulationByKey: map[string]float64{
|
||||
stubKey([]uint{501}, nil): 100,
|
||||
},
|
||||
transferSummaryByPFK: map[uint]struct {
|
||||
projectFlockID uint
|
||||
totalQty float64
|
||||
}{
|
||||
50: {projectFlockID: 11, totalQty: 100},
|
||||
},
|
||||
eggProductionByPFK: map[uint]struct {
|
||||
pieces float64
|
||||
kg float64
|
||||
}{
|
||||
50: {pieces: 20, kg: 10},
|
||||
},
|
||||
}
|
||||
|
||||
svc := NewHppV2Service(repo)
|
||||
result, err := svc.CalculateHppBreakdown(50, &reportDate)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if result.TotalPulletCost != 1000 {
|
||||
t.Fatalf("expected total pullet cost 1000, got %v", result.TotalPulletCost)
|
||||
}
|
||||
if result.TotalProductionCost != 100 {
|
||||
t.Fatalf("expected total production cost 100, got %v", result.TotalProductionCost)
|
||||
}
|
||||
|
||||
var depreciation *HppV2Component
|
||||
for i := range result.Components {
|
||||
if result.Components[i].Code == hppV2ComponentDepreciation {
|
||||
depreciation = &result.Components[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if depreciation == nil {
|
||||
t.Fatal("expected depreciation component")
|
||||
}
|
||||
if depreciation.Total != 100 {
|
||||
t.Fatalf("expected depreciation total 100, got %v", depreciation.Total)
|
||||
}
|
||||
if len(depreciation.Parts) != 1 {
|
||||
t.Fatalf("expected single depreciation part, got %d", len(depreciation.Parts))
|
||||
}
|
||||
if depreciation.Parts[0].Details["schedule_day"] != 1 {
|
||||
t.Fatalf("expected schedule day 1, got %+v", depreciation.Parts[0].Details)
|
||||
}
|
||||
if depreciation.Parts[0].Details["origin_date"] != "2026-01-01" {
|
||||
t.Fatalf("expected origin date 2026-01-01, got %+v", depreciation.Parts[0].Details)
|
||||
}
|
||||
if result.Hpp.Estimation.HargaKg != 10 {
|
||||
t.Fatalf("expected estimation harga/kg 10, got %v", result.Hpp.Estimation.HargaKg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHppV2CalculateHppBreakdown_AddsDepreciationForManualCutoverFromCutoverDate(t *testing.T) {
|
||||
originDate := mustTime(t, "2026-01-01")
|
||||
cutoverDate := originDate.AddDate(0, 0, 155)
|
||||
|
||||
repo := &hppV2RepoStub{
|
||||
contextByPFK: map[uint]*commonRepo.HppV2ProjectFlockKandangContext{
|
||||
60: {
|
||||
ProjectFlockKandangID: 60,
|
||||
ProjectFlockID: 12,
|
||||
ProjectFlockCategory: "LAYING",
|
||||
KandangID: 600,
|
||||
KandangName: "Kandang G",
|
||||
LocationID: 22,
|
||||
HouseType: "close_house",
|
||||
},
|
||||
},
|
||||
pfkIDsByProject: map[uint][]uint{
|
||||
12: {60},
|
||||
},
|
||||
manualInputByProject: map[uint]*commonRepo.HppV2ManualDepreciationInputRow{
|
||||
12: {
|
||||
ID: 801,
|
||||
ProjectFlockID: 12,
|
||||
TotalCost: 1000,
|
||||
CutoverDate: cutoverDate,
|
||||
},
|
||||
},
|
||||
chickInDateByProject: map[uint]*time.Time{
|
||||
12: &originDate,
|
||||
},
|
||||
depreciationByHouse: map[string]map[int]float64{
|
||||
"close_house": {
|
||||
1: 10,
|
||||
2: 20,
|
||||
},
|
||||
},
|
||||
totalPopulationByKey: map[string]float64{
|
||||
stubKey([]uint{60}, nil): 100,
|
||||
},
|
||||
eggProductionByPFK: map[uint]struct {
|
||||
pieces float64
|
||||
kg float64
|
||||
}{
|
||||
60: {pieces: 20, kg: 10},
|
||||
},
|
||||
}
|
||||
|
||||
svc := NewHppV2Service(repo)
|
||||
result, err := svc.CalculateHppBreakdown(60, &cutoverDate)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if result.TotalPulletCost != 1000 {
|
||||
t.Fatalf("expected total pullet cost 1000, got %v", result.TotalPulletCost)
|
||||
}
|
||||
if result.TotalProductionCost != 200 {
|
||||
t.Fatalf("expected total production cost 200, got %v", result.TotalProductionCost)
|
||||
}
|
||||
|
||||
componentTotals := map[string]float64{}
|
||||
for _, component := range result.Components {
|
||||
componentTotals[component.Code] = component.Total
|
||||
}
|
||||
if componentTotals[hppV2ComponentManualPulletCost] != 1000 {
|
||||
t.Fatalf("expected manual pullet cost 1000, got %v", componentTotals[hppV2ComponentManualPulletCost])
|
||||
}
|
||||
if componentTotals[hppV2ComponentDepreciation] != 200 {
|
||||
t.Fatalf("expected depreciation 200, got %v", componentTotals[hppV2ComponentDepreciation])
|
||||
}
|
||||
|
||||
var depreciation *HppV2Component
|
||||
for i := range result.Components {
|
||||
if result.Components[i].Code == hppV2ComponentDepreciation {
|
||||
depreciation = &result.Components[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if depreciation == nil || len(depreciation.Parts) != 1 {
|
||||
t.Fatalf("expected one depreciation part, got %+v", depreciation)
|
||||
}
|
||||
if depreciation.Parts[0].Details["schedule_day"] != 2 {
|
||||
t.Fatalf("expected schedule day 2, got %+v", depreciation.Parts[0].Details)
|
||||
}
|
||||
if depreciation.Parts[0].Details["start_schedule_day"] != 2 {
|
||||
t.Fatalf("expected start schedule day 2, got %+v", depreciation.Parts[0].Details)
|
||||
}
|
||||
if result.Hpp.Estimation.HargaKg != 20 {
|
||||
t.Fatalf("expected estimation harga/kg 20, got %v", result.Hpp.Estimation.HargaKg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user