mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-25 07:45:44 +00:00
774 lines
27 KiB
Go
774 lines
27 KiB
Go
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 = '<purchase_stockable_key>' THEN COALESCE(sa.qty, 0)
|
|
WHEN sa.stockable_type = '<transfer_stockable_key>' THEN COALESCE(std.usage_qty, 0)
|
|
ELSE 0
|
|
END
|
|
), 0) AS total_qty,
|
|
COALESCE(SUM(
|
|
CASE
|
|
WHEN sa.stockable_type = '<purchase_stockable_key>' THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
|
|
WHEN sa.stockable_type = '<transfer_stockable_key>' THEN COALESCE(std.usage_qty, 0) * COALESCE(tpi.price, 0)
|
|
ELSE 0
|
|
END
|
|
), 0) AS total_price,
|
|
COALESCE(SUM(
|
|
CASE
|
|
WHEN sa.stockable_type = '<purchase_stockable_key>' THEN COALESCE(sa.qty, 0)
|
|
WHEN sa.stockable_type = '<transfer_stockable_key>' THEN COALESCE(std.usage_qty, 0)
|
|
ELSE 0
|
|
END
|
|
), 0) /
|
|
NULLIF(COALESCE(SUM(
|
|
CASE
|
|
WHEN sa.stockable_type = '<purchase_stockable_key>' THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
|
|
WHEN sa.stockable_type = '<transfer_stockable_key>' 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 = '<active_status>'
|
|
LEFT JOIN purchase_items pi ON pi.id = sa.stockable_id AND sa.stockable_type = '<purchase_stockable_key>'
|
|
LEFT JOIN stock_transfer_details std ON std.id = sa.stockable_id AND sa.stockable_type = '<transfer_stockable_key>'
|
|
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 (<pfk_ids>)
|
|
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 (<pfk_ids>)
|
|
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
|
|
}
|