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]>
415 lines
15 KiB
SQL
415 lines
15 KiB
SQL
-- Customer Project System Database Schema
|
|
-- Opretter tabeller for det nye strukturerede tilbuds-flow
|
|
|
|
-- Kunde projekter
|
|
CREATE TABLE IF NOT EXISTS customer_projects (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
project_name VARCHAR(255) NOT NULL,
|
|
customer_name VARCHAR(255) NOT NULL,
|
|
customer_number VARCHAR(50),
|
|
customer_email VARCHAR(255),
|
|
customer_phone VARCHAR(50),
|
|
customer_address TEXT,
|
|
project_description TEXT,
|
|
selected_packages JSON,
|
|
suggestion_snapshot JSON,
|
|
input_normalization_log JSON,
|
|
project_status ENUM(
|
|
'draft',
|
|
'geometry_complete',
|
|
'smart_package_complete',
|
|
'review_pending',
|
|
'ready_for_ordrestyring',
|
|
'sent_to_ordrestyring',
|
|
'accepted',
|
|
'rejected',
|
|
'geometry_pending',
|
|
'labor_pending',
|
|
'materials_pending',
|
|
'calculation_ready',
|
|
'quote_generated',
|
|
'sent'
|
|
) DEFAULT 'draft',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
INDEX idx_status (project_status),
|
|
INDEX idx_customer (customer_name),
|
|
INDEX idx_customer_number (customer_number),
|
|
INDEX idx_created (created_at)
|
|
);
|
|
|
|
-- Tag geometri specifikt for tag arbejde
|
|
CREATE TABLE IF NOT EXISTS roof_geometry (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
project_id INT 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
|
|
length_main DECIMAL(8,2) COMMENT 'Hovedlængde i meter',
|
|
width_main DECIMAL(8,2) COMMENT 'Hovedbredde i meter',
|
|
|
|
-- Specielle forhold
|
|
has_dormers BOOLEAN DEFAULT FALSE COMMENT 'Har kviste',
|
|
has_chimneys BOOLEAN DEFAULT FALSE COMMENT 'Har skorstene',
|
|
has_skylights BOOLEAN DEFAULT FALSE COMMENT 'Har ovenlys',
|
|
access_difficulty ENUM('let', 'medium', 'svær') DEFAULT 'medium',
|
|
|
|
-- 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,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
FOREIGN KEY (project_id) REFERENCES customer_projects(id) ON DELETE CASCADE,
|
|
UNIQUE INDEX unique_project (project_id),
|
|
INDEX idx_project (project_id),
|
|
INDEX idx_roof_type (roof_type),
|
|
INDEX idx_roof_geometry_material (roof_material)
|
|
);
|
|
|
|
-- Arbejdstimer og tømrere
|
|
CREATE TABLE IF NOT EXISTS project_labor (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
project_id INT NOT NULL,
|
|
carpenter_count INT NOT NULL DEFAULT 1,
|
|
estimated_hours_per_carpenter DECIMAL(8,2) NOT NULL,
|
|
total_work_hours DECIMAL(8,2) NOT NULL,
|
|
hourly_rate DECIMAL(8,2) NOT NULL DEFAULT 580.00 COMMENT 'Fast pris 580 kr/time',
|
|
total_labor_cost DECIMAL(12,2) NOT NULL,
|
|
|
|
-- Detaljer om arbejdsopgaver
|
|
work_breakdown JSON COMMENT 'Detaljeret opdeling af arbejdsopgaver',
|
|
|
|
notes TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
FOREIGN KEY (project_id) REFERENCES customer_projects(id) ON DELETE CASCADE,
|
|
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,
|
|
unit VARCHAR(50) NOT NULL,
|
|
unit_price DECIMAL(10,2) NOT NULL,
|
|
total_price DECIMAL(12,2) NOT NULL,
|
|
supplier VARCHAR(255),
|
|
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_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,
|
|
project_id INT NOT NULL,
|
|
|
|
-- Geometri opsummering
|
|
total_area DECIMAL(10,2),
|
|
complexity_factor DECIMAL(3,2),
|
|
|
|
-- Arbejdstimer opsummering
|
|
total_work_hours DECIMAL(8,2),
|
|
carpenter_count INT,
|
|
total_labor_cost DECIMAL(12,2),
|
|
|
|
-- Materialer opsummering
|
|
total_material_cost DECIMAL(12,2),
|
|
material_count INT,
|
|
|
|
-- Totaler
|
|
subtotal DECIMAL(12,2),
|
|
overhead_percentage DECIMAL(5,2) DEFAULT 15.00,
|
|
overhead_amount DECIMAL(12,2),
|
|
profit_percentage DECIMAL(5,2) DEFAULT 20.00,
|
|
profit_amount DECIMAL(12,2),
|
|
total_excl_vat DECIMAL(12,2),
|
|
vat_percentage DECIMAL(5,2) DEFAULT 25.00,
|
|
vat_amount DECIMAL(12,2),
|
|
total_incl_vat DECIMAL(12,2),
|
|
|
|
-- Metadata
|
|
calculation_data JSON COMMENT 'Detaljerede beregningsdata',
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
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)
|
|
);
|
|
|
|
-- Genererede tilbud
|
|
CREATE TABLE IF NOT EXISTS generated_quotes (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
project_id INT NOT NULL,
|
|
calculation_id INT NOT NULL,
|
|
|
|
quote_text TEXT NOT NULL COMMENT 'AI-genereret tilbudstekst',
|
|
quote_format ENUM('text', 'html', 'pdf') DEFAULT 'html',
|
|
|
|
-- AI usage tracking
|
|
ai_tokens_used INT,
|
|
ai_cost DECIMAL(8,4),
|
|
ai_model VARCHAR(50),
|
|
|
|
quote_status ENUM('draft', 'approved', 'sent', 'accepted', 'rejected') DEFAULT 'draft',
|
|
sent_at TIMESTAMP NULL,
|
|
valid_until DATE,
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
FOREIGN KEY (project_id) REFERENCES customer_projects(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (calculation_id) REFERENCES project_calculations(id) ON DELETE CASCADE,
|
|
INDEX idx_project (project_id),
|
|
INDEX idx_status (quote_status)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS ordrestyring_case_features (
|
|
case_number VARCHAR(64) PRIMARY KEY,
|
|
customer_number VARCHAR(64),
|
|
customer_name VARCHAR(255),
|
|
description TEXT,
|
|
remarks TEXT,
|
|
search_text MEDIUMTEXT,
|
|
inferred_roof_type VARCHAR(100),
|
|
inferred_package_id VARCHAR(100),
|
|
total_hours DECIMAL(10,2) DEFAULT 0,
|
|
hour_entries INT DEFAULT 0,
|
|
material_lines_count INT DEFAULT 0,
|
|
material_total DECIMAL(12,2) DEFAULT 0,
|
|
created_at_ordrestyring DATETIME NULL,
|
|
last_activity_at_ordrestyring DATETIME NULL,
|
|
synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
INDEX idx_case_features_customer_number (customer_number),
|
|
INDEX idx_case_features_customer_name (customer_name),
|
|
INDEX idx_case_features_roof_type (inferred_roof_type),
|
|
INDEX idx_case_features_package (inferred_package_id),
|
|
INDEX idx_case_features_created_at (created_at_ordrestyring),
|
|
INDEX idx_case_features_last_activity (last_activity_at_ordrestyring)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS ordrestyring_case_material_snapshots (
|
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
case_number VARCHAR(64) NOT NULL,
|
|
line_id INT NOT NULL,
|
|
product_number VARCHAR(128),
|
|
product_text TEXT,
|
|
supplier VARCHAR(255),
|
|
quantity DECIMAL(12,3) DEFAULT 0,
|
|
cost_price DECIMAL(12,2) DEFAULT 0,
|
|
sales_price DECIMAL(12,2) DEFAULT 0,
|
|
created_at_ordrestyring DATETIME NULL,
|
|
synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE KEY uniq_ordrestyring_case_material_line (case_number, line_id),
|
|
INDEX idx_ordrestyring_case_material_case_number (case_number),
|
|
INDEX idx_ordrestyring_case_material_supplier (supplier)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS ordrestyring_offer_snapshots (
|
|
offer_id INT PRIMARY KEY,
|
|
offer_number VARCHAR(64),
|
|
customer_name VARCHAR(255),
|
|
customer_email VARCHAR(255),
|
|
description TEXT,
|
|
status_text VARCHAR(255),
|
|
search_text MEDIUMTEXT,
|
|
total_sales_price DECIMAL(12,2) DEFAULT 0,
|
|
total_sales_price_with_vat DECIMAL(12,2) DEFAULT 0,
|
|
created_at_ordrestyring DATETIME NULL,
|
|
synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
INDEX idx_offer_snapshots_customer_name (customer_name),
|
|
INDEX idx_offer_snapshots_status (status_text),
|
|
INDEX idx_offer_snapshots_created_at (created_at_ordrestyring)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS ordrestyring_offer_line_snapshots (
|
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
offer_id INT NOT NULL,
|
|
line_id INT NOT NULL,
|
|
description TEXT,
|
|
quantity DECIMAL(12,3) DEFAULT 0,
|
|
unit VARCHAR(50),
|
|
unit_price DECIMAL(12,2) DEFAULT 0,
|
|
line_total DECIMAL(12,2) DEFAULT 0,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE KEY uniq_ordrestyring_offer_line (offer_id, line_id),
|
|
INDEX idx_ordrestyring_offer_line_offer_id (offer_id),
|
|
CONSTRAINT fk_ordrestyring_offer_line_offer
|
|
FOREIGN KEY (offer_id) REFERENCES ordrestyring_offer_snapshots(offer_id)
|
|
ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS ordrestyring_sync_metadata (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
project_id INT NULL,
|
|
case_number VARCHAR(64) NULL,
|
|
offer_id INT NULL,
|
|
offer_number VARCHAR(64) NULL,
|
|
last_status VARCHAR(128) NULL,
|
|
last_status_at DATETIME NULL,
|
|
last_price_list_synced_at DATETIME NULL,
|
|
last_material_sync_at DATETIME NULL,
|
|
notes TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
FOREIGN KEY (project_id) REFERENCES customer_projects(id) ON DELETE SET NULL,
|
|
INDEX idx_ordrestyring_metadata_project (project_id),
|
|
INDEX idx_ordrestyring_metadata_case (case_number),
|
|
UNIQUE KEY uniq_ordrestyring_metadata_case (case_number),
|
|
UNIQUE KEY uniq_ordrestyring_metadata_offer (offer_id)
|
|
);
|
|
|
|
-- Bygma API cache (for version 2)
|
|
CREATE TABLE IF NOT EXISTS bygma_materials_cache (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
product_id VARCHAR(100) UNIQUE,
|
|
product_name VARCHAR(255),
|
|
category VARCHAR(100),
|
|
subcategory VARCHAR(100),
|
|
unit VARCHAR(50),
|
|
price DECIMAL(10,2),
|
|
stock_status VARCHAR(50),
|
|
supplier_info JSON,
|
|
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
INDEX idx_category (category),
|
|
INDEX idx_name (product_name),
|
|
INDEX idx_updated (last_updated)
|
|
);
|
|
|
|
-- Stark API cache (samme struktur som Bygma)
|
|
CREATE TABLE IF NOT EXISTS stark_materials_cache (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
product_id VARCHAR(100) UNIQUE,
|
|
product_name VARCHAR(255),
|
|
category VARCHAR(100),
|
|
subcategory VARCHAR(100),
|
|
unit VARCHAR(50),
|
|
price DECIMAL(10,2),
|
|
stock_status VARCHAR(50),
|
|
supplier_info JSON,
|
|
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
INDEX idx_category (category),
|
|
INDEX idx_name (product_name),
|
|
INDEX idx_updated (last_updated)
|
|
);
|