adding revenue handlers and shell

This commit is contained in:
samantha42
2026-03-24 12:09:19 +01:00
parent 33b100f325
commit 1a9fe3adc7
13 changed files with 688 additions and 37 deletions

View File

@@ -21,14 +21,23 @@ type Period struct {
}
// RevenueCategory is what the revenue is broken down by
type RevenueCategory string
// RevenueCategory is now just a string — categories are dynamic per company
type RevenueCategory = string
// Built-in reserved categories
const (
CategoryProduct RevenueCategory = "product"
CategoryLocation RevenueCategory = "location"
CategoryTotal RevenueCategory = "total"
CategoryTotal RevenueCategory = "total" // always required, never custom
)
// CategoryDef defines a custom category for a specific company
// e.g. Company: Apple, Name: "product", Labels: ["iPhone", "Mac", "Services"]
type CategoryDef struct {
ID int
Company *Company
Name string // e.g. "product", "location", "segment"
Labels []string // populated from category_labels table
}
// Revenue is a single line in a financial report
type Revenue struct {
ID int
@@ -59,11 +68,57 @@ func (r *RevenueReport) Total(category RevenueCategory) float64 {
return sum
}
// Validate checks that product + location sums match the total entry
func (r *RevenueReport) Validate() (bool, float64, float64) {
total := r.Total(CategoryTotal)
products := r.Total(CategoryProduct)
locations := r.Total(CategoryLocation)
// both breakdowns should equal the total
return products == total && locations == total, products, locations
// SumByCategory returns a map of category → sum for all entries
func (r *RevenueReport) SumByCategory() map[RevenueCategory]float64 {
sums := make(map[RevenueCategory]float64)
for _, e := range r.Entries {
sums[e.Category] += e.Value
}
return sums
}
// Validate checks every category independently sums to total
func (r *RevenueReport) Validate() (bool, map[RevenueCategory]float64) {
total := r.Total(CategoryTotal)
sums := r.SumByCategory()
delete(sums, CategoryTotal) // don't compare total to itself
allMatch := true
for cat, sum := range sums {
if sum != total {
allMatch = false
_ = cat // caller can inspect the map to see which failed
}
}
return allMatch, sums
}
// RevSum aggregates revenue across multiple reports or periods
// Use this when you want e.g. FY = Q1+Q2+Q3+Q4
type RevSum struct {
Company *Company
Categories map[RevenueCategory]float64 // category → total across all periods
Labels map[string]float64 // label → total (e.g. "iPhone" across quarters)
Total float64
Periods []Period // which periods were summed
}
func NewRevSum(company *Company, reports []RevenueReport) *RevSum {
rs := &RevSum{
Company: company,
Categories: make(map[RevenueCategory]float64),
Labels: make(map[string]float64),
}
for _, report := range reports {
rs.Periods = append(rs.Periods, report.Period)
for _, e := range report.Entries {
if e.Category == CategoryTotal {
rs.Total += e.Value
continue
}
rs.Categories[e.Category] += e.Value
rs.Labels[e.Label] += e.Value
}
}
return rs
}