Merge branch 'feat/hpp-harian' into 'development'

[FEAT][BE]: add hpp harian

See merge request mbugroup/lti-api!195
This commit is contained in:
Hafizh A. Y.
2026-01-15 10:54:29 +00:00
3 changed files with 238 additions and 100 deletions
+25 -33
View File
@@ -25,14 +25,12 @@ type HppPerKandangResponseData struct {
} }
type HppPerKandangRowDTO struct { type HppPerKandangRowDTO struct {
ID int `json:"id"` ID int `json:"id"`
Kandang HppPerKandangRowKandangDTO `json:"kandang"` Kandang HppPerKandangRowKandangDTO `json:"kandang"`
WeightRange HppPerKandangWeightRangeDTO `json:"weight_range"` WeightRange HppPerKandangWeightRangeDTO `json:"weight_range"`
RemainingChickenBirds int64 `json:"remaining_chicken_birds"` AvgWeightKg float64 `json:"avg_weight_kg"`
RemainingChickenWeightKg float64 `json:"remaining_chicken_weight_kg"` EggProductionPieces int64 `json:"egg_production_pieces"`
AvgWeightKg float64 `json:"avg_weight_kg"` EggProductionKg float64 `json:"egg_production_kg"`
EggProductionPieces int64 `json:"egg_production_pieces"`
EggProductionKg float64 `json:"egg_production_kg"`
// FeedCostRp float64 `json:"feed_cost_rp"` // FeedCostRp float64 `json:"feed_cost_rp"`
// OvkCostRp float64 `json:"ovk_cost_rp"` // OvkCostRp float64 `json:"ovk_cost_rp"`
EggHppRpPerKg float64 `json:"egg_hpp_rp_per_kg"` EggHppRpPerKg float64 `json:"egg_hpp_rp_per_kg"`
@@ -80,34 +78,28 @@ type HppPerKandangSummaryDTO struct {
} }
type HppPerKandangSummaryWeightRangeDTO struct { type HppPerKandangSummaryWeightRangeDTO struct {
ID int `json:"id"` ID int `json:"id"`
WeightRange HppPerKandangWeightRangeDTO `json:"weight_range"` WeightRange HppPerKandangWeightRangeDTO `json:"weight_range"`
Label string `json:"label"` Label string `json:"label"`
RemainingChickenBirds int64 `json:"remaining_chicken_birds"` AvgWeightKg float64 `json:"avg_weight_kg"`
RemainingChickenWeightKg float64 `json:"remaining_chicken_weight_kg"` EggProductionPieces int64 `json:"egg_production_pieces"`
AvgWeightKg float64 `json:"avg_weight_kg"` EggProductionKg float64 `json:"egg_production_kg"`
EggProductionPieces int64 `json:"egg_production_pieces"` EggHppRpPerKg float64 `json:"egg_hpp_rp_per_kg"`
EggProductionKg float64 `json:"egg_production_kg"` EggValueRp int64 `json:"egg_value_rp"`
EggHppRpPerKg float64 `json:"egg_hpp_rp_per_kg"` FeedSuppliers []HppPerKandangSupplierDTO `json:"feed_suppliers"`
EggValueRp int64 `json:"egg_value_rp"` DocSuppliers []HppPerKandangSupplierDTO `json:"doc_suppliers"`
FeedSuppliers []HppPerKandangSupplierDTO `json:"feed_suppliers"` AverageDocPriceRp float64 `json:"average_doc_price_rp"`
DocSuppliers []HppPerKandangSupplierDTO `json:"doc_suppliers"` HppRp float64 `json:"hpp_rp"`
AverageDocPriceRp float64 `json:"average_doc_price_rp"` RemainingValueRp int64 `json:"remaining_value_rp"`
HppRp float64 `json:"hpp_rp"`
RemainingValueRp int64 `json:"remaining_value_rp"`
} }
type HppPerKandangSummaryTotalDTO struct { type HppPerKandangSummaryTotalDTO struct {
TotalRemainingChickenBirds int64 `json:"total_remaining_chicken_birds"` AverageWeightKg float64 `json:"average_weight_kg"`
TotalRemainingChickenWeightKg float64 `json:"total_remaining_chicken_weight_kg"` TotalEggProductionPieces int64 `json:"total_egg_production_pieces"`
AverageWeightKg float64 `json:"average_weight_kg"` TotalEggProductionKg float64 `json:"total_egg_production_kg"`
TotalRemainingValueRp int64 `json:"total_remaining_value_rp"` AverageEggHppRpPerKg float64 `json:"average_egg_hpp_rp_per_kg"`
TotalEggProductionPieces int64 `json:"total_egg_production_pieces"` TotalEggValueRp int64 `json:"total_egg_value_rp"`
TotalEggProductionKg float64 `json:"total_egg_production_kg"` TotalAverageDocPriceRp float64 `json:"total_average_doc_price_rp"`
AverageEggHppRpPerKg float64 `json:"average_egg_hpp_rp_per_kg"`
TotalEggValueRp int64 `json:"total_egg_value_rp"`
TotalHppRp float64 `json:"total_hpp_rp"`
TotalAverageDocPriceRp float64 `json:"total_average_doc_price_rp"`
} }
func NewHppPerKandangFiltersDTO(area, location, kandang, weightMin, weightMax, period, showUnrecorded string) HppPerKandangFiltersDTO { func NewHppPerKandangFiltersDTO(area, location, kandang, weightMin, weightMax, period, showUnrecorded string) HppPerKandangFiltersDTO {
@@ -11,6 +11,7 @@ import (
) )
type HppPerKandangRow struct { type HppPerKandangRow struct {
ProjectFlockKandangID uint
KandangID uint KandangID uint
KandangName string KandangName string
KandangStatus string KandangStatus string
@@ -18,6 +19,7 @@ type HppPerKandangRow struct {
LocationName string LocationName string
PicID uint PicID uint
PicName string PicName string
RecordingCount int64
RemainingChickenBirds float64 RemainingChickenBirds float64
RemainingChickenWeight float64 RemainingChickenWeight float64
EggProductionWeightKg float64 EggProductionWeightKg float64
@@ -44,7 +46,8 @@ type HppPerKandangSupplierRow struct {
type HppPerKandangRepository interface { type HppPerKandangRepository interface {
GetRowsByPeriod(ctx context.Context, start, end time.Time, areaIDs, locationIDs, kandangIDs []int64) ([]HppPerKandangRow, error) GetRowsByPeriod(ctx context.Context, start, end time.Time, areaIDs, locationIDs, kandangIDs []int64) ([]HppPerKandangRow, error)
GetFeedOvkDocCostByPeriod(ctx context.Context, start, end time.Time, areaIDs, locationIDs, kandangIDs []int64) ([]HppPerKandangCostRow, []HppPerKandangSupplierRow, error) GetFeedOvkDocCostByPeriod(ctx context.Context, start, end time.Time, projectFlockKandangIDs []uint) ([]HppPerKandangCostRow, []HppPerKandangSupplierRow, error)
GetEggProductionByProjectFlockKandangIDs(ctx context.Context, start, end time.Time, projectFlockKandangIDs []uint) (map[uint]HppPerKandangRow, error)
} }
type hppPerKandangRepository struct { type hppPerKandangRepository struct {
@@ -58,9 +61,31 @@ func NewHppPerKandangRepository(db *gorm.DB) HppPerKandangRepository {
func (r *hppPerKandangRepository) GetRowsByPeriod(ctx context.Context, start, end time.Time, areaIDs, locationIDs, kandangIDs []int64) ([]HppPerKandangRow, error) { func (r *hppPerKandangRepository) GetRowsByPeriod(ctx context.Context, start, end time.Time, areaIDs, locationIDs, kandangIDs []int64) ([]HppPerKandangRow, error) {
var rows []HppPerKandangRow var rows []HppPerKandangRow
query := r.db.WithContext(ctx). latestApproval := r.db.WithContext(ctx).
Table("approvals AS a").
Select("a.approvable_id, a.action").
Joins(`
JOIN (
SELECT approvable_id, MAX(action_at) AS latest_action_at
FROM approvals
WHERE approvable_type = ?
GROUP BY approvable_id
) AS la ON la.approvable_id = a.approvable_id AND la.latest_action_at = a.action_at`,
string(utils.ApprovalWorkflowRecording),
)
validRecordings := r.db.WithContext(ctx).
Table("recordings AS r"). Table("recordings AS r").
Select("r.id, r.project_flock_kandangs_id, r.total_chick_qty").
Joins("LEFT JOIN (?) AS la ON la.approvable_id = r.id", latestApproval).
Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
Where("r.deleted_at IS NULL").
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected))
query := r.db.WithContext(ctx).
Table("project_flocks AS pf").
Select(` Select(`
pfk.id AS project_flock_kandang_id,
k.id AS kandang_id, k.id AS kandang_id,
k.name AS kandang_name, k.name AS kandang_name,
k.status AS kandang_status, k.status AS kandang_status,
@@ -68,22 +93,21 @@ func (r *hppPerKandangRepository) GetRowsByPeriod(ctx context.Context, start, en
loc.name AS location_name, loc.name AS location_name,
pic.id AS pic_id, pic.id AS pic_id,
pic.name AS pic_name, pic.name AS pic_name,
COALESCE(MAX(r.total_chick_qty), 0) AS remaining_chicken_birds, COALESCE(COUNT(vr.id), 0) AS recording_count,
COALESCE(SUM(rbw.total_weight), 0) AS remaining_chicken_weight, COALESCE(MAX(vr.total_chick_qty), 0) AS remaining_chicken_birds,
COALESCE(SUM(re.weight), 0) AS egg_production_weight_kg, 0 AS remaining_chicken_weight,
COALESCE(SUM(re.qty), 0) AS egg_production_pieces`). 0 AS egg_production_weight_kg,
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). 0 AS egg_production_pieces`).
Joins("JOIN project_flock_kandangs AS pfk ON pfk.project_flock_id = pf.id").
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
Joins("JOIN locations AS loc ON loc.id = k.location_id"). Joins("JOIN locations AS loc ON loc.id = k.location_id").
Joins("JOIN users AS pic ON pic.id = k.pic_id"). Joins("JOIN users AS pic ON pic.id = k.pic_id").
Joins("LEFT JOIN recording_bws AS rbw ON rbw.recording_id = r.id"). Joins("LEFT JOIN (?) AS vr ON vr.project_flock_kandangs_id = pfk.id", validRecordings).
Joins("LEFT JOIN recording_eggs AS re ON re.recording_id = r.id"). Where("pfk.closed_at IS NULL")
Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
Where("r.deleted_at IS NULL")
query = applyLocationFilters(query, areaIDs, locationIDs, kandangIDs) query = applyLocationFilters(query, areaIDs, locationIDs, kandangIDs)
query = query.Group("k.id, k.name, k.status, loc.id, loc.name, pic.id, pic.name"). query = query.Group("pfk.id, k.id, k.name, k.status, loc.id, loc.name, pic.id, pic.name").
Order("k.id ASC") Order("k.id ASC")
if err := query.Scan(&rows).Error; err != nil { if err := query.Scan(&rows).Error; err != nil {
@@ -93,41 +117,44 @@ func (r *hppPerKandangRepository) GetRowsByPeriod(ctx context.Context, start, en
return rows, nil return rows, nil
} }
func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context, start, end time.Time, areaIDs, locationIDs, kandangIDs []int64) ([]HppPerKandangCostRow, []HppPerKandangSupplierRow, error) { func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context, start, end time.Time, projectFlockKandangIDs []uint) ([]HppPerKandangCostRow, []HppPerKandangSupplierRow, error) {
var rows []HppPerKandangCostRow var rows []HppPerKandangCostRow
recordingPfk := r.db.WithContext(ctx).
Table("recordings AS r").
Select("DISTINCT pfk.id").
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
Joins("JOIN locations AS loc ON loc.id = k.location_id").
Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
Where("r.deleted_at IS NULL")
recordingPfk = applyLocationFilters(recordingPfk, areaIDs, locationIDs, kandangIDs)
purchaseStockableKey := fifo.StockableKeyPurchaseItems.String() purchaseStockableKey := fifo.StockableKeyPurchaseItems.String()
transferStockableKey := fifo.StockableKeyStockTransferIn.String() transferStockableKey := fifo.StockableKeyStockTransferIn.String()
latestApproval := r.db.WithContext(ctx).
Table("approvals AS a").
Select("a.approvable_id, a.action").
Joins(`
JOIN (
SELECT approvable_id, MAX(action_at) AS latest_action_at
FROM approvals
WHERE approvable_type = ?
GROUP BY approvable_id
) AS la ON la.approvable_id = a.approvable_id AND la.latest_action_at = a.action_at`,
string(utils.ApprovalWorkflowRecording),
)
query := r.db.WithContext(ctx). query := r.db.WithContext(ctx).
Table("recordings AS r"). Table("recordings AS r").
Select(` Select(`
k.id AS kandang_id, k.id AS kandang_id,
COALESCE(SUM(CASE COALESCE(SUM(CASE
WHEN f.name = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0) WHEN f.name = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
WHEN sa.stockable_type = ? AND tf.name = ? THEN COALESCE(std.quantity, 0) * COALESCE(tpi.price, 0) WHEN sa.stockable_type = ? AND tf.name = ? THEN COALESCE(std.total_qty, 0) * COALESCE(tpi.price, 0)
ELSE 0 ELSE 0
END), 0) AS feed_cost, END), 0) AS feed_cost,
COALESCE(SUM(CASE COALESCE(SUM(CASE
WHEN f.name = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0) WHEN f.name = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
WHEN sa.stockable_type = ? AND tf.name = ? THEN COALESCE(std.quantity, 0) * COALESCE(tpi.price, 0) WHEN sa.stockable_type = ? AND tf.name = ? THEN COALESCE(std.total_qty, 0) * COALESCE(tpi.price, 0)
ELSE 0 ELSE 0
END), 0) AS ovk_cost`, END), 0) AS ovk_cost`,
utils.FlagPakan, transferStockableKey, utils.FlagPakan, utils.FlagPakan, transferStockableKey, utils.FlagPakan,
utils.FlagOVK, transferStockableKey, utils.FlagOVK). utils.FlagOVK, transferStockableKey, utils.FlagOVK).
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id"). Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id"). Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
Joins("JOIN locations AS loc ON loc.id = k.location_id"). Joins("LEFT JOIN (?) AS la ON la.approvable_id = r.id", latestApproval).
Joins("LEFT JOIN recording_stocks AS rs ON rs.recording_id = r.id"). Joins("LEFT JOIN recording_stocks AS rs ON rs.recording_id = r.id").
Joins("LEFT JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND sa.status = ?", fifo.UsableKeyRecordingStock.String(), entity.StockAllocationStatusActive). Joins("LEFT JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND sa.status = ?", fifo.UsableKeyRecordingStock.String(), entity.StockAllocationStatusActive).
Joins("LEFT JOIN purchase_items AS pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", purchaseStockableKey). Joins("LEFT JOIN purchase_items AS pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", purchaseStockableKey).
@@ -136,11 +163,10 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
Joins("LEFT JOIN purchase_items AS tpi ON tpi.product_id = std.product_id AND tpi.warehouse_id = st.from_warehouse_id"). Joins("LEFT JOIN purchase_items AS tpi ON tpi.product_id = std.product_id AND tpi.warehouse_id = st.from_warehouse_id").
Joins("LEFT JOIN flags AS f ON f.flagable_id = pi.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct). Joins("LEFT JOIN flags AS f ON f.flagable_id = pi.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct).
Joins("LEFT JOIN flags AS tf ON tf.flagable_id = std.product_id AND tf.flagable_type = ?", entity.FlagableTypeProduct). Joins("LEFT JOIN flags AS tf ON tf.flagable_id = std.product_id AND tf.flagable_type = ?", entity.FlagableTypeProduct).
Where("r.project_flock_kandangs_id IN (?)", recordingPfk.Session(&gorm.Session{NewDB: true})). Where("r.project_flock_kandangs_id IN ?", projectFlockKandangIDs).
Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). // Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
Where("r.deleted_at IS NULL") Where("r.deleted_at IS NULL").
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected))
query = applyLocationFilters(query, areaIDs, locationIDs, kandangIDs)
query = query.Group("k.id").Order("k.id ASC") query = query.Group("k.id").Order("k.id ASC")
@@ -172,9 +198,8 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
Joins("LEFT JOIN purchase_items AS pi ON pi.product_warehouse_id = pc.product_warehouse_id"). Joins("LEFT JOIN purchase_items AS pi ON pi.product_warehouse_id = pc.product_warehouse_id").
Joins("LEFT JOIN purchases AS pur ON pur.id = pi.purchase_id"). Joins("LEFT JOIN purchases AS pur ON pur.id = pi.purchase_id").
Joins("LEFT JOIN suppliers AS s ON s.id = pur.supplier_id"). Joins("LEFT JOIN suppliers AS s ON s.id = pur.supplier_id").
Where("pc.project_flock_kandang_id IN (?)", recordingPfk.Session(&gorm.Session{NewDB: true})). Where("pc.project_flock_kandang_id IN ?", projectFlockKandangIDs).
Group("pfk.kandang_id, s.id, s.name, s.alias") Group("pfk.kandang_id, s.id, s.name, s.alias")
docQuery = applyLocationFilters(docQuery, areaIDs, locationIDs, kandangIDs)
if err := docQuery.Scan(&docRows).Error; err != nil { if err := docQuery.Scan(&docRows).Error; err != nil {
return nil, nil, err return nil, nil, err
@@ -254,9 +279,9 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
Joins("JOIN project_budgets AS pb ON pb.project_flock_id = pfk.project_flock_id"). Joins("JOIN project_budgets AS pb ON pb.project_flock_id = pfk.project_flock_id").
Joins("LEFT JOIN (?) AS k_usage ON k_usage.project_flock_kandang_id = pfk.id", pfkUsageSub). Joins("LEFT JOIN (?) AS k_usage ON k_usage.project_flock_kandang_id = pfk.id", pfkUsageSub).
Joins("LEFT JOIN (?) AS p_usage ON p_usage.project_flock_id = pfk.project_flock_id", projectUsageSub). Joins("LEFT JOIN (?) AS p_usage ON p_usage.project_flock_id = pfk.project_flock_id", projectUsageSub).
Where("pfk.id IN (?)", recordingPfk.Session(&gorm.Session{NewDB: true})). Where("pfk.id IN (?)", projectFlockKandangIDs).
Group("k.id") Group("k.id")
budgetQuery = applyLocationFilters(budgetQuery, areaIDs, locationIDs, kandangIDs) // budgetQuery = applyLocationFilters(budgetQuery, areaIDs, locationIDs, kandangIDs)
if err := budgetQuery.Scan(&budgetRows).Error; err != nil { if err := budgetQuery.Scan(&budgetRows).Error; err != nil {
return nil, nil, err return nil, nil, err
@@ -288,9 +313,9 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
Joins("JOIN locations AS loc ON loc.id = k.location_id"). Joins("JOIN locations AS loc ON loc.id = k.location_id").
Joins("JOIN expense_nonstocks AS en ON en.project_flock_kandang_id = pfk.id"). Joins("JOIN expense_nonstocks AS en ON en.project_flock_kandang_id = pfk.id").
Joins("JOIN expense_realizations AS er ON er.expense_nonstock_id = en.id"). Joins("JOIN expense_realizations AS er ON er.expense_nonstock_id = en.id").
Where("pfk.id IN (?)", recordingPfk.Session(&gorm.Session{NewDB: true})). Where("pfk.id IN (?)", projectFlockKandangIDs).
Group("k.id") Group("k.id")
expenseQuery = applyLocationFilters(expenseQuery, areaIDs, locationIDs, kandangIDs) // expenseQuery = applyLocationFilters(expenseQuery, areaIDs, locationIDs, kandangIDs)
if err := expenseQuery.Scan(&expenseRows).Error; err != nil { if err := expenseQuery.Scan(&expenseRows).Error; err != nil {
return nil, nil, err return nil, nil, err
@@ -323,10 +348,10 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
Joins("LEFT JOIN suppliers AS s ON s.id = pur.supplier_id"). Joins("LEFT JOIN suppliers AS s ON s.id = pur.supplier_id").
Joins("LEFT JOIN flags AS f ON f.flagable_id = pi.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct). Joins("LEFT JOIN flags AS f ON f.flagable_id = pi.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct).
Where("f.name IN ?", []utils.FlagType{utils.FlagPakan, utils.FlagOVK}). Where("f.name IN ?", []utils.FlagType{utils.FlagPakan, utils.FlagOVK}).
Where("r.project_flock_kandangs_id IN (?)", recordingPfk.Session(&gorm.Session{NewDB: true})). Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs).
Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). // Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
Where("r.deleted_at IS NULL") Where("r.deleted_at IS NULL")
feedQuery = applyLocationFilters(feedQuery, areaIDs, locationIDs, kandangIDs) // feedQuery = applyLocationFilters(feedQuery, areaIDs, locationIDs, kandangIDs)
if err := feedQuery.Scan(&feedSuppliers).Error; err != nil { if err := feedQuery.Scan(&feedSuppliers).Error; err != nil {
return nil, nil, err return nil, nil, err
@@ -347,6 +372,61 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
return rows, supplierRows, nil return rows, supplierRows, nil
} }
func (r *hppPerKandangRepository) GetEggProductionByProjectFlockKandangIDs(ctx context.Context, start, end time.Time, projectFlockKandangIDs []uint) (map[uint]HppPerKandangRow, error) {
if len(projectFlockKandangIDs) == 0 {
return map[uint]HppPerKandangRow{}, nil
}
latestApproval := r.db.WithContext(ctx).
Table("approvals AS a").
Select("a.approvable_id, a.action").
Joins(`
JOIN (
SELECT approvable_id, MAX(action_at) AS latest_action_at
FROM approvals
WHERE approvable_type = ?
GROUP BY approvable_id
) AS la ON la.approvable_id = a.approvable_id AND la.latest_action_at = a.action_at`,
string(utils.ApprovalWorkflowRecording),
)
type eggRow struct {
ProjectFlockKandangID uint
EggProductionWeightKg float64
EggProductionPieces float64
}
eggRows := make([]eggRow, 0)
query := r.db.WithContext(ctx).
Table("recordings AS r").
Select(`
r.project_flock_kandangs_id AS project_flock_kandang_id,
COALESCE(SUM(re.weight), 0) AS egg_production_weight_kg,
COALESCE(SUM(re.qty), 0) AS egg_production_pieces`).
Joins("LEFT JOIN (?) AS la ON la.approvable_id = r.id", latestApproval).
Joins("LEFT JOIN recording_eggs AS re ON re.recording_id = r.id").
Where("r.project_flock_kandangs_id IN ?", projectFlockKandangIDs).
// Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
Where("r.deleted_at IS NULL").
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
Group("r.project_flock_kandangs_id")
if err := query.Scan(&eggRows).Error; err != nil {
return nil, err
}
result := make(map[uint]HppPerKandangRow, len(eggRows))
for _, row := range eggRows {
result[row.ProjectFlockKandangID] = HppPerKandangRow{
ProjectFlockKandangID: row.ProjectFlockKandangID,
EggProductionWeightKg: row.EggProductionWeightKg,
EggProductionPieces: row.EggProductionPieces,
}
}
return result, nil
}
func applyLocationFilters(query *gorm.DB, areaIDs, locationIDs, kandangIDs []int64) *gorm.DB { func applyLocationFilters(query *gorm.DB, areaIDs, locationIDs, kandangIDs []int64) *gorm.DB {
if len(areaIDs) > 0 { if len(areaIDs) > 0 {
query = query.Where("loc.area_id IN ?", areaIDs) query = query.Where("loc.area_id IN ?", areaIDs)
@@ -1267,10 +1267,37 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
costRows, supplierRows, err := s.HppPerKandangRepo.GetFeedOvkDocCostByPeriod(ctx.Context(), startOfDay, endOfDay, params.AreaIDs, params.LocationIDs, params.KandangIDs)
if err != nil { validPfkIDs := make([]uint, 0, len(repoRows))
return nil, nil, err pfkIndex := make(map[uint]int, len(repoRows))
for idx := range repoRows {
row := repoRows[idx]
pfkIndex[row.ProjectFlockKandangID] = idx
if row.RecordingCount > 0 {
validPfkIDs = append(validPfkIDs, row.ProjectFlockKandangID)
}
} }
costRows := make([]repportRepo.HppPerKandangCostRow, 0)
supplierRows := make([]repportRepo.HppPerKandangSupplierRow, 0)
if len(validPfkIDs) > 0 {
costRows, supplierRows, err = s.HppPerKandangRepo.GetFeedOvkDocCostByPeriod(ctx.Context(), startOfDay, endOfDay, validPfkIDs)
if err != nil {
return nil, nil, err
}
eggMap, err := s.HppPerKandangRepo.GetEggProductionByProjectFlockKandangIDs(ctx.Context(), startOfDay, endOfDay, validPfkIDs)
if err != nil {
return nil, nil, err
}
for pfkID, egg := range eggMap {
if rowIdx, ok := pfkIndex[pfkID]; ok {
repoRows[rowIdx].EggProductionWeightKg = egg.EggProductionWeightKg
repoRows[rowIdx].EggProductionPieces = egg.EggProductionPieces
}
}
}
costMap := make(map[uint]HppCostAggregate, len(costRows)) costMap := make(map[uint]HppCostAggregate, len(costRows))
for _, row := range costRows { for _, row := range costRows {
costMap[row.KandangID] = HppCostAggregate{ costMap[row.KandangID] = HppCostAggregate{
@@ -1323,9 +1350,15 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
Max float64 Max float64
} }
type weightRangeAggregate struct { type weightRangeAggregate struct {
Summary *dto.HppPerKandangSummaryWeightRangeDTO Summary *dto.HppPerKandangSummaryWeightRangeDTO
EggHppSum float64 RemainingBirds int64
EggHppCount int RemainingWeightKg float64
AvgWeightSum float64
AvgWeightCount int64
EggHppSum float64
EggHppCount int
FeedSuppliers map[int64]dto.HppPerKandangSupplierDTO
DocSuppliers map[int64]dto.HppPerKandangSupplierDTO
} }
dataRows := make([]dto.HppPerKandangRowDTO, 0, len(repoRows)) dataRows := make([]dto.HppPerKandangRowDTO, 0, len(repoRows))
@@ -1342,8 +1375,14 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
var totalDocPriceCount int var totalDocPriceCount int
var totalEggHppSum float64 var totalEggHppSum float64
var totalEggHppCount int var totalEggHppCount int
var totalAvgWeightSum float64
var totalAvgWeightCount int64
for _, row := range repoRows { for _, row := range repoRows {
if !params.ShowUnrecorded && row.RecordingCount == 0 {
continue
}
birdsFloat := row.RemainingChickenBirds birdsFloat := row.RemainingChickenBirds
if math.IsNaN(birdsFloat) || math.IsInf(birdsFloat, 0) { if math.IsNaN(birdsFloat) || math.IsInf(birdsFloat, 0) {
birdsFloat = 0 birdsFloat = 0
@@ -1362,9 +1401,16 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
} }
avgWeight := 0.0 avgWeight := 0.0
if birdsFloat > 0 { if eggPiecesFloat > 0 {
avgWeight = weightFloat / birdsFloat avgWeight = eggWeightFloat / eggPiecesFloat
} }
if params.WeightMin != nil && avgWeight < *params.WeightMin {
continue
}
if params.WeightMax != nil && avgWeight > *params.WeightMax {
continue
}
weightMin := math.Floor(avgWeight*10) / 10 weightMin := math.Floor(avgWeight*10) / 10
if weightMin < 0 { if weightMin < 0 {
weightMin = 0 weightMin = 0
@@ -1411,9 +1457,7 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
WeightMin: weightMin, WeightMin: weightMin,
WeightMax: weightMax, WeightMax: weightMax,
}, },
RemainingChickenBirds: rowBirds, AvgWeightKg: avgWeight,
RemainingChickenWeightKg: weightFloat,
AvgWeightKg: avgWeight,
// FeedCostRp: costEntry.FeedCost, // FeedCostRp: costEntry.FeedCost,
// OvkCostRp: costEntry.OvkCost, // OvkCostRp: costEntry.OvkCost,
DocSuppliers: docSupplierMap[row.KandangID], DocSuppliers: docSupplierMap[row.KandangID],
@@ -1421,10 +1465,10 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
EggProductionPieces: rowEggPieces, EggProductionPieces: rowEggPieces,
EggProductionKg: eggWeightFloat, EggProductionKg: eggWeightFloat,
AverageDocPriceRp: avgDocPrice, AverageDocPriceRp: avgDocPrice,
HppRp: hppRp, // HppRp: hppRp,
EggHppRpPerKg: eggHpp, EggHppRpPerKg: eggHpp,
RemainingValueRp: rowRemainingValue, RemainingValueRp: rowRemainingValue,
EggValueRp: rowEggValue, EggValueRp: rowEggValue,
}) })
totalBirds += rowBirds totalBirds += rowBirds
@@ -1433,6 +1477,8 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
totalEggKg += eggWeightFloat totalEggKg += eggWeightFloat
totalRemainingValueRp += rowRemainingValue totalRemainingValueRp += rowRemainingValue
totalEggValueRp += rowEggValue totalEggValueRp += rowEggValue
totalAvgWeightSum += avgWeight
totalAvgWeightCount++
if weightFloat > 0 { if weightFloat > 0 {
totalHppSum += hppRp totalHppSum += hppRp
totalHppCount++ totalHppCount++
@@ -1456,13 +1502,27 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
}, },
Label: fmt.Sprintf("%.2f - %.2f", weightMin, weightMax), Label: fmt.Sprintf("%.2f - %.2f", weightMin, weightMax),
}, },
FeedSuppliers: make(map[int64]dto.HppPerKandangSupplierDTO),
DocSuppliers: make(map[int64]dto.HppPerKandangSupplierDTO),
} }
perRangeMap[rangeKey] = rangeAgg perRangeMap[rangeKey] = rangeAgg
} }
rangeSummary := rangeAgg.Summary rangeSummary := rangeAgg.Summary
rangeSummary.RemainingChickenBirds += rowBirds rangeAgg.RemainingBirds += rowBirds
rangeSummary.RemainingChickenWeightKg += row.RemainingChickenWeight rangeAgg.RemainingWeightKg += row.RemainingChickenWeight
rangeAgg.AvgWeightSum += avgWeight
rangeAgg.AvgWeightCount++
for _, supplier := range feedSupplierMap[row.KandangID] {
if _, ok := rangeAgg.FeedSuppliers[supplier.ID]; !ok {
rangeAgg.FeedSuppliers[supplier.ID] = supplier
}
}
for _, supplier := range docSupplierMap[row.KandangID] {
if _, ok := rangeAgg.DocSuppliers[supplier.ID]; !ok {
rangeAgg.DocSuppliers[supplier.ID] = supplier
}
}
rangeSummary.EggProductionPieces += rowEggPieces rangeSummary.EggProductionPieces += rowEggPieces
rangeSummary.EggProductionKg += eggWeightFloat rangeSummary.EggProductionKg += eggWeightFloat
rangeSummary.RemainingValueRp += rowRemainingValue rangeSummary.RemainingValueRp += rowRemainingValue
@@ -1489,31 +1549,37 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
agg := perRangeMap[key] agg := perRangeMap[key]
entry := agg.Summary entry := agg.Summary
entry.ID = idx + 1 entry.ID = idx + 1
if entry.RemainingChickenBirds > 0 { if agg.AvgWeightCount > 0 {
entry.AvgWeightKg = entry.RemainingChickenWeightKg / float64(entry.RemainingChickenBirds) entry.AvgWeightKg = agg.AvgWeightSum / float64(agg.AvgWeightCount)
} }
if agg.EggHppCount > 0 { if agg.EggHppCount > 0 {
entry.EggHppRpPerKg = agg.EggHppSum / float64(agg.EggHppCount) entry.EggHppRpPerKg = agg.EggHppSum / float64(agg.EggHppCount)
} }
entry.FeedSuppliers = make([]dto.HppPerKandangSupplierDTO, 0, len(agg.FeedSuppliers))
for _, supplier := range agg.FeedSuppliers {
entry.FeedSuppliers = append(entry.FeedSuppliers, supplier)
}
entry.DocSuppliers = make([]dto.HppPerKandangSupplierDTO, 0, len(agg.DocSuppliers))
for _, supplier := range agg.DocSuppliers {
entry.DocSuppliers = append(entry.DocSuppliers, supplier)
}
perRangeSummary = append(perRangeSummary, *entry) perRangeSummary = append(perRangeSummary, *entry)
} }
totalSummary := dto.HppPerKandangSummaryTotalDTO{ totalSummary := dto.HppPerKandangSummaryTotalDTO{
TotalRemainingChickenBirds: totalBirds, TotalEggProductionPieces: totalEggPieces,
TotalRemainingChickenWeightKg: totalWeight, TotalEggProductionKg: totalEggKg,
TotalEggProductionPieces: totalEggPieces, TotalEggValueRp: totalEggValueRp,
TotalEggProductionKg: totalEggKg,
TotalRemainingValueRp: totalRemainingValueRp,
TotalEggValueRp: totalEggValueRp,
} }
if totalBirds > 0 { if totalBirds > 0 {
totalSummary.AverageWeightKg = totalWeight / float64(totalBirds) }
if totalAvgWeightCount > 0 {
totalSummary.AverageWeightKg = totalAvgWeightSum / float64(totalAvgWeightCount)
} }
if totalEggHppCount > 0 { if totalEggHppCount > 0 {
totalSummary.AverageEggHppRpPerKg = totalEggHppSum / float64(totalEggHppCount) totalSummary.AverageEggHppRpPerKg = totalEggHppSum / float64(totalEggHppCount)
} }
if totalHppCount > 0 { if totalHppCount > 0 {
totalSummary.TotalHppRp = totalHppSum / float64(totalHppCount)
} }
if totalDocPriceCount > 0 { if totalDocPriceCount > 0 {
totalSummary.TotalAverageDocPriceRp = totalDocPriceSum / float64(totalDocPriceCount) totalSummary.TotalAverageDocPriceRp = totalDocPriceSum / float64(totalDocPriceCount)