basic online
This commit is contained in:
28
internal/service/bugdet-service.go
Normal file
28
internal/service/bugdet-service.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"Engine/internal/database"
|
||||
"Engine/internal/model"
|
||||
)
|
||||
|
||||
type BudgetService struct {
|
||||
repo *database.BudgetRepo
|
||||
}
|
||||
|
||||
func NewBudgetService(repo *database.BudgetRepo) *BudgetService {
|
||||
return &BudgetService{repo: repo}
|
||||
}
|
||||
|
||||
func (s *BudgetService) Create(ctx context.Context, req model.CreateBudgetRequest) (*model.Budget, error) {
|
||||
return s.repo.Create(ctx, req)
|
||||
}
|
||||
|
||||
func (s *BudgetService) Update(ctx context.Context, id int, amount float64, notes, changedBy string) (*model.Budget, error) {
|
||||
return s.repo.Update(ctx, id, amount, notes, changedBy)
|
||||
}
|
||||
|
||||
func (s *BudgetService) Delete(ctx context.Context, id int) error {
|
||||
return s.repo.Delete(ctx, id)
|
||||
}
|
||||
116
internal/service/variance-service.go
Normal file
116
internal/service/variance-service.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
|
||||
"Engine/internal/database"
|
||||
"Engine/internal/model"
|
||||
)
|
||||
|
||||
type VarianceService struct {
|
||||
budgets *database.BudgetRepo
|
||||
actuals *database.ActualsRepo
|
||||
}
|
||||
|
||||
func NewVarianceService(b *database.BudgetRepo, a *database.ActualsRepo) *VarianceService {
|
||||
return &VarianceService{budgets: b, actuals: a}
|
||||
}
|
||||
|
||||
type VarianceFilter struct {
|
||||
FiscalYear int
|
||||
FiscalPeriod int
|
||||
DeptCode string
|
||||
Version model.BudgetVersion
|
||||
}
|
||||
|
||||
func (s *VarianceService) Report(ctx context.Context, f VarianceFilter) (*model.VarianceReport, error) {
|
||||
budgets, err := s.budgets.List(ctx, f.FiscalYear, f.FiscalPeriod, f.DeptCode, f.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
actualsByGL, err := s.actuals.ListByPeriod(ctx, f.FiscalYear, f.FiscalPeriod, f.DeptCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
report := &model.VarianceReport{
|
||||
FiscalYear: f.FiscalYear,
|
||||
FiscalPeriod: f.FiscalPeriod,
|
||||
Version: f.Version,
|
||||
Department: f.DeptCode,
|
||||
Currency: "DKK",
|
||||
}
|
||||
|
||||
for _, b := range budgets {
|
||||
actual := actualsByGL[b.GLCode]
|
||||
varAbs := actual - b.Amount
|
||||
|
||||
var varPct *float64
|
||||
if b.Amount != 0 {
|
||||
v := math.Round((varAbs/b.Amount)*10000) / 100
|
||||
varPct = &v
|
||||
}
|
||||
|
||||
report.Lines = append(report.Lines, model.VarianceLine{
|
||||
GLCode: b.GLCode,
|
||||
GLDescription: b.GLDescription,
|
||||
GLType: b.GLType,
|
||||
Budget: b.Amount,
|
||||
Actual: actual,
|
||||
VarianceAbs: varAbs,
|
||||
VariancePct: varPct,
|
||||
Status: computeStatus(varAbs, b.FavourHigh),
|
||||
Currency: b.Currency,
|
||||
})
|
||||
|
||||
report.TotalBudget += b.Amount
|
||||
report.TotalActual += actual
|
||||
}
|
||||
|
||||
report.TotalVariance = report.TotalActual - report.TotalBudget
|
||||
if report.TotalBudget != 0 {
|
||||
v := math.Round((report.TotalVariance/report.TotalBudget)*10000) / 100
|
||||
report.VariancePct = &v
|
||||
}
|
||||
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func (s *VarianceService) Alerts(ctx context.Context, f VarianceFilter, thresholdPct float64) ([]model.AlertThreshold, error) {
|
||||
report, err := s.Report(ctx, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var alerts []model.AlertThreshold
|
||||
for _, line := range report.Lines {
|
||||
if line.VariancePct == nil {
|
||||
continue
|
||||
}
|
||||
if math.Abs(*line.VariancePct) >= thresholdPct {
|
||||
alerts = append(alerts, model.AlertThreshold{
|
||||
GLCode: line.GLCode,
|
||||
Description: line.GLDescription,
|
||||
Budget: line.Budget,
|
||||
Actual: line.Actual,
|
||||
VariancePct: *line.VariancePct,
|
||||
Status: line.Status,
|
||||
Department: report.Department,
|
||||
})
|
||||
}
|
||||
}
|
||||
return alerts, nil
|
||||
}
|
||||
|
||||
func computeStatus(varAbs float64, favourHigh bool) model.VarianceStatus {
|
||||
const epsilon = 0.01
|
||||
if math.Abs(varAbs) < epsilon {
|
||||
return model.StatusOnBudget
|
||||
}
|
||||
if (favourHigh && varAbs > 0) || (!favourHigh && varAbs < 0) {
|
||||
return model.StatusFavourable
|
||||
}
|
||||
return model.StatusUnfavourable
|
||||
}
|
||||
Reference in New Issue
Block a user