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
|
Note *string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HppV2FarmDepreciationSnapshotRow struct {
|
||||||
|
ID uint
|
||||||
|
ProjectFlockID uint
|
||||||
|
PeriodDate time.Time
|
||||||
|
DepreciationPercentEffective float64
|
||||||
|
DepreciationValue float64
|
||||||
|
PulletCostDayNTotal float64
|
||||||
|
}
|
||||||
|
|
||||||
type HppV2CostRepository interface {
|
type HppV2CostRepository interface {
|
||||||
GetProjectFlockKandangContext(ctx context.Context, projectFlockKandangId uint) (*HppV2ProjectFlockKandangContext, error)
|
GetProjectFlockKandangContext(ctx context.Context, projectFlockKandangId uint) (*HppV2ProjectFlockKandangContext, error)
|
||||||
GetProjectFlockKandangIDs(ctx context.Context, projectFlockId uint) ([]uint, error)
|
GetProjectFlockKandangIDs(ctx context.Context, projectFlockId uint) ([]uint, error)
|
||||||
GetLatestTransferInputByProjectFlockKandangID(ctx context.Context, projectFlockKandangId uint, period time.Time) (*HppV2LatestTransferInputRow, error)
|
GetLatestTransferInputByProjectFlockKandangID(ctx context.Context, projectFlockKandangId uint, period time.Time) (*HppV2LatestTransferInputRow, error)
|
||||||
GetManualDepreciationInputByProjectFlockID(ctx context.Context, projectFlockID uint) (*HppV2ManualDepreciationInputRow, 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)
|
GetEarliestChickInDateByProjectFlockID(ctx context.Context, projectFlockID uint) (*time.Time, error)
|
||||||
GetDepreciationPercents(ctx context.Context, houseTypes []string, maxDay int) (map[string]map[int]float64, 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)
|
ListUsageCostRowsByProductFlags(ctx context.Context, projectFlockKandangIDs []uint, flagNames []string, date *time.Time) ([]HppV2UsageCostRow, error)
|
||||||
@@ -239,6 +249,29 @@ func (r *HppV2RepositoryImpl) GetManualDepreciationInputByProjectFlockID(
|
|||||||
return &row, nil
|
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) {
|
func (r *HppV2RepositoryImpl) GetEarliestChickInDateByProjectFlockID(ctx context.Context, projectFlockID uint) (*time.Time, error) {
|
||||||
type row struct {
|
type row struct {
|
||||||
ChickInDate *time.Time
|
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.product_id, ast_pw.product_id, 0) AS source_product_id,
|
||||||
COALESCE(pi_prod.name, ast_prod.name, '') AS source_product_name,
|
COALESCE(pi_prod.name, ast_prod.name, '') AS source_product_name,
|
||||||
COALESCE(SUM(sa.qty), 0) AS qty,
|
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(pi.price, 0)
|
||||||
WHEN sa.stockable_type = ? THEN COALESCE(ast.price, 0)
|
WHEN sa.stockable_type = ? THEN COALESCE(ast.price, 0)
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END AS unit_price,
|
END), 0) AS unit_price,
|
||||||
COALESCE(SUM(sa.qty * CASE
|
COALESCE(SUM(sa.qty * CASE
|
||||||
WHEN sa.stockable_type = ? THEN COALESCE(pi.price, 0)
|
WHEN sa.stockable_type = ? THEN COALESCE(pi.price, 0)
|
||||||
WHEN sa.stockable_type = ? THEN COALESCE(ast.price, 0)
|
WHEN sa.stockable_type = ? THEN COALESCE(ast.price, 0)
|
||||||
@@ -367,12 +400,7 @@ func (r *HppV2RepositoryImpl) ListUsageCostRowsByProductFlags(
|
|||||||
sa.stockable_type,
|
sa.stockable_type,
|
||||||
sa.stockable_id,
|
sa.stockable_id,
|
||||||
COALESCE(pi.product_id, ast_pw.product_id, 0),
|
COALESCE(pi.product_id, ast_pw.product_id, 0),
|
||||||
COALESCE(pi_prod.name, ast_prod.name, ''),
|
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
|
|
||||||
`).
|
`).
|
||||||
Order("MIN(r.record_datetime) ASC, sa.stockable_type ASC, sa.stockable_id ASC").
|
Order("MIN(r.record_datetime) ASC, sa.stockable_type ASC, sa.stockable_id ASC").
|
||||||
Scan(&rows).Error
|
Scan(&rows).Error
|
||||||
@@ -417,7 +445,7 @@ func (r *HppV2RepositoryImpl) ListAdjustmentCostRowsByProductFlags(
|
|||||||
Joins("JOIN products AS p ON p.id = pw.product_id").
|
Joins("JOIN products AS p ON p.id = pw.product_id").
|
||||||
Joins("JOIN warehouses AS w ON w.id = pw.warehouse_id").
|
Joins("JOIN warehouses AS w ON w.id = pw.warehouse_id").
|
||||||
Where("pw.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
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("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).
|
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").
|
Order("ast.created_at ASC, ast.id ASC").
|
||||||
@@ -450,6 +478,15 @@ func (r *HppV2RepositoryImpl) ListChickinCostRowsByProductFlags(
|
|||||||
stockableTransferToLaying := fifo.StockableKeyTransferToLayingIn.String()
|
stockableTransferToLaying := fifo.StockableKeyTransferToLayingIn.String()
|
||||||
usableProjectChickin := fifo.UsableKeyProjectChickin.String()
|
usableProjectChickin := fifo.UsableKeyProjectChickin.String()
|
||||||
usableStockTransferOut := fifo.UsableKeyStockTransferOut.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)
|
rows := make([]HppV2ChickinCostRow, 0)
|
||||||
query := r.db.WithContext(ctx).
|
query := r.db.WithContext(ctx).
|
||||||
@@ -479,30 +516,9 @@ func (r *HppV2RepositoryImpl) ListChickinCostRowsByProductFlags(
|
|||||||
''
|
''
|
||||||
) AS source_product_name,
|
) AS source_product_name,
|
||||||
COALESCE(SUM(sa.qty), 0) AS qty,
|
COALESCE(SUM(sa.qty), 0) AS qty,
|
||||||
CASE
|
`+unitPriceExpr+` AS unit_price,
|
||||||
WHEN sa.stockable_type = ? THEN COALESCE(pi.price, 0)
|
COALESCE(SUM(sa.qty * (`+unitPriceExpr+`)), 0) AS total_cost
|
||||||
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,
|
|
||||||
).
|
|
||||||
Joins(
|
Joins(
|
||||||
"JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = pc.id AND sa.status = ? AND sa.allocation_purpose = ?",
|
"JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = pc.id AND sa.status = ? AND sa.allocation_purpose = ?",
|
||||||
usableProjectChickin,
|
usableProjectChickin,
|
||||||
@@ -563,7 +579,7 @@ func (r *HppV2RepositoryImpl) ListChickinCostRowsByProductFlags(
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := query.
|
err := query.
|
||||||
Group(`
|
Group(fmt.Sprintf(`
|
||||||
pc.id,
|
pc.id,
|
||||||
pc.project_flock_kandang_id,
|
pc.project_flock_kandang_id,
|
||||||
pc.chick_in_date,
|
pc.chick_in_date,
|
||||||
@@ -587,14 +603,8 @@ func (r *HppV2RepositoryImpl) ListChickinCostRowsByProductFlags(
|
|||||||
sast_prod.name,
|
sast_prod.name,
|
||||||
''
|
''
|
||||||
),
|
),
|
||||||
CASE
|
%s
|
||||||
WHEN sa.stockable_type = '` + stockablePurchase + `' THEN COALESCE(pi.price, 0)
|
`, unitPriceExpr)).
|
||||||
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
|
|
||||||
`).
|
|
||||||
Order("pc.chick_in_date ASC, pc.id ASC, sa.stockable_type ASC, sa.stockable_id ASC").
|
Order("pc.chick_in_date ASC, pc.id ASC, sa.stockable_type ASC, sa.stockable_id ASC").
|
||||||
Scan(&rows).Error
|
Scan(&rows).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
|
||||||
"math"
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -40,108 +39,91 @@ func NewHppService(hppRepo commonRepo.HppCostRepository) HppService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *hppService) CalculateHppCost(projectFlockKandangId uint, date *time.Time) (*HppCostResponse, error) {
|
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 {
|
if date == nil {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
date = &now
|
date = &now
|
||||||
}
|
}
|
||||||
logHpp("CalculateHppCost", "normalized_date=%s", formatTimePtr(date))
|
|
||||||
|
|
||||||
location, err := time.LoadLocation("Asia/Jakarta")
|
location, err := time.LoadLocation("Asia/Jakarta")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("CalculateHppCost", "load_location_error=%v", err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
logHpp("CalculateHppCost", "location=%s", location.String())
|
|
||||||
|
|
||||||
startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, location)
|
startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, location)
|
||||||
endOfDay := startOfDay.Add(24 * time.Hour)
|
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)
|
depresiasiTransfer, err := s.GetDepresiasiTransfer(projectFlockKandangId, &endOfDay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("CalculateHppCost", "get_depresiasi_transfer_error=%v", err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
logHpp("CalculateHppCost", "depresiasi_transfer=%f", depresiasiTransfer)
|
|
||||||
|
|
||||||
totalProductionCost, err := s.GetTotalProductionCost(projectFlockKandangId, &endOfDay, depresiasiTransfer)
|
totalProductionCost, err := s.GetTotalProductionCost(projectFlockKandangId, &endOfDay, depresiasiTransfer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("CalculateHppCost", "get_total_production_cost_error=%v", err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
logHpp("CalculateHppCost", "total_production_cost=%f", totalProductionCost)
|
|
||||||
result, err := s.GetHppEstimationDanRealisasi(totalProductionCost, projectFlockKandangId, &startOfDay, &endOfDay)
|
result, err := s.GetHppEstimationDanRealisasi(totalProductionCost, projectFlockKandangId, &startOfDay, &endOfDay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("CalculateHppCost", "get_hpp_estimation_dan_realisasi_error=%v", err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
logHpp("CalculateHppCost", "done estimation=%+v real=%+v", result.Estimation, result.Real)
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hppService) GetTotalDepresiasiFlockGrowing(sourceProjectFlockID uint, date *time.Time) (float64, error) {
|
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 {
|
if date == nil {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
date = &now
|
date = &now
|
||||||
}
|
}
|
||||||
logHpp("GetTotalDepresiasiFlockGrowing", "normalized_date=%s", formatTimePtr(date))
|
|
||||||
|
|
||||||
if s.hppRepo == nil {
|
if s.hppRepo == nil {
|
||||||
logHpp("GetTotalDepresiasiFlockGrowing", "repo_nil return=0")
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
kandangIDs, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), sourceProjectFlockID)
|
kandangIDs, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), sourceProjectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetTotalDepresiasiFlockGrowing", "get_project_flock_kandang_ids_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetTotalDepresiasiFlockGrowing", "kandang_ids=%v", kandangIDs)
|
|
||||||
|
|
||||||
docCost, err := s.hppRepo.GetDocCost(context.Background(), kandangIDs)
|
docCost, err := s.hppRepo.GetDocCost(context.Background(), kandangIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetTotalDepresiasiFlockGrowing", "get_doc_cost_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetTotalDepresiasiFlockGrowing", "doc_cost=%f", docCost)
|
|
||||||
|
|
||||||
budgetCost, err := s.hppRepo.GetBudgetCostByProjectFlockId(context.Background(), sourceProjectFlockID)
|
budgetCost, err := s.hppRepo.GetBudgetCostByProjectFlockId(context.Background(), sourceProjectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetTotalDepresiasiFlockGrowing", "get_budget_cost_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetTotalDepresiasiFlockGrowing", "budget_cost=%f", budgetCost)
|
|
||||||
|
|
||||||
expedisionCost, err := s.hppRepo.GetExpedisionCost(context.Background(), kandangIDs)
|
expedisionCost, err := s.hppRepo.GetExpedisionCost(context.Background(), kandangIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetTotalDepresiasiFlockGrowing", "get_expedision_cost_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetTotalDepresiasiFlockGrowing", "expedision_cost=%f", expedisionCost)
|
|
||||||
|
|
||||||
feedCost, err := s.hppRepo.GetFeedUsageCost(context.Background(), kandangIDs, date)
|
feedCost, err := s.hppRepo.GetFeedUsageCost(context.Background(), kandangIDs, date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetTotalDepresiasiFlockGrowing", "get_feed_usage_cost_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetTotalDepresiasiFlockGrowing", "feed_cost=%f", feedCost)
|
|
||||||
|
|
||||||
ovkCost, err := s.hppRepo.GetOvkUsageCost(context.Background(), kandangIDs, date)
|
ovkCost, err := s.hppRepo.GetOvkUsageCost(context.Background(), kandangIDs, date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetTotalDepresiasiFlockGrowing", "get_ovk_usage_cost_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetTotalDepresiasiFlockGrowing", "ovk_cost=%f", ovkCost)
|
|
||||||
|
|
||||||
total := docCost + budgetCost + expedisionCost + feedCost + ovkCost
|
total := docCost + budgetCost + expedisionCost + feedCost + ovkCost
|
||||||
logHpp("GetTotalDepresiasiFlockGrowing", "done total=%f", total)
|
|
||||||
return total, nil
|
return total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hppService) GetTotalProductionCost(projectFlockKandangId uint, endDate *time.Time, depresiasiTransfer float64) (float64, error) {
|
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 {
|
// if date == nil {
|
||||||
// now := time.Now()
|
// now := time.Now()
|
||||||
// date = &now
|
// date = &now
|
||||||
@@ -149,248 +131,210 @@ func (s *hppService) GetTotalProductionCost(projectFlockKandangId uint, endDate
|
|||||||
|
|
||||||
costPullet, err := s.hppRepo.GetPulletCost(context.Background(), projectFlockKandangId)
|
costPullet, err := s.hppRepo.GetPulletCost(context.Background(), projectFlockKandangId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetTotalProductionCost", "get_pullet_cost_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetTotalProductionCost", "cost_pullet=%f", costPullet)
|
|
||||||
|
|
||||||
costFeed, err := s.hppRepo.GetFeedUsageCost(context.Background(), []uint{projectFlockKandangId}, endDate)
|
costFeed, err := s.hppRepo.GetFeedUsageCost(context.Background(), []uint{projectFlockKandangId}, endDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetTotalProductionCost", "get_feed_usage_cost_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetTotalProductionCost", "cost_feed=%f", costFeed)
|
|
||||||
|
|
||||||
costOvk, err := s.hppRepo.GetOvkUsageCost(context.Background(), []uint{projectFlockKandangId}, endDate)
|
costOvk, err := s.hppRepo.GetOvkUsageCost(context.Background(), []uint{projectFlockKandangId}, endDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetTotalProductionCost", "get_ovk_usage_cost_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetTotalProductionCost", "cost_ovk=%f", costOvk)
|
|
||||||
|
|
||||||
costExpedision, err := s.hppRepo.GetExpedisionCost(context.Background(), []uint{projectFlockKandangId})
|
costExpedision, err := s.hppRepo.GetExpedisionCost(context.Background(), []uint{projectFlockKandangId})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetTotalProductionCost", "get_expedision_cost_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetTotalProductionCost", "cost_expedision=%f", costExpedision)
|
|
||||||
|
|
||||||
costBudget, err := s.GetBudgetKandangLaying(projectFlockKandangId, endDate)
|
costBudget, err := s.GetBudgetKandangLaying(projectFlockKandangId, endDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetTotalProductionCost", "get_budget_kandang_laying_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetTotalProductionCost", "cost_budget=%f", costBudget)
|
|
||||||
|
|
||||||
// fmt.Println(costBudget, costExpedision, costOvk, costFeed, costPullet, depresiasiTransfer)
|
// fmt.Println(costBudget, costExpedision, costOvk, costFeed, costPullet, depresiasiTransfer)
|
||||||
|
|
||||||
// depresiasiTransfer = 0
|
// depresiasiTransfer = 0
|
||||||
|
|
||||||
total := depresiasiTransfer + costPullet + costFeed + costOvk + costExpedision + costBudget
|
total := depresiasiTransfer + costPullet + costFeed + costOvk + costExpedision + costBudget
|
||||||
logHpp("GetTotalProductionCost", "done total=%f", total)
|
|
||||||
return total, nil
|
return total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hppService) GetBudgetKandangLaying(projectFlockKandangId uint, endDate *time.Time) (float64, error) {
|
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 {
|
// if date == nil {
|
||||||
// now := time.Now()
|
// now := time.Now()
|
||||||
// date = &now
|
// date = &now
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if s.hppRepo == nil {
|
if s.hppRepo == nil {
|
||||||
logHpp("GetBudgetKandangLaying", "repo_nil return=0")
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
projectFlockId, err := s.hppRepo.GetProjectFlockIDByProjectFlockKandangID(context.Background(), projectFlockKandangId)
|
projectFlockId, err := s.hppRepo.GetProjectFlockIDByProjectFlockKandangID(context.Background(), projectFlockKandangId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetBudgetKandangLaying", "get_project_flock_id_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetBudgetKandangLaying", "project_flock_id=%d", projectFlockId)
|
|
||||||
|
|
||||||
projectFlockKandangIds, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), projectFlockId)
|
projectFlockKandangIds, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), projectFlockId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetBudgetKandangLaying", "get_project_flock_kandang_ids_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetBudgetKandangLaying", "project_flock_kandang_ids=%v", projectFlockKandangIds)
|
|
||||||
|
|
||||||
eggProduksiPiecesFlock, _, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), projectFlockKandangIds, endDate)
|
eggProduksiPiecesFlock, _, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), projectFlockKandangIds, endDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetBudgetKandangLaying", "get_egg_produksi_pieces_flock_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetBudgetKandangLaying", "egg_produksi_pieces_flock=%f", eggProduksiPiecesFlock)
|
|
||||||
|
|
||||||
eggProduksiPiecesKandang, _, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, endDate)
|
eggProduksiPiecesKandang, _, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, endDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetBudgetKandangLaying", "get_egg_produksi_pieces_kandang_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetBudgetKandangLaying", "egg_produksi_pieces_kandang=%f", eggProduksiPiecesKandang)
|
|
||||||
|
|
||||||
totalBudgetCost, err := s.hppRepo.GetBudgetCostByProjectFlockId(context.Background(), projectFlockId)
|
totalBudgetCost, err := s.hppRepo.GetBudgetCostByProjectFlockId(context.Background(), projectFlockId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetBudgetKandangLaying", "get_budget_cost_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetBudgetKandangLaying", "total_budget_cost=%f", totalBudgetCost)
|
|
||||||
|
|
||||||
if eggProduksiPiecesFlock == 0 {
|
if eggProduksiPiecesFlock == 0 {
|
||||||
logHpp("GetBudgetKandangLaying", "egg_produksi_pieces_flock_zero return=0")
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result := (totalBudgetCost * eggProduksiPiecesKandang) / eggProduksiPiecesFlock
|
result := (totalBudgetCost * eggProduksiPiecesKandang) / eggProduksiPiecesFlock
|
||||||
logHpp("GetBudgetKandangLaying", "done result=%f", result)
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hppService) GetDepresiasiTransfer(projectFlockKandangId uint, endDate *time.Time) (float64, error) {
|
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 {
|
if endDate == nil {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
endDate = &now
|
endDate = &now
|
||||||
}
|
}
|
||||||
logHpp("GetDepresiasiTransfer", "normalized_end_date=%s", formatTimePtr(endDate))
|
|
||||||
|
|
||||||
if s.hppRepo == nil {
|
if s.hppRepo == nil {
|
||||||
logHpp("GetDepresiasiTransfer", "repo_nil return=0")
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceProjectFlockID, transferTotalQty, err := s.hppRepo.GetTransferSourceSummary(context.Background(), projectFlockKandangId)
|
sourceProjectFlockID, transferTotalQty, err := s.hppRepo.GetTransferSourceSummary(context.Background(), projectFlockKandangId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetDepresiasiTransfer", "get_transfer_source_summary_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetDepresiasiTransfer", "source_project_flock_id=%d transfer_total_qty=%f", sourceProjectFlockID, transferTotalQty)
|
|
||||||
if sourceProjectFlockID == 0 || transferTotalQty <= 0 {
|
if sourceProjectFlockID == 0 || transferTotalQty <= 0 {
|
||||||
logHpp("GetDepresiasiTransfer", "use_manual_fallback=true")
|
|
||||||
result, fallbackErr := s.getManualDepresiasiTransferFallback(projectFlockKandangId)
|
result, fallbackErr := s.getManualDepresiasiTransferFallback(projectFlockKandangId)
|
||||||
if fallbackErr != nil {
|
if fallbackErr != nil {
|
||||||
logHpp("GetDepresiasiTransfer", "manual_fallback_error=%v", fallbackErr)
|
|
||||||
return 0, fallbackErr
|
return 0, fallbackErr
|
||||||
}
|
}
|
||||||
logHpp("GetDepresiasiTransfer", "done_fallback result=%f", result)
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
kandangIDsGrowing, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), sourceProjectFlockID)
|
kandangIDsGrowing, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), sourceProjectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetDepresiasiTransfer", "get_project_flock_kandang_ids_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetDepresiasiTransfer", "kandang_ids_growing=%v", kandangIDsGrowing)
|
|
||||||
|
|
||||||
totalPopulationFlockGrowing, err := s.hppRepo.GetTotalPopulation(context.Background(), kandangIDsGrowing)
|
totalPopulationFlockGrowing, err := s.hppRepo.GetTotalPopulation(context.Background(), kandangIDsGrowing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetDepresiasiTransfer", "get_total_population_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetDepresiasiTransfer", "total_population_flock_growing=%f", totalPopulationFlockGrowing)
|
|
||||||
if totalPopulationFlockGrowing == 0 {
|
if totalPopulationFlockGrowing == 0 {
|
||||||
logHpp("GetDepresiasiTransfer", "total_population_flock_growing_zero return=0")
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
totalDepresiasiFlockGrowing, err := s.GetTotalDepresiasiFlockGrowing(sourceProjectFlockID, endDate)
|
totalDepresiasiFlockGrowing, err := s.GetTotalDepresiasiFlockGrowing(sourceProjectFlockID, endDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetDepresiasiTransfer", "get_total_depresiasi_flock_growing_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("GetDepresiasiTransfer", "total_depresiasi_flock_growing=%f", totalDepresiasiFlockGrowing)
|
|
||||||
|
|
||||||
result := (totalDepresiasiFlockGrowing * transferTotalQty) / totalPopulationFlockGrowing
|
result := (totalDepresiasiFlockGrowing * transferTotalQty) / totalPopulationFlockGrowing
|
||||||
logHpp("GetDepresiasiTransfer", "done result=%f", result)
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hppService) getManualDepresiasiTransferFallback(projectFlockKandangId uint) (float64, error) {
|
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)
|
projectFlockID, err := s.hppRepo.GetProjectFlockIDByProjectFlockKandangID(context.Background(), projectFlockKandangId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("getManualDepresiasiTransferFallback", "get_project_flock_id_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("getManualDepresiasiTransferFallback", "project_flock_id=%d", projectFlockID)
|
|
||||||
if projectFlockID == 0 {
|
if projectFlockID == 0 {
|
||||||
logHpp("getManualDepresiasiTransferFallback", "project_flock_id_zero return=0")
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
manualCost, err := s.hppRepo.GetManualDepreciationCostByProjectFlockID(context.Background(), projectFlockID)
|
manualCost, err := s.hppRepo.GetManualDepreciationCostByProjectFlockID(context.Background(), projectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("getManualDepresiasiTransferFallback", "get_manual_depreciation_cost_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("getManualDepresiasiTransferFallback", "manual_cost=%f", manualCost)
|
|
||||||
if manualCost <= 0 {
|
if manualCost <= 0 {
|
||||||
logHpp("getManualDepresiasiTransferFallback", "manual_cost_non_positive return=0")
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
kandangIDs, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), projectFlockID)
|
kandangIDs, err := s.hppRepo.GetProjectFlockKandangIDs(context.Background(), projectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("getManualDepresiasiTransferFallback", "get_project_flock_kandang_ids_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("getManualDepresiasiTransferFallback", "kandang_ids=%v", kandangIDs)
|
|
||||||
if len(kandangIDs) == 0 {
|
if len(kandangIDs) == 0 {
|
||||||
logHpp("getManualDepresiasiTransferFallback", "kandang_ids_empty return=0")
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
totalUsageQty, err := s.hppRepo.GetTotalPopulation(context.Background(), kandangIDs)
|
totalUsageQty, err := s.hppRepo.GetTotalPopulation(context.Background(), kandangIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("getManualDepresiasiTransferFallback", "get_total_usage_qty_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("getManualDepresiasiTransferFallback", "total_usage_qty=%f", totalUsageQty)
|
|
||||||
if totalUsageQty <= 0 {
|
if totalUsageQty <= 0 {
|
||||||
logHpp("getManualDepresiasiTransferFallback", "total_usage_qty_non_positive return=0")
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
kandangUsageQty, err := s.hppRepo.GetTotalPopulation(context.Background(), []uint{projectFlockKandangId})
|
kandangUsageQty, err := s.hppRepo.GetTotalPopulation(context.Background(), []uint{projectFlockKandangId})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("getManualDepresiasiTransferFallback", "get_kandang_usage_qty_error=%v", err)
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
logHpp("getManualDepresiasiTransferFallback", "kandang_usage_qty=%f", kandangUsageQty)
|
|
||||||
if kandangUsageQty <= 0 {
|
if kandangUsageQty <= 0 {
|
||||||
logHpp("getManualDepresiasiTransferFallback", "kandang_usage_qty_non_positive return=0")
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result := manualCost * (kandangUsageQty / totalUsageQty)
|
result := manualCost * (kandangUsageQty / totalUsageQty)
|
||||||
logHpp("getManualDepresiasiTransferFallback", "done result=%f", result)
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hppService) GetHppEstimationDanRealisasi(totalProductionCost float64, projectFlockKandangId uint, startDate *time.Time, endDate *time.Time) (*HppCostResponse, error) {
|
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 {
|
if s.hppRepo == nil {
|
||||||
logHpp("GetHppEstimationDanRealisasi", "repo_nil return_empty_response")
|
|
||||||
return &HppCostResponse{}, nil
|
return &HppCostResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
estimPieces, estimWeightKg, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, endDate)
|
estimPieces, estimWeightKg, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, endDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetHppEstimationDanRealisasi", "get_egg_produksi_error=%v", err)
|
|
||||||
return nil, 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)
|
realPieces, realWeightKg, err := s.hppRepo.GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, startDate, endDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logHpp("GetHppEstimationDanRealisasi", "get_egg_terjual_error=%v", err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
logHpp("GetHppEstimationDanRealisasi", "real_pieces=%f real_weight_kg=%f", realPieces, realWeightKg)
|
|
||||||
|
|
||||||
estimation := HppCostDetail{
|
estimation := HppCostDetail{
|
||||||
Total: totalProductionCost,
|
Total: totalProductionCost,
|
||||||
@@ -403,7 +347,6 @@ func (s *hppService) GetHppEstimationDanRealisasi(totalProductionCost float64, p
|
|||||||
if estimPieces > 0 {
|
if estimPieces > 0 {
|
||||||
estimation.HargaButir = roundToTwoDecimals(totalProductionCost / estimPieces)
|
estimation.HargaButir = roundToTwoDecimals(totalProductionCost / estimPieces)
|
||||||
}
|
}
|
||||||
logHpp("GetHppEstimationDanRealisasi", "estimation=%+v", estimation)
|
|
||||||
|
|
||||||
real := HppCostDetail{
|
real := HppCostDetail{
|
||||||
Total: totalProductionCost,
|
Total: totalProductionCost,
|
||||||
@@ -416,19 +359,16 @@ func (s *hppService) GetHppEstimationDanRealisasi(totalProductionCost float64, p
|
|||||||
if realPieces > 0 {
|
if realPieces > 0 {
|
||||||
real.HargaButir = roundToTwoDecimals(totalProductionCost / realPieces)
|
real.HargaButir = roundToTwoDecimals(totalProductionCost / realPieces)
|
||||||
}
|
}
|
||||||
logHpp("GetHppEstimationDanRealisasi", "real=%+v", real)
|
|
||||||
|
|
||||||
result := &HppCostResponse{
|
result := &HppCostResponse{
|
||||||
Estimation: estimation,
|
Estimation: estimation,
|
||||||
Real: real,
|
Real: real,
|
||||||
}
|
}
|
||||||
logHpp("GetHppEstimationDanRealisasi", "done response=%+v", *result)
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func roundToTwoDecimals(value float64) float64 {
|
func roundToTwoDecimals(value float64) float64 {
|
||||||
result := math.Round(value*100) / 100
|
result := math.Round(value*100) / 100
|
||||||
logHpp("roundToTwoDecimals", "input=%f output=%f", value, result)
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,7 +378,3 @@ func formatTimePtr(value *time.Time) string {
|
|||||||
}
|
}
|
||||||
return value.Format(time.RFC3339)
|
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"`
|
ProjectFlockKandangID uint `json:"project_flock_kandang_id"`
|
||||||
ProjectFlockID uint `json:"project_flock_id"`
|
ProjectFlockID uint `json:"project_flock_id"`
|
||||||
ProjectFlockCategory string `json:"project_flock_category,omitempty"`
|
ProjectFlockCategory string `json:"project_flock_category,omitempty"`
|
||||||
|
HouseType string `json:"house_type,omitempty"`
|
||||||
KandangID uint `json:"kandang_id,omitempty"`
|
KandangID uint `json:"kandang_id,omitempty"`
|
||||||
KandangName string `json:"kandang_name,omitempty"`
|
KandangName string `json:"kandang_name,omitempty"`
|
||||||
LocationID uint `json:"location_id,omitempty"`
|
LocationID uint `json:"location_id,omitempty"`
|
||||||
|
|||||||
@@ -28,13 +28,14 @@ const (
|
|||||||
hppV2PartManualCutover = "manual_cutover"
|
hppV2PartManualCutover = "manual_cutover"
|
||||||
hppV2PartDepreciationNormal = "normal_transfer"
|
hppV2PartDepreciationNormal = "normal_transfer"
|
||||||
hppV2PartDepreciationCutover = "manual_cutover"
|
hppV2PartDepreciationCutover = "manual_cutover"
|
||||||
|
hppV2PartDepreciationFarmSnapshot = "farm_snapshot"
|
||||||
hppV2ProrationPopulation = "growing_population_share"
|
hppV2ProrationPopulation = "growing_population_share"
|
||||||
hppV2ProrationEggWeight = "laying_egg_weight_share"
|
hppV2ProrationEggWeight = "laying_egg_weight_share"
|
||||||
hppV2ProrationEggPiece = "laying_egg_piece_share"
|
hppV2ProrationEggPiece = "laying_egg_piece_share"
|
||||||
hppV2ScopePulletCost = "pullet_cost"
|
hppV2ScopePulletCost = "pullet_cost"
|
||||||
hppV2ScopeProductionCost = "production_cost"
|
hppV2ScopeProductionCost = "production_cost"
|
||||||
hppV2CutoverFlagPakan = "PAKAN-CUTOVER"
|
hppV2CutoverFlagPakan = "PAKAN-CUTOVER"
|
||||||
hppV2CutoverFlagOvk = "OVK-CUTOVER"
|
hppV2CutoverFlagOvk = "OVK"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HppV2Service interface {
|
type HppV2Service interface {
|
||||||
@@ -115,57 +116,101 @@ func (s *hppV2Service) CalculateHppBreakdown(projectFlockKandangId uint, date *t
|
|||||||
totalPulletCost := 0.0
|
totalPulletCost := 0.0
|
||||||
totalProductionCost := 0.0
|
totalProductionCost := 0.0
|
||||||
components := make([]HppV2Component, 0, 8)
|
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) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pulletAdded := componentScopeTotal(component, hppV2ScopePulletCost)
|
||||||
|
productionAdded := componentScopeTotal(component, hppV2ScopeProductionCost)
|
||||||
components = append(components, *component)
|
components = append(components, *component)
|
||||||
totalPulletCost += componentScopeTotal(component, hppV2ScopePulletCost)
|
totalPulletCost += pulletAdded
|
||||||
totalProductionCost += componentScopeTotal(component, hppV2ScopeProductionCost)
|
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)
|
ovkComponent, err := s.GetOvkBreakdown(projectFlockKandangId, &endOfDay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
appendComponent(ovkComponent)
|
appendComponent(hppV2ComponentOvk, ovkComponent)
|
||||||
|
|
||||||
docComponent, err := s.GetDocChickinBreakdown(projectFlockKandangId, &endOfDay)
|
docComponent, err := s.GetDocChickinBreakdown(projectFlockKandangId, &endOfDay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
appendComponent(docComponent)
|
appendComponent(hppV2ComponentDocChickin, docComponent)
|
||||||
|
|
||||||
directPulletComponent, err := s.GetDirectPulletPurchaseBreakdown(projectFlockKandangId, &endOfDay)
|
directPulletComponent, err := s.GetDirectPulletPurchaseBreakdown(projectFlockKandangId, &endOfDay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
appendComponent(directPulletComponent)
|
appendComponent(hppV2ComponentDirectPulletPurchase, directPulletComponent)
|
||||||
|
|
||||||
bopRegularComponent, err := s.GetBopRegularBreakdown(projectFlockKandangId, &endOfDay)
|
bopRegularComponent, err := s.GetBopRegularBreakdown(projectFlockKandangId, &endOfDay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
appendComponent(bopRegularComponent)
|
appendComponent(hppV2ComponentBopRegular, bopRegularComponent)
|
||||||
|
|
||||||
bopEkspedisiComponent, err := s.GetBopEkspedisiBreakdown(projectFlockKandangId, &endOfDay)
|
bopEkspedisiComponent, err := s.GetBopEkspedisiBreakdown(projectFlockKandangId, &endOfDay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
appendComponent(bopEkspedisiComponent)
|
appendComponent(hppV2ComponentBopEksp, bopEkspedisiComponent)
|
||||||
|
|
||||||
manualPulletComponent, err := s.getManualPulletCostComponent(projectFlockKandangId, contextRow, startOfDay)
|
manualPulletComponent, err := s.getManualPulletCostComponent(projectFlockKandangId, contextRow, startOfDay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
hppCost, err := s.GetHppEstimationDanRealisasi(totalProductionCost, projectFlockKandangId, &startOfDay, &endOfDay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -179,6 +224,7 @@ func (s *hppV2Service) CalculateHppBreakdown(projectFlockKandangId uint, date *t
|
|||||||
ProjectFlockKandangID: projectFlockKandangId,
|
ProjectFlockKandangID: projectFlockKandangId,
|
||||||
ProjectFlockID: contextRow.ProjectFlockID,
|
ProjectFlockID: contextRow.ProjectFlockID,
|
||||||
ProjectFlockCategory: contextRow.ProjectFlockCategory,
|
ProjectFlockCategory: contextRow.ProjectFlockCategory,
|
||||||
|
HouseType: contextRow.HouseType,
|
||||||
KandangID: contextRow.KandangID,
|
KandangID: contextRow.KandangID,
|
||||||
KandangName: contextRow.KandangName,
|
KandangName: contextRow.KandangName,
|
||||||
LocationID: contextRow.LocationID,
|
LocationID: contextRow.LocationID,
|
||||||
@@ -1022,9 +1068,28 @@ func (s *hppV2Service) getDepreciationComponent(
|
|||||||
projectFlockKandangId uint,
|
projectFlockKandangId uint,
|
||||||
contextRow *commonRepo.HppV2ProjectFlockKandangContext,
|
contextRow *commonRepo.HppV2ProjectFlockKandangContext,
|
||||||
periodDate time.Time,
|
periodDate time.Time,
|
||||||
|
endDate time.Time,
|
||||||
totalPulletCost float64,
|
totalPulletCost float64,
|
||||||
) (*HppV2Component, error) {
|
) (*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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1058,6 +1123,101 @@ func (s *hppV2Service) getDepreciationComponent(
|
|||||||
}, nil
|
}, 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(
|
func (s *hppV2Service) buildNormalTransferDepreciationPart(
|
||||||
contextRow *commonRepo.HppV2ProjectFlockKandangContext,
|
contextRow *commonRepo.HppV2ProjectFlockKandangContext,
|
||||||
transferInput *commonRepo.HppV2LatestTransferInputRow,
|
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) {
|
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 {
|
if s.hppRepo == nil {
|
||||||
|
utils.Log.Warnf(
|
||||||
|
"GetHppEstimationDanRealisasi skipped: hpp repository is nil (project_flock_kandang_id=%d)",
|
||||||
|
projectFlockKandangId,
|
||||||
|
)
|
||||||
return &HppCostResponse{}, nil
|
return &HppCostResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
estimPieces, estimWeightKg, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, endDate)
|
estimPieces, estimWeightKg, err := s.hppRepo.GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, endDate)
|
||||||
if err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
realPieces, realWeightKg, err := s.hppRepo.GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, startDate, endDate)
|
realPieces, realWeightKg, err := s.hppRepo.GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds(context.Background(), []uint{projectFlockKandangId}, startDate, endDate)
|
||||||
if err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1249,6 +1432,20 @@ func (s *hppV2Service) GetHppEstimationDanRealisasi(totalProductionCost float64,
|
|||||||
real.HargaButir = roundToTwoDecimals(totalProductionCost / realPieces)
|
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{
|
return &HppCostResponse{
|
||||||
Estimation: estimation,
|
Estimation: estimation,
|
||||||
Real: real,
|
Real: real,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type hppV2RepoStub struct {
|
|||||||
pfkIDsByProject map[uint][]uint
|
pfkIDsByProject map[uint][]uint
|
||||||
latestTransferByPFK map[uint]*commonRepo.HppV2LatestTransferInputRow
|
latestTransferByPFK map[uint]*commonRepo.HppV2LatestTransferInputRow
|
||||||
manualInputByProject map[uint]*commonRepo.HppV2ManualDepreciationInputRow
|
manualInputByProject map[uint]*commonRepo.HppV2ManualDepreciationInputRow
|
||||||
|
snapshotByProjectKey map[string]*commonRepo.HppV2FarmDepreciationSnapshotRow
|
||||||
chickInDateByProject map[uint]*time.Time
|
chickInDateByProject map[uint]*time.Time
|
||||||
depreciationByHouse map[string]map[int]float64
|
depreciationByHouse map[string]map[int]float64
|
||||||
usageRowsByKey map[string][]commonRepo.HppV2UsageCostRow
|
usageRowsByKey map[string][]commonRepo.HppV2UsageCostRow
|
||||||
@@ -59,6 +60,13 @@ func (s *hppV2RepoStub) GetManualDepreciationInputByProjectFlockID(_ context.Con
|
|||||||
return s.manualInputByProject[projectFlockID], nil
|
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) {
|
func (s *hppV2RepoStub) GetEarliestChickInDateByProjectFlockID(_ context.Context, projectFlockID uint) (*time.Time, error) {
|
||||||
return s.chickInDateByProject[projectFlockID], nil
|
return s.chickInDateByProject[projectFlockID], nil
|
||||||
}
|
}
|
||||||
@@ -319,10 +327,10 @@ func TestHppV2CalculateHppBreakdown_IncludesOvkComponent(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
adjustRowsByKey: map[string][]commonRepo.HppV2AdjustmentCostRow{
|
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},
|
{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},
|
{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 {
|
func stubKey(ids []uint, flags []string) string {
|
||||||
idParts := make([]string, 0, len(ids))
|
idParts := make([]string, 0, len(ids))
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
|
|||||||
@@ -0,0 +1,445 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
approvalService "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
dto "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto"
|
||||||
|
repportRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/repositories"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/validations"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type expenseDepreciationRepoMock struct {
|
||||||
|
repportRepo.ExpenseDepreciationRepository
|
||||||
|
manualInputs []repportRepo.FarmDepreciationManualInputRow
|
||||||
|
|
||||||
|
upsertedRow *entity.FarmDepreciationManualInput
|
||||||
|
deleteCalled bool
|
||||||
|
deleteDate time.Time
|
||||||
|
deleteFarmIDs []uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *expenseDepreciationRepoMock) DB() *gorm.DB {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *expenseDepreciationRepoMock) UpsertManualInput(_ context.Context, row *entity.FarmDepreciationManualInput) error {
|
||||||
|
if row == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cloned := *row
|
||||||
|
if cloned.Id == 0 {
|
||||||
|
cloned.Id = 123
|
||||||
|
}
|
||||||
|
m.upsertedRow = &cloned
|
||||||
|
row.Id = cloned.Id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *expenseDepreciationRepoMock) DeleteSnapshotsFromDate(_ context.Context, fromDate time.Time, farmIDs []uint) error {
|
||||||
|
m.deleteCalled = true
|
||||||
|
m.deleteDate = fromDate
|
||||||
|
m.deleteFarmIDs = append([]uint{}, farmIDs...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *expenseDepreciationRepoMock) GetLatestManualInputsByFarms(_ context.Context, _ []int64, _ []int64, _ []int64) ([]repportRepo.FarmDepreciationManualInputRow, error) {
|
||||||
|
return append([]repportRepo.FarmDepreciationManualInputRow{}, m.manualInputs...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type hppCostRepoMock struct {
|
||||||
|
commonRepo.HppCostRepository
|
||||||
|
kandangIDsByFarm map[uint][]uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *hppCostRepoMock) GetProjectFlockKandangIDs(_ context.Context, projectFlockID uint) ([]uint, error) {
|
||||||
|
return append([]uint{}, m.kandangIDsByFarm[projectFlockID]...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type hppV2ServiceMock struct {
|
||||||
|
approvalService.HppV2Service
|
||||||
|
breakdownByPFK map[uint]*approvalService.HppV2Breakdown
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *hppV2ServiceMock) CalculateHppBreakdown(projectFlockKandangId uint, _ *time.Time) (*approvalService.HppV2Breakdown, error) {
|
||||||
|
return m.breakdownByPFK[projectFlockKandangId], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeExpenseDepreciationSnapshots_FromHppV2NormalTransfer(t *testing.T) {
|
||||||
|
periodDate := mustJakartaDate(t, "2026-06-05")
|
||||||
|
svc := &repportService{
|
||||||
|
HppCostRepo: &hppCostRepoMock{
|
||||||
|
kandangIDsByFarm: map[uint][]uint{
|
||||||
|
1: {10},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HppV2Svc: &hppV2ServiceMock{
|
||||||
|
breakdownByPFK: map[uint]*approvalService.HppV2Breakdown{
|
||||||
|
10: {
|
||||||
|
ProjectFlockKandangID: 10,
|
||||||
|
KandangID: 100,
|
||||||
|
KandangName: "Kandang A",
|
||||||
|
HouseType: "close_house",
|
||||||
|
Components: []approvalService.HppV2Component{
|
||||||
|
{
|
||||||
|
Code: "DEPRECIATION",
|
||||||
|
Title: "Depreciation",
|
||||||
|
Total: 100,
|
||||||
|
Parts: []approvalService.HppV2ComponentPart{
|
||||||
|
{
|
||||||
|
Code: "normal_transfer",
|
||||||
|
Total: 100,
|
||||||
|
Details: map[string]any{
|
||||||
|
"schedule_day": 2,
|
||||||
|
"depreciation_percent": 10.0,
|
||||||
|
"pullet_cost_day_n": 1000.0,
|
||||||
|
"source_project_flock_id": 77,
|
||||||
|
"origin_date": "2026-01-01",
|
||||||
|
},
|
||||||
|
References: []approvalService.HppV2Reference{
|
||||||
|
{
|
||||||
|
Type: "laying_transfer",
|
||||||
|
ID: 701,
|
||||||
|
Date: "2026-05-20",
|
||||||
|
Qty: 150,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := svc.computeExpenseDepreciationSnapshots(context.Background(), periodDate, []uint{1}, map[uint]string{1: "Farm A"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
if len(rows) != 1 {
|
||||||
|
t.Fatalf("expected 1 row, got %d", len(rows))
|
||||||
|
}
|
||||||
|
if rows[0].DepreciationValue != 100 {
|
||||||
|
t.Fatalf("expected depreciation value 100, got %v", rows[0].DepreciationValue)
|
||||||
|
}
|
||||||
|
if rows[0].PulletCostDayNTotal != 1000 {
|
||||||
|
t.Fatalf("expected pullet cost day n 1000, got %v", rows[0].PulletCostDayNTotal)
|
||||||
|
}
|
||||||
|
assertFloatEqual(t, rows[0].DepreciationPercentEffective, 10)
|
||||||
|
|
||||||
|
components := decodeDepreciationComponents(t, rows[0].Components)
|
||||||
|
if components.KandangCount != 1 {
|
||||||
|
t.Fatalf("expected kandang_count 1, got %d", components.KandangCount)
|
||||||
|
}
|
||||||
|
entry := components.Kandang[0]
|
||||||
|
if entry.ProjectFlockKandangID != 10 || entry.KandangID != 100 || entry.KandangName != "Kandang A" {
|
||||||
|
t.Fatalf("unexpected kandang identity: %+v", entry)
|
||||||
|
}
|
||||||
|
if entry.TransferID != 701 || entry.TransferDate != "2026-05-20" || entry.TransferQty != 150 {
|
||||||
|
t.Fatalf("unexpected transfer metadata: %+v", entry)
|
||||||
|
}
|
||||||
|
if entry.DepreciationSource != "normal_transfer" {
|
||||||
|
t.Fatalf("expected depreciation_source normal_transfer, got %q", entry.DepreciationSource)
|
||||||
|
}
|
||||||
|
if entry.ManualInputID != nil || entry.CutoverDate != "" || entry.StartScheduleDay != nil {
|
||||||
|
t.Fatalf("expected manual fields empty for normal transfer, got %+v", entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeExpenseDepreciationSnapshots_FromHppV2ManualCutover(t *testing.T) {
|
||||||
|
periodDate := mustJakartaDate(t, "2026-06-05")
|
||||||
|
svc := &repportService{
|
||||||
|
HppCostRepo: &hppCostRepoMock{
|
||||||
|
kandangIDsByFarm: map[uint][]uint{
|
||||||
|
2: {20},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HppV2Svc: &hppV2ServiceMock{
|
||||||
|
breakdownByPFK: map[uint]*approvalService.HppV2Breakdown{
|
||||||
|
20: {
|
||||||
|
ProjectFlockKandangID: 20,
|
||||||
|
KandangID: 200,
|
||||||
|
KandangName: "Kandang B",
|
||||||
|
HouseType: "open_house",
|
||||||
|
Components: []approvalService.HppV2Component{
|
||||||
|
{
|
||||||
|
Code: "DEPRECIATION",
|
||||||
|
Title: "Depreciation",
|
||||||
|
Total: 200,
|
||||||
|
Parts: []approvalService.HppV2ComponentPart{
|
||||||
|
{
|
||||||
|
Code: "manual_cutover",
|
||||||
|
Total: 200,
|
||||||
|
Details: map[string]any{
|
||||||
|
"schedule_day": 2,
|
||||||
|
"start_schedule_day": 2,
|
||||||
|
"depreciation_percent": 25.0,
|
||||||
|
"pullet_cost_day_n": 800.0,
|
||||||
|
"manual_input_id": 901,
|
||||||
|
"cutover_date": "2026-06-01",
|
||||||
|
"origin_date": "2026-01-01",
|
||||||
|
},
|
||||||
|
References: []approvalService.HppV2Reference{
|
||||||
|
{
|
||||||
|
Type: "farm_depreciation_manual_input",
|
||||||
|
ID: 901,
|
||||||
|
Date: "2026-06-01",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := svc.computeExpenseDepreciationSnapshots(context.Background(), periodDate, []uint{2}, map[uint]string{2: "Farm B"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
if len(rows) != 1 {
|
||||||
|
t.Fatalf("expected 1 row, got %d", len(rows))
|
||||||
|
}
|
||||||
|
assertFloatEqual(t, rows[0].DepreciationPercentEffective, 25)
|
||||||
|
|
||||||
|
components := decodeDepreciationComponents(t, rows[0].Components)
|
||||||
|
if components.KandangCount != 1 {
|
||||||
|
t.Fatalf("expected kandang_count 1, got %d", components.KandangCount)
|
||||||
|
}
|
||||||
|
entry := components.Kandang[0]
|
||||||
|
if entry.DepreciationSource != "manual_cutover" {
|
||||||
|
t.Fatalf("expected depreciation_source manual_cutover, got %q", entry.DepreciationSource)
|
||||||
|
}
|
||||||
|
if entry.TransferID != 0 || entry.TransferDate != "" || entry.TransferQty != 0 {
|
||||||
|
t.Fatalf("expected transfer fields empty for manual path, got %+v", entry)
|
||||||
|
}
|
||||||
|
if entry.ManualInputID == nil || *entry.ManualInputID != 901 {
|
||||||
|
t.Fatalf("expected manual_input_id 901, got %+v", entry.ManualInputID)
|
||||||
|
}
|
||||||
|
if entry.CutoverDate != "2026-06-01" || entry.OriginDate != "2026-01-01" {
|
||||||
|
t.Fatalf("unexpected manual date fields: %+v", entry)
|
||||||
|
}
|
||||||
|
if entry.StartScheduleDay == nil || *entry.StartScheduleDay != 2 {
|
||||||
|
t.Fatalf("expected start_schedule_day 2, got %+v", entry.StartScheduleDay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeExpenseDepreciationSnapshots_AggregatesMultipleKandang(t *testing.T) {
|
||||||
|
periodDate := mustJakartaDate(t, "2026-06-05")
|
||||||
|
svc := &repportService{
|
||||||
|
HppCostRepo: &hppCostRepoMock{
|
||||||
|
kandangIDsByFarm: map[uint][]uint{
|
||||||
|
3: {30, 31},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HppV2Svc: &hppV2ServiceMock{
|
||||||
|
breakdownByPFK: map[uint]*approvalService.HppV2Breakdown{
|
||||||
|
30: {
|
||||||
|
ProjectFlockKandangID: 30,
|
||||||
|
KandangID: 300,
|
||||||
|
KandangName: "Kandang C1",
|
||||||
|
Components: []approvalService.HppV2Component{
|
||||||
|
{
|
||||||
|
Code: "DEPRECIATION",
|
||||||
|
Parts: []approvalService.HppV2ComponentPart{
|
||||||
|
{
|
||||||
|
Code: "normal_transfer",
|
||||||
|
Total: 50,
|
||||||
|
Details: map[string]any{
|
||||||
|
"schedule_day": 1,
|
||||||
|
"depreciation_percent": 10.0,
|
||||||
|
"pullet_cost_day_n": 500.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
31: {
|
||||||
|
ProjectFlockKandangID: 31,
|
||||||
|
KandangID: 301,
|
||||||
|
KandangName: "Kandang C2",
|
||||||
|
Components: []approvalService.HppV2Component{
|
||||||
|
{
|
||||||
|
Code: "DEPRECIATION",
|
||||||
|
Parts: []approvalService.HppV2ComponentPart{
|
||||||
|
{
|
||||||
|
Code: "normal_transfer",
|
||||||
|
Total: 100,
|
||||||
|
Details: map[string]any{
|
||||||
|
"schedule_day": 2,
|
||||||
|
"depreciation_percent": 10.0,
|
||||||
|
"pullet_cost_day_n": 1000.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := svc.computeExpenseDepreciationSnapshots(context.Background(), periodDate, []uint{3}, map[uint]string{3: "Farm C"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
if len(rows) != 1 {
|
||||||
|
t.Fatalf("expected 1 row, got %d", len(rows))
|
||||||
|
}
|
||||||
|
if rows[0].DepreciationValue != 150 {
|
||||||
|
t.Fatalf("expected depreciation value 150, got %v", rows[0].DepreciationValue)
|
||||||
|
}
|
||||||
|
if rows[0].PulletCostDayNTotal != 1500 {
|
||||||
|
t.Fatalf("expected pullet cost day n 1500, got %v", rows[0].PulletCostDayNTotal)
|
||||||
|
}
|
||||||
|
assertFloatEqual(t, rows[0].DepreciationPercentEffective, 10)
|
||||||
|
|
||||||
|
components := decodeDepreciationComponents(t, rows[0].Components)
|
||||||
|
if components.KandangCount != 2 {
|
||||||
|
t.Fatalf("expected kandang_count 2, got %d", components.KandangCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeExpenseDepreciationSnapshots_ZeroWhenDepreciationMissing(t *testing.T) {
|
||||||
|
periodDate := mustJakartaDate(t, "2026-06-05")
|
||||||
|
svc := &repportService{
|
||||||
|
HppCostRepo: &hppCostRepoMock{
|
||||||
|
kandangIDsByFarm: map[uint][]uint{
|
||||||
|
4: {40},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HppV2Svc: &hppV2ServiceMock{
|
||||||
|
breakdownByPFK: map[uint]*approvalService.HppV2Breakdown{
|
||||||
|
40: {
|
||||||
|
ProjectFlockKandangID: 40,
|
||||||
|
KandangID: 400,
|
||||||
|
KandangName: "Kandang D",
|
||||||
|
Components: []approvalService.HppV2Component{
|
||||||
|
{Code: "PAKAN", Total: 123},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := svc.computeExpenseDepreciationSnapshots(context.Background(), periodDate, []uint{4}, map[uint]string{4: "Farm D"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
if len(rows) != 1 {
|
||||||
|
t.Fatalf("expected 1 row, got %d", len(rows))
|
||||||
|
}
|
||||||
|
if rows[0].DepreciationValue != 0 || rows[0].PulletCostDayNTotal != 0 || rows[0].DepreciationPercentEffective != 0 {
|
||||||
|
t.Fatalf("expected zero snapshot values, got %+v", rows[0])
|
||||||
|
}
|
||||||
|
components := decodeDepreciationComponents(t, rows[0].Components)
|
||||||
|
if components.KandangCount != 0 || len(components.Kandang) != 0 {
|
||||||
|
t.Fatalf("expected empty components, got %+v", components)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpsertExpenseDepreciationManualInput_InvalidatesSnapshotsFromCutoverDate(t *testing.T) {
|
||||||
|
repo := &expenseDepreciationRepoMock{
|
||||||
|
manualInputs: []repportRepo.FarmDepreciationManualInputRow{
|
||||||
|
{
|
||||||
|
Id: 123,
|
||||||
|
ProjectFlockID: 99,
|
||||||
|
FarmName: "Farm Z",
|
||||||
|
TotalCost: 1000,
|
||||||
|
CutoverDate: mustJakartaDate(t, "2026-06-01"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
svc := &repportService{
|
||||||
|
Validate: validator.New(),
|
||||||
|
ExpenseDepreciationRepo: repo,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqPayload := &validation.ExpenseDepreciationManualInputUpsert{
|
||||||
|
ProjectFlockID: 99,
|
||||||
|
TotalCost: 1000,
|
||||||
|
CutoverDate: "2026-06-01",
|
||||||
|
}
|
||||||
|
|
||||||
|
app := fiber.New()
|
||||||
|
var response *dto.ExpenseDepreciationManualInputRowDTO
|
||||||
|
app.Put("/", func(c *fiber.Ctx) error {
|
||||||
|
result, err := svc.UpsertExpenseDepreciationManualInput(c, reqPayload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
response = result
|
||||||
|
return c.SendStatus(fiber.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
httpResp, err := app.Test(httptest.NewRequest(http.MethodPut, "/", nil))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no app error, got %v", err)
|
||||||
|
}
|
||||||
|
if httpResp.StatusCode != fiber.StatusOK {
|
||||||
|
t.Fatalf("expected status %d, got %d", fiber.StatusOK, httpResp.StatusCode)
|
||||||
|
}
|
||||||
|
if !repo.deleteCalled {
|
||||||
|
t.Fatal("expected DeleteSnapshotsFromDate to be called")
|
||||||
|
}
|
||||||
|
if len(repo.deleteFarmIDs) != 1 || repo.deleteFarmIDs[0] != 99 {
|
||||||
|
t.Fatalf("expected delete farm ids [99], got %v", repo.deleteFarmIDs)
|
||||||
|
}
|
||||||
|
if repo.deleteDate.Format("2006-01-02") != "2026-06-01" {
|
||||||
|
t.Fatalf("expected delete date 2026-06-01, got %s", repo.deleteDate.Format("2006-01-02"))
|
||||||
|
}
|
||||||
|
if response == nil {
|
||||||
|
t.Fatal("expected response")
|
||||||
|
}
|
||||||
|
if response.FarmName != "Farm Z" {
|
||||||
|
t.Fatalf("expected farm name Farm Z, got %s", response.FarmName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeDepreciationComponents(t *testing.T, raw []byte) depreciationFarmComponents {
|
||||||
|
t.Helper()
|
||||||
|
var out depreciationFarmComponents
|
||||||
|
if len(raw) == 0 {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(raw, &out); err != nil {
|
||||||
|
t.Fatalf("failed to decode components: %v", err)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustJakartaDate(t *testing.T, raw string) time.Time {
|
||||||
|
t.Helper()
|
||||||
|
location, err := time.LoadLocation("Asia/Jakarta")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed loading timezone: %v", err)
|
||||||
|
}
|
||||||
|
value, err := time.ParseInLocation("2006-01-02", raw, location)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed parsing date %q: %v", raw, err)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertFloatEqual(t *testing.T, got float64, want float64) {
|
||||||
|
t.Helper()
|
||||||
|
const epsilon = 0.000001
|
||||||
|
if got > want+epsilon || got < want-epsilon {
|
||||||
|
t.Fatalf("expected %.6f, got %.6f", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -416,6 +416,13 @@ func (s *repportService) UpsertExpenseDepreciationManualInput(ctx *fiber.Ctx, re
|
|||||||
if err := s.ExpenseDepreciationRepo.UpsertManualInput(ctx.Context(), &row); err != nil {
|
if err := s.ExpenseDepreciationRepo.UpsertManualInput(ctx.Context(), &row); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := s.ExpenseDepreciationRepo.DeleteSnapshotsFromDate(
|
||||||
|
ctx.Context(),
|
||||||
|
cutoverDate,
|
||||||
|
[]uint{row.ProjectFlockId},
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
response := &dto.ExpenseDepreciationManualInputRowDTO{
|
response := &dto.ExpenseDepreciationManualInputRowDTO{
|
||||||
ID: int64(row.Id),
|
ID: int64(row.Id),
|
||||||
@@ -456,6 +463,11 @@ type depreciationKandangComponent struct {
|
|||||||
TransferQty float64 `json:"transfer_qty"`
|
TransferQty float64 `json:"transfer_qty"`
|
||||||
PulletCostDayN float64 `json:"pullet_cost_day_n"`
|
PulletCostDayN float64 `json:"pullet_cost_day_n"`
|
||||||
DepreciationValue float64 `json:"depreciation_value"`
|
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 {
|
type depreciationFarmComponents struct {
|
||||||
@@ -469,124 +481,98 @@ func (s *repportService) computeExpenseDepreciationSnapshots(
|
|||||||
farmIDs []uint,
|
farmIDs []uint,
|
||||||
farmNameByID map[uint]string,
|
farmNameByID map[uint]string,
|
||||||
) ([]entity.FarmDepreciationSnapshot, error) {
|
) ([]entity.FarmDepreciationSnapshot, error) {
|
||||||
|
_ = farmNameByID
|
||||||
|
|
||||||
if len(farmIDs) == 0 {
|
if len(farmIDs) == 0 {
|
||||||
return []entity.FarmDepreciationSnapshot{}, nil
|
return []entity.FarmDepreciationSnapshot{}, nil
|
||||||
}
|
}
|
||||||
|
if s.HppCostRepo == nil {
|
||||||
inputRows, err := s.ExpenseDepreciationRepo.GetLatestTransferInputsByFarms(ctx, periodDate, farmIDs)
|
return nil, errors.New("hpp cost repository is not configured")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
if s.HppV2Svc == nil {
|
||||||
groupedByFarm := make(map[uint][]repportRepo.FarmDepreciationLatestTransferRow, len(farmIDs))
|
return nil, errors.New("hpp v2 service is not configured")
|
||||||
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{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
result := make([]entity.FarmDepreciationSnapshot, 0, len(farmIDs))
|
||||||
for _, farmID := range farmIDs {
|
for _, farmID := range farmIDs {
|
||||||
farmRows := groupedByFarm[farmID]
|
kandangIDs, err := s.HppCostRepo.GetProjectFlockKandangIDs(ctx, farmID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
components := depreciationFarmComponents{
|
components := depreciationFarmComponents{
|
||||||
KandangCount: len(farmRows),
|
Kandang: make([]depreciationKandangComponent, 0, len(kandangIDs)),
|
||||||
Kandang: make([]depreciationKandangComponent, 0, len(farmRows)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
totalDepreciationValue := 0.0
|
totalDepreciationValue := 0.0
|
||||||
totalPulletCostDayN := 0.0
|
totalPulletCostDayN := 0.0
|
||||||
for _, row := range farmRows {
|
for _, kandangID := range kandangIDs {
|
||||||
dayN := approvalService.DepreciationScheduleDay(row.TransferDate, periodDate, valueOrEmptyString(row.HouseType))
|
breakdown, err := s.HppV2Svc.CalculateHppBreakdown(kandangID, &periodDate)
|
||||||
houseType := approvalService.NormalizeDepreciationHouseType(valueOrEmptyString(row.HouseType))
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
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
|
|
||||||
}
|
}
|
||||||
cached = sourceCostCacheItem{totalDepCost: sourceDepCost}
|
if breakdown == nil {
|
||||||
sourceCostCache[cacheKey] = cached
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
sourcePopulation, popExists := sourcePopulationCache[row.SourceProjectFlockID]
|
depreciationComponent := hppV2FindDepreciationComponent(breakdown)
|
||||||
if !popExists {
|
if depreciationComponent == nil {
|
||||||
if s.HppCostRepo == nil {
|
continue
|
||||||
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
|
|
||||||
}
|
|
||||||
sourcePopulationCache[row.SourceProjectFlockID] = sourcePopulation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initialPulletCost := 0.0
|
for _, part := range depreciationComponent.Parts {
|
||||||
if sourcePopulation > 0 {
|
if part.Total <= 0 {
|
||||||
initialPulletCost = (cached.totalDepCost * row.TransferQty) / sourcePopulation
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
pulletCostDayN, depreciationValue, depreciationPercent := approvalService.CalculateDepreciationAtDayN(
|
houseType := approvalService.NormalizeDepreciationHouseType(breakdown.HouseType)
|
||||||
initialPulletCost,
|
component := depreciationKandangComponent{
|
||||||
dayN,
|
ProjectFlockKandangID: breakdown.ProjectFlockKandangID,
|
||||||
houseType,
|
KandangID: breakdown.KandangID,
|
||||||
percentByHouseType,
|
KandangName: breakdown.KandangName,
|
||||||
)
|
SourceProjectFlockID: hppV2DetailUint(part.Details, "source_project_flock_id"),
|
||||||
|
|
||||||
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,
|
HouseType: houseType,
|
||||||
DayN: dayN,
|
DayN: hppV2DetailInt(part.Details, "schedule_day"),
|
||||||
DepreciationPercent: depreciationPercent,
|
DepreciationPercent: hppV2DetailFloat(part.Details, "depreciation_percent"),
|
||||||
TransferQty: row.TransferQty,
|
PulletCostDayN: hppV2DetailFloat(part.Details, "pullet_cost_day_n"),
|
||||||
PulletCostDayN: pulletCostDayN,
|
DepreciationValue: part.Total,
|
||||||
DepreciationValue: depreciationValue,
|
DepreciationSource: part.Code,
|
||||||
})
|
OriginDate: hppV2DetailString(part.Details, "origin_date"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
components.KandangCount = len(components.Kandang)
|
||||||
effectivePercent := approvalService.CalculateEffectiveDepreciationPercent(totalDepreciationValue, totalPulletCostDayN)
|
effectivePercent := approvalService.CalculateEffectiveDepreciationPercent(totalDepreciationValue, totalPulletCostDayN)
|
||||||
|
|
||||||
componentsJSON, marshalErr := json.Marshal(components)
|
componentsJSON, marshalErr := json.Marshal(components)
|
||||||
@@ -607,6 +593,106 @@ func (s *repportService) computeExpenseDepreciationSnapshots(
|
|||||||
return result, nil
|
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 {
|
func parseSnapshotComponents(raw []byte) any {
|
||||||
if len(raw) == 0 {
|
if len(raw) == 0 {
|
||||||
return map[string]any{}
|
return map[string]any{}
|
||||||
@@ -2280,13 +2366,15 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
|
|||||||
}
|
}
|
||||||
if hppCost != nil {
|
if hppCost != nil {
|
||||||
eggPiecesFloatRemaining = hppCost.Estimation.Butir - hppCost.Real.Butir
|
eggPiecesFloatRemaining = hppCost.Estimation.Butir - hppCost.Real.Butir
|
||||||
eggHpp = hppCost.Estimation.HargaKg
|
// eggHpp = hppCost.Estimation.HargaKg
|
||||||
|
eggHpp = hppCost.Real.HargaKg
|
||||||
eggTotalPiecesFloat = hppCost.Estimation.Butir
|
eggTotalPiecesFloat = hppCost.Estimation.Butir
|
||||||
eggWeightFloat = hppCost.Estimation.Kg
|
eggWeightFloat = hppCost.Estimation.Kg
|
||||||
if eggTotalPiecesFloat > 0 {
|
if eggTotalPiecesFloat > 0 {
|
||||||
avgWeight = eggWeightFloat / eggTotalPiecesFloat
|
avgWeight = eggWeightFloat / eggTotalPiecesFloat
|
||||||
}
|
}
|
||||||
eggRemainingWeightFloatRemaining = avgWeight * eggPiecesFloatRemaining
|
// eggRemainingWeightFloatRemaining = avgWeight * eggPiecesFloatRemaining
|
||||||
|
eggRemainingWeightFloatRemaining = hppCost.Estimation.Kg - hppCost.Real.Kg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if math.IsNaN(eggPiecesFloatRemaining) || math.IsInf(eggPiecesFloatRemaining, 0) {
|
if math.IsNaN(eggPiecesFloatRemaining) || math.IsInf(eggPiecesFloatRemaining, 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user