package repository import ( "context" "fmt" "gitlab.com/mbugroup/lti-api.git/internal/common/repository" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo" "gorm.io/gorm" ) // ClosingKeuanganRepository handles database operations for closing keuangan type ClosingKeuanganRepository interface { repository.BaseRepository[interface{}] // Egg Production GetTotalEggProductionByProjectFlockID(ctx context.Context, projectFlockID uint) (totalQty int, totalWeightKg float64, err error) GetTotalEggProductionByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (totalQty int, totalWeightKg float64, totalRecordings int, err error) GetEggProductionByProjectFlockKandangIDsWithDetails(ctx context.Context, projectFlockKandangIDs []uint) ([]EggProductionDetailRow, error) GetCumulativeEggProductionByProjectFlockID(ctx context.Context, projectFlockID uint) (totalQty int, totalWeightKg float64, err error) // Population Data GetTotalPopulationByProjectFlockID(ctx context.Context, projectFlockID uint) (totalPopulation float64, err error) GetTotalPopulationByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (totalPopulation float64, err error) GetRemainingPopulationByProjectFlockID(ctx context.Context, projectFlockID uint) (remainingPopulation float64, err error) // Budget Data GetTotalBudgetByProjectFlockID(ctx context.Context, projectFlockID uint) (totalBudget float64, err error) GetTotalBudgetByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (totalBudget float64, err error) // Realization/Expense Data GetTotalRealizationByProjectFlockID(ctx context.Context, projectFlockID uint) (totalRealization float64, err error) GetTotalRealizationByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (totalRealization float64, err error) GetActualUsageCostByProjectFlockID(ctx context.Context, projectFlockID uint) ([]ActualUsageCostRow, error) // Expedition GetExpeditionHPP(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]ExpeditionHPPRow, error) // Products GetProductsWithFlagsByIDs(ctx context.Context, productIDs []uint) ([]entity.Product, error) // All Product Usage GetAllProductUsageByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]ProductUsageRow, error) } type ClosingKeuanganRepositoryImpl struct { *repository.BaseRepositoryImpl[interface{}] } func NewClosingKeuanganRepository(db *gorm.DB) ClosingKeuanganRepository { return &ClosingKeuanganRepositoryImpl{ BaseRepositoryImpl: repository.NewBaseRepository[interface{}](db), } } // Result Rows type EggProductionDetailRow struct { ProjectFlockKandangID uint `gorm:"column:project_flock_kandang_id"` KandangName string `gorm:"column:kandang_name"` TotalQty int `gorm:"column:total_qty"` TotalWeightKg float64 `gorm:"column:total_weight_kg"` TotalRecordings int `gorm:"column:total_recordings"` } type ExpeditionHPPRow struct { SupplierName string `gorm:"column:supplier_name"` TotalAmount float64 `gorm:"column:total_amount"` } type ActualUsageCostRow struct { ProductID uint `gorm:"column:product_id"` ProductName string `gorm:"column:product_name"` FlagName string `gorm:"column:flag_name"` TotalQty float64 `gorm:"column:total_qty"` TotalPrice float64 `gorm:"column:total_price"` AveragePrice float64 `gorm:"column:average_price"` } type ProductUsageRow struct { ProductID uint `gorm:"column:product_id"` ProductName string `gorm:"column:product_name"` FlagNames string `gorm:"column:flag_names"` TotalQty float64 `gorm:"column:total_qty"` Price float64 `gorm:"column:price"` TotalPengeluaran float64 `gorm:"column:total_pengeluaran"` } // === EGG PRODUCTION QUERIES === // GetTotalEggProductionByProjectFlockID gets total egg production for all kandangs in a project flock func (r *ClosingKeuanganRepositoryImpl) GetTotalEggProductionByProjectFlockID(ctx context.Context, projectFlockID uint) (int, float64, error) { if projectFlockID == 0 { return 0, 0, nil } var result struct { TotalQty float64 TotalWeightKg float64 } err := r.DB().WithContext(ctx). Table("project_flocks pf"). Select(` COALESCE(SUM(re.qty), 0) AS total_qty, COALESCE(SUM(re.qty * COALESCE(re.weight, 0)) / 1000, 0) AS total_weight_kg `). Joins("JOIN project_flock_kandangs pfk ON pfk.project_flock_id = pf.id"). Joins("LEFT JOIN recordings r ON r.project_flock_kandangs_id = pfk.id"). Joins("LEFT JOIN recording_eggs re ON re.recording_id = r.id"). Where("pf.id = ?", projectFlockID). Where("pf.deleted_at IS NULL"). Where("r.deleted_at IS NULL"). Scan(&result).Error if err != nil { return 0, 0, err } return int(result.TotalQty), result.TotalWeightKg, nil } // GetTotalEggProductionByProjectFlockKandangID gets total egg production for a specific kandang func (r *ClosingKeuanganRepositoryImpl) GetTotalEggProductionByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (int, float64, int, error) { if projectFlockKandangID == 0 { return 0, 0, 0, nil } var result struct { TotalQty float64 TotalWeightKg float64 TotalRecordings int } err := r.DB().WithContext(ctx). Table("project_flock_kandangs pfk"). Select(` COALESCE(SUM(re.qty), 0) AS total_qty, COALESCE(SUM(re.qty * COALESCE(re.weight, 0)) / 1000, 0) AS total_weight_kg, COUNT(DISTINCT r.id) AS total_recordings `). Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id"). Joins("LEFT JOIN recordings r ON r.project_flock_kandangs_id = pfk.id"). Joins("LEFT JOIN recording_eggs re ON re.recording_id = r.id"). Where("pfk.id = ?", projectFlockKandangID). Where("pf.deleted_at IS NULL"). Where("r.deleted_at IS NULL"). Scan(&result).Error if err != nil { return 0, 0, 0, err } return int(result.TotalQty), result.TotalWeightKg, result.TotalRecordings, nil } // GetEggProductionByProjectFlockKandangIDsWithDetails gets egg production details for multiple kandangs func (r *ClosingKeuanganRepositoryImpl) GetEggProductionByProjectFlockKandangIDsWithDetails(ctx context.Context, projectFlockKandangIDs []uint) ([]EggProductionDetailRow, error) { if len(projectFlockKandangIDs) == 0 { return []EggProductionDetailRow{}, nil } var results []EggProductionDetailRow err := r.DB().WithContext(ctx). Table("project_flock_kandangs pfk"). Select(` pfk.id AS project_flock_kandang_id, k.name AS kandang_name, COALESCE(SUM(re.qty), 0) AS total_qty, COALESCE(SUM(re.qty * COALESCE(re.weight, 0)) / 1000, 0) AS total_weight_kg, COUNT(DISTINCT r.id) AS total_recordings `). Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id"). Joins("JOIN kandangs k ON k.id = pfk.kandang_id"). Joins("LEFT JOIN recordings r ON r.project_flock_kandangs_id = pfk.id"). Joins("LEFT JOIN recording_eggs re ON re.recording_id = r.id"). Where("pfk.id IN ?", projectFlockKandangIDs). Where("pf.deleted_at IS NULL"). Where("r.deleted_at IS NULL"). Group("pfk.id, k.name"). Scan(&results).Error if err != nil { return nil, err } return results, nil } // GetCumulativeEggProductionByProjectFlockID gets cumulative egg production for project flock func (r *ClosingKeuanganRepositoryImpl) GetCumulativeEggProductionByProjectFlockID(ctx context.Context, projectFlockID uint) (int, float64, error) { if projectFlockID == 0 { return 0, 0, nil } var result struct { TotalQty float64 TotalWeightKg float64 } err := r.DB().WithContext(ctx). Table("project_flocks pf"). Select(` COALESCE(SUM(re.qty), 0) AS total_qty, COALESCE(SUM(re.qty * COALESCE(re.weight, 0)) / 1000, 0) AS total_weight_kg `). Joins("JOIN project_flock_kandangs pfk ON pfk.project_flock_id = pf.id"). Joins("JOIN recordings r ON r.project_flock_kandangs_id = pfk.id"). Joins("JOIN recording_eggs re ON re.recording_id = r.id"). Where("pf.id = ?", projectFlockID). Where("pf.deleted_at IS NULL"). Where("r.deleted_at IS NULL"). Where("r.record_datetime <= (SELECT MAX(record_datetime) FROM recordings WHERE project_flock_kandangs_id IN (SELECT id FROM project_flock_kandangs WHERE project_flock_id = ?))", projectFlockID). Scan(&result).Error if err != nil { return 0, 0, err } return int(result.TotalQty), result.TotalWeightKg, nil } // === POPULATION QUERIES === // GetTotalPopulationByProjectFlockID gets total initial population for project flock func (r *ClosingKeuanganRepositoryImpl) GetTotalPopulationByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error) { if projectFlockID == 0 { return 0, nil } var result float64 err := r.DB().WithContext(ctx). Table("project_chickins"). Select("COALESCE(SUM(qty), 0)"). Where("project_flock_id = ?", projectFlockID). Scan(&result).Error return result, err } // GetTotalPopulationByProjectFlockKandangIDs gets total population for multiple kandangs func (r *ClosingKeuanganRepositoryImpl) GetTotalPopulationByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) { if len(projectFlockKandangIDs) == 0 { return 0, nil } var result float64 err := r.DB().WithContext(ctx). Table("project_chickins"). Select("COALESCE(SUM(qty), 0)"). Where("project_flock_kandang_id IN ?", projectFlockKandangIDs). Scan(&result).Error return result, err } // GetRemainingPopulationByProjectFlockID gets remaining population based on depletion func (r *ClosingKeuanganRepositoryImpl) GetRemainingPopulationByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error) { if projectFlockID == 0 { return 0, nil } var result struct { TotalChickin float64 TotalDepletion float64 } err := r.DB().WithContext(ctx). Table("project_flocks pf"). Select(` COALESCE((SELECT SUM(qty) FROM project_chickins WHERE project_flock_id = pf.id), 0) AS total_chickin, COALESCE((SELECT SUM(COALESCE(rd.qty, 0)) FROM recordings r JOIN recording_depletions rd ON rd.recording_id = r.id JOIN project_flock_kandangs pfk ON pfk.id = r.project_flock_kandangs_id WHERE pfk.project_flock_id = pf.id), 0) AS total_depletion `). Where("pf.id = ?", projectFlockID). Scan(&result).Error if err != nil { return 0, err } return result.TotalChickin - result.TotalDepletion, nil } // === BUDGET QUERIES === // GetTotalBudgetByProjectFlockID gets total budget for project flock func (r *ClosingKeuanganRepositoryImpl) GetTotalBudgetByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error) { if projectFlockID == 0 { return 0, nil } var result float64 err := r.DB().WithContext(ctx). Table("project_budgets pb"). Select("COALESCE(SUM(pb.amount), 0)"). Joins("JOIN project_flock_kandangs pfk ON pfk.id = pb.project_flock_kandang_id"). Where("pfk.project_flock_id = ?", projectFlockID). Where("pb.deleted_at IS NULL"). Scan(&result).Error return result, err } // GetTotalBudgetByProjectFlockKandangIDs gets total budget for multiple kandangs func (r *ClosingKeuanganRepositoryImpl) GetTotalBudgetByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) { if len(projectFlockKandangIDs) == 0 { return 0, nil } var result float64 err := r.DB().WithContext(ctx). Table("project_budgets pb"). Select("COALESCE(SUM(pb.amount), 0)"). Where("pb.project_flock_kandang_id IN ?", projectFlockKandangIDs). Where("pb.deleted_at IS NULL"). Scan(&result).Error return result, err } // === REALIZATION/EXPENSE QUERIES === // GetTotalRealizationByProjectFlockID gets total expense realization for project flock func (r *ClosingKeuanganRepositoryImpl) GetTotalRealizationByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error) { if projectFlockID == 0 { return 0, nil } var result float64 err := r.DB().WithContext(ctx). Table("expense_realizations er"). Select("COALESCE(SUM(er.amount), 0)"). Joins("JOIN project_flock_kandangs pfk ON pfk.id = er.project_flock_kandang_id"). Where("pfk.project_flock_id = ?", projectFlockID). Where("er.deleted_at IS NULL"). Scan(&result).Error return result, err } // GetTotalRealizationByProjectFlockKandangIDs gets total realization for multiple kandangs func (r *ClosingKeuanganRepositoryImpl) GetTotalRealizationByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) { if len(projectFlockKandangIDs) == 0 { return 0, nil } var result float64 err := r.DB().WithContext(ctx). Table("expense_realizations er"). Select("COALESCE(SUM(er.amount), 0)"). Where("er.project_flock_kandang_id IN ?", projectFlockKandangIDs). Where("er.deleted_at IS NULL"). Scan(&result).Error return result, err } // GetActualUsageCostByProjectFlockID gets actual usage cost by project flock func (r *ClosingKeuanganRepositoryImpl) GetActualUsageCostByProjectFlockID(ctx context.Context, projectFlockID uint) ([]ActualUsageCostRow, error) { if projectFlockID == 0 { return []ActualUsageCostRow{}, nil } db := r.DB().WithContext(ctx) // Get all project flock kandang IDs for this project flock var pfkIDs []uint err := db.Table("project_flock_kandangs"). Where("project_flock_id = ?", projectFlockID). Pluck("id", &pfkIDs).Error if err != nil { return nil, err } if len(pfkIDs) == 0 { return []ActualUsageCostRow{}, nil } var rows []ActualUsageCostRow purchaseStockableKey := fifo.StockableKeyPurchaseItems.String() transferStockableKey := fifo.StockableKeyStockTransferIn.String() /* RAW SQL FOR RECORDING QUERY (untuk pengecekan database): SELECT pw.product_id, p.name AS product_name, COALESCE(f.name, tf.name) AS flag_name, COALESCE(SUM( CASE WHEN sa.stockable_type = '' THEN COALESCE(sa.qty, 0) WHEN sa.stockable_type = '' THEN COALESCE(std.usage_qty, 0) ELSE 0 END ), 0) AS total_qty, COALESCE(SUM( CASE WHEN sa.stockable_type = '' THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0) WHEN sa.stockable_type = '' THEN COALESCE(std.usage_qty, 0) * COALESCE(tpi.price, 0) ELSE 0 END ), 0) AS total_price, COALESCE(SUM( CASE WHEN sa.stockable_type = '' THEN COALESCE(sa.qty, 0) WHEN sa.stockable_type = '' THEN COALESCE(std.usage_qty, 0) ELSE 0 END ), 0) / NULLIF(COALESCE(SUM( CASE WHEN sa.stockable_type = '' THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0) WHEN sa.stockable_type = '' THEN COALESCE(std.usage_qty, 0) * COALESCE(tpi.price, 0) ELSE 0 END ), 0), 0) AS average_price FROM recordings r JOIN recording_stocks rs ON rs.recording_id = r.id JOIN product_warehouses pw ON pw.id = rs.product_warehouse_id JOIN products p ON p.id = pw.product_id LEFT JOIN stock_allocations sa ON sa.usable_type = 'recording_stocks' AND sa.usable_id = rs.id AND sa.status = '' LEFT JOIN purchase_items pi ON pi.id = sa.stockable_id AND sa.stockable_type = '' LEFT JOIN stock_transfer_details std ON std.id = sa.stockable_id AND sa.stockable_type = '' LEFT JOIN stock_transfers st ON st.id = std.stock_transfer_id LEFT JOIN purchase_items tpi ON tpi.product_id = std.product_id AND tpi.warehouse_id = st.from_warehouse_id LEFT JOIN flags f ON f.flagable_id = pi.product_id AND f.flagable_type = 'products' LEFT JOIN flags tf ON tf.flagable_id = std.product_id AND tf.flagable_type = 'products' WHERE r.project_flock_kandangs_id IN () AND r.deleted_at IS NULL GROUP BY pw.product_id, p.name, COALESCE(f.name, tf.name) */ // Recording stock query (pakan, OVK, dll) dengan FIFO logic recordingQuery := db. Table("recordings AS r"). Select(` pw.product_id, p.name AS product_name, COALESCE(f.name, tf.name) AS flag_name, COALESCE(SUM( CASE WHEN sa.stockable_type = ? THEN COALESCE(sa.qty, 0) WHEN sa.stockable_type = ? THEN COALESCE(std.usage_qty, 0) ELSE 0 END ), 0) AS total_qty, COALESCE(SUM( CASE WHEN sa.stockable_type = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0) WHEN sa.stockable_type = ? THEN COALESCE(std.usage_qty, 0) * COALESCE(tpi.price, 0) ELSE 0 END ), 0) AS total_price, COALESCE(SUM( CASE WHEN sa.stockable_type = ? THEN COALESCE(sa.qty, 0) WHEN sa.stockable_type = ? THEN COALESCE(std.usage_qty, 0) ELSE 0 END ), 0) AS qty_divisor, COALESCE(SUM( CASE WHEN sa.stockable_type = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0) WHEN sa.stockable_type = ? THEN COALESCE(std.usage_qty, 0) * COALESCE(tpi.price, 0) ELSE 0 END ), 0) / NULLIF(COALESCE(SUM( CASE WHEN sa.stockable_type = ? THEN COALESCE(sa.qty, 0) WHEN sa.stockable_type = ? THEN COALESCE(std.usage_qty, 0) ELSE 0 END ), 0), 0) AS average_price`, purchaseStockableKey, transferStockableKey, purchaseStockableKey, transferStockableKey, purchaseStockableKey, transferStockableKey, purchaseStockableKey, transferStockableKey, purchaseStockableKey, transferStockableKey). 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 products AS p ON p.id = pw.product_id"). Joins("LEFT JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND sa.status = ?", "recording_stocks", 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 ?", pfkIDs). Where("r.deleted_at IS NULL"). Group("pw.product_id, p.name, COALESCE(f.name, tf.name)") if err := recordingQuery.Scan(&rows).Error; err != nil { return nil, err } /* RAW SQL FOR CHICKIN QUERY (untuk pengecekan database): SELECT pw.product_id, p.name AS product_name, f.name AS flag_name, COALESCE(SUM(pc.usage_qty), 0) AS total_qty, COALESCE(SUM(pc.usage_qty * COALESCE(pi.price, 0)), 0) AS total_price, COALESCE(AVG(COALESCE(pi.price, 0)), 0) AS average_price FROM project_chickins pc JOIN product_warehouses pw ON pw.id = pc.product_warehouse_id JOIN products p ON p.id = pw.product_id LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pc.product_warehouse_id LEFT JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = 'products' WHERE pc.project_flock_kandang_id IN () AND pc.usage_qty > 0 GROUP BY pw.product_id, p.name, f.name */ // Chickin query (DOC, pullet) dengan FIFO sederhana chickinQuery := db. Table("project_chickins AS pc"). Select(` pw.product_id, p.name AS product_name, f.name AS flag_name, COALESCE(SUM(pc.usage_qty), 0) AS total_qty, COALESCE(SUM(pc.usage_qty * COALESCE(pi.price, 0)), 0) AS total_price, COALESCE(AVG(COALESCE(pi.price, 0)), 0) AS average_price `). Joins("JOIN product_warehouses AS pw ON pw.id = pc.product_warehouse_id"). Joins("JOIN products AS p ON p.id = pw.product_id"). Joins("LEFT JOIN purchase_items AS pi ON pi.product_warehouse_id = pc.product_warehouse_id"). Joins("LEFT JOIN flags AS f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct). Where("pc.project_flock_kandang_id IN ?", pfkIDs). Where("pc.usage_qty > 0"). Group("pw.product_id, p.name, f.name") var chickinRows []ActualUsageCostRow if err := chickinQuery.Scan(&chickinRows).Error; err != nil { return nil, err } rows = append(rows, chickinRows...) return rows, nil } // === EXPEDITION === // GetExpeditionHPP gets expedition HPP func (r *ClosingKeuanganRepositoryImpl) GetExpeditionHPP(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]ExpeditionHPPRow, error) { db := r.DB().WithContext(ctx) if projectFlockID == 0 { return nil, fmt.Errorf("invalid project flock id") } var results []ExpeditionHPPRow query := db. Table("expense_realizations er"). Select(` s.name AS supplier_name, COALESCE(SUM(er.amount), 0) AS total_amount `). Joins("JOIN suppliers s ON s.id = er.supplier_id"). Where("er.category = 'EKSPEDISI'"). Where("er.deleted_at IS NULL") if projectFlockKandangID != nil { query = query.Where("er.project_flock_kandang_id = ?", *projectFlockKandangID) } else { query = query.Joins("JOIN project_flock_kandangs pfk ON pfk.id = er.project_flock_kandang_id"). Where("pfk.project_flock_id = ?", projectFlockID) } err := query. Group("s.name"). Scan(&results).Error if err != nil { return nil, err } return results, nil } // === PRODUCTS === // GetProductsWithFlagsByIDs gets products with their flags func (r *ClosingKeuanganRepositoryImpl) GetProductsWithFlagsByIDs(ctx context.Context, productIDs []uint) ([]entity.Product, error) { if len(productIDs) == 0 { return []entity.Product{}, nil } var products []entity.Product err := r.DB().WithContext(ctx). Where("id IN ?", productIDs). Preload("Flags", func(db *gorm.DB) *gorm.DB { return db.Order("flagable_type, name") }). Find(&products).Error if err != nil { return nil, err } return products, nil } // GetAllProductUsageByProjectFlockKandangID gets all product usage for a project flock kandang // Combines data from all usable types: recordings, chickins, marketing, transfers, adjustments func (r *ClosingKeuanganRepositoryImpl) GetAllProductUsageByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]ProductUsageRow, error) { if projectFlockKandangID == 0 { return []ProductUsageRow{}, nil } var results []ProductUsageRow rawQuery := ` SELECT q.product_id, q.product_name, f.flag_names, q.total_qty, q.price, q.total_qty * q.price AS total_pengeluaran FROM ( SELECT product_id, product_name, SUM(total_qty) AS total_qty, AVG(price) AS price FROM ( SELECT pw.product_id, p.name AS product_name, COALESCE(SUM( CASE WHEN sa.stockable_type = 'PURCHASE_ITEMS' THEN COALESCE(sa.qty, 0) WHEN sa.stockable_type = 'STOCK_TRANSFER_IN' THEN COALESCE(std.usage_qty, 0) WHEN sa.stockable_type = 'TRANSFERTOLAYING_IN' THEN COALESCE(ltt.total_used, 0) WHEN sa.stockable_type = 'ADJUSTMENT_IN' THEN COALESCE(adjs.total_used, 0) WHEN sa.stockable_type = 'PROJECT_FLOCK_POPULATION' THEN COALESCE(pfp.total_used_qty, 0) ELSE 0 END ), 0) AS total_qty, COALESCE(AVG(CASE WHEN sa.stockable_type = 'PURCHASE_ITEMS' THEN COALESCE(pi.price, 0) END), 0) AS price FROM recordings r JOIN recording_stocks rs ON rs.recording_id = r.id JOIN product_warehouses pw ON pw.id = rs.product_warehouse_id JOIN products p ON p.id = pw.product_id LEFT JOIN stock_allocations sa ON sa.usable_type = 'RECORDING_STOCK' AND sa.usable_id = rs.id AND sa.status = 'ACTIVE' LEFT JOIN purchase_items pi ON pi.id = sa.stockable_id AND sa.stockable_type = 'PURCHASE_ITEMS' LEFT JOIN stock_transfer_details std ON std.id = sa.stockable_id AND sa.stockable_type = 'STOCK_TRANSFER_IN' LEFT JOIN laying_transfer_targets ltt ON ltt.id = sa.stockable_id AND sa.stockable_type = 'TRANSFERTOLAYING_IN' LEFT JOIN adjustment_stocks adjs ON adjs.id = sa.stockable_id AND sa.stockable_type = 'ADJUSTMENT_IN' LEFT JOIN project_flock_populations pfp ON pfp.id = sa.stockable_id AND sa.stockable_type = 'PROJECT_FLOCK_POPULATION' WHERE r.project_flock_kandangs_id = ? AND r.deleted_at IS NULL GROUP BY pw.product_id, p.name UNION ALL SELECT pw.product_id, p.name AS product_name, COALESCE(SUM(pc.usage_qty), 0) AS total_qty, COALESCE(AVG(COALESCE(pi.price, 0)), 0) AS price FROM project_chickins pc JOIN product_warehouses pw ON pw.id = pc.product_warehouse_id JOIN products p ON p.id = pw.product_id LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id WHERE pc.project_flock_kandang_id = ? AND pc.usage_qty > 0 GROUP BY pw.product_id, p.name UNION ALL SELECT pw.product_id, p.name AS product_name, COALESCE(SUM(mdp.usage_qty), 0) AS total_qty, COALESCE(AVG(COALESCE(pi.price, 0)), 0) AS price 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 products p ON p.id = pw.product_id LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id WHERE pw.project_flock_kandang_id = ? GROUP BY pw.product_id, p.name UNION ALL SELECT pw.product_id, p.name AS product_name, COALESCE(SUM(lts.usage_qty), 0) AS total_qty, COALESCE(AVG(COALESCE(pi.price, 0)), 0) AS price FROM laying_transfer_sources lts JOIN laying_transfers lt ON lt.id = lts.laying_transfer_id JOIN product_warehouses pw ON pw.id = lts.product_warehouse_id JOIN products p ON p.id = pw.product_id LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id WHERE pw.project_flock_kandang_id = ? GROUP BY pw.product_id, p.name UNION ALL SELECT pw.product_id, p.name AS product_name, COALESCE(SUM(std.usage_qty), 0) AS total_qty, COALESCE(AVG(COALESCE(pi.price, 0)), 0) AS price FROM stock_transfer_details std JOIN product_warehouses pw ON pw.id = std.source_product_warehouse_id JOIN products p ON p.id = std.product_id LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id WHERE pw.project_flock_kandang_id = ? GROUP BY pw.product_id, p.name UNION ALL SELECT pw.product_id, p.name AS product_name, COALESCE(SUM(ads.usage_qty), 0) AS total_qty, COALESCE(AVG(COALESCE(pi.price, 0)), 0) AS price FROM adjustment_stocks ads JOIN product_warehouses pw ON pw.id = ads.product_warehouse_id JOIN products p ON p.id = pw.product_id LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id WHERE pw.project_flock_kandang_id = ? AND ads.usage_qty > 0 GROUP BY pw.product_id, p.name ) x GROUP BY product_id, product_name ) q LEFT JOIN ( SELECT p.id AS product_id, STRING_AGG(DISTINCT f.name, ', ') AS flag_names FROM products p LEFT JOIN flags f ON f.flagable_type = 'products' AND f.flagable_id = p.id GROUP BY p.id ) f ON f.product_id = q.product_id ORDER BY q.product_name ` err := r.DB().WithContext(ctx). Raw(rawQuery, projectFlockKandangID, projectFlockKandangID, projectFlockKandangID, projectFlockKandangID, projectFlockKandangID, projectFlockKandangID). Scan(&results).Error if err != nil { return nil, fmt.Errorf("failed to get all product usage: %w", err) } return results, nil }