12 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 05-template-data-model-and-api | 01 | execute | 1 |
|
true |
|
|
Purpose: Establish the data layer foundation that handlers (Plan 02) will call. Output: Migration SQL, updated models, complete query functions for templates and updated budget item queries.
<execution_context> @/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md @/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/05-template-data-model-and-api/05-CONTEXT.mdFrom backend/internal/models/models.go:
type CategoryType string
// Constants: CategoryBill, CategoryVariableExpense, CategoryDebt, CategorySaving, CategoryInvestment, CategoryIncome
type BudgetItem struct {
ID uuid.UUID `json:"id"`
BudgetID uuid.UUID `json:"budget_id"`
CategoryID uuid.UUID `json:"category_id"`
CategoryName string `json:"category_name,omitempty"`
CategoryType CategoryType `json:"category_type,omitempty"`
BudgetedAmount decimal.Decimal `json:"budgeted_amount"`
ActualAmount decimal.Decimal `json:"actual_amount"`
Notes string `json:"notes"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type BudgetDetail struct {
Budget
Items []BudgetItem `json:"items"`
Totals BudgetTotals `json:"totals"`
}
From backend/internal/db/queries.go:
type Queries struct { pool *pgxpool.Pool }
func NewQueries(pool *pgxpool.Pool) *Queries
func (q *Queries) CreateBudgetItem(ctx context.Context, budgetID, categoryID uuid.UUID, budgeted, actual decimal.Decimal, notes string) (*models.BudgetItem, error)
func (q *Queries) UpdateBudgetItem(ctx context.Context, id, budgetID uuid.UUID, budgeted, actual decimal.Decimal, notes string) (*models.BudgetItem, error)
func (q *Queries) GetBudgetWithItems(ctx context.Context, id, userID uuid.UUID) (*models.BudgetDetail, error)
func (q *Queries) CopyBudgetItems(ctx context.Context, targetBudgetID, sourceBudgetID, userID uuid.UUID) error
From backend/migrations/001_initial.sql:
-- budget_items has: id, budget_id, category_id, budgeted_amount, actual_amount, notes, created_at, updated_at
-- No item_type or item_tier column exists yet
-- category_type ENUM already exists as a pattern to follow
- Update
backend/internal/models/models.go:- Add ItemTier type (string) with constants: ItemTierFixed = "fixed", ItemTierVariable = "variable", ItemTierOneOff = "one_off"
- Add ItemTier field to BudgetItem struct:
ItemTier ItemTier json:"item_tier"— place it after CategoryType field - Add Template struct: ID uuid.UUID, UserID uuid.UUID, Name string, CreatedAt time.Time, UpdatedAt time.Time (all with json tags)
- Add TemplateItem struct: ID uuid.UUID, TemplateID uuid.UUID, CategoryID uuid.UUID, CategoryName string (omitempty), CategoryType CategoryType (omitempty), CategoryIcon string (omitempty), ItemTier ItemTier, BudgetedAmount *decimal.Decimal (pointer for nullable), SortOrder int, CreatedAt time.Time, UpdatedAt time.Time (all with json tags)
- Add TemplateDetail struct: Template embedded + Items []TemplateItem json:"items" cd /home/jean-luc-makiola/Development/projects/SimpleFinanceDash/backend && go vet ./internal/models/... Migration file exists with item_tier enum, budget_items ALTER, templates table, template_items table with CHECK constraint. Models compile with ItemTier type, Template, TemplateItem, TemplateDetail structs.
Update existing budget item queries to include item_tier:
-
CreateBudgetItem— additemTier models.ItemTierparameter (after notes). Update INSERT to include item_tier column. Update RETURNING to include item_tier. Update Scan to include&i.ItemTier. Default: if itemTier is empty string, use "one_off" (per context decision: new items default to one_off). -
UpdateBudgetItem— additemTier models.ItemTierparameter. Update SET to include item_tier. Update RETURNING and Scan to include item_tier. -
GetBudgetWithItems— update SELECT to includebi.item_tierafterc.type. Update Scan to include&i.ItemTier(between CategoryType and BudgetedAmount). -
CopyBudgetItems— update INSERT...SELECT to copy item_tier column from source items.
Add new template query functions:
-
GetTemplate(ctx, userID) (*models.TemplateDetail, error)— SELECT template by user_id. If no rows, return&models.TemplateDetail{Items: []models.TemplateItem{}}with zero-value Template (ID will be uuid.Nil). If found, query template_items JOIN categories (get c.name, c.type, c.icon) ORDER BY sort_order, return TemplateDetail with items. This handles the "no template yet returns empty" case. -
UpdateTemplateName(ctx, userID uuid.UUID, name string) (*models.Template, error)— UPDATE templates SET name WHERE user_id. Return error if no template exists. -
CreateTemplateItem(ctx, userID uuid.UUID, categoryID uuid.UUID, itemTier models.ItemTier, budgetedAmount *decimal.Decimal, sortOrder int) (*models.TemplateItem, error)— First ensure template exists: INSERT INTO templates (user_id) VALUES ($1) ON CONFLICT (user_id) DO UPDATE SET updated_at = now() RETURNING id. Then INSERT INTO template_items using the template_id. RETURNING with JOIN to get category details. This implements lazy template creation. -
UpdateTemplateItem(ctx, userID, itemID uuid.UUID, itemTier models.ItemTier, budgetedAmount *decimal.Decimal, sortOrder int) (*models.TemplateItem, error)— UPDATE template_items SET ... WHERE id = $1 AND template_id = (SELECT id FROM templates WHERE user_id = $2). Return with joined category details. -
DeleteTemplateItem(ctx, userID, itemID uuid.UUID) error— DELETE FROM template_items WHERE id = $1 AND template_id = (SELECT id FROM templates WHERE user_id = $2). -
ReorderTemplateItems(ctx, userID uuid.UUID, itemOrders []struct{ID uuid.UUID; SortOrder int}) error— Batch update sort_order for multiple items. Use a transaction. Verify each item belongs to user's template. -
GenerateBudgetFromTemplate(ctx, userID uuid.UUID, month string, currency string) (*models.BudgetDetail, error)— Single transaction: a. Parse month string ("2026-04") to compute startDate (first of month) and endDate (last of month). b. Check if budget with overlapping dates exists for user. If yes, return a sentinel error (definevar ErrBudgetExists = fmt.Errorf("budget already exists")) and include the existing budget ID. c. Get user's preferred_locale from users table to format budget name (use a simple map: "de" -> German month names, "en" -> English month names, default to English). Budget name = "MonthName Year" (e.g. "April 2026" or "April 2026" in German). d. Create budget with computed name, dates, currency, zero carryover. e. Get template items for user (if no template or no items, return the empty budget — no error per context decision). f. For each template item: INSERT INTO budget_items with budget_id, category_id, item_tier, budgeted_amount (use template amount for fixed, 0 for variable), actual_amount = 0, notes = ''. g. Return full BudgetDetail (reuse GetBudgetWithItems pattern).
For the ErrBudgetExists sentinel, also create a BudgetExistsError struct type that holds the existing budget_id so the handler can include it in the 409 response. cd /home/jean-luc-makiola/Development/projects/SimpleFinanceDash/backend && go vet ./... All query functions compile. CreateBudgetItem/UpdateBudgetItem accept item_tier. GetBudgetWithItems returns item_tier. Template CRUD queries exist. GenerateBudgetFromTemplate creates budget + items from template in a transaction. ErrBudgetExists sentinel defined.
- `go vet ./...` passes with no errors from the backend directory - Models file contains ItemTier type with 3 constants, Template, TemplateItem, TemplateDetail structs - Queries file contains all 7 new template functions plus updated budget item functions - Migration SQL is syntactically valid (item_tier enum, ALTER budget_items, CREATE templates, CREATE template_items with CHECK)<success_criteria>
- Migration 002 creates item_tier type, adds column to budget_items, creates templates and template_items tables
- BudgetItem struct includes ItemTier field returned in JSON
- Template CRUD queries handle lazy creation and user scoping
- GenerateBudgetFromTemplate handles empty template, duplicate month (409), and normal generation
go vet ./...passes cleanly </success_criteria>