Add project and todo management

This commit is contained in:
michael committed 2026-09-02 11:03:50 +02:00
1 parent 2792051a16
commit 3da436423f
12 files changed
+1865 -43

No files matched your search

+40
View File
@@ -435,6 +435,42 @@ export async function migrate() {
);
`);
await query(`
CREATE TABLE IF NOT EXISTS projects (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
ticket_id BIGINT NOT NULL REFERENCES tickets(id) ON DELETE CASCADE,
teamspace_project TEXT,
description TEXT NOT NULL DEFAULT '',
goals TEXT NOT NULL DEFAULT '',
starts_on DATE,
ends_on DATE,
hour_budget_minutes INTEGER,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (user_id, ticket_id),
CONSTRAINT projects_budget_non_negative CHECK (hour_budget_minutes IS NULL OR hour_budget_minutes >= 0),
CONSTRAINT projects_valid_range CHECK (starts_on IS NULL OR ends_on IS NULL OR ends_on >= starts_on)
);
`);
await query(`
CREATE TABLE IF NOT EXISTS ticket_todos (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
ticket_id BIGINT NOT NULL REFERENCES tickets(id) ON DELETE CASCADE,
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
priority TEXT NOT NULL DEFAULT 'medium',
status TEXT NOT NULL DEFAULT 'open',
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT ticket_todos_priority_valid CHECK (priority IN ('low', 'medium', 'high', 'urgent')),
CONSTRAINT ticket_todos_status_valid CHECK (status IN ('open', 'done'))
);
`);
await ensureClosureOwnership("ticket_month_closures", "month", ["ticket_id", "month"], adminId);
await ensureClosureOwnership("month_closures", "month", ["month"], adminId);
await ensureClosureOwnership("ticket_day_closures", "day", ["ticket_id", "day"], adminId);
@@ -450,6 +486,10 @@ export async function migrate() {
await query("CREATE INDEX IF NOT EXISTS idx_ticket_day_closures_day ON ticket_day_closures(day);");
await query("CREATE INDEX IF NOT EXISTS idx_ticket_day_billings_user_day ON ticket_day_billings(user_id, day);");
await query("CREATE INDEX IF NOT EXISTS idx_ticket_day_billing_ack_user_day ON ticket_day_billing_acknowledgements(user_id, day);");
await query("CREATE INDEX IF NOT EXISTS idx_projects_user_ticket ON projects(user_id, ticket_id);");
await query("CREATE INDEX IF NOT EXISTS idx_projects_user_updated ON projects(user_id, updated_at DESC);");
await query("CREATE INDEX IF NOT EXISTS idx_ticket_todos_user_status_priority ON ticket_todos(user_id, status, priority);");
await query("CREATE INDEX IF NOT EXISTS idx_ticket_todos_ticket_user ON ticket_todos(ticket_id, user_id);");
await query("CREATE INDEX IF NOT EXISTS idx_recurring_billings_user_active ON recurring_billings(user_id, active);");
await query("UPDATE recurring_billings SET active = true WHERE active = false;");
await query(`