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
@@ -416,6 +416,13 @@ func (s *repportService) UpsertExpenseDepreciationManualInput(ctx *fiber.Ctx, re
if err := s.ExpenseDepreciationRepo.UpsertManualInput(ctx.Context(), &row); err != nil {
return nil, err
}
if err := s.ExpenseDepreciationRepo.DeleteSnapshotsFromDate(
ctx.Context(),
cutoverDate,
[]uint{row.ProjectFlockId},
); err != nil {
return nil, err
}
response := &dto.ExpenseDepreciationManualInputRowDTO{
ID: int64(row.Id),
@@ -456,6 +463,11 @@ type depreciationKandangComponent struct {
TransferQty float64 `json:"transfer_qty"`
PulletCostDayN float64 `json:"pullet_cost_day_n"`
DepreciationValue float64 `json:"depreciation_value"`
DepreciationSource string `json:"depreciation_source,omitempty"`
ManualInputID *uint `json:"manual_input_id,omitempty"`
CutoverDate string `json:"cutover_date,omitempty"`
OriginDate string `json:"origin_date,omitempty"`
StartScheduleDay *int `json:"start_schedule_day,omitempty"`
}
type depreciationFarmComponents struct {
@@ -469,124 +481,98 @@ func (s *repportService) computeExpenseDepreciationSnapshots(
farmIDs []uint,
farmNameByID map[uint]string,
) ([]entity.FarmDepreciationSnapshot, error) {
_ = farmNameByID
if len(farmIDs) == 0 {
return []entity.FarmDepreciationSnapshot{}, nil
}
inputRows, err := s.ExpenseDepreciationRepo.GetLatestTransferInputsByFarms(ctx, periodDate, farmIDs)
if err != nil {
return nil, err
if s.HppCostRepo == nil {
return nil, errors.New("hpp cost repository is not configured")
}
groupedByFarm := make(map[uint][]repportRepo.FarmDepreciationLatestTransferRow, len(farmIDs))
houseTypeSet := make(map[string]struct{})
maxDay := 0
for _, row := range inputRows {
groupedByFarm[row.ProjectFlockID] = append(groupedByFarm[row.ProjectFlockID], row)
dayN := approvalService.DepreciationScheduleDay(row.TransferDate, periodDate, valueOrEmptyString(row.HouseType))
if dayN > maxDay {
maxDay = dayN
}
houseType := approvalService.NormalizeDepreciationHouseType(valueOrEmptyString(row.HouseType))
if houseType != "" {
houseTypeSet[houseType] = struct{}{}
}
if s.HppV2Svc == nil {
return nil, errors.New("hpp v2 service is not configured")
}
houseTypes := make([]string, 0, len(houseTypeSet))
for houseType := range houseTypeSet {
houseTypes = append(houseTypes, houseType)
}
sort.Strings(houseTypes)
percentByHouseType, err := s.ExpenseDepreciationRepo.GetDepreciationPercents(ctx, houseTypes, maxDay)
if err != nil {
return nil, err
}
type sourceCostCacheItem struct {
totalDepCost float64
}
sourceCostCache := make(map[string]sourceCostCacheItem)
sourcePopulationCache := make(map[uint]float64)
result := make([]entity.FarmDepreciationSnapshot, 0, len(farmIDs))
for _, farmID := range farmIDs {
farmRows := groupedByFarm[farmID]
kandangIDs, err := s.HppCostRepo.GetProjectFlockKandangIDs(ctx, farmID)
if err != nil {
return nil, err
}
components := depreciationFarmComponents{
KandangCount: len(farmRows),
Kandang: make([]depreciationKandangComponent, 0, len(farmRows)),
Kandang: make([]depreciationKandangComponent, 0, len(kandangIDs)),
}
totalDepreciationValue := 0.0
totalPulletCostDayN := 0.0
for _, row := range farmRows {
dayN := approvalService.DepreciationScheduleDay(row.TransferDate, periodDate, valueOrEmptyString(row.HouseType))
houseType := approvalService.NormalizeDepreciationHouseType(valueOrEmptyString(row.HouseType))
for _, kandangID := range kandangIDs {
breakdown, err := s.HppV2Svc.CalculateHppBreakdown(kandangID, &periodDate)
if err != nil {
return nil, err
}
if breakdown == nil {
continue
}
transferDateKey := row.TransferDate.Format("2006-01-02")
cacheKey := fmt.Sprintf("%d|%s", row.SourceProjectFlockID, transferDateKey)
cached, exists := sourceCostCache[cacheKey]
if !exists {
endOfDay := row.TransferDate.Add(24 * time.Hour)
sourceDepCost, calcErr := s.HppSvc.GetTotalDepresiasiFlockGrowing(row.SourceProjectFlockID, &endOfDay)
if calcErr != nil {
return nil, calcErr
depreciationComponent := hppV2FindDepreciationComponent(breakdown)
if depreciationComponent == nil {
continue
}
for _, part := range depreciationComponent.Parts {
if part.Total <= 0 {
continue
}
cached = sourceCostCacheItem{totalDepCost: sourceDepCost}
sourceCostCache[cacheKey] = cached
}
sourcePopulation, popExists := sourcePopulationCache[row.SourceProjectFlockID]
if !popExists {
if s.HppCostRepo == nil {
sourcePopulation = 0
} else {
kandangIDs, idsErr := s.HppCostRepo.GetProjectFlockKandangIDs(ctx, row.SourceProjectFlockID)
if idsErr != nil {
return nil, idsErr
}
population, popErr := s.HppCostRepo.GetTotalPopulation(ctx, kandangIDs)
if popErr != nil {
return nil, popErr
}
sourcePopulation = population
houseType := approvalService.NormalizeDepreciationHouseType(breakdown.HouseType)
component := depreciationKandangComponent{
ProjectFlockKandangID: breakdown.ProjectFlockKandangID,
KandangID: breakdown.KandangID,
KandangName: breakdown.KandangName,
SourceProjectFlockID: hppV2DetailUint(part.Details, "source_project_flock_id"),
HouseType: houseType,
DayN: hppV2DetailInt(part.Details, "schedule_day"),
DepreciationPercent: hppV2DetailFloat(part.Details, "depreciation_percent"),
PulletCostDayN: hppV2DetailFloat(part.Details, "pullet_cost_day_n"),
DepreciationValue: part.Total,
DepreciationSource: part.Code,
OriginDate: hppV2DetailString(part.Details, "origin_date"),
}
sourcePopulationCache[row.SourceProjectFlockID] = sourcePopulation
if component.HouseType == "" {
component.HouseType = approvalService.NormalizeDepreciationHouseType(hppV2DetailString(part.Details, "house_type"))
}
if ref := hppV2FindReference(part.References, "laying_transfer"); ref != nil {
component.TransferID = ref.ID
component.TransferDate = ref.Date
component.TransferQty = ref.Qty
}
if part.Code == "manual_cutover" {
if startDay := hppV2DetailInt(part.Details, "start_schedule_day"); startDay > 0 {
component.StartScheduleDay = &startDay
}
component.CutoverDate = hppV2DetailString(part.Details, "cutover_date")
if manualID := hppV2DetailUint(part.Details, "manual_input_id"); manualID > 0 {
component.ManualInputID = &manualID
}
if component.ManualInputID == nil {
if ref := hppV2FindReference(part.References, "farm_depreciation_manual_input"); ref != nil && ref.ID > 0 {
manualID := ref.ID
component.ManualInputID = &manualID
}
}
}
totalPulletCostDayN += component.PulletCostDayN
totalDepreciationValue += component.DepreciationValue
components.Kandang = append(components.Kandang, component)
}
initialPulletCost := 0.0
if sourcePopulation > 0 {
initialPulletCost = (cached.totalDepCost * row.TransferQty) / sourcePopulation
}
pulletCostDayN, depreciationValue, depreciationPercent := approvalService.CalculateDepreciationAtDayN(
initialPulletCost,
dayN,
houseType,
percentByHouseType,
)
totalPulletCostDayN += pulletCostDayN
totalDepreciationValue += depreciationValue
components.Kandang = append(components.Kandang, depreciationKandangComponent{
ProjectFlockKandangID: row.ProjectFlockKandangID,
KandangID: row.KandangID,
KandangName: row.KandangName,
TransferID: row.TransferID,
TransferDate: row.TransferDate.Format("2006-01-02"),
SourceProjectFlockID: row.SourceProjectFlockID,
HouseType: houseType,
DayN: dayN,
DepreciationPercent: depreciationPercent,
TransferQty: row.TransferQty,
PulletCostDayN: pulletCostDayN,
DepreciationValue: depreciationValue,
})
}
components.KandangCount = len(components.Kandang)
effectivePercent := approvalService.CalculateEffectiveDepreciationPercent(totalDepreciationValue, totalPulletCostDayN)
componentsJSON, marshalErr := json.Marshal(components)
@@ -607,6 +593,106 @@ func (s *repportService) computeExpenseDepreciationSnapshots(
return result, nil
}
func hppV2FindDepreciationComponent(breakdown *approvalService.HppV2Breakdown) *approvalService.HppV2Component {
if breakdown == nil {
return nil
}
for idx := range breakdown.Components {
if breakdown.Components[idx].Code == "DEPRECIATION" {
return &breakdown.Components[idx]
}
}
return nil
}
func hppV2FindReference(references []approvalService.HppV2Reference, refType string) *approvalService.HppV2Reference {
if refType == "" {
return nil
}
for idx := range references {
if references[idx].Type == refType {
return &references[idx]
}
}
return nil
}
func hppV2DetailFloat(details map[string]any, key string) float64 {
if details == nil || key == "" {
return 0
}
raw, exists := details[key]
if !exists || raw == nil {
return 0
}
switch value := raw.(type) {
case float64:
return value
case float32:
return float64(value)
case int:
return float64(value)
case int8:
return float64(value)
case int16:
return float64(value)
case int32:
return float64(value)
case int64:
return float64(value)
case uint:
return float64(value)
case uint8:
return float64(value)
case uint16:
return float64(value)
case uint32:
return float64(value)
case uint64:
return float64(value)
case string:
parsed, err := strconv.ParseFloat(strings.TrimSpace(value), 64)
if err != nil {
return 0
}
return parsed
default:
return 0
}
}
func hppV2DetailInt(details map[string]any, key string) int {
return int(math.Round(hppV2DetailFloat(details, key)))
}
func hppV2DetailUint(details map[string]any, key string) uint {
value := hppV2DetailInt(details, key)
if value < 0 {
return 0
}
return uint(value)
}
func hppV2DetailString(details map[string]any, key string) string {
if details == nil || key == "" {
return ""
}
raw, exists := details[key]
if !exists || raw == nil {
return ""
}
switch value := raw.(type) {
case string:
return value
case time.Time:
return value.Format("2006-01-02")
default:
return fmt.Sprintf("%v", value)
}
}
func parseSnapshotComponents(raw []byte) any {
if len(raw) == 0 {
return map[string]any{}
@@ -2280,13 +2366,15 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
}
if hppCost != nil {
eggPiecesFloatRemaining = hppCost.Estimation.Butir - hppCost.Real.Butir
eggHpp = hppCost.Estimation.HargaKg
// eggHpp = hppCost.Estimation.HargaKg
eggHpp = hppCost.Real.HargaKg
eggTotalPiecesFloat = hppCost.Estimation.Butir
eggWeightFloat = hppCost.Estimation.Kg
if eggTotalPiecesFloat > 0 {
avgWeight = eggWeightFloat / eggTotalPiecesFloat
}
eggRemainingWeightFloatRemaining = avgWeight * eggPiecesFloatRemaining
// eggRemainingWeightFloatRemaining = avgWeight * eggPiecesFloatRemaining
eggRemainingWeightFloatRemaining = hppCost.Estimation.Kg - hppCost.Real.Kg
}
}
if math.IsNaN(eggPiecesFloatRemaining) || math.IsInf(eggPiecesFloatRemaining, 0) {