package repository 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 HppV2CostRepository interface { GetProjectFlockKandangIDs(ctx context.Context, projectFlockId uint) ([]uint, error) GetFeedUsageCost(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, error) GetTotalPopulation(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, float64, error) GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds(ctx context.Context, projectFlockKandangIDs []uint, startDate *time.Time, endDate *time.Time) (float64, float64, error) GetTransferSourceSummary(ctx context.Context, projectFlockKandangId uint) (uint, float64, error) } type HppV2RepositoryImpl struct { db *gorm.DB } func NewHppV2CostRepository(db *gorm.DB) HppV2CostRepository { return &HppV2RepositoryImpl{db: db} } func (r *HppV2RepositoryImpl) GetProjectFlockKandangIDs(ctx context.Context, projectFlockId uint) ([]uint, error) { var ids []uint err := r.db.WithContext(ctx). Table("project_flock_kandangs"). Select("id"). Where("project_flock_id = ?", projectFlockId). Scan(&ids).Error if err != nil { return nil, err } return ids, nil } func (r *HppV2RepositoryImpl) GetFeedUsageCost(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, error) { if date == nil { now := time.Now() date = &now } stockablePurchase := fifo.StockableKeyPurchaseItems.String() stockableAdjustment := fifo.StockableKeyAdjustmentIn.String() usableRecordingStock := fifo.UsableKeyRecordingStock.String() var total float64 err := r.db.WithContext(ctx). Table("recordings AS r"). Select(` COALESCE(SUM(sa.qty * CASE WHEN sa.stockable_type = ? THEN COALESCE(pi.price, 0) WHEN sa.stockable_type = ? THEN COALESCE(ast.price, 0) ELSE 0 END), 0)`, stockablePurchase, stockableAdjustment, ). Joins("JOIN recording_stocks AS rs ON rs.recording_id = r.id"). Joins("JOIN product_warehouses AS pw ON pw.id = rs.product_warehouse_id"). Joins("JOIN flags AS f ON f.flagable_id = pw.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct). Joins( "JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND (sa.stockable_type = ? OR sa.stockable_type = ?) AND sa.status = ? AND sa.allocation_purpose = ?", usableRecordingStock, stockablePurchase, stockableAdjustment, entity.StockAllocationStatusActive, entity.StockAllocationPurposeConsume, ). Joins("LEFT JOIN purchase_items AS pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", stockablePurchase). Joins("LEFT JOIN adjustment_stocks AS ast ON ast.id = sa.stockable_id AND sa.stockable_type = ?", stockableAdjustment). Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs). Where("r.record_datetime <= ?", *date). Where("f.name = ?", utils.FlagPakan). Scan(&total).Error if err != nil { return 0, err } return total, nil } func (r *HppV2RepositoryImpl) GetTotalPopulation(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) { var total float64 err := r.db.WithContext(ctx). Table("project_chickins AS pc"). Select("COALESCE(SUM(pc.usage_qty), 0)"). Where("pc.project_flock_kandang_id IN (?)", projectFlockKandangIDs). Scan(&total).Error if err != nil { return 0, err } return total, nil } func (r *HppV2RepositoryImpl) GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, float64, error) { if date == nil { now := time.Now() date = &now } var totals struct { TotalPieces float64 TotalWeightKg float64 } err := r.db.WithContext(ctx). Table("recordings AS r"). Select("COALESCE(SUM(re.qty), 0) AS total_pieces, COALESCE(SUM(re.weight), 0)AS total_weight_kg"). Joins("JOIN recording_eggs AS re ON re.recording_id = r.id"). Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs). Where("r.record_datetime <= ?", *date). Scan(&totals).Error if err != nil { return 0, 0, err } var adjustmentTotalWeight float64 adjustmentSubQuery := r.db.WithContext(ctx). Table("recordings AS r"). Select("DISTINCT ast.id AS adjustment_id, ast.price AS price"). Joins("JOIN recording_eggs AS re ON re.recording_id = r.id"). Joins("JOIN stock_transfer_details AS std ON std.dest_product_warehouse_id = re.product_warehouse_id"). Joins( "JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = std.id AND sa.stockable_type = ? AND sa.status = ? AND sa.allocation_purpose = ?", fifo.UsableKeyStockTransferOut.String(), fifo.StockableKeyAdjustmentIn.String(), entity.StockAllocationStatusActive, entity.StockAllocationPurposeConsume, ). Joins("JOIN adjustment_stocks AS ast ON ast.id = sa.stockable_id AND ast.product_warehouse_id = std.source_product_warehouse_id"). Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs). Where("r.record_datetime <= ?", *date) err = r.db.WithContext(ctx). Table("(?) AS adjustment_sources", adjustmentSubQuery). Select("COALESCE(SUM(adjustment_sources.price), 0)"). Scan(&adjustmentTotalWeight).Error if err != nil { return 0, 0, err } totals.TotalWeightKg += adjustmentTotalWeight return totals.TotalPieces, totals.TotalWeightKg, nil } func (r *HppV2RepositoryImpl) GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds( ctx context.Context, projectFlockKandangIDs []uint, startDate *time.Time, endDate *time.Time, ) (float64, float64, error) { if len(projectFlockKandangIDs) == 0 { return 0, 0, nil } if endDate == nil { now := time.Now() endDate = &now } if startDate == nil { startDate = endDate } eggFlags := []string{ string(utils.FlagTelur), string(utils.FlagTelurUtuh), string(utils.FlagTelurPecah), string(utils.FlagTelurPutih), string(utils.FlagTelurRetak), string(utils.FlagTelurPapacal), string(utils.FlagTelurJumbo), } query := ` WITH selected_pfk AS ( SELECT pfk.id, k.location_id FROM project_flock_kandangs pfk JOIN kandangs k ON k.id = pfk.kandang_id WHERE pfk.id IN ? ), selected_locations AS ( SELECT DISTINCT location_id FROM selected_pfk ), sales_kandang AS ( SELECT DISTINCT mdp.id AS mdp_id, COALESCE(mdp.usage_qty, 0) AS usage_qty, COALESCE(mdp.total_weight, 0) AS total_weight FROM marketing_delivery_products mdp JOIN marketing_products mp ON mp.id = mdp.marketing_product_id JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id JOIN warehouses w ON w.id = pw.warehouse_id WHERE mdp.delivery_date IS NOT NULL AND mdp.delivery_date <= ? AND UPPER(COALESCE(w.type, '')) = 'KANDANG' AND pw.project_flock_kandang_id IN (SELECT id FROM selected_pfk) AND EXISTS ( SELECT 1 FROM recording_eggs re JOIN recordings rr ON rr.id = re.recording_id WHERE re.product_warehouse_id = mp.product_warehouse_id AND COALESCE(re.project_flock_kandang_id, rr.project_flock_kandangs_id) IN (SELECT id FROM selected_pfk) AND rr.deleted_at IS NULL AND DATE(rr.record_datetime) <= DATE(mdp.delivery_date) ) AND EXISTS ( SELECT 1 FROM flags f WHERE f.flagable_type = ? AND f.flagable_id = pw.product_id AND f.name IN ? ) ), sales_lokasi AS ( SELECT DISTINCT mdp.id AS mdp_id, COALESCE(mdp.usage_qty, 0) AS usage_qty, COALESCE(mdp.total_weight, 0) AS total_weight, mdp.delivery_date AS delivery_date, pw.id AS lokasi_pw_id, pw.product_id AS product_id, w.location_id AS location_id FROM marketing_delivery_products mdp JOIN marketing_products mp ON mp.id = mdp.marketing_product_id JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id JOIN warehouses w ON w.id = pw.warehouse_id WHERE mdp.delivery_date IS NOT NULL AND mdp.delivery_date <= ? AND UPPER(COALESCE(w.type, '')) = 'LOKASI' AND w.location_id IN (SELECT location_id FROM selected_locations) AND EXISTS ( SELECT 1 FROM flags f WHERE f.flagable_type = ? AND f.flagable_id = pw.product_id AND f.name IN ? ) ), transfer_pairs AS ( SELECT std.source_product_warehouse_id AS source_pw_id, std.dest_product_warehouse_id AS dest_pw_id, MIN(st.transfer_date) AS first_transfer_date FROM stock_transfer_details std JOIN stock_transfers st ON st.id = std.stock_transfer_id WHERE std.source_product_warehouse_id IS NOT NULL AND std.dest_product_warehouse_id IS NOT NULL GROUP BY std.source_product_warehouse_id, std.dest_product_warehouse_id ), adj_pool AS ( SELECT sl.mdp_id, SUM(CASE WHEN spw.project_flock_kandang_id IN (SELECT id FROM selected_pfk) THEN COALESCE(ast.usage_qty, 0) ELSE 0 END) AS sel_usage_qty, SUM(COALESCE(ast.usage_qty, 0)) AS farm_usage_qty, SUM(CASE WHEN spw.project_flock_kandang_id IN (SELECT id FROM selected_pfk) THEN COALESCE(ast.price, 0) ELSE 0 END) AS sel_price_sum, SUM(COALESCE(ast.price, 0)) AS farm_price_sum FROM sales_lokasi sl JOIN transfer_pairs tf ON tf.dest_pw_id = sl.lokasi_pw_id AND DATE(tf.first_transfer_date) <= DATE(sl.delivery_date) JOIN product_warehouses spw ON spw.id = tf.source_pw_id AND spw.product_id = sl.product_id JOIN warehouses sw ON sw.id = spw.warehouse_id JOIN adjustment_stocks ast ON ast.product_warehouse_id = tf.source_pw_id WHERE UPPER(COALESCE(sw.type, '')) = 'KANDANG' AND sw.location_id = sl.location_id AND UPPER(COALESCE(ast.function_code, '')) = UPPER(?) AND UPPER(COALESCE(ast.transaction_type, '')) = UPPER(?) AND DATE(ast.created_at) <= DATE(sl.delivery_date) GROUP BY sl.mdp_id ), sales_lokasi_adj AS ( SELECT sl.* FROM sales_lokasi sl JOIN adj_pool ap ON ap.mdp_id = sl.mdp_id WHERE COALESCE(ap.farm_usage_qty, 0) > 0 OR COALESCE(ap.farm_price_sum, 0) > 0 ), sales_lokasi_rec AS ( SELECT sl.* FROM sales_lokasi sl WHERE NOT EXISTS ( SELECT 1 FROM sales_lokasi_adj sla WHERE sla.mdp_id = sl.mdp_id ) ), rec_pool AS ( SELECT sl.mdp_id, SUM(CASE WHEN COALESCE(re.project_flock_kandang_id, r.project_flock_kandangs_id) IN (SELECT id FROM selected_pfk) THEN COALESCE(re.qty, 0) ELSE 0 END) AS sel_qty, SUM(COALESCE(re.qty, 0)) AS farm_qty, SUM(CASE WHEN COALESCE(re.project_flock_kandang_id, r.project_flock_kandangs_id) IN (SELECT id FROM selected_pfk) THEN COALESCE(re.weight, 0) ELSE 0 END) AS sel_weight, SUM(COALESCE(re.weight, 0)) AS farm_weight FROM sales_lokasi_rec sl JOIN recordings r ON r.deleted_at IS NULL AND DATE(r.record_datetime) <= DATE(sl.delivery_date) JOIN recording_eggs re ON re.recording_id = r.id AND re.product_warehouse_id = sl.lokasi_pw_id JOIN project_flock_kandangs pfk ON pfk.id = COALESCE(re.project_flock_kandang_id, r.project_flock_kandangs_id) JOIN kandangs k ON k.id = pfk.kandang_id WHERE k.location_id = sl.location_id GROUP BY sl.mdp_id ), kandang_totals AS ( SELECT COALESCE(SUM(sk.usage_qty), 0) AS total_pieces, COALESCE(SUM(sk.total_weight), 0) AS total_weight FROM sales_kandang sk ), lokasi_adj_totals AS ( SELECT COALESCE(SUM( sla.usage_qty * CASE WHEN COALESCE(ap.farm_usage_qty, 0) > 0 THEN (COALESCE(ap.sel_usage_qty, 0) * 1.0) / NULLIF(ap.farm_usage_qty, 0) ELSE 0 END ), 0) AS total_pieces, COALESCE(SUM( sla.total_weight * CASE WHEN COALESCE(ap.farm_price_sum, 0) > 0 THEN (COALESCE(ap.sel_price_sum, 0) * 1.0) / NULLIF(ap.farm_price_sum, 0) ELSE 0 END ), 0) AS total_weight FROM sales_lokasi_adj sla JOIN adj_pool ap ON ap.mdp_id = sla.mdp_id ), lokasi_rec_totals AS ( SELECT COALESCE(SUM( slr.usage_qty * CASE WHEN COALESCE(rp.farm_qty, 0) > 0 THEN (COALESCE(rp.sel_qty, 0) * 1.0) / NULLIF(rp.farm_qty, 0) ELSE 0 END ), 0) AS total_pieces, COALESCE(SUM( slr.total_weight * CASE WHEN COALESCE(rp.farm_weight, 0) > 0 THEN (COALESCE(rp.sel_weight, 0) * 1.0) / NULLIF(rp.farm_weight, 0) ELSE 0 END ), 0) AS total_weight FROM sales_lokasi_rec slr LEFT JOIN rec_pool rp ON rp.mdp_id = slr.mdp_id ) SELECT COALESCE(kt.total_pieces, 0) + COALESCE(lat.total_pieces, 0) + COALESCE(lrt.total_pieces, 0) AS total_pieces, COALESCE(kt.total_weight, 0) + COALESCE(lat.total_weight, 0) + COALESCE(lrt.total_weight, 0) AS total_weight FROM kandang_totals kt CROSS JOIN lokasi_adj_totals lat CROSS JOIN lokasi_rec_totals lrt ` var totals struct { TotalPieces float64 TotalWeight float64 } err := r.db.WithContext(ctx). Raw( query, projectFlockKandangIDs, *startDate, entity.FlagableTypeProduct, eggFlags, *startDate, entity.FlagableTypeProduct, eggFlags, string(utils.AdjustmentTransactionSubtypeRecordingEggIn), string(utils.AdjustmentTransactionTypeRecording), ). Scan(&totals).Error if err != nil { return 0, 0, err } return totals.TotalPieces, totals.TotalWeight, nil } func (r *HppV2RepositoryImpl) GetTransferSourceSummary(ctx context.Context, projectFlockKandangId uint) (uint, float64, error) { var summary struct { ProjectFlockID uint TotalQty float64 } err := r.db.WithContext(ctx). Table("laying_transfer_targets AS ltt"). Select("lt.from_project_flock_id AS project_flock_id, COALESCE(SUM(ltt.total_qty), 0) AS total_qty"). Joins("JOIN laying_transfers AS lt ON lt.id = ltt.laying_transfer_id"). Where("lt.deleted_at IS NULL"). Where("ltt.deleted_at IS NULL"). Where("lt.executed_at IS NOT NULL"). Where("ltt.target_project_flock_kandang_id = ?", projectFlockKandangId). Group("lt.from_project_flock_id"). Scan(&summary).Error if err != nil { return 0, 0, err } return summary.ProjectFlockID, summary.TotalQty, nil }