feat: deliver auditable Smart Pakke quote flow and free site geometry (#31)
CI - Test & Build / Lint & Type Check (push) Canceled after 0s
CI - Test & Build / Backend Unit Tests (push) Canceled after 0s
CI - Test & Build / Frontend Build (push) Canceled after 0s
CI - Test & Build / Security Scan (push) Canceled after 0s
CI - Test & Build / E2E Tests (Playwright) (push) Canceled after 0s
CI - Test & Build / CI Summary (push) Canceled after 0s

* 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]>
This commit is contained in:
Alex
2026-09-26 22:39:18 +02:00
committed by GitHub
co-authored by Claude Sonnet 5 alexpolo1
parent 8b16d35287
commit f37adae2cb
242 changed files with 35912 additions and 7286 deletions
+99 -4
View File
@@ -43,11 +43,13 @@ CREATE TABLE IF NOT EXISTS customer_projects (
CREATE TABLE IF NOT EXISTS roof_geometry (
id INT AUTO_INCREMENT PRIMARY KEY,
project_id INT NOT NULL,
roof_type ENUM('fladt_tag', 'skraat_tag', 'mansard', 'komplekst') NOT NULL,
roof_type ENUM('gable', 'pult', 'flat', 'fladt_tag', 'skraat_tag', 'mansard', 'komplekst') NOT NULL,
roof_material ENUM('tegl','tagpap','eternit','betontag','staal','andet') NULL,
total_area DECIMAL(10,2) NOT NULL COMMENT 'Samlet areal i m²',
roof_covering_area DECIMAL(10,2) NULL COMMENT 'Faktisk tagbeklædningsareal i m²',
roof_pitch DECIMAL(5,2) COMMENT 'Taghældning i grader',
roof_height DECIMAL(8,2) COMMENT 'Taghøjde i meter',
wall_height DECIMAL(8,2) NULL COMMENT 'Væghøjde i meter',
complexity_factor DECIMAL(3,2) DEFAULT 1.0 COMMENT 'Kompleksitetsfaktor 1.0-2.0',
-- Detaljerede målinger
@@ -63,6 +65,13 @@ CREATE TABLE IF NOT EXISTS roof_geometry (
-- Beregnet data
estimated_work_hours DECIMAL(8,2) COMMENT 'Estimerede arbejdstimer',
estimated_carpenters INT COMMENT 'Anbefalede antal tømrere',
geometry_json JSON NULL,
edges_json JSON NULL,
planes_json JSON NULL,
openings_json JSON NULL,
provenance_json JSON NULL,
replacement_scope VARCHAR(80) NULL,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
@@ -93,13 +102,41 @@ CREATE TABLE IF NOT EXISTS project_labor (
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (project_id) REFERENCES customer_projects(id) ON DELETE CASCADE,
INDEX idx_project (project_id)
UNIQUE KEY unique_project_labor (project_id)
);
-- Existing installs may contain duplicates from before saveProjectLabor used upsert.
-- Keep the newest row, verify the cleanup, then enforce the one-row contract.
DELETE stale
FROM project_labor stale
JOIN project_labor newest
ON newest.project_id = stale.project_id AND newest.id > stale.id;
SELECT project_id, COUNT(*) AS remaining_rows
FROM project_labor
GROUP BY project_id
HAVING COUNT(*) > 1;
CREATE UNIQUE INDEX IF NOT EXISTS unique_project_labor ON project_labor (project_id);
-- Versioneret Smart Pakke-builder: klodser, rækkefølge, geometri og overrides.
CREATE TABLE IF NOT EXISTS project_smart_package_workspaces (
id INT AUTO_INCREMENT PRIMARY KEY,
project_id INT NOT NULL,
version INT NOT NULL DEFAULT 0,
workspace_json JSON NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY unique_project_workspace (project_id),
CONSTRAINT fk_smart_package_workspace_project
FOREIGN KEY (project_id) REFERENCES customer_projects(id) ON DELETE CASCADE
);
-- Projekt materialer
CREATE TABLE IF NOT EXISTS project_materials (
id INT AUTO_INCREMENT PRIMARY KEY,
project_id INT NOT NULL,
package_instance_id VARCHAR(100) NULL,
source_package_id INT NULL,
material_id INT NULL,
material_name VARCHAR(255) NOT NULL,
material_category VARCHAR(100),
quantity DECIMAL(10,3) NOT NULL,
@@ -107,16 +144,74 @@ CREATE TABLE IF NOT EXISTS project_materials (
unit_price DECIMAL(10,2) NOT NULL,
total_price DECIMAL(12,2) NOT NULL,
supplier VARCHAR(255),
material_source ENUM('manual', 'database', 'bygma_api') DEFAULT 'manual',
material_source ENUM('manual', 'database', 'bygma_api', 'package') DEFAULT 'manual',
price_source VARCHAR(120) NULL,
price_source_updated_at DATETIME NULL,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (project_id) REFERENCES customer_projects(id) ON DELETE CASCADE,
INDEX idx_project (project_id),
INDEX idx_category (material_category)
INDEX idx_category (material_category),
INDEX idx_project_materials_package_instance (project_id, package_instance_id),
INDEX idx_project_materials_material_id (material_id)
);
-- Projektudlejning og referenceydelser med Smart Pakke-ejerskab.
CREATE TABLE IF NOT EXISTS project_rentals (
id INT AUTO_INCREMENT PRIMARY KEY,
project_id INT NOT NULL,
package_instance_id VARCHAR(100) NULL,
source_package_id INT NULL,
rental_name VARCHAR(255) NOT NULL,
rental_category VARCHAR(100),
quantity DECIMAL(10,3) NOT NULL,
unit VARCHAR(50) NOT NULL,
unit_price DECIMAL(10,2) NOT NULL,
total_price DECIMAL(12,2) NOT NULL,
supplier VARCHAR(255),
rental_source ENUM('manual', 'database', 'import') DEFAULT 'manual',
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (project_id) REFERENCES customer_projects(id) ON DELETE CASCADE,
INDEX idx_project (project_id),
INDEX idx_category (rental_category),
INDEX idx_project_rentals_package_instance (project_id, package_instance_id)
);
-- Idempotente opgraderinger når bootstrap køres mod eksisterende tabeller.
ALTER TABLE project_materials
ADD COLUMN IF NOT EXISTS package_instance_id VARCHAR(100) NULL AFTER project_id,
ADD COLUMN IF NOT EXISTS source_package_id INT NULL AFTER package_instance_id,
ADD COLUMN IF NOT EXISTS material_id INT NULL AFTER source_package_id,
ADD COLUMN IF NOT EXISTS price_source VARCHAR(120) NULL AFTER material_source,
ADD COLUMN IF NOT EXISTS price_source_updated_at DATETIME NULL AFTER price_source,
MODIFY COLUMN material_source ENUM('manual', 'database', 'bygma_api', 'package') DEFAULT 'manual';
ALTER TABLE project_rentals
ADD COLUMN IF NOT EXISTS package_instance_id VARCHAR(100) NULL AFTER project_id,
ADD COLUMN IF NOT EXISTS source_package_id INT NULL AFTER package_instance_id;
ALTER TABLE material_packages
ADD COLUMN IF NOT EXISTS geometry_basis VARCHAR(40) NULL AFTER unit,
ADD COLUMN IF NOT EXISTS geometry_factor DECIMAL(10,3) NULL AFTER geometry_basis,
ADD COLUMN IF NOT EXISTS default_count DECIMAL(10,3) NULL AFTER geometry_factor,
ADD COLUMN IF NOT EXISTS default_quantity DECIMAL(10,3) NULL AFTER default_count,
ADD COLUMN IF NOT EXISTS replacement_scope VARCHAR(40) NULL AFTER default_quantity,
ADD COLUMN IF NOT EXISTS compatible_roof_materials JSON NULL AFTER replacement_scope,
ADD COLUMN IF NOT EXISTS allowed_roof_forms JSON NULL AFTER compatible_roof_materials,
ADD COLUMN IF NOT EXISTS min_pitch_degrees DECIMAL(5,2) NULL AFTER allowed_roof_forms,
ADD COLUMN IF NOT EXISTS max_pitch_degrees DECIMAL(5,2) NULL AFTER min_pitch_degrees;
ALTER TABLE roof_geometry
ADD COLUMN IF NOT EXISTS roof_covering_area DECIMAL(10,2) NULL AFTER total_area,
ADD COLUMN IF NOT EXISTS wall_height DECIMAL(8,2) NULL AFTER roof_height,
ADD COLUMN IF NOT EXISTS geometry_json JSON NULL,
ADD COLUMN IF NOT EXISTS edges_json JSON NULL,
ADD COLUMN IF NOT EXISTS planes_json JSON NULL,
ADD COLUMN IF NOT EXISTS openings_json JSON NULL,
ADD COLUMN IF NOT EXISTS provenance_json JSON NULL,
ADD COLUMN IF NOT EXISTS replacement_scope VARCHAR(80) NULL,
MODIFY COLUMN roof_type ENUM('gable', 'pult', 'flat', 'fladt_tag', 'skraat_tag', 'mansard', 'komplekst') NOT NULL;
-- Projekt beregninger (råtilbud)
CREATE TABLE IF NOT EXISTS project_calculations (
id INT AUTO_INCREMENT PRIMARY KEY,