83 lines
2.2 KiB
Go
83 lines
2.2 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"Engine/internal/database"
|
|
"Engine/internal/model"
|
|
)
|
|
|
|
type ReportService struct {
|
|
repo *database.ReportRepo
|
|
}
|
|
|
|
func NewReportService(repo *database.ReportRepo) *ReportService {
|
|
return &ReportService{repo: repo}
|
|
}
|
|
|
|
type PnLFilter struct {
|
|
FiscalYear int
|
|
FiscalPeriod int
|
|
DeptCode string
|
|
Version model.BudgetVersion
|
|
}
|
|
|
|
func (s *ReportService) CreatePnL(ctx context.Context, f PnLFilter) (*model.PnLReport, error) {
|
|
// fetch all five GL type buckets in parallel would be nicer, but keeping
|
|
// it simple and sequential matches the rest of your codebase style.
|
|
|
|
revenue, err := s.repo.GetGLRevenueActuals(ctx, f.FiscalYear, f.FiscalPeriod, f.DeptCode)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("CreatePnL: revenue actuals: %w", err)
|
|
}
|
|
|
|
cogs, err := s.repo.GetGLCOGSActuals(ctx, f.FiscalYear, f.FiscalPeriod, f.DeptCode)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("CreatePnL: cogs actuals: %w", err)
|
|
}
|
|
|
|
opex, err := s.repo.GetGLOpexActuals(ctx, f.FiscalYear, f.FiscalPeriod, f.DeptCode)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("CreatePnL: opex actuals: %w", err)
|
|
}
|
|
|
|
headcount, err := s.repo.GetGLHeadcountActuals(ctx, f.FiscalYear, f.FiscalPeriod, f.DeptCode)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("CreatePnL: headcount actuals: %w", err)
|
|
}
|
|
|
|
capex, err := s.repo.GetGLCapexActuals(ctx, f.FiscalYear, f.FiscalPeriod, f.DeptCode)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("CreatePnL: capex actuals: %w", err)
|
|
}
|
|
|
|
report := &model.PnLReport{
|
|
Department: f.DeptCode,
|
|
FiscalYear: f.FiscalYear,
|
|
FiscalPeriod: f.FiscalPeriod,
|
|
Version: f.Version,
|
|
Currency: "DKK",
|
|
Revenue: toSection(revenue),
|
|
COGS: toSection(cogs),
|
|
Opex: toSection(opex),
|
|
Headcount: toSection(headcount),
|
|
Capex: toSection(capex),
|
|
}
|
|
|
|
report.GrossProfit = report.Revenue.Total - report.COGS.Total
|
|
report.EBIT = report.GrossProfit - report.Opex.Total - report.Headcount.Total
|
|
report.NetIncome = report.EBIT - report.Capex.Total
|
|
|
|
return report, nil
|
|
}
|
|
|
|
// toSection sums the rows and packages them into a PnLSection.
|
|
func toSection(rows []model.GLAmountRow) model.PnLSection {
|
|
s := model.PnLSection{Lines: rows}
|
|
for _, r := range rows {
|
|
s.Total += r.Amount
|
|
}
|
|
return s
|
|
}
|