adding revenue

This commit is contained in:
samantha42
2026-03-24 11:37:45 +01:00
parent bea8582282
commit 33b100f325
5 changed files with 176 additions and 24 deletions

View File

@@ -18,11 +18,41 @@ func InitDB(db *sql.DB) {
CREATE TABLE IF NOT EXISTS companies ( CREATE TABLE IF NOT EXISTS companies (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, name TEXT NOT NULL UNIQUE,
shares_outstanding INTEGER NOT NULL, shares_outstanding INTEGER NOT NULL,
price REAL NOT NULL, price REAL NOT NULL,
currency_id INTEGER NOT NULL, currency_id INTEGER NOT NULL,
FOREIGN KEY (currency_id) REFERENCES currencies(id) FOREIGN KEY (currency_id) REFERENCES currencies(id)
);
CREATE TABLE IF NOT EXISTS periods (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL CHECK(type IN ('Q', 'H', 'Y')),
year INTEGER NOT NULL,
idx INTEGER NOT NULL,
start_date TEXT NOT NULL,
end_date TEXT NOT NULL,
UNIQUE(type, year, idx)
);
CREATE TABLE IF NOT EXISTS revenue_reports (
id INTEGER PRIMARY KEY AUTOINCREMENT,
company_id INTEGER NOT NULL,
period_id INTEGER NOT NULL,
FOREIGN KEY (company_id) REFERENCES companies(id),
FOREIGN KEY (period_id) REFERENCES periods(id),
UNIQUE(company_id, period_id)
);
CREATE TABLE IF NOT EXISTS revenue_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
report_id INTEGER NOT NULL,
currency_id INTEGER NOT NULL,
category TEXT NOT NULL CHECK(category IN ('product', 'location', 'total')),
label TEXT NOT NULL,
value REAL NOT NULL,
FOREIGN KEY (report_id) REFERENCES revenue_reports(id),
FOREIGN KEY (currency_id) REFERENCES currencies(id)
);` );`
if _, err := db.Exec(schema); err != nil { if _, err := db.Exec(schema); err != nil {

View File

@@ -1,11 +1,5 @@
package model package model
type Currency struct {
ID int
Code string
Name string
}
type Company struct { type Company struct {
ID int ID int
Name string Name string
@@ -21,8 +15,3 @@ type CompanyInput struct {
Price float64 `json:"price"` Price float64 `json:"price"`
CurrencyID int `json:"currency_id"` CurrencyID int `json:"currency_id"`
} }
type CurrencyInput struct {
Code string `json:"code"`
Name string `json:"name"`
}

View File

@@ -0,0 +1,12 @@
package model
type Currency struct {
ID int
Code string
Name string
}
type CurrencyInput struct {
Code string `json:"code"`
Name string `json:"name"`
}

52
internal/model/periode.go Normal file
View File

@@ -0,0 +1,52 @@
// model/period.go
package model
import (
"fmt"
"time"
)
func QuarterPeriod(year, q int) Period {
months := map[int][2]int{
1: {1, 3}, 2: {4, 6}, 3: {7, 9}, 4: {10, 12},
}
m := months[q]
return Period{
Type: PeriodQuarter,
Year: year,
Index: q,
Start: date(year, m[0], 1),
End: date(year, m[1]+1, 0), // last day of end month
}
}
func HalfYearPeriod(year, h int) Period {
if h == 1 {
return Period{Type: PeriodHalfYear, Year: year, Index: 1,
Start: date(year, 1, 1), End: date(year, 7, 0)}
}
return Period{Type: PeriodHalfYear, Year: year, Index: 2,
Start: date(year, 7, 1), End: date(year, 12, 31)}
}
func FullYearPeriod(year int) Period {
return Period{Type: PeriodYear, Year: year, Index: 1,
Start: date(year, 1, 1), End: date(year, 12, 31)}
}
func (p Period) String() string {
switch p.Type {
case PeriodQuarter:
return fmt.Sprintf("Q%d %d", p.Index, p.Year)
case PeriodHalfYear:
return fmt.Sprintf("H%d %d", p.Index, p.Year)
case PeriodYear:
return fmt.Sprintf("FY%d", p.Year)
}
return "unknown"
}
func date(year, month, day int) time.Time {
// day=0 means last day of previous month
return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
}

69
internal/model/revenue.go Normal file
View File

@@ -0,0 +1,69 @@
package model
import "time"
// PeriodType defines the granularity of the revenue entry
type PeriodType string
const (
PeriodQuarter PeriodType = "Q"
PeriodHalfYear PeriodType = "H"
PeriodYear PeriodType = "Y"
)
// Period holds the actual time range for a revenue entry
type Period struct {
Type PeriodType
Year int
Index int // Q1=1 Q2=2 Q3=3 Q4=4 | H1=1 H2=2 | FY=1
Start time.Time
End time.Time
}
// RevenueCategory is what the revenue is broken down by
type RevenueCategory string
const (
CategoryProduct RevenueCategory = "product"
CategoryLocation RevenueCategory = "location"
CategoryTotal RevenueCategory = "total"
)
// Revenue is a single line in a financial report
type Revenue struct {
ID int
Company *Company
Currency *Currency
Category RevenueCategory
Label string // e.g. "North America", "iPhone", "Total"
Value float64
Period Period
}
// RevenueReport groups revenue lines for a company/period
// and verifies that sub-categories sum to total
type RevenueReport struct {
Company *Company
Period Period
Entries []Revenue
}
// Total returns the sum of all entries matching a category
func (r *RevenueReport) Total(category RevenueCategory) float64 {
var sum float64
for _, e := range r.Entries {
if e.Category == category {
sum += e.Value
}
}
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
}