refactor: clean up codebase, remove dead code and obsolete docs

Remove influencer-era documentation, unused components, boilerplate
assets, and ghost tiktok cron job. Add supplier invoice management,
document API routes, and PWA icons. Replace boilerplate README.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-02-20 12:23:17 +01:00
co-authored by Claude Opus 4.6
parent e8743e6e03
commit 838dc6b8b5
113 changed files with 6289 additions and 19036 deletions
@@ -260,10 +260,14 @@ begin
insert into public.chart_of_accounts (user_id, account_number, account_name, account_class, account_group, account_type, normal_balance, plan_type, is_system_account, sru_code)
values
(p_user_id, '2440', 'Leverantorsskulder', 2, '24', 'liability', 'credit', 'k1', true, '7230'),
(p_user_id, '2610', 'Utgaende moms 25%', 2, '26', 'liability', 'credit', 'k1', true, '7231'),
(p_user_id, '2611', 'Utgaende moms 12%', 2, '26', 'liability', 'credit', 'k1', true, '7231'),
(p_user_id, '2612', 'Utgaende moms 6%', 2, '26', 'liability', 'credit', 'k1', true, '7231'),
(p_user_id, '2611', 'Utgaende moms forsaljning 25%', 2, '26', 'liability', 'credit', 'k1', true, '7231'),
(p_user_id, '2621', 'Utgaende moms forsaljning 12%', 2, '26', 'liability', 'credit', 'k1', true, '7231'),
(p_user_id, '2631', 'Utgaende moms forsaljning 6%', 2, '26', 'liability', 'credit', 'k1', true, '7231'),
(p_user_id, '2614', 'Utg moms omvand skattskyldighet 25%', 2, '26', 'liability', 'credit', 'k1', true, '7231'),
(p_user_id, '2624', 'Utg moms omvand skattskyldighet 12%', 2, '26', 'liability', 'credit', 'k1', true, '7231'),
(p_user_id, '2634', 'Utg moms omvand skattskyldighet 6%', 2, '26', 'liability', 'credit', 'k1', true, '7231'),
(p_user_id, '2641', 'Debiterad ingaende moms', 2, '26', 'liability', 'credit', 'k1', true, '7231'),
(p_user_id, '2645', 'Beraknad ingaende moms forvarv utlandet', 2, '26', 'liability', 'credit', 'k1', true, '7231'),
(p_user_id, '2650', 'Redovisningskonto for moms', 2, '26', 'liability', 'credit', 'k1', true, '7231'),
(p_user_id, '2710', 'Personalskatt', 2, '27', 'liability', 'credit', 'k1', true, '7231'),
(p_user_id, '2731', 'Avrakning socialavgifter', 2, '27', 'liability', 'credit', 'k1', true, '7231');
@@ -282,7 +286,10 @@ begin
values
(p_user_id, '3001', 'Forsaljning tjanster 25%', 3, '30', 'revenue', 'credit', 'k1', true, '7310'),
(p_user_id, '3002', 'Forsaljning varor 25%', 3, '30', 'revenue', 'credit', 'k1', true, '7310'),
(p_user_id, '3003', 'Forsaljning tjanster 6%', 3, '30', 'revenue', 'credit', 'k1', true, '7310'),
(p_user_id, '3100', 'Momsfri forsaljning', 3, '31', 'revenue', 'credit', 'k1', true, '7311'),
(p_user_id, '3305', 'Forsaljning tjanst export', 3, '33', 'revenue', 'credit', 'k1', true, '7310'),
(p_user_id, '3308', 'Forsaljning tjanst EU', 3, '33', 'revenue', 'credit', 'k1', true, '7310'),
(p_user_id, '3900', 'Ovriga rorelseintakter', 3, '39', 'revenue', 'credit', 'k1', true, '7311'),
(p_user_id, '3960', 'Valutakursvinster', 3, '39', 'revenue', 'credit', 'k1', true, '7310');
@@ -0,0 +1,163 @@
-- Migration 23: Document Version Chain Hardening
-- Adds cryptographic hash chain + atomic versioning via RPC
-- Resolves: Gap 4 (no hash-chain) + Gap 5 (race condition in versioning)
-- =============================================================================
-- 1. New columns on document_attachments
-- =============================================================================
ALTER TABLE public.document_attachments
ADD COLUMN IF NOT EXISTS prev_version_hash text,
ADD COLUMN IF NOT EXISTS last_integrity_check_at timestamptz;
COMMENT ON COLUMN public.document_attachments.prev_version_hash IS
'SHA-256 hash of the previous version. NULL for version 1 (genesis). Creates tamper-evident chain.';
COMMENT ON COLUMN public.document_attachments.last_integrity_check_at IS
'Timestamp of the last batch integrity verification (set by cron job).';
-- Index for cron job: prioritize documents never checked or least recently checked
CREATE INDEX IF NOT EXISTS idx_doc_attachments_integrity_check
ON public.document_attachments (last_integrity_check_at NULLS FIRST)
WHERE is_current_version = true;
-- =============================================================================
-- 2. RPC: create_document_version (atomic versioning with row-level lock)
-- Pattern follows next_voucher_number() from migration 016
-- =============================================================================
CREATE OR REPLACE FUNCTION public.create_document_version(
p_user_id uuid,
p_original_doc_id uuid,
p_storage_path text,
p_file_name text,
p_file_size_bytes bigint,
p_mime_type text,
p_sha256_hash text,
p_upload_source text DEFAULT NULL
)
RETURNS uuid
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
v_current RECORD;
v_root_original_id uuid;
v_new_version integer;
v_new_id uuid;
BEGIN
-- Lock the current version row (prevents concurrent versioning)
SELECT id, version, sha256_hash, original_id, journal_entry_id,
journal_entry_line_id, upload_source
INTO v_current
FROM public.document_attachments
WHERE id = p_original_doc_id
AND user_id = p_user_id
AND is_current_version = true
FOR UPDATE;
IF NOT FOUND THEN
RAISE EXCEPTION 'Document not found, not owned by user, or not the current version';
END IF;
v_root_original_id := COALESCE(v_current.original_id, v_current.id);
v_new_version := v_current.version + 1;
-- Insert new version with hash chain link
INSERT INTO public.document_attachments (
user_id,
storage_path,
file_name,
file_size_bytes,
mime_type,
sha256_hash,
version,
original_id,
is_current_version,
uploaded_by,
upload_source,
digitization_date,
journal_entry_id,
journal_entry_line_id,
prev_version_hash
) VALUES (
p_user_id,
p_storage_path,
p_file_name,
p_file_size_bytes,
p_mime_type,
p_sha256_hash,
v_new_version,
v_root_original_id,
true,
p_user_id,
COALESCE(p_upload_source, v_current.upload_source),
now(),
v_current.journal_entry_id,
v_current.journal_entry_line_id,
v_current.sha256_hash -- cryptographic link to previous version
)
RETURNING id INTO v_new_id;
-- Mark old version as superseded (within same transaction = atomic)
UPDATE public.document_attachments
SET is_current_version = false,
superseded_by_id = v_new_id
WHERE id = v_current.id;
RETURN v_new_id;
END;
$$;
GRANT EXECUTE ON FUNCTION public.create_document_version(uuid, uuid, text, text, bigint, text, text, text) TO authenticated;
-- =============================================================================
-- 3. RPC: validate_version_chain
-- Verifies that prev_version_hash matches sha256_hash of the preceding version
-- =============================================================================
CREATE OR REPLACE FUNCTION public.validate_version_chain(
p_user_id uuid,
p_original_doc_id uuid
)
RETURNS TABLE (
doc_id uuid,
version integer,
sha256_hash text,
prev_version_hash text,
expected_prev_hash text,
chain_valid boolean
)
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
RETURN QUERY
WITH chain AS (
SELECT
da.id,
da.version,
da.sha256_hash AS current_hash,
da.prev_version_hash AS stored_prev_hash,
LAG(da.sha256_hash) OVER (ORDER BY da.version) AS computed_prev_hash
FROM public.document_attachments da
WHERE da.user_id = p_user_id
AND (da.id = p_original_doc_id OR da.original_id = p_original_doc_id)
ORDER BY da.version
)
SELECT
chain.id AS doc_id,
chain.version,
chain.current_hash AS sha256_hash,
chain.stored_prev_hash AS prev_version_hash,
chain.computed_prev_hash AS expected_prev_hash,
CASE
WHEN chain.version = 1 THEN (chain.stored_prev_hash IS NULL)
ELSE (chain.stored_prev_hash IS NOT NULL AND chain.stored_prev_hash = chain.computed_prev_hash)
END AS chain_valid
FROM chain
ORDER BY chain.version;
END;
$$;
GRANT EXECUTE ON FUNCTION public.validate_version_chain(uuid, uuid) TO authenticated;
@@ -0,0 +1,57 @@
-- Migration 24: Storage Bucket Policies for WORM Document Archive
-- Resolves: Gap 3 (no immutability on Storage level)
--
-- Creates a private 'documents' bucket with:
-- - INSERT policy (authenticated users upload to their own folder)
-- - SELECT policy (authenticated users read their own documents)
-- - NO UPDATE policy (prevents overwriting files — WORM)
-- - NO DELETE policy (prevents client-side deletion — WORM)
--
-- Service role (cron, admin) bypasses RLS and can still access everything.
-- =============================================================================
-- 1. Create the 'documents' bucket (private, 50MB file limit)
-- =============================================================================
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
VALUES (
'documents',
'documents',
false,
52428800, -- 50 MB
NULL -- Allow all MIME types (PDFs, images, etc.)
)
ON CONFLICT (id) DO NOTHING;
-- =============================================================================
-- 2. INSERT policy: Authenticated users can upload to their own folder
-- =============================================================================
CREATE POLICY "documents_insert_own"
ON storage.objects
FOR INSERT
TO authenticated
WITH CHECK (
bucket_id = 'documents'
AND (storage.foldername(name))[1] = 'documents'
AND (storage.foldername(name))[2] = auth.uid()::text
);
-- =============================================================================
-- 3. SELECT policy: Authenticated users can read their own documents
-- =============================================================================
CREATE POLICY "documents_select_own"
ON storage.objects
FOR SELECT
TO authenticated
USING (
bucket_id = 'documents'
AND (storage.foldername(name))[1] = 'documents'
AND (storage.foldername(name))[2] = auth.uid()::text
);
-- =============================================================================
-- No UPDATE or DELETE policies — WORM compliance
-- Service role bypasses RLS for admin/cron access
-- =============================================================================
@@ -0,0 +1,342 @@
-- Migration 25: Leverantorsreskontra (Accounts Payable)
-- Suppliers, supplier invoices, line items, partial payments
-- BFL 5:6-9 verifikationskrav, ML 17:24 inkommande fakturor
-- =============================================================================
-- 1. suppliers table
-- =============================================================================
CREATE TABLE IF NOT EXISTS public.suppliers (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
-- Basic info
name text NOT NULL,
supplier_type text NOT NULL DEFAULT 'swedish_business'
CHECK (supplier_type IN ('swedish_business', 'eu_business', 'non_eu_business')),
org_number text,
vat_number text,
-- Contact
email text,
phone text,
-- Address
address_line1 text,
address_line2 text,
postal_code text,
city text,
country text NOT NULL DEFAULT 'SE',
-- Payment details
bankgiro text,
plusgiro text,
bank_account text,
iban text,
bic text,
-- Defaults
default_expense_account text, -- e.g. '5010'
default_payment_terms integer NOT NULL DEFAULT 30,
default_currency text NOT NULL DEFAULT 'SEK',
-- Notes
notes text,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
ALTER TABLE public.suppliers ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own suppliers"
ON public.suppliers FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own suppliers"
ON public.suppliers FOR INSERT
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own suppliers"
ON public.suppliers FOR UPDATE
USING (auth.uid() = user_id);
CREATE POLICY "Users can delete own suppliers"
ON public.suppliers FOR DELETE
USING (auth.uid() = user_id);
CREATE INDEX idx_suppliers_user_id ON public.suppliers (user_id);
CREATE INDEX idx_suppliers_name ON public.suppliers (user_id, name);
-- =============================================================================
-- 2. supplier_invoices table
-- =============================================================================
CREATE TABLE IF NOT EXISTS public.supplier_invoices (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
supplier_id uuid NOT NULL REFERENCES public.suppliers(id) ON DELETE RESTRICT,
-- Ankomstnummer (BFL running number for incoming invoices)
arrival_number integer NOT NULL,
-- Supplier's reference
supplier_invoice_number text NOT NULL,
-- Dates (ML 17:24 requirements)
invoice_date date NOT NULL,
due_date date NOT NULL,
received_date date NOT NULL DEFAULT CURRENT_DATE,
delivery_date date, -- ML krav
-- Status
status text NOT NULL DEFAULT 'registered'
CHECK (status IN ('registered', 'approved', 'paid', 'partially_paid', 'overdue', 'disputed', 'credited')),
-- Currency
currency text NOT NULL DEFAULT 'SEK',
exchange_rate numeric,
exchange_rate_date date,
-- Amounts
subtotal numeric NOT NULL DEFAULT 0,
subtotal_sek numeric,
vat_amount numeric NOT NULL DEFAULT 0,
vat_amount_sek numeric,
total numeric NOT NULL DEFAULT 0,
total_sek numeric,
-- VAT
vat_treatment text NOT NULL DEFAULT 'standard_25',
reverse_charge boolean NOT NULL DEFAULT false,
-- Payment
payment_reference text, -- OCR-nummer
paid_at timestamptz,
paid_amount numeric NOT NULL DEFAULT 0,
remaining_amount numeric NOT NULL DEFAULT 0,
-- Credit note
is_credit_note boolean NOT NULL DEFAULT false,
credited_invoice_id uuid REFERENCES public.supplier_invoices(id) ON DELETE SET NULL,
-- Bookkeeping links
registration_journal_entry_id uuid REFERENCES public.journal_entries(id) ON DELETE SET NULL,
payment_journal_entry_id uuid REFERENCES public.journal_entries(id) ON DELETE SET NULL,
-- Transaction link
transaction_id uuid REFERENCES public.transactions(id) ON DELETE SET NULL,
-- Document (verifikationsunderlag)
document_id uuid REFERENCES public.document_attachments(id) ON DELETE SET NULL,
-- Notes
notes text,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
-- Constraints
CONSTRAINT uq_supplier_invoices_arrival UNIQUE (user_id, arrival_number),
CONSTRAINT uq_supplier_invoices_ref UNIQUE (user_id, supplier_id, supplier_invoice_number)
);
ALTER TABLE public.supplier_invoices ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own supplier invoices"
ON public.supplier_invoices FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own supplier invoices"
ON public.supplier_invoices FOR INSERT
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own supplier invoices"
ON public.supplier_invoices FOR UPDATE
USING (auth.uid() = user_id);
CREATE POLICY "Users can delete own supplier invoices"
ON public.supplier_invoices FOR DELETE
USING (auth.uid() = user_id);
CREATE INDEX idx_supplier_invoices_user_id ON public.supplier_invoices (user_id);
CREATE INDEX idx_supplier_invoices_supplier ON public.supplier_invoices (supplier_id);
CREATE INDEX idx_supplier_invoices_status ON public.supplier_invoices (user_id, status);
CREATE INDEX idx_supplier_invoices_due_date ON public.supplier_invoices (user_id, due_date);
-- =============================================================================
-- 3. Per-user arrival number generation (ankomstnummer)
-- =============================================================================
CREATE OR REPLACE FUNCTION public.get_next_arrival_number(p_user_id uuid)
RETURNS integer AS $$
DECLARE
next_num integer;
BEGIN
SELECT COALESCE(MAX(arrival_number), 0) + 1 INTO next_num
FROM public.supplier_invoices
WHERE user_id = p_user_id;
RETURN next_num;
END;
$$ LANGUAGE plpgsql;
-- =============================================================================
-- 4. supplier_invoice_items table
-- =============================================================================
CREATE TABLE IF NOT EXISTS public.supplier_invoice_items (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
supplier_invoice_id uuid NOT NULL REFERENCES public.supplier_invoices(id) ON DELETE CASCADE,
sort_order integer NOT NULL DEFAULT 0,
description text NOT NULL,
quantity numeric NOT NULL DEFAULT 1,
unit text NOT NULL DEFAULT 'st',
unit_price numeric NOT NULL DEFAULT 0, -- exkl. moms (ML krav)
line_total numeric NOT NULL DEFAULT 0,
-- Bookkeeping
account_number text NOT NULL, -- BAS expense account (4xxx-6xxx)
vat_code text,
vat_rate numeric NOT NULL DEFAULT 0.25,
vat_amount numeric NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now()
);
ALTER TABLE public.supplier_invoice_items ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own supplier invoice items"
ON public.supplier_invoice_items FOR SELECT
USING (
EXISTS (
SELECT 1 FROM public.supplier_invoices si
WHERE si.id = supplier_invoice_id AND si.user_id = auth.uid()
)
);
CREATE POLICY "Users can insert own supplier invoice items"
ON public.supplier_invoice_items FOR INSERT
WITH CHECK (
EXISTS (
SELECT 1 FROM public.supplier_invoices si
WHERE si.id = supplier_invoice_id AND si.user_id = auth.uid()
)
);
CREATE POLICY "Users can update own supplier invoice items"
ON public.supplier_invoice_items FOR UPDATE
USING (
EXISTS (
SELECT 1 FROM public.supplier_invoices si
WHERE si.id = supplier_invoice_id AND si.user_id = auth.uid()
)
);
CREATE POLICY "Users can delete own supplier invoice items"
ON public.supplier_invoice_items FOR DELETE
USING (
EXISTS (
SELECT 1 FROM public.supplier_invoices si
WHERE si.id = supplier_invoice_id AND si.user_id = auth.uid()
)
);
CREATE INDEX idx_supplier_invoice_items_invoice ON public.supplier_invoice_items (supplier_invoice_id);
-- =============================================================================
-- 5. supplier_invoice_payments table (partial payments)
-- =============================================================================
CREATE TABLE IF NOT EXISTS public.supplier_invoice_payments (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
supplier_invoice_id uuid NOT NULL REFERENCES public.supplier_invoices(id) ON DELETE CASCADE,
payment_date date NOT NULL,
amount numeric NOT NULL,
currency text NOT NULL DEFAULT 'SEK',
exchange_rate numeric,
exchange_rate_difference numeric DEFAULT 0, -- kursdifferens
journal_entry_id uuid REFERENCES public.journal_entries(id) ON DELETE SET NULL,
transaction_id uuid REFERENCES public.transactions(id) ON DELETE SET NULL,
notes text,
created_at timestamptz NOT NULL DEFAULT now()
);
ALTER TABLE public.supplier_invoice_payments ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own supplier invoice payments"
ON public.supplier_invoice_payments FOR SELECT
USING (
EXISTS (
SELECT 1 FROM public.supplier_invoices si
WHERE si.id = supplier_invoice_id AND si.user_id = auth.uid()
)
);
CREATE POLICY "Users can insert own supplier invoice payments"
ON public.supplier_invoice_payments FOR INSERT
WITH CHECK (
EXISTS (
SELECT 1 FROM public.supplier_invoices si
WHERE si.id = supplier_invoice_id AND si.user_id = auth.uid()
)
);
CREATE INDEX idx_supplier_invoice_payments_invoice ON public.supplier_invoice_payments (supplier_invoice_id);
-- =============================================================================
-- 6. Add supplier_invoice_id to transactions
-- =============================================================================
ALTER TABLE public.transactions
ADD COLUMN IF NOT EXISTS supplier_invoice_id uuid REFERENCES public.supplier_invoices(id) ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS idx_transactions_supplier_invoice ON public.transactions (supplier_invoice_id);
-- =============================================================================
-- 7. Expand journal_entries source_type constraint
-- =============================================================================
ALTER TABLE public.journal_entries
DROP CONSTRAINT IF EXISTS journal_entries_source_type_check;
ALTER TABLE public.journal_entries
ADD CONSTRAINT journal_entries_source_type_check
CHECK (source_type IN (
'manual', 'bank_transaction', 'invoice_created',
'invoice_paid', 'invoice_cash_payment', 'credit_note', 'salary_payment',
'opening_balance', 'year_end',
'storno', 'correction', 'import', 'system',
'supplier_invoice_registered', 'supplier_invoice_paid',
'supplier_invoice_cash_payment', 'supplier_credit_note'
));
-- =============================================================================
-- 8. Audit triggers for new tables
-- =============================================================================
CREATE TRIGGER audit_suppliers
AFTER INSERT OR UPDATE OR DELETE ON public.suppliers
FOR EACH ROW EXECUTE FUNCTION public.write_audit_log();
CREATE TRIGGER audit_supplier_invoices
AFTER INSERT OR UPDATE OR DELETE ON public.supplier_invoices
FOR EACH ROW EXECUTE FUNCTION public.write_audit_log();
CREATE TRIGGER audit_supplier_invoice_items
AFTER INSERT OR UPDATE OR DELETE ON public.supplier_invoice_items
FOR EACH ROW EXECUTE FUNCTION public.write_audit_log();
-- =============================================================================
-- 9. Updated_at triggers
-- =============================================================================
CREATE OR REPLACE FUNCTION public.update_updated_at_column()
RETURNS trigger AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER set_updated_at_suppliers
BEFORE UPDATE ON public.suppliers
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
CREATE TRIGGER set_updated_at_supplier_invoices
BEFORE UPDATE ON public.supplier_invoices
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
@@ -0,0 +1,20 @@
-- Drop 14 restaurant module tables — zero code references, zero rows.
-- These are industry-specific tables that don't belong in base ERP.
-- Children first
DROP TABLE IF EXISTS public.recipe_ingredients CASCADE;
DROP TABLE IF EXISTS public.supplier_order_items CASCADE;
DROP TABLE IF EXISTS public.menu_items CASCADE;
DROP TABLE IF EXISTS public.menu_categories CASCADE;
DROP TABLE IF EXISTS public.reservations CASCADE;
DROP TABLE IF EXISTS public.shifts CASCADE;
-- Parents / standalone
DROP TABLE IF EXISTS public.menus CASCADE;
DROP TABLE IF EXISTS public.recipes CASCADE;
DROP TABLE IF EXISTS public.ingredients CASCADE;
DROP TABLE IF EXISTS public.staff_members CASCADE;
DROP TABLE IF EXISTS public.supplier_orders CASCADE;
DROP TABLE IF EXISTS public.restaurant_tables CASCADE;
DROP TABLE IF EXISTS public.waste_entries CASCADE;
DROP TABLE IF EXISTS public.restaurant_capacity CASCADE;
@@ -0,0 +1,58 @@
-- Drop 36 unused tables — zero .from() calls, zero rows.
-- Grouped by module; children before parents.
-- HR / Payroll
DROP TABLE IF EXISTS public.salary_additions CASCADE;
DROP TABLE IF EXISTS public.salary_run_items CASCADE;
DROP TABLE IF EXISTS public.salary_payments CASCADE;
DROP TABLE IF EXISTS public.salary_runs CASCADE;
DROP TABLE IF EXISTS public.absence_records CASCADE;
DROP TABLE IF EXISTS public.mileage_entries CASCADE;
DROP TABLE IF EXISTS public.employees CASCADE;
-- Asset Management
DROP TABLE IF EXISTS public.depreciation_schedule CASCADE;
DROP TABLE IF EXISTS public.assets CASCADE;
DROP TABLE IF EXISTS public.asset_categories CASCADE;
-- Bank Reconciliation
DROP TABLE IF EXISTS public.bank_reconciliation_items CASCADE;
DROP TABLE IF EXISTS public.bank_reconciliation_sessions CASCADE;
DROP TABLE IF EXISTS public.account_balances CASCADE;
-- Budgeting / Forecasting
DROP TABLE IF EXISTS public.budget_entries CASCADE;
DROP TABLE IF EXISTS public.budgets CASCADE;
DROP TABLE IF EXISTS public.cash_flow_forecasts CASCADE;
-- Orders / Quotes
DROP TABLE IF EXISTS public.order_items CASCADE;
DROP TABLE IF EXISTS public.orders CASCADE;
DROP TABLE IF EXISTS public.quote_items CASCADE;
DROP TABLE IF EXISTS public.quotes CASCADE;
-- Analytics / KPI
DROP TABLE IF EXISTS public.ai_insights_cache CASCADE;
DROP TABLE IF EXISTS public.financial_insights CASCADE;
DROP TABLE IF EXISTS public.kpi_snapshots CASCADE;
-- Tax / Compliance
DROP TABLE IF EXISTS public.agi_declarations CASCADE;
DROP TABLE IF EXISTS public.annual_reports CASCADE;
DROP TABLE IF EXISTS public.tax_rates CASCADE;
DROP TABLE IF EXISTS public.year_end_closings CASCADE;
-- Supplier Payments
DROP TABLE IF EXISTS public.supplier_payment_items CASCADE;
DROP TABLE IF EXISTS public.supplier_payments CASCADE;
-- Module System
DROP TABLE IF EXISTS public.module_configs CASCADE;
DROP TABLE IF EXISTS public.module_imports CASCADE;
DROP TABLE IF EXISTS public.module_kpi_targets CASCADE;
-- Other
DROP TABLE IF EXISTS public.onboarding_checklist CASCADE;
DROP TABLE IF EXISTS public.payment_methods CASCADE;
DROP TABLE IF EXISTS public.recurring_invoices CASCADE;
DROP TABLE IF EXISTS public.supplier_invoice_attestations CASCADE;