Initial commit

This commit is contained in:
Jan
2026-04-08 16:30:44 +02:00
commit f9b17e9964
65 changed files with 7574 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
CREATE TYPE "Role" AS ENUM ('ADMIN', 'FINANCE', 'MEMBER');
CREATE TYPE "ApprovalType" AS ENUM ('CHAIR_A', 'CHAIR_B', 'FINANCE');
CREATE TYPE "ApprovalStatus" AS ENUM ('PENDING', 'APPROVED');
CREATE TABLE "working_groups" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"total_budget" DECIMAL(10,2) NOT NULL,
"color_code" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "working_groups_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "users" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password_hash" TEXT NOT NULL,
"role" "Role" NOT NULL,
"approval_preference" "ApprovalType",
"working_group_id" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "expenses" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"amount" DECIMAL(10,2) NOT NULL,
"creator_id" TEXT NOT NULL,
"ag_id" TEXT NOT NULL,
"approval_status" "ApprovalStatus" NOT NULL DEFAULT 'PENDING',
"paid_at" TIMESTAMP(3),
"documented_at" TIMESTAMP(3),
"proof_url" TEXT,
"notes" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "expenses_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "approvals" (
"id" TEXT NOT NULL,
"expense_id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"approval_type" "ApprovalType" NOT NULL,
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "approvals_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "working_groups_name_key" ON "working_groups"("name");
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
CREATE UNIQUE INDEX "approvals_expense_id_approval_type_key" ON "approvals"("expense_id", "approval_type");
ALTER TABLE "users"
ADD CONSTRAINT "users_working_group_id_fkey"
FOREIGN KEY ("working_group_id") REFERENCES "working_groups"("id")
ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "expenses"
ADD CONSTRAINT "expenses_creator_id_fkey"
FOREIGN KEY ("creator_id") REFERENCES "users"("id")
ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "expenses"
ADD CONSTRAINT "expenses_ag_id_fkey"
FOREIGN KEY ("ag_id") REFERENCES "working_groups"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "approvals"
ADD CONSTRAINT "approvals_expense_id_fkey"
FOREIGN KEY ("expense_id") REFERENCES "expenses"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "approvals"
ADD CONSTRAINT "approvals_user_id_fkey"
FOREIGN KEY ("user_id") REFERENCES "users"("id")
ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,84 @@
ALTER TABLE "users" ADD COLUMN "username" TEXT;
WITH ranked AS (
SELECT
id,
CASE
WHEN lower(regexp_replace(split_part(email, '@', 1), '[^a-zA-Z0-9_-]', '-', 'g')) = ''
THEN CONCAT('user-', substring(id from 1 for 6))
ELSE lower(regexp_replace(split_part(email, '@', 1), '[^a-zA-Z0-9_-]', '-', 'g'))
END AS base,
ROW_NUMBER() OVER (
PARTITION BY CASE
WHEN lower(regexp_replace(split_part(email, '@', 1), '[^a-zA-Z0-9_-]', '-', 'g')) = ''
THEN CONCAT('user-', substring(id from 1 for 6))
ELSE lower(regexp_replace(split_part(email, '@', 1), '[^a-zA-Z0-9_-]', '-', 'g'))
END
ORDER BY created_at, id
) AS rn
FROM "users"
)
UPDATE "users" AS u
SET "username" = CASE
WHEN ranked.rn = 1 THEN ranked.base
ELSE CONCAT(ranked.base, '-', ranked.rn)
END
FROM ranked
WHERE u.id = ranked.id;
ALTER TABLE "users" ALTER COLUMN "username" SET NOT NULL;
ALTER TABLE "users" ALTER COLUMN "email" DROP NOT NULL;
CREATE UNIQUE INDEX "users_username_key" ON "users"("username");
CREATE TABLE "budgets" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"total_budget" DECIMAL(10,2) NOT NULL,
"color_code" TEXT NOT NULL,
"working_group_id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "budgets_pkey" PRIMARY KEY ("id")
);
INSERT INTO "budgets" (
"id",
"name",
"total_budget",
"color_code",
"working_group_id",
"created_at",
"updated_at"
)
SELECT
CONCAT('budget_', "id"),
'Hauptbudget',
"total_budget",
"color_code",
"id",
"created_at",
"updated_at"
FROM "working_groups";
CREATE UNIQUE INDEX "budgets_working_group_id_name_key" ON "budgets"("working_group_id", "name");
ALTER TABLE "budgets"
ADD CONSTRAINT "budgets_working_group_id_fkey"
FOREIGN KEY ("working_group_id") REFERENCES "working_groups"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "expenses" RENAME COLUMN "notes" TO "description";
ALTER TABLE "expenses" ADD COLUMN "budget_id" TEXT;
UPDATE "expenses"
SET "budget_id" = CONCAT('budget_', "ag_id");
ALTER TABLE "expenses" ALTER COLUMN "budget_id" SET NOT NULL;
ALTER TABLE "expenses"
ADD CONSTRAINT "expenses_budget_id_fkey"
FOREIGN KEY ("budget_id") REFERENCES "budgets"("id")
ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "working_groups" DROP COLUMN "total_budget";
ALTER TABLE "working_groups" DROP COLUMN "color_code";

View File

@@ -0,0 +1,60 @@
CREATE TYPE "ExpenseRecurrence" AS ENUM ('NONE', 'MONTHLY');
CREATE TABLE "accounting_periods" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"starts_at" TIMESTAMP(3) NOT NULL,
"ends_at" TIMESTAMP(3) NOT NULL,
"is_current" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "accounting_periods_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "accounting_periods_name_key" ON "accounting_periods"("name");
CREATE UNIQUE INDEX "accounting_periods_is_current_key" ON "accounting_periods"("is_current") WHERE "is_current" = true;
INSERT INTO "accounting_periods" (
"id",
"name",
"starts_at",
"ends_at",
"is_current",
"created_at",
"updated_at"
)
VALUES (
'period_current',
CONCAT('Haushalt ', EXTRACT(YEAR FROM CURRENT_DATE)::text),
date_trunc('year', CURRENT_DATE)::timestamp(3),
(date_trunc('year', CURRENT_DATE) + interval '1 year' - interval '1 day')::timestamp(3),
true,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
);
ALTER TABLE "budgets" ADD COLUMN "period_id" TEXT;
ALTER TABLE "expenses" ADD COLUMN "period_id" TEXT;
ALTER TABLE "expenses" ADD COLUMN "recurrence" "ExpenseRecurrence" NOT NULL DEFAULT 'NONE';
UPDATE "budgets"
SET "period_id" = 'period_current';
UPDATE "expenses"
SET "period_id" = 'period_current';
ALTER TABLE "budgets" ALTER COLUMN "period_id" SET NOT NULL;
ALTER TABLE "expenses" ALTER COLUMN "period_id" SET NOT NULL;
DROP INDEX "budgets_working_group_id_name_key";
CREATE UNIQUE INDEX "budgets_working_group_id_period_id_name_key" ON "budgets"("working_group_id", "period_id", "name");
ALTER TABLE "budgets"
ADD CONSTRAINT "budgets_period_id_fkey"
FOREIGN KEY ("period_id") REFERENCES "accounting_periods"("id")
ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "expenses"
ADD CONSTRAINT "expenses_period_id_fkey"
FOREIGN KEY ("period_id") REFERENCES "accounting_periods"("id")
ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,20 @@
CREATE TABLE "audit_logs" (
"id" TEXT NOT NULL,
"actor_id" TEXT,
"action" TEXT NOT NULL,
"entity_type" TEXT NOT NULL,
"entity_id" TEXT,
"entity_label" TEXT,
"summary" TEXT NOT NULL,
"metadata" JSONB,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "audit_logs_pkey" PRIMARY KEY ("id")
);
CREATE INDEX "audit_logs_created_at_idx" ON "audit_logs"("created_at");
ALTER TABLE "audit_logs"
ADD CONSTRAINT "audit_logs_actor_id_fkey"
FOREIGN KEY ("actor_id") REFERENCES "users"("id")
ON DELETE SET NULL ON UPDATE CASCADE;