package model import ( "database/sql" "fmt" "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 { ID int 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 } func (p *Period) Insert(db *sql.DB) error { _, err := db.Exec( `INSERT INTO periods (type, year, idx, start_date, end_date) VALUES (?, ?, ?, ?, ?) ON CONFLICT(type, year, idx) DO UPDATE SET start_date=excluded.start_date, end_date=excluded.end_date`, string(p.Type), p.Year, p.Index, p.Start.Format("2006-01-02"), p.End.Format("2006-01-02"), ) if err != nil { return fmt.Errorf("upsert period: %w", err) } var id int err = db.QueryRow( `SELECT id FROM periods WHERE type = ? AND year = ? AND idx = ?`, string(p.Type), p.Year, p.Index, ).Scan(&id) if err != nil { return fmt.Errorf("select period: %w", err) } p.ID = id return nil } type RevenueCategory struct { ID int CompanyID int ParentID *int Name string // e.g. "product", "location", "segment" } func (rc *RevenueCategory) Validate() error { if rc.ID == 0 { return fmt.Errorf("No ID Set") } else if rc.Name == "" { return fmt.Errorf("No Name found") } else if rc.CompanyID == 0 { return fmt.Errorf("No Company Set") } return nil } func (rc *RevenueCategory) Insert(db *sql.DB) error { if err := rc.Validate(); err != nil { return fmt.Errorf("failed to insert: %w", err) } _, err := db.Exec( `INSERT INTO category (company_id, parent_id, name) VALUES (?, ?, ?) ON CONFLICT(company_id, name) DO UPDATE SET parent_id=excluded.parent_id`, rc.CompanyID, rc.ParentID, rc.Name, ) if err != nil { return fmt.Errorf("upsert category: %w", err) } err = db.QueryRow( `SELECT id FROM category WHERE company_id = ? AND name = ?`, rc.CompanyID, rc.Name, ).Scan(&rc.ID) if err != nil { return fmt.Errorf("select category id: %w", err) } return nil } // Revenue is a single line in a financial report type Revenue struct { ID int Company *Company Currency *Currency Category *RevenueCategory Period *Period Value float64 }