From b3082ca14fd796fbd4b6f03ce2661ef1cb03f727 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Thu, 12 Mar 2026 12:05:01 +0100 Subject: [PATCH] feat(05-01): migration SQL and Go model types for template system - Create 002_templates.sql: item_tier enum, ALTER budget_items, templates and template_items tables with CHECK constraint - Add ItemTier type with fixed/variable/one_off constants to models.go - Add ItemTier field to BudgetItem struct - Add Template, TemplateItem, TemplateDetail structs --- backend/internal/models/models.go | 36 ++++++++++++++++++++++++++++ backend/migrations/002_templates.sql | 27 +++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 backend/migrations/002_templates.sql diff --git a/backend/internal/models/models.go b/backend/internal/models/models.go index abc8813..de4c466 100644 --- a/backend/internal/models/models.go +++ b/backend/internal/models/models.go @@ -18,6 +18,14 @@ const ( CategoryIncome CategoryType = "income" ) +type ItemTier string + +const ( + ItemTierFixed ItemTier = "fixed" + ItemTierVariable ItemTier = "variable" + ItemTierOneOff ItemTier = "one_off" +) + type User struct { ID uuid.UUID `json:"id"` Email string `json:"email"` @@ -58,6 +66,7 @@ type BudgetItem struct { CategoryID uuid.UUID `json:"category_id"` CategoryName string `json:"category_name,omitempty"` CategoryType CategoryType `json:"category_type,omitempty"` + ItemTier ItemTier `json:"item_tier"` BudgetedAmount decimal.Decimal `json:"budgeted_amount"` ActualAmount decimal.Decimal `json:"actual_amount"` Notes string `json:"notes"` @@ -86,3 +95,30 @@ type BudgetDetail struct { Items []BudgetItem `json:"items"` Totals BudgetTotals `json:"totals"` } + +type Template struct { + ID uuid.UUID `json:"id"` + UserID uuid.UUID `json:"user_id"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type TemplateItem struct { + ID uuid.UUID `json:"id"` + TemplateID uuid.UUID `json:"template_id"` + CategoryID uuid.UUID `json:"category_id"` + CategoryName string `json:"category_name,omitempty"` + CategoryType CategoryType `json:"category_type,omitempty"` + CategoryIcon string `json:"category_icon,omitempty"` + ItemTier ItemTier `json:"item_tier"` + BudgetedAmount *decimal.Decimal `json:"budgeted_amount"` + SortOrder int `json:"sort_order"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type TemplateDetail struct { + Template + Items []TemplateItem `json:"items"` +} diff --git a/backend/migrations/002_templates.sql b/backend/migrations/002_templates.sql new file mode 100644 index 0000000..d10c2f4 --- /dev/null +++ b/backend/migrations/002_templates.sql @@ -0,0 +1,27 @@ +CREATE TYPE item_tier AS ENUM ('fixed', 'variable', 'one_off'); + +ALTER TABLE budget_items ADD COLUMN item_tier item_tier NOT NULL DEFAULT 'fixed'; + +CREATE TABLE IF NOT EXISTS templates ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + name TEXT NOT NULL DEFAULT 'My Template', + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE UNIQUE INDEX idx_templates_user_id ON templates (user_id); + +CREATE TABLE IF NOT EXISTS template_items ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + template_id UUID NOT NULL REFERENCES templates(id) ON DELETE CASCADE, + category_id UUID NOT NULL REFERENCES categories(id) ON DELETE RESTRICT, + item_tier item_tier NOT NULL, + budgeted_amount NUMERIC(12, 2), + sort_order INT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT chk_template_items_no_one_off CHECK (item_tier IN ('fixed', 'variable')) +); + +CREATE INDEX idx_template_items_template_id ON template_items (template_id);