108 lines
2.7 KiB
Go
108 lines
2.7 KiB
Go
// 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)
|
|
}
|