Compare commits

..

2 Commits

Author SHA1 Message Date
giovanni 3052497fc0 adjust grouping by project flock kandang 2026-01-19 17:05:43 +07:00
giovanni 71c62c5e02 [FIX][BE]: LSS390 2026-01-19 16:19:47 +07:00
4 changed files with 195 additions and 141 deletions
@@ -1,6 +1,7 @@
package dto package dto
import ( import (
"strconv"
"time" "time"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities" entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
@@ -53,6 +54,7 @@ type ProjectFlockKandangListDTO struct {
ProjectFlockKandangRelationDTO ProjectFlockKandangRelationDTO
ProjectFlock *ProjectFlockDTO `json:"project_flock,omitempty"` ProjectFlock *ProjectFlockDTO `json:"project_flock,omitempty"`
Kandang *kandangDTO.KandangRelationDTO `json:"kandang,omitempty"` Kandang *kandangDTO.KandangRelationDTO `json:"kandang,omitempty"`
NameWithPeriod string `json:"name_with_period"`
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"` CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
Approval *approvalDTO.ApprovalRelationDTO `json:"approval,omitempty"` Approval *approvalDTO.ApprovalRelationDTO `json:"approval,omitempty"`
@@ -104,6 +106,7 @@ func ToProjectFlockKandangDetailDTOWithAvailableQty(e entity.ProjectFlockKandang
ProjectFlockKandangRelationDTO: ToProjectFlockKandangRelationDTO(e), ProjectFlockKandangRelationDTO: ToProjectFlockKandangRelationDTO(e),
ProjectFlock: toProjectFlockDTO(projectFlockSummary), ProjectFlock: toProjectFlockDTO(projectFlockSummary),
Kandang: toKandangRelation(e.Kandang), Kandang: toKandangRelation(e.Kandang),
NameWithPeriod: toNameWithPeriod(e.Kandang, e.Period),
CreatedAt: e.CreatedAt, CreatedAt: e.CreatedAt,
CreatedUser: toCreatedUserDTO(e.ProjectFlock), CreatedUser: toCreatedUserDTO(e.ProjectFlock),
Approval: toApprovalDTOSelector(e, func(x entity.ProjectFlockKandang) *entity.Approval { return x.LatestProjectFlockApproval }), Approval: toApprovalDTOSelector(e, func(x entity.ProjectFlockKandang) *entity.Approval { return x.LatestProjectFlockApproval }),
@@ -126,6 +129,16 @@ func toKandangRelation(kandang entity.Kandang) *kandangDTO.KandangRelationDTO {
return &mapped return &mapped
} }
func toNameWithPeriod(kandang entity.Kandang, period int) string {
if kandang.Name == "" {
return ""
}
if period == 0 {
return kandang.Name
}
return kandang.Name + " Period " + strconv.Itoa(period)
}
func toApprovalDTOSelector( func toApprovalDTOSelector(
e entity.ProjectFlockKandang, selector func(entity.ProjectFlockKandang) *entity.Approval) *approvalDTO.ApprovalRelationDTO { e entity.ProjectFlockKandang, selector func(entity.ProjectFlockKandang) *entity.Approval) *approvalDTO.ApprovalRelationDTO {
approval := selector(e) approval := selector(e)
@@ -147,6 +160,7 @@ func ToProjectFlockKandangListDTO(e entity.ProjectFlockKandang) ProjectFlockKand
ProjectFlockKandangRelationDTO: ToProjectFlockKandangRelationDTO(e), ProjectFlockKandangRelationDTO: ToProjectFlockKandangRelationDTO(e),
ProjectFlock: toProjectFlockDTO(projectFlockSummary), ProjectFlock: toProjectFlockDTO(projectFlockSummary),
Kandang: toKandangRelation(e.Kandang), Kandang: toKandangRelation(e.Kandang),
NameWithPeriod: toNameWithPeriod(e.Kandang, e.Period),
CreatedAt: e.CreatedAt, CreatedAt: e.CreatedAt,
CreatedUser: toCreatedUserDTO(e.ProjectFlock), CreatedUser: toCreatedUserDTO(e.ProjectFlock),
Approval: toApprovalDTOSelector(e, func(x entity.ProjectFlockKandang) *entity.Approval { return x.LatestProjectFlockApproval }), Approval: toApprovalDTOSelector(e, func(x entity.ProjectFlockKandang) *entity.Approval { return x.LatestProjectFlockApproval }),
@@ -27,10 +27,13 @@ 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"`
NameWithPeriode string `json:"name_with_periode"`
WeightRange HppPerKandangWeightRangeDTO `json:"weight_range"` WeightRange HppPerKandangWeightRangeDTO `json:"weight_range"`
AvgWeightKg float64 `json:"avg_weight_kg"` AvgWeightKg float64 `json:"avg_weight_kg"`
EggProductionPieces int64 `json:"egg_production_pieces"` EggProductionPieces int64 `json:"egg_production_pieces"`
EggProductionKg float64 `json:"egg_production_kg"` EggProductionKg float64 `json:"egg_production_kg"`
// EggProductionTotalWeightKg float64 `json:"egg_production_total_weight_kg"`
// EggProductionTotalPieces int64 `json:"egg_production_total_pieces"`
// 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"`
@@ -38,8 +41,8 @@ type HppPerKandangRowDTO struct {
FeedSuppliers []HppPerKandangSupplierDTO `json:"feed_suppliers"` FeedSuppliers []HppPerKandangSupplierDTO `json:"feed_suppliers"`
DocSuppliers []HppPerKandangSupplierDTO `json:"doc_suppliers"` DocSuppliers []HppPerKandangSupplierDTO `json:"doc_suppliers"`
AverageDocPriceRp int64 `json:"average_doc_price_rp"` AverageDocPriceRp int64 `json:"average_doc_price_rp"`
HppRp float64 `json:"hpp_rp"` // HppRp float64 `json:"hpp_rp"`
RemainingValueRp int64 `json:"remaining_value_rp"` // RemainingValueRp int64 `json:"remaining_value_rp"`
} }
type HppPerKandangRowKandangDTO struct { type HppPerKandangRowKandangDTO struct {
@@ -12,6 +12,7 @@ import (
type HppPerKandangRow struct { type HppPerKandangRow struct {
ProjectFlockKandangID uint ProjectFlockKandangID uint
ProjectFlockPeriod int
KandangID uint KandangID uint
KandangName string KandangName string
KandangStatus string KandangStatus string
@@ -20,14 +21,16 @@ type HppPerKandangRow struct {
PicID uint PicID uint
PicName string PicName string
RecordingCount int64 RecordingCount int64
RemainingChickenBirds float64 // RemainingChickenBirds float64
RemainingChickenWeight float64 // RemainingChickenWeight float64
EggProductionWeightKg float64 EggProductionWeightKgRemaining float64
EggProductionPieces float64 EggProductionPiecesRemaining float64
EggProductionTotalWeightKg float64
EggProductionTotalPieces float64
} }
type HppPerKandangCostRow struct { type HppPerKandangCostRow struct {
KandangID uint ProjectFlockKandangID uint
FeedCost float64 FeedCost float64
OvkCost float64 OvkCost float64
DocCost float64 DocCost float64
@@ -37,7 +40,7 @@ type HppPerKandangCostRow struct {
} }
type HppPerKandangSupplierRow struct { type HppPerKandangSupplierRow struct {
KandangID uint ProjectFlockKandangID uint
SupplierID uint SupplierID uint
SupplierName string SupplierName string
SupplierAlias string SupplierAlias string
@@ -86,6 +89,7 @@ func (r *hppPerKandangRepository) GetRowsByPeriod(ctx context.Context, start, en
Table("project_flocks AS pf"). Table("project_flocks AS pf").
Select(` Select(`
pfk.id AS project_flock_kandang_id, pfk.id AS project_flock_kandang_id,
pfk.period AS project_flock_period,
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,
@@ -97,18 +101,27 @@ func (r *hppPerKandangRepository) GetRowsByPeriod(ctx context.Context, start, en
COALESCE(MAX(vr.total_chick_qty), 0) AS remaining_chicken_birds, COALESCE(MAX(vr.total_chick_qty), 0) AS remaining_chicken_birds,
0 AS remaining_chicken_weight, 0 AS remaining_chicken_weight,
0 AS egg_production_weight_kg, 0 AS egg_production_weight_kg,
0 AS egg_production_pieces`). 0 AS egg_production_pieces,
0 AS egg_production_total_weight_kg,
0 AS egg_production_total_pieces`).
Joins("JOIN project_flock_kandangs AS pfk ON pfk.project_flock_id = pf.id"). Joins("JOIN project_flock_kandangs AS pfk ON pfk.project_flock_id = pf.id").
Joins(`
LEFT JOIN (
SELECT project_flock_kandang_id, MIN(chick_in_date) AS chick_in_date
FROM project_chickins
GROUP BY project_flock_kandang_id
) AS pc ON pc.project_flock_kandang_id = pfk.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 (?) AS vr ON vr.project_flock_kandangs_id = pfk.id", validRecordings). Joins("LEFT JOIN (?) AS vr ON vr.project_flock_kandangs_id = pfk.id", validRecordings).
Where("pfk.closed_at IS NULL") Where("pf.category = ?", utils.ProjectFlockCategoryLaying).
Where("(pfk.closed_at IS NULL OR ? BETWEEN pc.chick_in_date AND pfk.closed_at)", start)
query = applyLocationFilters(query, areaIDs, locationIDs, kandangIDs) query = applyLocationFilters(query, areaIDs, locationIDs, kandangIDs)
query = query.Group("pfk.id, k.id, k.name, k.status, loc.id, loc.name, pic.id, pic.name"). query = query.Group("pfk.id, pfk.period, k.id, k.name, k.status, loc.id, loc.name, pic.id, pic.name").
Order("k.id ASC") Order("pfk.id ASC")
if err := query.Scan(&rows).Error; err != nil { if err := query.Scan(&rows).Error; err != nil {
return nil, err return nil, err
@@ -139,7 +152,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
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, pfk.id AS project_flock_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.total_qty, 0) * COALESCE(tpi.price, 0) WHEN sa.stockable_type = ? AND tf.name = ? THEN COALESCE(std.total_qty, 0) * COALESCE(tpi.price, 0)
@@ -164,18 +177,18 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
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 ?", projectFlockKandangIDs). Where("r.project_flock_kandangs_id IN ?", projectFlockKandangIDs).
// Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). Where("r.record_datetime < ?", end).
Where("r.deleted_at IS NULL"). Where("r.deleted_at IS NULL").
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)) Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected))
query = query.Group("k.id").Order("k.id ASC") query = query.Group("pfk.id").Order("pfk.id ASC")
if err := query.Scan(&rows).Error; err != nil { if err := query.Scan(&rows).Error; err != nil {
return nil, nil, err return nil, nil, err
} }
docRows := make([]struct { docRows := make([]struct {
KandangID uint ProjectFlockKandangID uint
DocCost float64 DocCost float64
DocQty float64 DocQty float64
SupplierID *uint SupplierID *uint
@@ -186,7 +199,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
docQuery := r.db.WithContext(ctx). docQuery := r.db.WithContext(ctx).
Table("project_chickins AS pc"). Table("project_chickins AS pc").
Select(` Select(`
pfk.kandang_id AS kandang_id, pfk.id AS project_flock_kandang_id,
COALESCE(SUM(pc.usage_qty * COALESCE(pi.price, 0)), 0) AS doc_cost, COALESCE(SUM(pc.usage_qty * COALESCE(pi.price, 0)), 0) AS doc_cost,
COALESCE(SUM(pc.usage_qty), 0) AS doc_qty, COALESCE(SUM(pc.usage_qty), 0) AS doc_qty,
s.id AS supplier_id, s.id AS supplier_id,
@@ -199,7 +212,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
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 ?", projectFlockKandangIDs). Where("pc.project_flock_kandang_id IN ?", projectFlockKandangIDs).
Group("pfk.kandang_id, s.id, s.name, s.alias") Group("pfk.id, s.id, s.name, s.alias")
if err := docQuery.Scan(&docRows).Error; err != nil { if err := docQuery.Scan(&docRows).Error; err != nil {
return nil, nil, err return nil, nil, err
@@ -208,28 +221,28 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
costMap := make(map[uint]*HppPerKandangCostRow, len(rows)) costMap := make(map[uint]*HppPerKandangCostRow, len(rows))
for i := range rows { for i := range rows {
row := rows[i] row := rows[i]
costMap[row.KandangID] = &rows[i] costMap[row.ProjectFlockKandangID] = &rows[i]
} }
docSuppliers := make([]HppPerKandangSupplierRow, 0) docSuppliers := make([]HppPerKandangSupplierRow, 0)
docSeen := make(map[uint]map[uint]bool) docSeen := make(map[uint]map[uint]bool)
for _, doc := range docRows { for _, doc := range docRows {
entry, ok := costMap[doc.KandangID] entry, ok := costMap[doc.ProjectFlockKandangID]
if !ok { if !ok {
rows = append(rows, HppPerKandangCostRow{ rows = append(rows, HppPerKandangCostRow{
KandangID: doc.KandangID, ProjectFlockKandangID: doc.ProjectFlockKandangID,
}) })
entry = &rows[len(rows)-1] entry = &rows[len(rows)-1]
costMap[doc.KandangID] = entry costMap[doc.ProjectFlockKandangID] = entry
} }
entry.DocCost += doc.DocCost entry.DocCost += doc.DocCost
entry.DocQty += doc.DocQty entry.DocQty += doc.DocQty
if doc.SupplierID != nil { if doc.SupplierID != nil {
if docSeen[doc.KandangID] == nil { if docSeen[doc.ProjectFlockKandangID] == nil {
docSeen[doc.KandangID] = make(map[uint]bool) docSeen[doc.ProjectFlockKandangID] = make(map[uint]bool)
} }
if !docSeen[doc.KandangID][*doc.SupplierID] { if !docSeen[doc.ProjectFlockKandangID][*doc.SupplierID] {
docSeen[doc.KandangID][*doc.SupplierID] = true docSeen[doc.ProjectFlockKandangID][*doc.SupplierID] = true
supplierName := "" supplierName := ""
if doc.SupplierName != nil { if doc.SupplierName != nil {
supplierName = *doc.SupplierName supplierName = *doc.SupplierName
@@ -239,7 +252,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
supplierAlias = *doc.SupplierAlias supplierAlias = *doc.SupplierAlias
} }
docSuppliers = append(docSuppliers, HppPerKandangSupplierRow{ docSuppliers = append(docSuppliers, HppPerKandangSupplierRow{
KandangID: doc.KandangID, ProjectFlockKandangID: doc.ProjectFlockKandangID,
SupplierID: *doc.SupplierID, SupplierID: *doc.SupplierID,
SupplierName: supplierName, SupplierName: supplierName,
SupplierAlias: supplierAlias, SupplierAlias: supplierAlias,
@@ -250,7 +263,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
} }
budgetRows := make([]struct { budgetRows := make([]struct {
KandangID uint ProjectFlockKandangID uint
BudgetCost float64 BudgetCost float64
}, 0) }, 0)
@@ -272,7 +285,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
budgetQuery := r.db.WithContext(ctx). budgetQuery := r.db.WithContext(ctx).
Table("project_flock_kandangs AS pfk"). Table("project_flock_kandangs AS pfk").
Select(` Select(`
k.id AS kandang_id, pfk.id AS project_flock_kandang_id,
COALESCE(SUM((pb.qty * pb.price) * COALESCE(k_usage.kandang_usage_qty, 0) / NULLIF(p_usage.project_usage_qty, 0)), 0) AS budget_cost`). COALESCE(SUM((pb.qty * pb.price) * COALESCE(k_usage.kandang_usage_qty, 0) / NULLIF(p_usage.project_usage_qty, 0)), 0) AS budget_cost`).
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").
@@ -280,7 +293,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
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 (?)", projectFlockKandangIDs). Where("pfk.id IN (?)", projectFlockKandangIDs).
Group("k.id") Group("pfk.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 {
@@ -288,33 +301,33 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
} }
for _, budget := range budgetRows { for _, budget := range budgetRows {
entry, ok := costMap[budget.KandangID] entry, ok := costMap[budget.ProjectFlockKandangID]
if !ok { if !ok {
rows = append(rows, HppPerKandangCostRow{ rows = append(rows, HppPerKandangCostRow{
KandangID: budget.KandangID, ProjectFlockKandangID: budget.ProjectFlockKandangID,
}) })
entry = &rows[len(rows)-1] entry = &rows[len(rows)-1]
costMap[budget.KandangID] = entry costMap[budget.ProjectFlockKandangID] = entry
} }
entry.BudgetCost += budget.BudgetCost entry.BudgetCost += budget.BudgetCost
} }
expenseRows := make([]struct { expenseRows := make([]struct {
KandangID uint ProjectFlockKandangID uint
ExpenseCost float64 ExpenseCost float64
}, 0) }, 0)
expenseQuery := r.db.WithContext(ctx). expenseQuery := r.db.WithContext(ctx).
Table("project_flock_kandangs AS pfk"). Table("project_flock_kandangs AS pfk").
Select(` Select(`
k.id AS kandang_id, pfk.id AS project_flock_kandang_id,
COALESCE(SUM(er.qty * er.price), 0) AS expense_cost`). COALESCE(SUM(er.qty * er.price), 0) AS expense_cost`).
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 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 (?)", projectFlockKandangIDs). Where("pfk.id IN (?)", projectFlockKandangIDs).
Group("k.id") Group("pfk.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 {
@@ -322,13 +335,13 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
} }
for _, exp := range expenseRows { for _, exp := range expenseRows {
entry, ok := costMap[exp.KandangID] entry, ok := costMap[exp.ProjectFlockKandangID]
if !ok { if !ok {
rows = append(rows, HppPerKandangCostRow{ rows = append(rows, HppPerKandangCostRow{
KandangID: exp.KandangID, ProjectFlockKandangID: exp.ProjectFlockKandangID,
}) })
entry = &rows[len(rows)-1] entry = &rows[len(rows)-1]
costMap[exp.KandangID] = entry costMap[exp.ProjectFlockKandangID] = entry
} }
entry.ExpenseCost += exp.ExpenseCost entry.ExpenseCost += exp.ExpenseCost
} }
@@ -337,7 +350,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
feedQuery := r.db.WithContext(ctx). feedQuery := r.db.WithContext(ctx).
Table("recordings AS r"). Table("recordings AS r").
Select("DISTINCT k.id AS kandang_id, s.id AS supplier_id, s.name AS supplier_name, s.alias AS supplier_alias"). Select("DISTINCT pfk.id AS project_flock_kandang_id, s.id AS supplier_id, s.name AS supplier_name, s.alias AS supplier_alias").
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("JOIN locations AS loc ON loc.id = k.location_id").
@@ -349,7 +362,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
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 (?)", projectFlockKandangIDs). Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs).
// Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). Where("r.record_datetime < ?", 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)
@@ -358,11 +371,11 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
} }
for i := range feedSuppliers { for i := range feedSuppliers {
if _, exists := costMap[feedSuppliers[i].KandangID]; !exists { if _, exists := costMap[feedSuppliers[i].ProjectFlockKandangID]; !exists {
rows = append(rows, HppPerKandangCostRow{ rows = append(rows, HppPerKandangCostRow{
KandangID: feedSuppliers[i].KandangID, ProjectFlockKandangID: feedSuppliers[i].ProjectFlockKandangID,
}) })
costMap[feedSuppliers[i].KandangID] = &rows[len(rows)-1] costMap[feedSuppliers[i].ProjectFlockKandangID] = &rows[len(rows)-1]
} }
feedSuppliers[i].Category = "FEED" feedSuppliers[i].Category = "FEED"
} }
@@ -392,8 +405,10 @@ func (r *hppPerKandangRepository) GetEggProductionByProjectFlockKandangIDs(ctx c
type eggRow struct { type eggRow struct {
ProjectFlockKandangID uint ProjectFlockKandangID uint
EggProductionWeightKg float64 EggProductionWeightKgRemaining float64
EggProductionPieces float64 EggProductionPiecesRemaining float64
EggProductionTotalWeightKg float64
EggProductionTotalPieces float64
} }
eggRows := make([]eggRow, 0) eggRows := make([]eggRow, 0)
@@ -401,12 +416,14 @@ func (r *hppPerKandangRepository) GetEggProductionByProjectFlockKandangIDs(ctx c
Table("recordings AS r"). Table("recordings AS r").
Select(` Select(`
r.project_flock_kandangs_id AS project_flock_kandang_id, r.project_flock_kandangs_id AS project_flock_kandang_id,
COALESCE(SUM(re.weight), 0) AS egg_production_weight_kg, COALESCE(SUM((re.total_qty - re.total_used) * re.weight / 1000), 0) AS egg_production_weight_kg_remaining,
COALESCE(SUM(re.qty), 0) AS egg_production_pieces`). COALESCE(SUM(re.total_qty - re.total_used), 0) AS egg_production_pieces_remaining,
COALESCE(SUM(re.weight / 1000), 0) AS egg_production_total_weight_kg,
COALESCE(SUM(re.total_qty), 0) AS egg_production_total_pieces`).
Joins("LEFT JOIN (?) AS la ON la.approvable_id = r.id", latestApproval). 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"). Joins("LEFT JOIN recording_eggs AS re ON re.recording_id = r.id").
Where("r.project_flock_kandangs_id IN ?", projectFlockKandangIDs). Where("r.project_flock_kandangs_id IN ?", projectFlockKandangIDs).
// Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end). Where("r.record_datetime < ?", end).
Where("r.deleted_at IS NULL"). Where("r.deleted_at IS NULL").
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)). Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
Group("r.project_flock_kandangs_id") Group("r.project_flock_kandangs_id")
@@ -419,8 +436,10 @@ func (r *hppPerKandangRepository) GetEggProductionByProjectFlockKandangIDs(ctx c
for _, row := range eggRows { for _, row := range eggRows {
result[row.ProjectFlockKandangID] = HppPerKandangRow{ result[row.ProjectFlockKandangID] = HppPerKandangRow{
ProjectFlockKandangID: row.ProjectFlockKandangID, ProjectFlockKandangID: row.ProjectFlockKandangID,
EggProductionWeightKg: row.EggProductionWeightKg, EggProductionWeightKgRemaining: row.EggProductionWeightKgRemaining,
EggProductionPieces: row.EggProductionPieces, EggProductionPiecesRemaining: row.EggProductionPiecesRemaining,
EggProductionTotalWeightKg: row.EggProductionTotalWeightKg,
EggProductionTotalPieces: row.EggProductionTotalPieces,
} }
} }
@@ -435,7 +454,7 @@ func applyLocationFilters(query *gorm.DB, areaIDs, locationIDs, kandangIDs []int
query = query.Where("k.location_id IN ?", locationIDs) query = query.Where("k.location_id IN ?", locationIDs)
} }
if len(kandangIDs) > 0 { if len(kandangIDs) > 0 {
query = query.Where("k.id IN ?", kandangIDs) query = query.Where("pfk.id IN ?", kandangIDs)
} }
return query return query
} }
@@ -1370,15 +1370,17 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
} }
for pfkID, egg := range eggMap { for pfkID, egg := range eggMap {
if rowIdx, ok := pfkIndex[pfkID]; ok { if rowIdx, ok := pfkIndex[pfkID]; ok {
repoRows[rowIdx].EggProductionWeightKg = egg.EggProductionWeightKg repoRows[rowIdx].EggProductionWeightKgRemaining = egg.EggProductionWeightKgRemaining
repoRows[rowIdx].EggProductionPieces = egg.EggProductionPieces repoRows[rowIdx].EggProductionPiecesRemaining = egg.EggProductionPiecesRemaining
repoRows[rowIdx].EggProductionTotalWeightKg = egg.EggProductionTotalWeightKg
repoRows[rowIdx].EggProductionTotalPieces = egg.EggProductionTotalPieces
} }
} }
} }
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.ProjectFlockKandangID] = HppCostAggregate{
FeedCost: row.FeedCost, FeedCost: row.FeedCost,
OvkCost: row.OvkCost, OvkCost: row.OvkCost,
DocCost: row.DocCost, DocCost: row.DocCost,
@@ -1407,15 +1409,15 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
category = "DOC" category = "DOC"
} }
if seen[sup.KandangID] == nil { if seen[sup.ProjectFlockKandangID] == nil {
seen[sup.KandangID] = make(map[uint]bool) seen[sup.ProjectFlockKandangID] = make(map[uint]bool)
} }
if seen[sup.KandangID][sup.SupplierID] { if seen[sup.ProjectFlockKandangID][sup.SupplierID] {
continue continue
} }
seen[sup.KandangID][sup.SupplierID] = true seen[sup.ProjectFlockKandangID][sup.SupplierID] = true
targetMap[sup.KandangID] = append(targetMap[sup.KandangID], dto.HppPerKandangSupplierDTO{ targetMap[sup.ProjectFlockKandangID] = append(targetMap[sup.ProjectFlockKandangID], dto.HppPerKandangSupplierDTO{
ID: int64(sup.SupplierID), ID: int64(sup.SupplierID),
Name: sup.SupplierName, Name: sup.SupplierName,
Alias: sup.SupplierAlias, Alias: sup.SupplierAlias,
@@ -1442,12 +1444,12 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
dataRows := make([]dto.HppPerKandangRowDTO, 0, len(repoRows)) dataRows := make([]dto.HppPerKandangRowDTO, 0, len(repoRows))
perRangeMap := make(map[weightRangeKey]*weightRangeAggregate) perRangeMap := make(map[weightRangeKey]*weightRangeAggregate)
var totalBirds int64 var totalBirds int64
var totalWeight float64 // var totalWeight float64
var totalEggPieces int64 var totalEggPieces int64
var totalEggKg float64 var totalEggKg float64
var totalRemainingValueRp int64 // var totalRemainingValueRp int64
var totalEggValueRp int64 var totalEggValueRp int64
var totalHppSum float64 // var totalHppSum float64
var totalHppCount int var totalHppCount int
var totalDocPriceSum float64 var totalDocPriceSum float64
var totalDocPriceCount int var totalDocPriceCount int
@@ -1461,26 +1463,34 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
continue 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
// }
// weightFloat := row.RemainingChickenWeight
// if math.IsNaN(weightFloat) || math.IsInf(weightFloat, 0) {
// weightFloat = 0
// }
eggPiecesFloatRemaining := row.EggProductionPiecesRemaining
if math.IsNaN(eggPiecesFloatRemaining) || math.IsInf(eggPiecesFloatRemaining, 0) {
eggPiecesFloatRemaining = 0
} }
weightFloat := row.RemainingChickenWeight eggTotalPiecesFloat := row.EggProductionTotalPieces
if math.IsNaN(weightFloat) || math.IsInf(weightFloat, 0) { if math.IsNaN(eggTotalPiecesFloat) || math.IsInf(eggTotalPiecesFloat, 0) {
weightFloat = 0 eggTotalPiecesFloat = 0
} }
eggPiecesFloat := row.EggProductionPieces eggRemainingWeightFloatRemaining := row.EggProductionWeightKgRemaining
if math.IsNaN(eggPiecesFloat) || math.IsInf(eggPiecesFloat, 0) { if math.IsNaN(eggRemainingWeightFloatRemaining) || math.IsInf(eggRemainingWeightFloatRemaining, 0) {
eggPiecesFloat = 0 eggRemainingWeightFloatRemaining = 0
} }
eggWeightFloat := row.EggProductionWeightKg eggWeightFloat := row.EggProductionTotalWeightKg
if math.IsNaN(eggWeightFloat) || math.IsInf(eggWeightFloat, 0) { if math.IsNaN(eggWeightFloat) || math.IsInf(eggWeightFloat, 0) {
eggWeightFloat = 0 eggWeightFloat = 0
} }
avgWeight := 0.0 avgWeight := 0.0
if eggPiecesFloat > 0 { if eggTotalPiecesFloat > 0 {
avgWeight = eggWeightFloat / eggPiecesFloat avgWeight = eggWeightFloat / eggTotalPiecesFloat
} }
if params.WeightMin != nil && avgWeight < *params.WeightMin { if params.WeightMin != nil && avgWeight < *params.WeightMin {
continue continue
@@ -1496,28 +1506,30 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
weightMax := weightMin + 0.09 weightMax := weightMin + 0.09
rangeKey := weightRangeKey{Min: weightMin, Max: weightMax} rangeKey := weightRangeKey{Min: weightMin, Max: weightMax}
rowBirds := int64(math.Round(birdsFloat)) // rowBirds := int64(math.Round(birdsFloat))
costEntry := costMap[row.KandangID] costEntry := costMap[row.ProjectFlockKandangID]
totalCost := costEntry.FeedCost + costEntry.OvkCost + costEntry.DocCost + costEntry.BudgetCost + costEntry.ExpenseCost totalCost := costEntry.FeedCost + costEntry.OvkCost + costEntry.DocCost + costEntry.BudgetCost + costEntry.ExpenseCost
hppRp := 0.0 // hppRp := 0.0
if weightFloat > 0 { // if weightFloat > 0 {
hppRp = totalCost / weightFloat // hppRp = totalCost / weightFloat
} // }
eggHpp := 0.0 eggHpp := 0.0
if eggWeightFloat > 0 { if eggWeightFloat > 0 {
eggHpp = totalCost / eggWeightFloat eggHpp = (totalCost / eggWeightFloat) / 1000
} }
rowEggPieces := int64(math.Round(eggPiecesFloat)) rowEggPieces := int64(math.Round(eggPiecesFloatRemaining))
rowEggValue := int64(eggHpp * eggWeightFloat) rowEggValue := int64(eggHpp * eggRemainingWeightFloatRemaining)
rowRemainingValue := int64(hppRp * weightFloat) // rowRemainingValue := int64(hppRp * weightFloat)
avgDocPrice := int64(0) avgDocPrice := int64(0)
if costEntry.DocQty > 0 { if costEntry.DocQty > 0 {
avgDocPrice = int64(math.Round(costEntry.DocCost / costEntry.DocQty)) avgDocPrice = int64(math.Round(costEntry.DocCost / costEntry.DocQty))
} }
nameWithPeriod := fmt.Sprintf("%s Period %d", row.KandangName, row.ProjectFlockPeriod)
dataRows = append(dataRows, dto.HppPerKandangRowDTO{ dataRows = append(dataRows, dto.HppPerKandangRowDTO{
ID: int(row.KandangID), ID: int(row.ProjectFlockKandangID),
Kandang: dto.HppPerKandangRowKandangDTO{ Kandang: dto.HppPerKandangRowKandangDTO{
ID: int64(row.KandangID), ID: int64(row.KandangID),
Name: row.KandangName, Name: row.KandangName,
@@ -1536,31 +1548,34 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
WeightMax: weightMax, WeightMax: weightMax,
}, },
AvgWeightKg: avgWeight, AvgWeightKg: avgWeight,
NameWithPeriode: nameWithPeriod,
// FeedCostRp: costEntry.FeedCost, // FeedCostRp: costEntry.FeedCost,
// OvkCostRp: costEntry.OvkCost, // OvkCostRp: costEntry.OvkCost,
DocSuppliers: docSupplierMap[row.KandangID], DocSuppliers: docSupplierMap[row.ProjectFlockKandangID],
FeedSuppliers: feedSupplierMap[row.KandangID], FeedSuppliers: feedSupplierMap[row.ProjectFlockKandangID],
EggProductionPieces: rowEggPieces, EggProductionPieces: int64(math.Round(eggPiecesFloatRemaining)),
EggProductionKg: eggWeightFloat, EggProductionKg: eggRemainingWeightFloatRemaining,
// EggProductionTotalWeightKg: eggWeightFloat,
// EggProductionTotalPieces: int64(math.Round(eggTotalPiecesFloat)),
AverageDocPriceRp: avgDocPrice, AverageDocPriceRp: avgDocPrice,
// HppRp: hppRp, // HppRp: hppRp,
EggHppRpPerKg: eggHpp, EggHppRpPerKg: eggHpp,
RemainingValueRp: rowRemainingValue, // RemainingValueRp: rowRemainingValue,
EggValueRp: rowEggValue, EggValueRp: rowEggValue,
}) })
totalBirds += rowBirds // totalBirds += rowBirds
totalWeight += weightFloat // totalWeight += weightFloat
totalEggPieces += rowEggPieces totalEggPieces += rowEggPieces
totalEggKg += eggWeightFloat totalEggKg += eggRemainingWeightFloatRemaining
totalRemainingValueRp += rowRemainingValue // totalRemainingValueRp += rowRemainingValue
totalEggValueRp += rowEggValue totalEggValueRp += rowEggValue
totalAvgWeightSum += avgWeight totalAvgWeightSum += avgWeight
totalAvgWeightCount++ totalAvgWeightCount++
if weightFloat > 0 { // if weightFloat > 0 {
totalHppSum += hppRp // totalHppSum += hppRp
totalHppCount++ // totalHppCount++
} // }
if avgDocPrice > 0 { if avgDocPrice > 0 {
totalDocPriceSum += float64(avgDocPrice) totalDocPriceSum += float64(avgDocPrice)
totalDocPriceCount++ totalDocPriceCount++
@@ -1587,23 +1602,23 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
} }
rangeSummary := rangeAgg.Summary rangeSummary := rangeAgg.Summary
rangeAgg.RemainingBirds += rowBirds // rangeAgg.RemainingBirds += rowBirds
rangeAgg.RemainingWeightKg += row.RemainingChickenWeight // rangeAgg.RemainingWeightKg += row.RemainingChickenWeight
rangeAgg.AvgWeightSum += avgWeight rangeAgg.AvgWeightSum += avgWeight
rangeAgg.AvgWeightCount++ rangeAgg.AvgWeightCount++
for _, supplier := range feedSupplierMap[row.KandangID] { for _, supplier := range feedSupplierMap[row.ProjectFlockKandangID] {
if _, ok := rangeAgg.FeedSuppliers[supplier.ID]; !ok { if _, ok := rangeAgg.FeedSuppliers[supplier.ID]; !ok {
rangeAgg.FeedSuppliers[supplier.ID] = supplier rangeAgg.FeedSuppliers[supplier.ID] = supplier
} }
} }
for _, supplier := range docSupplierMap[row.KandangID] { for _, supplier := range docSupplierMap[row.ProjectFlockKandangID] {
if _, ok := rangeAgg.DocSuppliers[supplier.ID]; !ok { if _, ok := rangeAgg.DocSuppliers[supplier.ID]; !ok {
rangeAgg.DocSuppliers[supplier.ID] = supplier rangeAgg.DocSuppliers[supplier.ID] = supplier
} }
} }
rangeSummary.EggProductionPieces += rowEggPieces rangeSummary.EggProductionPieces += rowEggPieces
rangeSummary.EggProductionKg += eggWeightFloat rangeSummary.EggProductionKg += eggRemainingWeightFloatRemaining
rangeSummary.RemainingValueRp += rowRemainingValue // rangeSummary.RemainingValueRp += rowRemainingValue
rangeSummary.EggValueRp += rowEggValue rangeSummary.EggValueRp += rowEggValue
if eggWeightFloat > 0 { if eggWeightFloat > 0 {
rangeAgg.EggHppSum += eggHpp rangeAgg.EggHppSum += eggHpp
@@ -1750,6 +1765,9 @@ func (s *repportService) parseHppPerKandangQuery(ctx *fiber.Ctx) (*validation.Hp
if err != nil { if err != nil {
return nil, dto.HppPerKandangFiltersDTO{}, fiber.NewError(fiber.StatusBadRequest, err.Error()) return nil, dto.HppPerKandangFiltersDTO{}, fiber.NewError(fiber.StatusBadRequest, err.Error())
} }
if weightMin != nil && weightMax != nil && *weightMin > *weightMax {
return nil, dto.HppPerKandangFiltersDTO{}, fiber.NewError(fiber.StatusBadRequest, "weight_min must be less than or equal to weight_max")
}
params := &validation.HppPerKandangQuery{ params := &validation.HppPerKandangQuery{
Page: page, Page: page,