basic online

This commit is contained in:
zipfriis
2026-03-20 14:01:46 +01:00
parent 5083212e07
commit cd2db504d1
16 changed files with 1137 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
package database
import "fmt"
// Migrate runs the schema bootstrap. SQLite doesn't need a migration tool —
// CREATE TABLE IF NOT EXISTS is idempotent so this is safe to call on every start.
func Migrate(db *DB) error {
stmts := []string{
`CREATE TABLE IF NOT EXISTS departments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
cost_center TEXT NOT NULL DEFAULT '',
active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
)`,
`CREATE TABLE IF NOT EXISTS gl_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT NOT NULL UNIQUE,
description TEXT NOT NULL,
type TEXT NOT NULL CHECK(type IN ('revenue','cogs','opex','capex','headcount')),
favour_high INTEGER NOT NULL DEFAULT 0,
active INTEGER NOT NULL DEFAULT 1
)`,
`CREATE TABLE IF NOT EXISTS budgets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
fiscal_year INTEGER NOT NULL,
fiscal_period INTEGER NOT NULL CHECK(fiscal_period BETWEEN 1 AND 12),
version TEXT NOT NULL DEFAULT 'original'
CHECK(version IN ('original','forecast_1','forecast_2','forecast_3')),
department_id INTEGER NOT NULL REFERENCES departments(id),
gl_account_id INTEGER NOT NULL REFERENCES gl_accounts(id),
amount REAL NOT NULL,
currency TEXT NOT NULL DEFAULT 'DKK',
notes TEXT NOT NULL DEFAULT '',
created_by TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
UNIQUE(fiscal_year, fiscal_period, version, department_id, gl_account_id)
)`,
`CREATE TABLE IF NOT EXISTS actuals (
id INTEGER PRIMARY KEY AUTOINCREMENT,
fiscal_year INTEGER NOT NULL,
fiscal_period INTEGER NOT NULL CHECK(fiscal_period BETWEEN 1 AND 12),
department_id INTEGER NOT NULL REFERENCES departments(id),
gl_account_id INTEGER NOT NULL REFERENCES gl_accounts(id),
amount REAL NOT NULL,
currency TEXT NOT NULL DEFAULT 'DKK',
source TEXT NOT NULL DEFAULT '',
ingested_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
UNIQUE(fiscal_year, fiscal_period, department_id, gl_account_id)
)`,
// Seed reference data if not already present
`INSERT OR IGNORE INTO departments (code, name, cost_center) VALUES
('ENG', 'Engineering', 'CC-100'),
('SALES', 'Sales', 'CC-200'),
('MKTG', 'Marketing', 'CC-300'),
('FINANCE', 'Finance & Operations', 'CC-400'),
('PRODUCT', 'Product', 'CC-500')`,
`INSERT OR IGNORE INTO gl_accounts (code, description, type, favour_high) VALUES
('4000', 'Subscription Revenue', 'revenue', 1),
('4100', 'Professional Services', 'revenue', 1),
('5000', 'Cloud Infrastructure', 'cogs', 0),
('5100', 'Third-party Licenses', 'cogs', 0),
('6100', 'Salaries & Wages', 'headcount', 0),
('6110', 'Employer Payroll Tax', 'headcount', 0),
('6120', 'Employee Benefits', 'headcount', 0),
('6300', 'Software Subscriptions', 'opex', 0),
('6310', 'Travel & Entertainment', 'opex', 0),
('6400', 'Marketing & Advertising', 'opex', 0),
('6500', 'Consulting & Contractors', 'opex', 0),
('6600', 'Office & Facilities', 'opex', 0)`,
}
for _, stmt := range stmts {
if _, err := db.Exec(stmt); err != nil {
return fmt.Errorf("migrate: %w", err)
}
}
return nil
}