feat[BE-378]:Create API Get All HPP Harian Kandang

This commit is contained in:
MacBook Air M1
2025-12-28 18:41:46 +07:00
parent 824eb5905f
commit 10f42ed9c4
7 changed files with 977 additions and 1 deletions
@@ -0,0 +1,361 @@
package repositories
import (
"context"
"time"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
"gorm.io/gorm"
)
type HppPerKandangRow struct {
KandangID uint
KandangName string
KandangStatus string
LocationID uint
LocationName string
PicID uint
PicName string
RemainingChickenBirds float64
RemainingChickenWeight float64
EggProductionWeightKg float64
EggProductionPieces float64
}
type HppPerKandangCostRow struct {
KandangID uint
FeedCost float64
OvkCost float64
DocCost float64
DocQty float64
BudgetCost float64
ExpenseCost float64
}
type HppPerKandangSupplierRow struct {
KandangID uint
SupplierID uint
SupplierName string
SupplierAlias string
Category string
}
type HppPerKandangRepository interface {
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)
}
type hppPerKandangRepository struct {
db *gorm.DB
}
func NewHppPerKandangRepository(db *gorm.DB) HppPerKandangRepository {
return &hppPerKandangRepository{db: db}
}
func (r *hppPerKandangRepository) GetRowsByPeriod(ctx context.Context, start, end time.Time, areaIDs, locationIDs, kandangIDs []int64) ([]HppPerKandangRow, error) {
var rows []HppPerKandangRow
query := r.db.WithContext(ctx).
Table("recordings AS r").
Select(`
k.id AS kandang_id,
k.name AS kandang_name,
k.status AS kandang_status,
loc.id AS location_id,
loc.name AS location_name,
pic.id AS pic_id,
pic.name AS pic_name,
COALESCE(MAX(r.total_chick_qty), 0) AS remaining_chicken_birds,
COALESCE(SUM(rbw.total_weight), 0) AS remaining_chicken_weight,
COALESCE(SUM(re.weight), 0) AS egg_production_weight_kg,
COALESCE(SUM(re.qty), 0) AS egg_production_pieces`).
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").
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 recording_eggs AS re ON re.recording_id = r.id").
Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
Where("r.deleted_at IS NULL")
query = applyLocationFilters(query, areaIDs, locationIDs, kandangIDs)
query = query.Group("k.id, k.name, k.status, loc.id, loc.name, pic.id, pic.name").
Order("k.id ASC")
if err := query.Scan(&rows).Error; err != nil {
return nil, err
}
return rows, nil
}
func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context, start, end time.Time, areaIDs, locationIDs, kandangIDs []int64) ([]HppPerKandangCostRow, []HppPerKandangSupplierRow, error) {
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.StockableKey("PURCHASE_ITEMS").String()
transferStockableKey := fifo.StockableKey("STOCK_TRANSFER_DETAILS").String()
query := r.db.WithContext(ctx).
Table("recordings AS r").
Select(`
k.id AS kandang_id,
COALESCE(SUM(CASE
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)
ELSE 0
END), 0) AS feed_cost,
COALESCE(SUM(CASE
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)
ELSE 0
END), 0) AS ovk_cost`,
utils.FlagPakan, transferStockableKey, utils.FlagPakan,
utils.FlagOVK, transferStockableKey, utils.FlagOVK).
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").
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 purchase_items AS pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", purchaseStockableKey).
Joins("LEFT JOIN stock_transfer_details AS std ON std.id = sa.stockable_id AND sa.stockable_type = ?", transferStockableKey).
Joins("LEFT JOIN stock_transfers AS st ON st.id = std.stock_transfer_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 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.record_datetime >= ? AND r.record_datetime < ?", start, end).
Where("r.deleted_at IS NULL")
query = applyLocationFilters(query, areaIDs, locationIDs, kandangIDs)
query = query.Group("k.id").Order("k.id ASC")
if err := query.Scan(&rows).Error; err != nil {
return nil, nil, err
}
docRows := make([]struct {
KandangID uint
DocCost float64
DocQty float64
SupplierID *uint
SupplierName *string
SupplierAlias *string
}, 0)
docQuery := r.db.WithContext(ctx).
Table("project_chickins AS pc").
Select(`
pfk.kandang_id AS kandang_id,
COALESCE(SUM(pc.usage_qty * COALESCE(pi.price, 0)), 0) AS doc_cost,
COALESCE(SUM(pc.usage_qty), 0) AS doc_qty,
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 = pc.project_flock_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 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 suppliers AS s ON s.id = pur.supplier_id").
Where("pc.project_flock_kandang_id IN (?)", recordingPfk.Session(&gorm.Session{NewDB: true})).
Group("pfk.kandang_id, s.id, s.name, s.alias")
docQuery = applyLocationFilters(docQuery, areaIDs, locationIDs, kandangIDs)
if err := docQuery.Scan(&docRows).Error; err != nil {
return nil, nil, err
}
costMap := make(map[uint]*HppPerKandangCostRow, len(rows))
for i := range rows {
row := rows[i]
costMap[row.KandangID] = &rows[i]
}
docSuppliers := make([]HppPerKandangSupplierRow, 0)
docSeen := make(map[uint]map[uint]bool)
for _, doc := range docRows {
entry, ok := costMap[doc.KandangID]
if !ok {
rows = append(rows, HppPerKandangCostRow{
KandangID: doc.KandangID,
})
entry = &rows[len(rows)-1]
costMap[doc.KandangID] = entry
}
entry.DocCost += doc.DocCost
entry.DocQty += doc.DocQty
if doc.SupplierID != nil {
if docSeen[doc.KandangID] == nil {
docSeen[doc.KandangID] = make(map[uint]bool)
}
if !docSeen[doc.KandangID][*doc.SupplierID] {
docSeen[doc.KandangID][*doc.SupplierID] = true
supplierName := ""
if doc.SupplierName != nil {
supplierName = *doc.SupplierName
}
supplierAlias := ""
if doc.SupplierAlias != nil {
supplierAlias = *doc.SupplierAlias
}
docSuppliers = append(docSuppliers, HppPerKandangSupplierRow{
KandangID: doc.KandangID,
SupplierID: *doc.SupplierID,
SupplierName: supplierName,
SupplierAlias: supplierAlias,
Category: "DOC",
})
}
}
}
budgetRows := make([]struct {
KandangID uint
BudgetCost float64
}, 0)
pfkUsageSub := r.db.
Table("project_chickins AS pc").
Select(`
pc.project_flock_kandang_id,
SUM(pc.usage_qty) AS kandang_usage_qty`).
Group("pc.project_flock_kandang_id")
projectUsageSub := r.db.
Table("project_chickins AS pc").
Select(`
pfk.project_flock_id,
SUM(pc.usage_qty) AS project_usage_qty`).
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = pc.project_flock_kandang_id").
Group("pfk.project_flock_id")
budgetQuery := r.db.WithContext(ctx).
Table("project_flock_kandangs AS pfk").
Select(`
k.id AS 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`).
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
Joins("JOIN locations AS loc ON loc.id = k.location_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 p_usage ON p_usage.project_flock_id = pfk.project_flock_id", projectUsageSub).
Where("pfk.id IN (?)", recordingPfk.Session(&gorm.Session{NewDB: true})).
Group("k.id")
budgetQuery = applyLocationFilters(budgetQuery, areaIDs, locationIDs, kandangIDs)
if err := budgetQuery.Scan(&budgetRows).Error; err != nil {
return nil, nil, err
}
for _, budget := range budgetRows {
entry, ok := costMap[budget.KandangID]
if !ok {
rows = append(rows, HppPerKandangCostRow{
KandangID: budget.KandangID,
})
entry = &rows[len(rows)-1]
costMap[budget.KandangID] = entry
}
entry.BudgetCost += budget.BudgetCost
}
expenseRows := make([]struct {
KandangID uint
ExpenseCost float64
}, 0)
expenseQuery := r.db.WithContext(ctx).
Table("project_flock_kandangs AS pfk").
Select(`
k.id AS kandang_id,
COALESCE(SUM(er.qty * er.price), 0) AS expense_cost`).
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_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_realizations AS er ON er.expense_nonstock_id = en.id").
Where("pfk.id IN (?)", recordingPfk.Session(&gorm.Session{NewDB: true})).
Group("k.id")
expenseQuery = applyLocationFilters(expenseQuery, areaIDs, locationIDs, kandangIDs)
if err := expenseQuery.Scan(&expenseRows).Error; err != nil {
return nil, nil, err
}
for _, exp := range expenseRows {
entry, ok := costMap[exp.KandangID]
if !ok {
rows = append(rows, HppPerKandangCostRow{
KandangID: exp.KandangID,
})
entry = &rows[len(rows)-1]
costMap[exp.KandangID] = entry
}
entry.ExpenseCost += exp.ExpenseCost
}
feedSuppliers := make([]HppPerKandangSupplierRow, 0)
feedQuery := r.db.WithContext(ctx).
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").
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").
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 purchase_items AS pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", purchaseStockableKey).
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 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("r.project_flock_kandangs_id IN (?)", recordingPfk.Session(&gorm.Session{NewDB: true})).
Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
Where("r.deleted_at IS NULL")
feedQuery = applyLocationFilters(feedQuery, areaIDs, locationIDs, kandangIDs)
if err := feedQuery.Scan(&feedSuppliers).Error; err != nil {
return nil, nil, err
}
for i := range feedSuppliers {
if _, exists := costMap[feedSuppliers[i].KandangID]; !exists {
rows = append(rows, HppPerKandangCostRow{
KandangID: feedSuppliers[i].KandangID,
})
costMap[feedSuppliers[i].KandangID] = &rows[len(rows)-1]
}
feedSuppliers[i].Category = "FEED"
}
supplierRows := append(docSuppliers, feedSuppliers...)
return rows, supplierRows, nil
}
func applyLocationFilters(query *gorm.DB, areaIDs, locationIDs, kandangIDs []int64) *gorm.DB {
if len(areaIDs) > 0 {
query = query.Where("loc.area_id IN ?", areaIDs)
}
if len(locationIDs) > 0 {
query = query.Where("k.location_id IN ?", locationIDs)
}
if len(kandangIDs) > 0 {
query = query.Where("k.id IN ?", kandangIDs)
}
return query
}