diff --git a/db/migrations/002_partnerships.sql b/db/migrations/002_partnerships.sql new file mode 100644 index 0000000..899e4d3 --- /dev/null +++ b/db/migrations/002_partnerships.sql @@ -0,0 +1,198 @@ +-- SeCu Partnerships (Subunternehmer-System) +-- Version: 1.1.0 + +-- Partnerschaften zwischen Organisationen +CREATE TABLE IF NOT EXISTS org_partnerships ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + + -- Hauptunternehmer (der Aufträge vergibt) + contractor_org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, + + -- Subunternehmer (der Mitarbeiter stellt) + subcontractor_org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, + + -- Status der Partnerschaft + status VARCHAR(20) NOT NULL DEFAULT 'pending' + CHECK (status IN ('pending', 'active', 'paused', 'terminated')), + + -- Vertragsdetails + contract_start DATE, + contract_end DATE, + notes TEXT, + + -- Wer hat eingeladen + invited_by UUID REFERENCES users(id), + accepted_by UUID REFERENCES users(id), + accepted_at TIMESTAMP WITH TIME ZONE, + + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Ein Subunternehmer kann nur einmal pro Hauptunternehmer existieren + UNIQUE(contractor_org_id, subcontractor_org_id), + + -- Keine Selbst-Partnerschaft + CHECK (contractor_org_id != subcontractor_org_id) +); + +-- Stundensätze und Konditionen pro Partnerschaft +CREATE TABLE IF NOT EXISTS partnership_rates ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + partnership_id UUID NOT NULL REFERENCES org_partnerships(id) ON DELETE CASCADE, + + -- Art des Satzes + rate_type VARCHAR(50) NOT NULL DEFAULT 'hourly', -- hourly, daily, fixed, per_order + + -- Bezeichnung (z.B. "Normaldienst", "Nachtdienst", "Wochenende") + name VARCHAR(100) NOT NULL, + description TEXT, + + -- Betrag in Cent (für Genauigkeit) + amount_cents INTEGER NOT NULL, + currency VARCHAR(3) DEFAULT 'EUR', + + -- Gültigkeit + valid_from DATE DEFAULT CURRENT_DATE, + valid_until DATE, + + -- Standard-Satz? + is_default BOOLEAN DEFAULT false, + + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Geteilte Aufträge (Hauptunternehmer -> Subunternehmer) +CREATE TABLE IF NOT EXISTS shared_orders ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + partnership_id UUID NOT NULL REFERENCES org_partnerships(id) ON DELETE CASCADE, + + -- Original-Auftrag beim Hauptunternehmer + original_order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE, + + -- Kopie/Referenz beim Subunternehmer (optional, wenn sie eigene Aufträge anlegen) + sub_order_id UUID REFERENCES orders(id) ON DELETE SET NULL, + + -- Welcher Satz gilt für diesen Auftrag + rate_id UUID REFERENCES partnership_rates(id), + + -- Anzahl benötigter Mitarbeiter vom Subunternehmer + required_staff INTEGER DEFAULT 1, + + -- Status + status VARCHAR(20) NOT NULL DEFAULT 'requested' + CHECK (status IN ('requested', 'accepted', 'declined', 'completed', 'cancelled')), + + -- Notizen zwischen den Unternehmen + contractor_notes TEXT, + subcontractor_notes TEXT, + + responded_at TIMESTAMP WITH TIME ZONE, + completed_at TIMESTAMP WITH TIME ZONE, + + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Stundenzettel von Subunternehmer-Mitarbeitern für geteilte Aufträge +CREATE TABLE IF NOT EXISTS partnership_timesheets ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + shared_order_id UUID NOT NULL REFERENCES shared_orders(id) ON DELETE CASCADE, + + -- Original-Stundenzettel beim Subunternehmer + timesheet_id UUID NOT NULL REFERENCES timesheets(id) ON DELETE CASCADE, + + -- Welcher Satz wurde angewendet + rate_id UUID REFERENCES partnership_rates(id), + + -- Berechneter Betrag + calculated_amount_cents INTEGER, + + -- Status der Freigabe durch Hauptunternehmer + approval_status VARCHAR(20) DEFAULT 'pending' + CHECK (approval_status IN ('pending', 'approved', 'disputed', 'rejected')), + + approved_by UUID REFERENCES users(id), + approved_at TIMESTAMP WITH TIME ZONE, + dispute_reason TEXT, + + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Abrechnungen zwischen Partnern +CREATE TABLE IF NOT EXISTS partnership_invoices ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + partnership_id UUID NOT NULL REFERENCES org_partnerships(id) ON DELETE CASCADE, + + -- Rechnungsnummer + invoice_number VARCHAR(50) NOT NULL, + + -- Abrechnungszeitraum + period_start DATE NOT NULL, + period_end DATE NOT NULL, + + -- Beträge + subtotal_cents INTEGER NOT NULL DEFAULT 0, + tax_percent DECIMAL(5,2) DEFAULT 19.00, + tax_cents INTEGER NOT NULL DEFAULT 0, + total_cents INTEGER NOT NULL DEFAULT 0, + + -- Status + status VARCHAR(20) NOT NULL DEFAULT 'draft' + CHECK (status IN ('draft', 'sent', 'paid', 'overdue', 'cancelled')), + + -- Daten + issued_at TIMESTAMP WITH TIME ZONE, + due_date DATE, + paid_at TIMESTAMP WITH TIME ZONE, + + -- PDF-Link (wenn generiert) + pdf_url TEXT, + + -- Notizen + notes TEXT, + + created_by UUID REFERENCES users(id), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Rechnungspositionen +CREATE TABLE IF NOT EXISTS partnership_invoice_items ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + invoice_id UUID NOT NULL REFERENCES partnership_invoices(id) ON DELETE CASCADE, + + -- Referenz zum Stundenzettel (optional) + partnership_timesheet_id UUID REFERENCES partnership_timesheets(id), + + -- Beschreibung + description TEXT NOT NULL, + + -- Mengen und Preise + quantity DECIMAL(10,2) NOT NULL DEFAULT 1, + unit VARCHAR(20) DEFAULT 'Stunden', + unit_price_cents INTEGER NOT NULL, + total_cents INTEGER NOT NULL, + + -- Datum der Leistung + service_date DATE, + + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Indexes +CREATE INDEX IF NOT EXISTS idx_partnerships_contractor ON org_partnerships(contractor_org_id); +CREATE INDEX IF NOT EXISTS idx_partnerships_subcontractor ON org_partnerships(subcontractor_org_id); +CREATE INDEX IF NOT EXISTS idx_partnerships_status ON org_partnerships(status); +CREATE INDEX IF NOT EXISTS idx_shared_orders_partnership ON shared_orders(partnership_id); +CREATE INDEX IF NOT EXISTS idx_shared_orders_original ON shared_orders(original_order_id); +CREATE INDEX IF NOT EXISTS idx_partnership_timesheets_shared ON partnership_timesheets(shared_order_id); +CREATE INDEX IF NOT EXISTS idx_partnership_invoices_partnership ON partnership_invoices(partnership_id); +CREATE INDEX IF NOT EXISTS idx_partnership_invoices_status ON partnership_invoices(status); + +-- Partnership-Modul registrieren +INSERT INTO modules (name, display_name, description, is_core, default_config) VALUES + ('partnerships', 'Subunternehmer', 'Zusammenarbeit mit Subunternehmen inkl. Abrechnung', false, + '{"auto_approve_timesheets": false, "default_payment_days": 30}') +ON CONFLICT (name) DO NOTHING;