* feat: move login credentials to a DB-backed users table with an admin management page Replaces the hardcoded AUTH_USERNAME/AUTH_PASSWORD login check with a new auth_accounts table (bcrypt-hashed passwords, admin/user roles). Adds admin-only /api/users CRUD routes and a "Brugere" admin page in the frontend for managing logins without redeploying. Removes the unused, unmounted duplicate login route in src/routes/auth.js. * docs: add architecture codemaps with diagrams for the whole system Adds codemaps/architecture.md, backend.md, frontend.md, and data.md — Mermaid-diagrammed design documentation verified against the live codebase and database rather than assumed from CLAUDE.md. Covers the unified-server.js request flow (mounted routers + ~183 inline routes), 68 backend services grouped by domain, the frontend's state-driven view-switch (no React Router in practice despite BrowserRouter being present), and the full 122-table DB schema with the auth_accounts vs unrelated users table naming trap flagged explicitly. Links added from the root README. Co-Authored-By: Claude Sonnet 5 <[email protected]> * feat: ship canonical roof quote workflow * fix: keep migration dry-run idempotent * [verified] feat: complete Smart Pakker management * [verified] fix: ignore blank task dependencies * [verified] fix: align package duplication with schema * [verified] fix: enforce Discord status limits * [verified] fix: link Smart Pakke materials safely * [verified] fix: harden material link review * [verified] feat: improve material matching * fix: scope pitch validation to roof packages * fix: support canonical snapshots on production schema * [verified] fix: hide internal package metadata from PDF * [verified] feat: deliver sales-ready customer PDF * [verified] feat: ship sales-ready PDF with AI overview * [verified] fix: authenticate project list requests * [verified] fix: refresh project-list authentication * [verified] fix: open existing project details * [verified] fix: keep roof components searchable in builder * [verified] fix: expose all Smart Package categories * [verified] fix: authenticate project creation * [verified] feat: make Smart Pakker the universal project flow * [verified] feat: preview Smart Package contents * [verified] test: keep generic release isolated from downpipe work * feat: add first-class Smart Pakke rentals * [verified] feat: add gutter and downpipe smart packages * [verified] fix: prepare six-house gutter quote flow * [verified] fix: open generic quotes without roof geometry * [verified] fix: review generic quotes with authenticated APIs * [verified] fix: calculate generic Smart Package quotes * [verified] fix: return generic calculation breakdown * feat: checkpoint generic signed snapshot validation with red-green tests * feat: complete fail-closed generic quote approval and customer PDF flow * feat: use generic signed snapshot in final review * feat: redesign generic quote final review * fix: harden generic review summaries * feat: add auditable six-house package basis * [verified] feat: finish auditable Smart Pakke UI * [verified] fix: bind auditable quantity and price bases * [verified] fix: keep six-house basis across package versions * [verified] fix: complete smart package discovery management * [verified] fix: simplify composition and generic scope * [verified] test: keep explicit roof contracts fail closed * [verified] fix: harden generic quote snapshots * fix: make generic quote delivery customer safe * [verified] fix: secure package catalog reads * [verified] fix: close workspace provenance blockers * fix: harden customer document language boundary * [verified] fix: secure smart package internal reads * fix: version package child mutations atomically * feat: add generic customer quote text flow * [verified] fix: allow manual customer numbers * [verified] fix: expose optional roof geometry * [verified] fix: rebase hydrated packages after geometry edits * [verified] feat: add free editable site area map * [verified] fix: harden map recovery and geocoding gate * fix: bind map quantities to authoritative geometry * fix: release geocoder lock before dispatch * fix: separate roof and site geometry provenance * fix: revoke stale admin authorization * fix: migrate task geometry basis * fix: make backend CI dependency-complete * ci: seed isolated e2e login account * fix: allow clean database bootstrap * fix: skip indexes for optional tables * test: use canonical mansard geometry in e2e * [verified] fix(auth): enforce live operator boundary * fix: fail close Ordrestyring offer transport * fix(frontend): authenticate customer project requests * fix: align canonical roof type contract * [verified] fix: reconcile legacy package labor safely * [verified] fix: audit site geometry deletion * docs: add PR 31 reviewer guide * docs: synchronize Obsidian vault * docs: sync integrated reviewer guide to Obsidian * ci: seed isolated auth account explicitly * fix: close offer bootstrap and service readiness gaps * fix: authenticate protected package callers * fix: provision initial admin and disable generic send * [verified] fix: close final quote release blockers * [verified] fix: seed gutter packages before deployment --------- Co-authored-by: alexpolo1 <[email protected]> Co-authored-by: Claude Sonnet 5 <[email protected]>
7.3 KiB
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:
backend/src/services/databaseService.js— contains ~37 inlineCREATE TABLE IF NOT EXISTSstatements that run automatically every time the server starts (Database tables created/verified successfullyin the PM2 logs). This is the primary source for a large share of the schema.database/migrations/— 11 standalone scripts (mix of.sqlrun manually via a MySQL client and.jsscripts run withnode <file>.js), timestamp-prefixed, each independent. No tracking table of "which migrations ran."backend/migrations/— a second, separate migrations folder with its own independent set of.sql/.jsfiles. Nothing unifies this withdatabase/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
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.