package database import ( "Engine/internal/model" "context" "database/sql" "fmt" ) type ReportRepo struct { db *sql.DB } func NewReportRepo(db *sql.DB) *ReportRepo { return &ReportRepo{db: db} } // getAmountsByGLType is the shared query for both actuals and budget tables, // filtered to a single GL account type, period, and department. func (rr *ReportRepo) getActualAmountsByGLType( ctx context.Context, accountType model.GLAccountType, fiscalYear, fiscalPeriod int, deptCode string, ) ([]model.GLAmountRow, error) { const q = ` SELECT g.code, g.description, SUM(a.amount), a.currency FROM actuals a JOIN gl_accounts g ON g.id = a.gl_account_id JOIN departments d ON d.id = a.department_id WHERE g.type = ? AND g.active = 1 AND a.fiscal_year = ? AND a.fiscal_period = ? AND d.code = ? GROUP BY g.id, a.currency ORDER BY g.code` rows, err := rr.db.QueryContext(ctx, q, string(accountType), fiscalYear, fiscalPeriod, deptCode) if err != nil { return nil, fmt.Errorf("getActualAmountsByGLType(%s): %w", accountType, err) } defer rows.Close() var out []model.GLAmountRow for rows.Next() { var r model.GLAmountRow if err := rows.Scan(&r.GLCode, &r.Description, &r.Amount, &r.Currency); err != nil { return nil, fmt.Errorf("getActualAmountsByGLType(%s): scan: %w", accountType, err) } out = append(out, r) } return out, rows.Err() } func (rr *ReportRepo) getBudgetAmountsByGLType( ctx context.Context, accountType model.GLAccountType, fiscalYear, fiscalPeriod int, deptCode string, version model.BudgetVersion, ) ([]model.RevenueAmounts, error) { const q = ` SELECT g.code, g.description, SUM(b.amount), b.currency FROM budgets b JOIN gl_accounts g ON g.id = b.gl_account_id JOIN departments d ON d.id = b.department_id WHERE g.type = ? AND g.active = 1 AND b.fiscal_year = ? AND b.fiscal_period = ? AND d.code = ? AND b.version = ? GROUP BY g.id, b.currency ORDER BY g.code` rows, err := rr.db.QueryContext(ctx, q, string(accountType), fiscalYear, fiscalPeriod, deptCode, string(version)) if err != nil { return nil, fmt.Errorf("getBudgetAmountsByGLType(%s): %w", accountType, err) } defer rows.Close() var out []model.RevenueAmounts for rows.Next() { var r model.RevenueAmounts if err := rows.Scan(&r.GLCode, &r.Description, &r.Amount, &r.Currency); err != nil { return nil, fmt.Errorf("getBudgetAmountsByGLType(%s): scan: %w", accountType, err) } out = append(out, r) } return out, rows.Err() } // ── Public surface ──────────────────────────────────────────────────────────── func (rr *ReportRepo) GetGLRevenueActuals(ctx context.Context, fiscalYear, fiscalPeriod int, deptCode string) ([]model.GLAmountRow, error) { return rr.getActualAmountsByGLType(ctx, model.GLRevenue, fiscalYear, fiscalPeriod, deptCode) } func (rr *ReportRepo) GetGLRevenueBudget(ctx context.Context, fiscalYear, fiscalPeriod int, deptCode string, version model.BudgetVersion) ([]model.RevenueAmounts, error) { return rr.getBudgetAmountsByGLType(ctx, model.GLRevenue, fiscalYear, fiscalPeriod, deptCode, version) } // Same pair for the other types — all delegate to the shared private methods. func (rr *ReportRepo) GetGLCOGSActuals(ctx context.Context, fiscalYear, fiscalPeriod int, deptCode string) ([]model.GLAmountRow, error) { return rr.getActualAmountsByGLType(ctx, model.GLCOGS, fiscalYear, fiscalPeriod, deptCode) } func (rr *ReportRepo) GetGLCOGSBudget(ctx context.Context, fiscalYear, fiscalPeriod int, deptCode string, version model.BudgetVersion) ([]model.RevenueAmounts, error) { return rr.getBudgetAmountsByGLType(ctx, model.GLCOGS, fiscalYear, fiscalPeriod, deptCode, version) } func (rr *ReportRepo) GetGLOpexActuals(ctx context.Context, fiscalYear, fiscalPeriod int, deptCode string) ([]model.GLAmountRow, error) { return rr.getActualAmountsByGLType(ctx, model.GLOpex, fiscalYear, fiscalPeriod, deptCode) } func (rr *ReportRepo) GetGLOpexBudget(ctx context.Context, fiscalYear, fiscalPeriod int, deptCode string, version model.BudgetVersion) ([]model.RevenueAmounts, error) { return rr.getBudgetAmountsByGLType(ctx, model.GLOpex, fiscalYear, fiscalPeriod, deptCode, version) } func (rr *ReportRepo) GetGLCapexActuals(ctx context.Context, fiscalYear, fiscalPeriod int, deptCode string) ([]model.GLAmountRow, error) { return rr.getActualAmountsByGLType(ctx, model.GLCapex, fiscalYear, fiscalPeriod, deptCode) } func (rr *ReportRepo) GetGLCapexBudget(ctx context.Context, fiscalYear, fiscalPeriod int, deptCode string, version model.BudgetVersion) ([]model.RevenueAmounts, error) { return rr.getBudgetAmountsByGLType(ctx, model.GLCapex, fiscalYear, fiscalPeriod, deptCode, version) } func (rr *ReportRepo) GetGLHeadcountActuals(ctx context.Context, fiscalYear, fiscalPeriod int, deptCode string) ([]model.GLAmountRow, error) { return rr.getActualAmountsByGLType(ctx, model.GLHeadcount, fiscalYear, fiscalPeriod, deptCode) } func (rr *ReportRepo) GetGLHeadcountBudget(ctx context.Context, fiscalYear, fiscalPeriod int, deptCode string, version model.BudgetVersion) ([]model.RevenueAmounts, error) { return rr.getBudgetAmountsByGLType(ctx, model.GLHeadcount, fiscalYear, fiscalPeriod, deptCode, version) }