# Data Models and Schema MariaDB, database `tilbudgivern`. Verified live against `SHOW TABLES` / `DESCRIBE` on 2026-09-04 — **122 tables/views**, far more than the tracked migration files account for (see "How schema actually gets created" below). This document groups them by domain rather than listing all 122 in full DDL. ## How schema actually gets created (read this before adding a migration) There is **no unified migration runner**. Schema comes from three uncoordinated sources: 1. **`backend/src/services/databaseService.js`** — contains ~37 inline `CREATE TABLE IF NOT EXISTS` statements that run automatically every time the server starts (`Database tables created/verified successfully` in the PM2 logs). This is the primary source for a large share of the schema. 2. **`database/migrations/`** — 11 standalone scripts (mix of `.sql` run manually via a MySQL client and `.js` scripts run with `node .js`), timestamp-prefixed, each independent. No tracking table of "which migrations ran." 3. **`backend/migrations/`** — a *second*, separate migrations folder with its own independent set of `.sql`/`.js` files. Nothing unifies this with `database/migrations/`. Plus `backend/sql/customer_project_system.sql`, a standalone schema file. When adding a table, check whether `databaseService.js` should own it (if the app must always have it present) or whether a one-off migration script is more appropriate — don't assume there's a single place new schema belongs. ## Table inventory by domain **Smart packages** (the app's core reusable-package system) — `smart_packages`, `package_tasks`, `package_materials`, `smart_package_categories`, `smart_package_components`, `smart_package_materials`, `smart_package_steps`, `smart_package_tasks`, `smart_package_types`, `smart_package_usage`, `custom_packages`, `custom_installation_tasks`. **A second, older/parallel package system** also exists: `material_packages` (with its own Excel-import provenance columns, `is_template`, `validation_status`, etc.) plus `project_packages`. It's not clearly deprecated in code — treat both systems as live until proven otherwise, and check which one a given feature actually reads before assuming "the" packages table. **Customer / project lifecycle** — `customer_projects` (the actual "projects" table — **note: there is no table literally named `projects`**, despite that name appearing informally in some docs/comments), `customer_project_packages` (links `customer_projects` ↔ `smart_packages`), `project_smart_package_workspaces` (JSON workspace snapshots, versioned), `project_labor`, `project_materials`, `project_calculations`, `project_documents`, `project_history`, `project_quotes`, `project_rentals`, `project_tasks`, `project_time_calculations`, `project_types`, `project_analytics`. **Roof / geometry** — `roof_geometry` (one row per project; roof type, dimensions, spær calculations, kvist/dormer fields), `roof_types`, `advanced_geometry_data`. **Materials / pricing / suppliers** — `materials`, `material_categories`, `material_category_mapping`, `material_formulas`, `material_prices`, `material_analytics`, `material_performance_view`, `dynamic_materials`, `carpenter_materials`, `pricing_rules`, `price_database`, `prices`, `labor_prices`, `labor_tasks`, `suppliers`, `vendors`, `vendor_products_history`, `vendor_price_history`, `vendor_current_prices`, `vendor_import_log`. **Supplier imports** — Bygma: `bygma_products`, `bygma_categories`, `bygma_product_groups`, `bygma_materials_cache`, `bygma_materials_mapping`, `bygma_price_history`, `bygma_import_log`, `v_bygma_import_stats`. Stark: `stark_materials_cache`. Håndværkpriser: `haandvaerkpriser_imports`. Generic: `import_logs`, `imported_quotes`, `installation_manuals`. **Quotes / generation** — `quotes`, `generated_quotes`, `submitted_quotes`, `enhanced_quote_details`, `quote_feedback`, `v_enhanced_quotes`. **Auth** — `auth_accounts` (**new, 2026-09-04**): `id`, `username`, `password_hash` (bcrypt), `role` enum(`admin`,`user`), `created_at`, `updated_at`, `last_login_at`. Owned by `backend/src/services/userService.js`. **⚠️ `users` (pre-existing, unrelated to auth)** — `id`, `first_name`, `last_name`, `init`, `email`, `created_at`. This is an **employee roster synced from Ordrestyring** (written by `ordrestyringSyncService.js`), not a login table. The name collision with "who's allowed to log in" is a trap — the auth migration (`database/migrations/20260904_users_table.js`) deliberately created `auth_accounts` instead of reusing/renaming this table. Do not point login logic at `users`. **Ordrestyring sync/cache** — `ordrestyring_calendar`, `ordrestyring_case_features`, `ordrestyring_case_latest`, `ordrestyring_case_material_snapshots`, `ordrestyring_data_quality_issues`, `ordrestyring_employee_types`, `ordrestyring_hours_normalized`, `ordrestyring_materials_normalized`, `ordrestyring_offer_line_snapshots`, `ordrestyring_offer_snapshots`, `ordrestyring_reference_cases`, `ordrestyring_reference_labor_entries`, `ordrestyring_reference_materials`, `ordrestyring_sync_metadata`, `ordrestyring_users`. **Mobile / field intake** — `mobile_order_intakes`, `mobile_order_attachments`, `mobile_order_checklist_answers`, `mobile_order_quote_drafts`. **OCR / documents** — `ocr_documents`, `ocr_materials`, `document_uploads`. **Analytics / business metrics** — `analytics_calculation_log`, `business_benchmarks`, `business_health_dashboard`, `customer_analytics`, `daily_business_metrics`, `employee_performance_analytics`, `high_value_customers_view`, `top_employees_view`, `v_category_statistics`, `product_flow_events`. **Platform / ops** — `system_logs`, `system_settings`, `request_logs`, `openai_usage_stats`, `openai_usage_tracking`, `ai_jobs`, `ai_suggestions`, `support_work_items`, `category_keywords`, `task_categories`, `task_categorization` support tables (`carpenter_task_categories`, `carpenter_task_steps`, `carpenter_tasks`, `carpenter_tools`), `quality_standards`, `safety_procedures`, `product_categories`. **Housekeeping artifact** — `materials_deleted_backup_nov14`: a manual backup table from a prior cleanup, still present. Evidence that cleanups here are done by renaming/backing up rather than hard-deleting — consistent with how this project prefers reversible operations. ## Core relationships ```mermaid erDiagram customer_projects ||--o| roof_geometry : "has (project_id)" customer_projects ||--o{ customer_project_packages : "selects" customer_project_packages }o--|| smart_packages : "references" smart_packages ||--o{ package_tasks : "has" smart_packages ||--o{ package_materials : "has" package_materials }o--o| materials : "matched to" customer_projects ||--o| project_smart_package_workspaces : "versioned workspace" customer_projects ||--o{ project_labor : "has" customer_projects ||--o{ project_materials : "has" customer_projects ||--o{ project_quotes : "generates" auth_accounts { int id PK varchar username varchar password_hash enum role } ``` `auth_accounts` is intentionally standalone in this diagram — it has no foreign-key relationship to `customer_projects` or anything else; it's purely login/authorization state. The unrelated `users` (employee roster) table is omitted here since it's outside the quoting domain.