-- PIRP vollständiges Schema (inkl. Journal-Modul) -- Reihenfolge beachten wegen Foreign Keys DROP TABLE IF EXISTS journal_monthly_summary_values CASCADE; DROP TABLE IF EXISTS journal_monthly_summary CASCADE; DROP TABLE IF EXISTS journal_entry_accounts CASCADE; DROP TABLE IF EXISTS journal_entries CASCADE; DROP TABLE IF EXISTS journal_summary_items CASCADE; DROP TABLE IF EXISTS journal_deduction_categories CASCADE; DROP TABLE IF EXISTS journal_expense_categories CASCADE; DROP TABLE IF EXISTS journal_revenue_categories CASCADE; DROP TABLE IF EXISTS journal_suppliers CASCADE; DROP TABLE IF EXISTS journal_years CASCADE; DROP TABLE IF EXISTS recurring_log CASCADE; DROP TABLE IF EXISTS recurring_template_items CASCADE; DROP TABLE IF EXISTS recurring_templates CASCADE; DROP TABLE IF EXISTS invoice_items CASCADE; DROP TABLE IF EXISTS invoices CASCADE; DROP TABLE IF EXISTS expenses CASCADE; DROP TABLE IF EXISTS customers CASCADE; DROP TABLE IF EXISTS settings CASCADE; DROP TABLE IF EXISTS users CASCADE; -- ============================================================ -- Basis-Tabellen -- ============================================================ CREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, password_hash TEXT NOT NULL, created_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE settings ( id SERIAL PRIMARY KEY, company_name TEXT, company_address TEXT, company_zip TEXT, company_city TEXT, company_country TEXT, tax_id TEXT, vat_mode VARCHAR(10) DEFAULT 'klein', default_vat_rate NUMERIC(5,2) DEFAULT 19.00, payment_terms TEXT, footer_text TEXT, logo_path TEXT, iban TEXT, phone TEXT, email TEXT, website TEXT ); CREATE TABLE customers ( id SERIAL PRIMARY KEY, customer_number VARCHAR(20) UNIQUE, name TEXT NOT NULL, address TEXT, zip TEXT, city TEXT, country TEXT, created_at TIMESTAMPTZ DEFAULT now() ); -- ============================================================ -- Rechnungen -- ============================================================ CREATE TABLE invoices ( id SERIAL PRIMARY KEY, invoice_number VARCHAR(50) UNIQUE NOT NULL, customer_id INTEGER NOT NULL REFERENCES customers(id) ON DELETE RESTRICT, invoice_date DATE NOT NULL, service_date DATE, vat_mode VARCHAR(10) NOT NULL DEFAULT 'klein', vat_rate NUMERIC(5,2) NOT NULL DEFAULT 19.00, payment_terms TEXT, notes_internal TEXT, total_net NUMERIC(12,2) NOT NULL DEFAULT 0, total_vat NUMERIC(12,2) NOT NULL DEFAULT 0, total_gross NUMERIC(12,2) NOT NULL DEFAULT 0, paid BOOLEAN NOT NULL DEFAULT FALSE, payment_date DATE, pdf_path TEXT, pdf_hash VARCHAR(64), pdf_generated_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE invoice_items ( id SERIAL PRIMARY KEY, invoice_id INTEGER NOT NULL REFERENCES invoices(id) ON DELETE CASCADE, position_no INTEGER NOT NULL, description TEXT NOT NULL, quantity NUMERIC(12,2) NOT NULL DEFAULT 1, unit_price NUMERIC(12,2) NOT NULL DEFAULT 0, vat_rate NUMERIC(5,2) NOT NULL DEFAULT 19.00 ); -- ============================================================ -- Ausgaben -- ============================================================ CREATE TABLE expenses ( id SERIAL PRIMARY KEY, expense_date DATE NOT NULL, description TEXT NOT NULL, category TEXT, amount NUMERIC(12,2) NOT NULL, vat_rate NUMERIC(5,2) DEFAULT 0, total_net NUMERIC(12,2), total_vat NUMERIC(12,2) DEFAULT 0, expense_category_id INTEGER, paid BOOLEAN NOT NULL DEFAULT TRUE, payment_date DATE, attachment_path TEXT, created_at TIMESTAMPTZ DEFAULT now() ); -- ============================================================ -- Wiederkehrende Rechnungen (Abo-Rechnungen) -- ============================================================ CREATE TABLE recurring_templates ( id SERIAL PRIMARY KEY, template_name VARCHAR(100) NOT NULL, customer_id INTEGER NOT NULL REFERENCES customers(id) ON DELETE RESTRICT, interval_type VARCHAR(20) NOT NULL CHECK (interval_type IN ('monthly', 'quarterly', 'yearly')), start_date DATE NOT NULL, end_date DATE, next_due_date DATE NOT NULL, vat_mode VARCHAR(10) NOT NULL DEFAULT 'klein', vat_rate NUMERIC(5,2) NOT NULL DEFAULT 19.00, is_active BOOLEAN NOT NULL DEFAULT TRUE, notes_internal TEXT, created_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE recurring_template_items ( id SERIAL PRIMARY KEY, template_id INTEGER NOT NULL REFERENCES recurring_templates(id) ON DELETE CASCADE, position_no INTEGER NOT NULL, description TEXT NOT NULL, quantity NUMERIC(12,2) NOT NULL DEFAULT 1, unit_price NUMERIC(12,2) NOT NULL DEFAULT 0 ); CREATE TABLE recurring_log ( id SERIAL PRIMARY KEY, template_id INTEGER NOT NULL REFERENCES recurring_templates(id) ON DELETE CASCADE, invoice_id INTEGER REFERENCES invoices(id) ON DELETE SET NULL, generated_at TIMESTAMPTZ DEFAULT now(), due_date DATE NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'generated' ); -- ============================================================ -- Journal-Modul: Doppelte Buchführung -- ============================================================ CREATE TABLE journal_years ( id SERIAL PRIMARY KEY, year INTEGER NOT NULL UNIQUE, is_closed BOOLEAN NOT NULL DEFAULT FALSE, notes TEXT, created_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE journal_suppliers ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, sort_order INTEGER NOT NULL DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT TRUE ); CREATE TABLE journal_revenue_categories ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, category_type VARCHAR(20) NOT NULL CHECK (category_type IN ('wareneingang', 'erloese')), vat_rate NUMERIC(5,2) NOT NULL DEFAULT 19.00, sort_order INTEGER NOT NULL DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT TRUE ); CREATE TABLE journal_expense_categories ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, side VARCHAR(10) NOT NULL DEFAULT 'soll' CHECK (side IN ('soll', 'soll_haben')), sort_order INTEGER NOT NULL DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT TRUE ); CREATE TABLE journal_deduction_categories ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, sort_order INTEGER NOT NULL DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT TRUE ); CREATE TABLE journal_entries ( id SERIAL PRIMARY KEY, year_id INTEGER NOT NULL REFERENCES journal_years(id) ON DELETE RESTRICT, entry_date DATE NOT NULL, month INTEGER NOT NULL CHECK (month BETWEEN 1 AND 12), description TEXT NOT NULL, attachment_note TEXT, amount NUMERIC(12,2) NOT NULL DEFAULT 0, supplier_id INTEGER REFERENCES journal_suppliers(id) ON DELETE SET NULL, invoice_id INTEGER REFERENCES invoices(id) ON DELETE SET NULL, expense_id INTEGER REFERENCES expenses(id) ON DELETE SET NULL, source_type VARCHAR(20) DEFAULT 'manual', sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE journal_entry_accounts ( id SERIAL PRIMARY KEY, entry_id INTEGER NOT NULL REFERENCES journal_entries(id) ON DELETE CASCADE, account_type VARCHAR(20) NOT NULL, side VARCHAR(5) NOT NULL CHECK (side IN ('soll', 'haben')), amount NUMERIC(12,2) NOT NULL DEFAULT 0, revenue_category_id INTEGER REFERENCES journal_revenue_categories(id) ON DELETE SET NULL, expense_category_id INTEGER REFERENCES journal_expense_categories(id) ON DELETE SET NULL, note TEXT ); CREATE TABLE journal_monthly_summary ( id SERIAL PRIMARY KEY, year_id INTEGER NOT NULL REFERENCES journal_years(id) ON DELETE CASCADE, month INTEGER NOT NULL CHECK (month BETWEEN 1 AND 12), manual_corrections JSONB DEFAULT '{}', notes TEXT, UNIQUE(year_id, month) ); CREATE TABLE journal_summary_items ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, sort_order INTEGER NOT NULL DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT TRUE ); CREATE TABLE journal_monthly_summary_values ( id SERIAL PRIMARY KEY, year_id INTEGER NOT NULL REFERENCES journal_years(id) ON DELETE CASCADE, month INTEGER NOT NULL CHECK (month BETWEEN 1 AND 12), summary_item_id INTEGER NOT NULL REFERENCES journal_summary_items(id) ON DELETE CASCADE, amount NUMERIC(12,2) NOT NULL DEFAULT 0, UNIQUE(year_id, month, summary_item_id) ); -- Foreign Key für expenses -> journal_expense_categories (nachträglich, da Tabelle erst jetzt existiert) ALTER TABLE expenses ADD CONSTRAINT fk_expenses_category FOREIGN KEY (expense_category_id) REFERENCES journal_expense_categories(id) ON DELETE SET NULL; -- ============================================================ -- Indizes -- ============================================================ CREATE INDEX idx_journal_entries_year_month ON journal_entries(year_id, month); CREATE INDEX idx_journal_entries_date ON journal_entries(entry_date); CREATE INDEX idx_journal_entries_invoice ON journal_entries(invoice_id); CREATE INDEX idx_journal_entries_expense ON journal_entries(expense_id); CREATE INDEX idx_journal_entries_source ON journal_entries(source_type); -- Unique-Constraints: max. 1 Journal-Eintrag pro Rechnung/Ausgabe (verhindert Doppelbuchungen) CREATE UNIQUE INDEX idx_unique_journal_invoice ON journal_entries(invoice_id) WHERE invoice_id IS NOT NULL; CREATE UNIQUE INDEX idx_unique_journal_expense ON journal_entries(expense_id) WHERE expense_id IS NOT NULL; CREATE INDEX idx_journal_entry_accounts_entry ON journal_entry_accounts(entry_id); CREATE INDEX idx_journal_entry_accounts_type ON journal_entry_accounts(account_type);