mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
adjust common hpp v2
This commit is contained in:
@@ -89,11 +89,21 @@ type HppV2ManualDepreciationInputRow struct {
|
||||
Note *string
|
||||
}
|
||||
|
||||
type HppV2FarmDepreciationSnapshotRow struct {
|
||||
ID uint
|
||||
ProjectFlockID uint
|
||||
PeriodDate time.Time
|
||||
DepreciationPercentEffective float64
|
||||
DepreciationValue float64
|
||||
PulletCostDayNTotal float64
|
||||
}
|
||||
|
||||
type HppV2CostRepository interface {
|
||||
GetProjectFlockKandangContext(ctx context.Context, projectFlockKandangId uint) (*HppV2ProjectFlockKandangContext, error)
|
||||
GetProjectFlockKandangIDs(ctx context.Context, projectFlockId uint) ([]uint, error)
|
||||
GetLatestTransferInputByProjectFlockKandangID(ctx context.Context, projectFlockKandangId uint, period time.Time) (*HppV2LatestTransferInputRow, error)
|
||||
GetManualDepreciationInputByProjectFlockID(ctx context.Context, projectFlockID uint) (*HppV2ManualDepreciationInputRow, error)
|
||||
GetFarmDepreciationSnapshotByProjectFlockIDAndPeriod(ctx context.Context, projectFlockID uint, periodDate time.Time) (*HppV2FarmDepreciationSnapshotRow, error)
|
||||
GetEarliestChickInDateByProjectFlockID(ctx context.Context, projectFlockID uint) (*time.Time, error)
|
||||
GetDepreciationPercents(ctx context.Context, houseTypes []string, maxDay int) (map[string]map[int]float64, error)
|
||||
ListUsageCostRowsByProductFlags(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string, date *time.Time) ([]HppV2UsageCostRow, error)
|
||||
@@ -239,6 +249,29 @@ func (r *HppV2RepositoryImpl) GetManualDepreciationInputByProjectFlockID(
|
||||
return &row, nil
|
||||
}
|
||||
|
||||
func (r *HppV2RepositoryImpl) GetFarmDepreciationSnapshotByProjectFlockIDAndPeriod(
|
||||
ctx context.Context,
|
||||
projectFlockID uint,
|
||||
periodDate time.Time,
|
||||
) (*HppV2FarmDepreciationSnapshotRow, error) {
|
||||
var row HppV2FarmDepreciationSnapshotRow
|
||||
err := r.db.WithContext(ctx).
|
||||
Table("farm_depreciation_snapshots").
|
||||
Select("id, project_flock_id, period_date, depreciation_percent_effective, depreciation_value, pullet_cost_day_n_total").
|
||||
Where("project_flock_id = ?", projectFlockID).
|
||||
Where("period_date = DATE(?)", periodDate).
|
||||
Limit(1).
|
||||
Take(&row).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &row, nil
|
||||
}
|
||||
|
||||
func (r *HppV2RepositoryImpl) GetEarliestChickInDateByProjectFlockID(ctx context.Context, projectFlockID uint) (*time.Time, error) {
|
||||
type row struct {
|
||||
ChickInDate *time.Time
|
||||
@@ -327,11 +360,11 @@ func (r *HppV2RepositoryImpl) ListUsageCostRowsByProductFlags(
|
||||
COALESCE(pi.product_id, ast_pw.product_id, 0) AS source_product_id,
|
||||
COALESCE(pi_prod.name, ast_prod.name, '') AS source_product_name,
|
||||
COALESCE(SUM(sa.qty), 0) AS qty,
|
||||
CASE
|
||||
COALESCE(MAX(CASE
|
||||
WHEN sa.stockable_type = ? THEN COALESCE(pi.price, 0)
|
||||
WHEN sa.stockable_type = ? THEN COALESCE(ast.price, 0)
|
||||
ELSE 0
|
||||
END AS unit_price,
|
||||
END), 0) AS unit_price,
|
||||
COALESCE(SUM(sa.qty * CASE
|
||||
WHEN sa.stockable_type = ? THEN COALESCE(pi.price, 0)
|
||||
WHEN sa.stockable_type = ? THEN COALESCE(ast.price, 0)
|
||||
@@ -367,12 +400,7 @@ func (r *HppV2RepositoryImpl) ListUsageCostRowsByProductFlags(
|
||||
sa.stockable_type,
|
||||
sa.stockable_id,
|
||||
COALESCE(pi.product_id, ast_pw.product_id, 0),
|
||||
COALESCE(pi_prod.name, ast_prod.name, ''),
|
||||
CASE
|
||||
WHEN sa.stockable_type = '` + stockablePurchase + `' THEN COALESCE(pi.price, 0)
|
||||
WHEN sa.stockable_type = '` + stockableAdjustment + `' THEN COALESCE(ast.price, 0)
|
||||
ELSE 0
|
||||
END
|
||||
COALESCE(pi_prod.name, ast_prod.name, '')
|
||||
`).
|
||||
Order("MIN(r.record_datetime) ASC, sa.stockable_type ASC, sa.stockable_id ASC").
|
||||
Scan(&rows).Error
|
||||
@@ -417,7 +445,7 @@ func (r *HppV2RepositoryImpl) ListAdjustmentCostRowsByProductFlags(
|
||||
Joins("JOIN products AS p ON p.id = pw.product_id").
|
||||
Joins("JOIN warehouses AS w ON w.id = pw.warehouse_id").
|
||||
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||
Where("ast.created_at <= ?", *date).
|
||||
// Where("ast.created_at <= ?", *date).
|
||||
Where("COALESCE(ast.total_qty, 0) > 0").
|
||||
Where("EXISTS (SELECT 1 FROM flags f WHERE f.flagable_id = pw.product_id AND f.flagable_type = ? AND f.name IN ?)", entity.FlagableTypeProduct, flagNames).
|
||||
Order("ast.created_at ASC, ast.id ASC").
|
||||
@@ -450,6 +478,15 @@ func (r *HppV2RepositoryImpl) ListChickinCostRowsByProductFlags(
|
||||
stockableTransferToLaying := fifo.StockableKeyTransferToLayingIn.String()
|
||||
usableProjectChickin := fifo.UsableKeyProjectChickin.String()
|
||||
usableStockTransferOut := fifo.UsableKeyStockTransferOut.String()
|
||||
unitPriceExpr := fmt.Sprintf(`
|
||||
CASE
|
||||
WHEN sa.stockable_type = '%s' THEN COALESCE(pi.price, 0)
|
||||
WHEN sa.stockable_type = '%s' THEN COALESCE(ast.price, 0)
|
||||
WHEN sa.stockable_type = '%s' THEN COALESCE(spi.price, sast.price, 0)
|
||||
WHEN sa.stockable_type = '%s' THEN COALESCE(tpi.price, tast.price, 0)
|
||||
ELSE 0
|
||||
END
|
||||
`, stockablePurchase, stockableAdjustment, stockableTransferIn, stockableTransferToLaying)
|
||||
|
||||
rows := make([]HppV2ChickinCostRow, 0)
|
||||
query := r.db.WithContext(ctx).
|
||||
@@ -479,30 +516,9 @@ func (r *HppV2RepositoryImpl) ListChickinCostRowsByProductFlags(
|
||||
''
|
||||
) AS source_product_name,
|
||||
COALESCE(SUM(sa.qty), 0) AS qty,
|
||||
CASE
|
||||
WHEN sa.stockable_type = ? THEN COALESCE(pi.price, 0)
|
||||
WHEN sa.stockable_type = ? THEN COALESCE(ast.price, 0)
|
||||
WHEN sa.stockable_type = ? THEN COALESCE(spi.price, sast.price, 0)
|
||||
WHEN sa.stockable_type = ? THEN COALESCE(tpi.price, tast.price, 0)
|
||||
ELSE 0
|
||||
END AS unit_price,
|
||||
COALESCE(SUM(sa.qty * CASE
|
||||
WHEN sa.stockable_type = ? THEN COALESCE(pi.price, 0)
|
||||
WHEN sa.stockable_type = ? THEN COALESCE(ast.price, 0)
|
||||
WHEN sa.stockable_type = ? THEN COALESCE(spi.price, sast.price, 0)
|
||||
WHEN sa.stockable_type = ? THEN COALESCE(tpi.price, tast.price, 0)
|
||||
ELSE 0
|
||||
END), 0) AS total_cost
|
||||
`,
|
||||
stockablePurchase,
|
||||
stockableAdjustment,
|
||||
stockableTransferIn,
|
||||
stockableTransferToLaying,
|
||||
stockablePurchase,
|
||||
stockableAdjustment,
|
||||
stockableTransferIn,
|
||||
stockableTransferToLaying,
|
||||
).
|
||||
`+unitPriceExpr+` AS unit_price,
|
||||
COALESCE(SUM(sa.qty * (`+unitPriceExpr+`)), 0) AS total_cost
|
||||
`).
|
||||
Joins(
|
||||
"JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = pc.id AND sa.status = ? AND sa.allocation_purpose = ?",
|
||||
usableProjectChickin,
|
||||
@@ -563,7 +579,7 @@ func (r *HppV2RepositoryImpl) ListChickinCostRowsByProductFlags(
|
||||
}
|
||||
|
||||
err := query.
|
||||
Group(`
|
||||
Group(fmt.Sprintf(`
|
||||
pc.id,
|
||||
pc.project_flock_kandang_id,
|
||||
pc.chick_in_date,
|
||||
@@ -587,14 +603,8 @@ func (r *HppV2RepositoryImpl) ListChickinCostRowsByProductFlags(
|
||||
sast_prod.name,
|
||||
''
|
||||
),
|
||||
CASE
|
||||
WHEN sa.stockable_type = '` + stockablePurchase + `' THEN COALESCE(pi.price, 0)
|
||||
WHEN sa.stockable_type = '` + stockableAdjustment + `' THEN COALESCE(ast.price, 0)
|
||||
WHEN sa.stockable_type = '` + stockableTransferIn + `' THEN COALESCE(spi.price, sast.price, 0)
|
||||
WHEN sa.stockable_type = '` + stockableTransferToLaying + `' THEN COALESCE(tpi.price, tast.price, 0)
|
||||
ELSE 0
|
||||
END
|
||||
`).
|
||||
%s
|
||||
`, unitPriceExpr)).
|
||||
Order("pc.chick_in_date ASC, pc.id ASC, sa.stockable_type ASC, sa.stockable_id ASC").
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
|
||||
@@ -2,7 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
@@ -40,108 +39,91 @@ func NewHppService(hppRepo commonRepo.HppCostRepository) HppService {
|
||||
}
|
||||
|
||||
func (s *hppService) CalculateHppCost(projectFlockKandangId uint, date *time.Time) (*HppCostResponse, error) {
|
||||
logHpp("CalculateHppCost", "start project_flock_kandang_id=%d input_date=%s", projectFlockKandangId, formatTimePtr(date))
|
||||
if date == nil {
|
||||
now := time.Now()
|
||||
date = &now
|
||||
}
|
||||
logHpp("CalculateHppCost", "normalized_date=%s", formatTimePtr(date))
|
||||
|
||||
location, err := time.LoadLocation("Asia/Jakarta")
|
||||
if err != nil {
|
||||
logHpp("CalculateHppCost", "load_location_error=%v", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
logHpp("CalculateHppCost", "location=%s", location.String())
|
||||
|
||||
startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, location)
|
||||
endOfDay := startOfDay.Add(24 * time.Hour)
|
||||
logHpp("CalculateHppCost", "start_of_day=%s end_of_day=%s", startOfDay.Format(time.RFC3339), endOfDay.Format(time.RFC3339))
|
||||
|
||||
depresiasiTransfer, err := s.GetDepresiasiTransfer(projectFlockKandangId, &endOfDay)
|
||||
if err != nil {
|
||||
logHpp("CalculateHppCost", "get_depresiasi_transfer_error=%v", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
logHpp("CalculateHppCost", "depresiasi_transfer=%f", depresiasiTransfer)
|
||||
|
||||
totalProductionCost, err := s.GetTotalProductionCost(projectFlockKandangId, &endOfDay, depresiasiTransfer)
|
||||
if err != nil {
|
||||
logHpp("CalculateHppCost", "get_total_production_cost_error=%v", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
logHpp("CalculateHppCost", "total_production_cost=%f", totalProductionCost)
|
||||
result, err := s.GetHppEstimationDanRealisasi(totalProductionCost, projectFlockKandangId, &startOfDay, &endOfDay)
|
||||
if err != nil {
|
||||
logHpp("CalculateHppCost", "get_hpp_estimation_dan_realisasi_error=%v", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
logHpp("CalculateHppCost", "done estimation=%+v real=%+v", result.Estimation, result.Real)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *hppService) GetTotalDepresiasiFlockGrowing(sourceProjectFlockID uint, date *time.Time) (float64, error) {
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "start source_project_flock_id=%d input_date=%s", sourceProjectFlockID, formatTimePtr(date))
|
||||
if date == nil {
|
||||
now := time.Now()
|
||||
date = &now
|
||||
}
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "normalized_date=%s", formatTimePtr(date))
|
||||
|
||||
if s.hppRepo == nil {
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "repo_nil return=0")
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
kandangIDs, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), sourceProjectFlockID)
|
||||
if err != nil {
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "get_project_flock_kandang_ids_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "kandang_ids=%v", kandangIDs)
|
||||
|
||||
docCost, err := s.hppRepo.GetDocCost(context.Background(), kandangIDs)
|
||||
if err != nil {
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "get_doc_cost_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "doc_cost=%f", docCost)
|
||||
|
||||
budgetCost, err := s.hppRepo.GetBudgetCostByProjectFlockId(context.Background(), sourceProjectFlockID)
|
||||
if err != nil {
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "get_budget_cost_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "budget_cost=%f", budgetCost)
|
||||
|
||||
expedisionCost, err := s.hppRepo.GetExpedisionCost(context.Background(), kandangIDs)
|
||||
if err != nil {
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "get_expedision_cost_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "expedision_cost=%f", expedisionCost)
|
||||
|
||||
feedCost, err := s.hppRepo.GetFeedUsageCost(context.Background(), kandangIDs, date)
|
||||
if err != nil {
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "get_feed_usage_cost_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "feed_cost=%f", feedCost)
|
||||
|
||||
ovkCost, err := s.hppRepo.GetOvkUsageCost(context.Background(), kandangIDs, date)
|
||||
if err != nil {
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "get_ovk_usage_cost_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "ovk_cost=%f", ovkCost)
|
||||
|
||||
total := docCost + budgetCost + expedisionCost + feedCost + ovkCost
|
||||
logHpp("GetTotalDepresiasiFlockGrowing", "done total=%f", total)
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (s *hppService) GetTotalProductionCost(projectFlockKandangId uint, endDate *time.Time, depresiasiTransfer float64) (float64, error) {
|
||||
logHpp("GetTotalProductionCost", "start project_flock_kandang_id=%d end_date=%s depresiasi_transfer=%f", projectFlockKandangId, formatTimePtr(endDate), depresiasiTransfer)
|
||||
// if date == nil {
|
||||
// now := time.Now()
|
||||
// date = &now
|
||||
@@ -149,248 +131,210 @@ func (s *hppService) GetTotalProductionCost(projectFlockKandangId uint, endDate
|
||||
|
||||
costPullet, err := s.hppRepo.GetPulletCost(context.Background(), projectFlockKandangId)
|
||||
if err != nil {
|
||||
logHpp("GetTotalProductionCost", "get_pullet_cost_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetTotalProductionCost", "cost_pullet=%f", costPullet)
|
||||
|
||||
costFeed, err := s.hppRepo.GetFeedUsageCost(context.Background(), []uint{projectFlockKandangId}, endDate)
|
||||
if err != nil {
|
||||
logHpp("GetTotalProductionCost", "get_feed_usage_cost_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetTotalProductionCost", "cost_feed=%f", costFeed)
|
||||
|
||||
costOvk, err := s.hppRepo.GetOvkUsageCost(context.Background(), []uint{projectFlockKandangId}, endDate)
|
||||
if err != nil {
|
||||
logHpp("GetTotalProductionCost", "get_ovk_usage_cost_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetTotalProductionCost", "cost_ovk=%f", costOvk)
|
||||
|
||||
costExpedision, err := s.hppRepo.GetExpedisionCost(context.Background(), []uint{projectFlockKandangId})
|
||||
if err != nil {
|
||||
logHpp("GetTotalProductionCost", "get_expedision_cost_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetTotalProductionCost", "cost_expedision=%f", costExpedision)
|
||||
|
||||
costBudget, err := s.GetBudgetKandangLaying(projectFlockKandangId, endDate)
|
||||
if err != nil {
|
||||
logHpp("GetTotalProductionCost", "get_budget_kandang_laying_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetTotalProductionCost", "cost_budget=%f", costBudget)
|
||||
|
||||
// fmt.Println(costBudget, costExpedision, costOvk, costFeed, costPullet, depresiasiTransfer)
|
||||
|
||||
// depresiasiTransfer = 0
|
||||
|
||||
total := depresiasiTransfer + costPullet + costFeed + costOvk + costExpedision + costBudget
|
||||
logHpp("GetTotalProductionCost", "done total=%f", total)
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (s *hppService) GetBudgetKandangLaying(projectFlockKandangId uint, endDate *time.Time) (float64, error) {
|
||||
logHpp("GetBudgetKandangLaying", "start project_flock_kandang_id=%d end_date=%s", projectFlockKandangId, formatTimePtr(endDate))
|
||||
// if date == nil {
|
||||
// now := time.Now()
|
||||
// date = &now
|
||||
// }
|
||||
|
||||
if s.hppRepo == nil {
|
||||
logHpp("GetBudgetKandangLaying", "repo_nil return=0")
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
projectFlockId, err := s.hppRepo.GetProjectFlockIDByProjectFlockKandangID(context.Background(), projectFlockKandangId)
|
||||
if err != nil {
|
||||
logHpp("GetBudgetKandangLaying", "get_project_flock_id_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetBudgetKandangLaying", "project_flock_id=%d", projectFlockId)
|
||||
|
||||
projectFlockKandangIds, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), projectFlockId)
|
||||
if err != nil {
|
||||
logHpp("GetBudgetKandangLaying", "get_project_flock_kandang_ids_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetBudgetKandangLaying", "project_flock_kandang_ids=%v", projectFlockKandangIds)
|
||||
|
||||
eggProduksiPiecesFlock, _, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), projectFlockKandangIds, endDate)
|
||||
if err != nil {
|
||||
logHpp("GetBudgetKandangLaying", "get_egg_produksi_pieces_flock_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetBudgetKandangLaying", "egg_produksi_pieces_flock=%f", eggProduksiPiecesFlock)
|
||||
|
||||
eggProduksiPiecesKandang, _, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, endDate)
|
||||
if err != nil {
|
||||
logHpp("GetBudgetKandangLaying", "get_egg_produksi_pieces_kandang_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetBudgetKandangLaying", "egg_produksi_pieces_kandang=%f", eggProduksiPiecesKandang)
|
||||
|
||||
totalBudgetCost, err := s.hppRepo.GetBudgetCostByProjectFlockId(context.Background(), projectFlockId)
|
||||
if err != nil {
|
||||
logHpp("GetBudgetKandangLaying", "get_budget_cost_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetBudgetKandangLaying", "total_budget_cost=%f", totalBudgetCost)
|
||||
|
||||
if eggProduksiPiecesFlock == 0 {
|
||||
logHpp("GetBudgetKandangLaying", "egg_produksi_pieces_flock_zero return=0")
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
result := (totalBudgetCost * eggProduksiPiecesKandang) / eggProduksiPiecesFlock
|
||||
logHpp("GetBudgetKandangLaying", "done result=%f", result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *hppService) GetDepresiasiTransfer(projectFlockKandangId uint, endDate *time.Time) (float64, error) {
|
||||
logHpp("GetDepresiasiTransfer", "start project_flock_kandang_id=%d end_date=%s", projectFlockKandangId, formatTimePtr(endDate))
|
||||
if endDate == nil {
|
||||
now := time.Now()
|
||||
endDate = &now
|
||||
}
|
||||
logHpp("GetDepresiasiTransfer", "normalized_end_date=%s", formatTimePtr(endDate))
|
||||
|
||||
if s.hppRepo == nil {
|
||||
logHpp("GetDepresiasiTransfer", "repo_nil return=0")
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
sourceProjectFlockID, transferTotalQty, err := s.hppRepo.GetTransferSourceSummary(context.Background(), projectFlockKandangId)
|
||||
if err != nil {
|
||||
logHpp("GetDepresiasiTransfer", "get_transfer_source_summary_error=%v", err)
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetDepresiasiTransfer", "source_project_flock_id=%d transfer_total_qty=%f", sourceProjectFlockID, transferTotalQty)
|
||||
if sourceProjectFlockID == 0 || transferTotalQty <= 0 {
|
||||
logHpp("GetDepresiasiTransfer", "use_manual_fallback=true")
|
||||
result, fallbackErr := s.getManualDepresiasiTransferFallback(projectFlockKandangId)
|
||||
if fallbackErr != nil {
|
||||
logHpp("GetDepresiasiTransfer", "manual_fallback_error=%v", fallbackErr)
|
||||
return 0, fallbackErr
|
||||
}
|
||||
logHpp("GetDepresiasiTransfer", "done_fallback result=%f", result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
kandangIDsGrowing, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), sourceProjectFlockID)
|
||||
if err != nil {
|
||||
logHpp("GetDepresiasiTransfer", "get_project_flock_kandang_ids_error=%v", err)
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetDepresiasiTransfer", "kandang_ids_growing=%v", kandangIDsGrowing)
|
||||
|
||||
totalPopulationFlockGrowing, err := s.hppRepo.GetTotalPopulation(context.Background(), kandangIDsGrowing)
|
||||
if err != nil {
|
||||
logHpp("GetDepresiasiTransfer", "get_total_population_error=%v", err)
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetDepresiasiTransfer", "total_population_flock_growing=%f", totalPopulationFlockGrowing)
|
||||
if totalPopulationFlockGrowing == 0 {
|
||||
logHpp("GetDepresiasiTransfer", "total_population_flock_growing_zero return=0")
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
totalDepresiasiFlockGrowing, err := s.GetTotalDepresiasiFlockGrowing(sourceProjectFlockID, endDate)
|
||||
if err != nil {
|
||||
logHpp("GetDepresiasiTransfer", "get_total_depresiasi_flock_growing_error=%v", err)
|
||||
return 0, err
|
||||
}
|
||||
logHpp("GetDepresiasiTransfer", "total_depresiasi_flock_growing=%f", totalDepresiasiFlockGrowing)
|
||||
|
||||
result := (totalDepresiasiFlockGrowing * transferTotalQty) / totalPopulationFlockGrowing
|
||||
logHpp("GetDepresiasiTransfer", "done result=%f", result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *hppService) getManualDepresiasiTransferFallback(projectFlockKandangId uint) (float64, error) {
|
||||
logHpp("getManualDepresiasiTransferFallback", "start project_flock_kandang_id=%d", projectFlockKandangId)
|
||||
projectFlockID, err := s.hppRepo.GetProjectFlockIDByProjectFlockKandangID(context.Background(), projectFlockKandangId)
|
||||
if err != nil {
|
||||
logHpp("getManualDepresiasiTransferFallback", "get_project_flock_id_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("getManualDepresiasiTransferFallback", "project_flock_id=%d", projectFlockID)
|
||||
if projectFlockID == 0 {
|
||||
logHpp("getManualDepresiasiTransferFallback", "project_flock_id_zero return=0")
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
manualCost, err := s.hppRepo.GetManualDepreciationCostByProjectFlockID(context.Background(), projectFlockID)
|
||||
if err != nil {
|
||||
logHpp("getManualDepresiasiTransferFallback", "get_manual_depreciation_cost_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("getManualDepresiasiTransferFallback", "manual_cost=%f", manualCost)
|
||||
if manualCost <= 0 {
|
||||
logHpp("getManualDepresiasiTransferFallback", "manual_cost_non_positive return=0")
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
kandangIDs, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), projectFlockID)
|
||||
if err != nil {
|
||||
logHpp("getManualDepresiasiTransferFallback", "get_project_flock_kandang_ids_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("getManualDepresiasiTransferFallback", "kandang_ids=%v", kandangIDs)
|
||||
if len(kandangIDs) == 0 {
|
||||
logHpp("getManualDepresiasiTransferFallback", "kandang_ids_empty return=0")
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
totalUsageQty, err := s.hppRepo.GetTotalPopulation(context.Background(), kandangIDs)
|
||||
if err != nil {
|
||||
logHpp("getManualDepresiasiTransferFallback", "get_total_usage_qty_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("getManualDepresiasiTransferFallback", "total_usage_qty=%f", totalUsageQty)
|
||||
if totalUsageQty <= 0 {
|
||||
logHpp("getManualDepresiasiTransferFallback", "total_usage_qty_non_positive return=0")
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
kandangUsageQty, err := s.hppRepo.GetTotalPopulation(context.Background(), []uint{projectFlockKandangId})
|
||||
if err != nil {
|
||||
logHpp("getManualDepresiasiTransferFallback", "get_kandang_usage_qty_error=%v", err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
logHpp("getManualDepresiasiTransferFallback", "kandang_usage_qty=%f", kandangUsageQty)
|
||||
if kandangUsageQty <= 0 {
|
||||
logHpp("getManualDepresiasiTransferFallback", "kandang_usage_qty_non_positive return=0")
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
result := manualCost * (kandangUsageQty / totalUsageQty)
|
||||
logHpp("getManualDepresiasiTransferFallback", "done result=%f", result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *hppService) GetHppEstimationDanRealisasi(totalProductionCost float64, projectFlockKandangId uint, startDate *time.Time, endDate *time.Time) (*HppCostResponse, error) {
|
||||
logHpp("GetHppEstimationDanRealisasi", "start total_production_cost=%f project_flock_kandang_id=%d start_date=%s end_date=%s", totalProductionCost, projectFlockKandangId, formatTimePtr(startDate), formatTimePtr(endDate))
|
||||
|
||||
if s.hppRepo == nil {
|
||||
logHpp("GetHppEstimationDanRealisasi", "repo_nil return_empty_response")
|
||||
|
||||
return &HppCostResponse{}, nil
|
||||
}
|
||||
|
||||
estimPieces, estimWeightKg, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, endDate)
|
||||
if err != nil {
|
||||
logHpp("GetHppEstimationDanRealisasi", "get_egg_produksi_error=%v", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
logHpp("GetHppEstimationDanRealisasi", "estim_pieces=%f estim_weight_kg=%f", estimPieces, estimWeightKg)
|
||||
|
||||
realPieces, realWeightKg, err := s.hppRepo.GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, startDate, endDate)
|
||||
if err != nil {
|
||||
logHpp("GetHppEstimationDanRealisasi", "get_egg_terjual_error=%v", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
logHpp("GetHppEstimationDanRealisasi", "real_pieces=%f real_weight_kg=%f", realPieces, realWeightKg)
|
||||
|
||||
estimation := HppCostDetail{
|
||||
Total: totalProductionCost,
|
||||
@@ -403,7 +347,6 @@ func (s *hppService) GetHppEstimationDanRealisasi(totalProductionCost float64, p
|
||||
if estimPieces > 0 {
|
||||
estimation.HargaButir = roundToTwoDecimals(totalProductionCost / estimPieces)
|
||||
}
|
||||
logHpp("GetHppEstimationDanRealisasi", "estimation=%+v", estimation)
|
||||
|
||||
real := HppCostDetail{
|
||||
Total: totalProductionCost,
|
||||
@@ -416,19 +359,16 @@ func (s *hppService) GetHppEstimationDanRealisasi(totalProductionCost float64, p
|
||||
if realPieces > 0 {
|
||||
real.HargaButir = roundToTwoDecimals(totalProductionCost / realPieces)
|
||||
}
|
||||
logHpp("GetHppEstimationDanRealisasi", "real=%+v", real)
|
||||
|
||||
result := &HppCostResponse{
|
||||
Estimation: estimation,
|
||||
Real: real,
|
||||
}
|
||||
logHpp("GetHppEstimationDanRealisasi", "done response=%+v", *result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func roundToTwoDecimals(value float64) float64 {
|
||||
result := math.Round(value*100) / 100
|
||||
logHpp("roundToTwoDecimals", "input=%f output=%f", value, result)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -438,7 +378,3 @@ func formatTimePtr(value *time.Time) string {
|
||||
}
|
||||
return value.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func logHpp(method, format string, args ...any) {
|
||||
log.Printf("[HPP][%s] "+format, append([]any{method}, args...)...)
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ type HppV2Breakdown struct {
|
||||
ProjectFlockKandangID uint `json:"project_flock_kandang_id"`
|
||||
ProjectFlockID uint `json:"project_flock_id"`
|
||||
ProjectFlockCategory string `json:"project_flock_category,omitempty"`
|
||||
HouseType string `json:"house_type,omitempty"`
|
||||
KandangID uint `json:"kandang_id,omitempty"`
|
||||
KandangName string `json:"kandang_name,omitempty"`
|
||||
LocationID uint `json:"location_id,omitempty"`
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -17,6 +17,7 @@ type hppV2RepoStub struct {
|
||||
pfkIDsByProject map[uint][]uint
|
||||
latestTransferByPFK map[uint]*commonRepo.HppV2LatestTransferInputRow
|
||||
manualInputByProject map[uint]*commonRepo.HppV2ManualDepreciationInputRow
|
||||
snapshotByProjectKey map[string]*commonRepo.HppV2FarmDepreciationSnapshotRow
|
||||
chickInDateByProject map[uint]*time.Time
|
||||
depreciationByHouse map[string]map[int]float64
|
||||
usageRowsByKey map[string][]commonRepo.HppV2UsageCostRow
|
||||
@@ -59,6 +60,13 @@ func (s *hppV2RepoStub) GetManualDepreciationInputByProjectFlockID(_ context.Con
|
||||
return s.manualInputByProject[projectFlockID], nil
|
||||
}
|
||||
|
||||
func (s *hppV2RepoStub) GetFarmDepreciationSnapshotByProjectFlockIDAndPeriod(_ context.Context, projectFlockID uint, periodDate time.Time) (*commonRepo.HppV2FarmDepreciationSnapshotRow, error) {
|
||||
if s.snapshotByProjectKey == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return s.snapshotByProjectKey[fmt.Sprintf("%d|%s", projectFlockID, periodDate.Format("2006-01-02"))], nil
|
||||
}
|
||||
|
||||
func (s *hppV2RepoStub) GetEarliestChickInDateByProjectFlockID(_ context.Context, projectFlockID uint) (*time.Time, error) {
|
||||
return s.chickInDateByProject[projectFlockID], nil
|
||||
}
|
||||
@@ -319,10 +327,10 @@ func TestHppV2CalculateHppBreakdown_IncludesOvkComponent(t *testing.T) {
|
||||
},
|
||||
},
|
||||
adjustRowsByKey: map[string][]commonRepo.HppV2AdjustmentCostRow{
|
||||
stubKey([]uint{301, 302}, []string{"OVK-CUTOVER"}): {
|
||||
stubKey([]uint{301, 302}, []string{"OVK"}): {
|
||||
{AdjustmentID: 8201, ProductID: 34, ProductName: "OVK Growing Cut-over", Qty: 10, Price: 10, GrandTotal: 100},
|
||||
},
|
||||
stubKey([]uint{30}, []string{"OVK-CUTOVER"}): {
|
||||
stubKey([]uint{30}, []string{"OVK"}): {
|
||||
{AdjustmentID: 8202, ProductID: 35, ProductName: "OVK Laying Cut-over", Qty: 5, Price: 10, GrandTotal: 50},
|
||||
},
|
||||
},
|
||||
@@ -743,6 +751,82 @@ func TestHppV2CalculateHppBreakdown_AddsDepreciationForManualCutoverFromCutoverD
|
||||
}
|
||||
}
|
||||
|
||||
func TestHppV2CalculateHppBreakdown_UsesFarmSnapshotDepreciationProratedByEggProduction(t *testing.T) {
|
||||
reportDate := mustTime(t, "2026-06-05")
|
||||
|
||||
repo := &hppV2RepoStub{
|
||||
contextByPFK: map[uint]*commonRepo.HppV2ProjectFlockKandangContext{
|
||||
70: {
|
||||
ProjectFlockKandangID: 70,
|
||||
ProjectFlockID: 15,
|
||||
ProjectFlockCategory: "LAYING",
|
||||
KandangID: 700,
|
||||
KandangName: "Kandang Snapshot",
|
||||
LocationID: 25,
|
||||
HouseType: "close_house",
|
||||
},
|
||||
},
|
||||
pfkIDsByProject: map[uint][]uint{
|
||||
15: {70, 71},
|
||||
},
|
||||
snapshotByProjectKey: map[string]*commonRepo.HppV2FarmDepreciationSnapshotRow{
|
||||
"15|2026-06-05": {
|
||||
ID: 901,
|
||||
ProjectFlockID: 15,
|
||||
PeriodDate: reportDate,
|
||||
DepreciationPercentEffective: 10,
|
||||
DepreciationValue: 1000,
|
||||
PulletCostDayNTotal: 10000,
|
||||
},
|
||||
},
|
||||
eggProductionByPFK: map[uint]struct {
|
||||
pieces float64
|
||||
kg float64
|
||||
}{
|
||||
70: {pieces: 200, kg: 20},
|
||||
71: {pieces: 800, kg: 80},
|
||||
},
|
||||
}
|
||||
|
||||
svc := NewHppV2Service(repo)
|
||||
result, err := svc.CalculateHppBreakdown(70, &reportDate)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if result == nil {
|
||||
t.Fatal("expected breakdown result")
|
||||
}
|
||||
|
||||
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 != 200 {
|
||||
t.Fatalf("expected depreciation total 200, got %v", depreciation.Total)
|
||||
}
|
||||
if result.TotalProductionCost != 200 {
|
||||
t.Fatalf("expected total production cost 200, got %v", result.TotalProductionCost)
|
||||
}
|
||||
if len(depreciation.Parts) != 1 {
|
||||
t.Fatalf("expected one depreciation part, got %d", len(depreciation.Parts))
|
||||
}
|
||||
if depreciation.Parts[0].Code != hppV2PartDepreciationFarmSnapshot {
|
||||
t.Fatalf("expected farm snapshot depreciation part, got %s", depreciation.Parts[0].Code)
|
||||
}
|
||||
if depreciation.Parts[0].Proration == nil || depreciation.Parts[0].Proration.Ratio != 0.2 {
|
||||
t.Fatalf("expected proration ratio 0.2, got %+v", depreciation.Parts[0].Proration)
|
||||
}
|
||||
if depreciation.Parts[0].Details["snapshot_id"] != uint(901) {
|
||||
t.Fatalf("expected snapshot id 901, got %+v", depreciation.Parts[0].Details)
|
||||
}
|
||||
}
|
||||
|
||||
func stubKey(ids []uint, flags []string) string {
|
||||
idParts := make([]string, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
|
||||
Reference in New Issue
Block a user