// model/period.go 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) Validate(checkID bool) error { if p.ID == 0 && checkID { return fmt.Errorf("No ID Set") } else if p.Type == PeriodQuarter && (p.Index > 4 || p.Index < 1) { return fmt.Errorf("Not Valid Quarter index") } else if p.Type == PeriodHalfYear && (p.Index > 2 || p.Index < 1) { return fmt.Errorf("Not Valid HalfYear index") } else if p.Type == PeriodHalfYear && (p.Index != 1 || p.Year < 1) { return fmt.Errorf("Not Valid Year index") } return nil } 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 } 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) }