Files
tilbudgivern/migrations/create_tomrer_smart_packages_batch_3.sql
alexpolo1 80632c996b feat: Complete Ordrestyring GraphQL integration with automatic offer creation
FEATURES IMPLEMENTED:
 Full GraphQL integration with Ordrestyring API
 Automatic offer creation from Tilbudsgiveren quotes
 Customer management (find existing or create new)
 Task/Opgave management with full quote description
 Material lines (tilbudslinjer) with pricing
 Internal notes in bemærkninger field
 Department assignment (Tømrer = ID 2)
 Status management (Tilbudsgiver Oprettet Tilbud)

KEY COMPONENTS:

1. OrdrestyringQuoteService (backend/src/services/ordrestyringQuoteService.js)
   - generateDetailedQuoteDescription(): Creates formatted quote text
   - findOrCreateCustomer(): Handles customer, contact, delivery address
   - createOfferLines(): Updates default task & adds material lines
   - sendQuoteToOrdrestyring(): Main orchestration method
   - getOfferWithTasks(): Query offers with task details

2. Smart Task Management:
   - Finds and updates existing default 'Opgave 1' task
   - Renames to 'Tilbudsgivertekst' for clarity
   - Adds full 2000+ character quote description
   - Creates material lines under the task
   - Fallback: Creates new task if none exists

3. GraphQL Mutations & Queries:
   - createOffer: Creates offer with customer, status, department
   - updateOfferTask: Updates existing task with description
   - createOfferTask: Fallback for new task creation
   - createOfferLine: Adds material lines with pricing
   - Proper error handling and logging throughout

4. Database Integration:
   - Migration: add_ordrestyring_columns.sql
   - Tracks ordrestyring_offer_id, ordrestyring_offer_number
   - Status synchronization with ordrestyring_status
   - Timestamp tracking with ordrestyring_sent_at

5. Route Updates (backend/routes/ordrestyring.js):
   - POST /api/ordrestyring/send-quote-graphql
   - Optional customerId parameter (uses existing or creates new)
   - Validation for projectId and calculationId
   - Returns complete offer details

TECHNICAL HIGHLIGHTS:

• GraphQL Client: graphql-request v7.3.1
• API Endpoint: https://graphql.ordrestyring.dk/graphql
• Authentication: Bearer token in headers
• Error Handling: Graceful fallbacks, detailed logging
• Field Discovery: Used introspection to find correct types
  - UpdateOfferTaskInput (not OfferTaskInput)
  - Task fields: header & text (not name & description)
  - productNumber required for offer lines

TESTING:

• Test scripts: test_roesevangen_ordrestyring.py
• Verification: test_check_offer.js
• Live testing with Project 22 (Røsevangen 44)
• Confirmed: Single task with full description
• Confirmed: 10 material lines with correct pricing
• Confirmed: Department, status, and internal notes working

RESULT:
Complete end-to-end integration from Tilbudsgiveren → Ordrestyring
- Creates professional offers with all data
- Single 'Tilbudsgivertekst' task (not duplicate Opgave 1)
- Full quote description visible in task
- Material lines with quantities and prices
- Ready for production use

Co-authored-by: AI Assistant <copilot@github.com>
2025-10-31 21:19:00 +00:00

121 lines
14 KiB
SQL

-- ═══════════════════════════════════════════════════════════════════════════
-- TØMRER SMART PACKAGES - BATCH 3 (Pakker 12-15)
-- Oprettet: 2025-10-31
-- Matcher eksisterende database struktur 100%
-- ═══════════════════════════════════════════════════════════════════════════
USE tilbudgivern;
-- ═══════════════════════════════════════════════════════════════════════════
-- PAKKE 12: Velux vindue - Montering
-- ═══════════════════════════════════════════════════════════════════════════
INSERT INTO material_packages (name, description, category, difficulty_level, estimated_hours, hourly_rate, installation_notes, is_template, area_based, auto_calculated, created_by)
VALUES ('Montering af Velux tagvindue', 'Komplet montering af nyt Velux tagvindue inkl. inddækningskit og isolering. Auto-beregner per vindue med præcis tidsestimat.', 'Tagarbejde', 'svær', 3.5, 580, 'BEREGNING: 3.5 timer per vindue. Kræver præcis udskæring og vandtæt montering. Inkluderer ikke selve vinduet.', 1, 0, 1, 'system');
SET @velux_mont_pkg = LAST_INSERT_ID();
INSERT INTO package_materials (package_id, material_name, material_category, quantity, unit, unit_price, total_price, supplier, notes, geometry_multiplier, base_quantity) VALUES
(@velux_mont_pkg, 'VELUX INDDÆKNINGSKIT B7 78X98 [EDJ0000]', 'Tagvindue tilbehør', 1, 'stk', 785.00, 785.00, 'Velux', 'Per vindue - tilpas størrelse', 'antal_vinduer', 1),
(@velux_mont_pkg, 'VELUX ISOLERINGSKIT [BDX0000]', 'Isolering', 1, 'stk', 385.00, 385.00, 'Velux', 'Per vindue', 'antal_vinduer', 1),
(@velux_mont_pkg, 'VELUX DAMPSPÆRRE [BBX0000]', 'Isolering', 1, 'stk', 145.00, 145.00, 'Velux', 'Per vindue', 'antal_vinduer', 1),
(@velux_mont_pkg, 'MONTAGESKINNER VELUX [SKINNER]', 'Beslag', 1, 'sæt', 125.00, 125.00, 'Velux', 'Per vindue', 'antal_vinduer', 1),
(@velux_mont_pkg, 'ETERNIT TAGSKRUER 100 STK [144855]', 'Beslag', 0.2, 'pakke', 115.00, 23.00, 'Eternit', '20 skruer per vindue', 'antal_vinduer * 20 / 100', 0.2),
(@velux_mont_pkg, 'ISOLERINGSBÅND 25M [ISOBAND]', 'Isolering', 0.16, 'rulle', 185.00, 29.60, 'Bygma', '4 meter per vindue', 'antal_vinduer * 4 / 25', 0.16);
INSERT INTO smart_package_tasks (package_id, name, description, task_order, hours, time_unit, time_per_unit, is_custom) VALUES
(@velux_mont_pkg, 'Opmåling og markering', 'Præcis markering af vinduesplacering. Vinduets placering markeres nøjagtigt på tag - Hensyn tages til lægteafstand og spær - Højde fra gulv kontrolleres', 1, 0.5, 'per_piece', 0.5, 0),
(@velux_mont_pkg, 'Udskæring i tagdækning', 'Udskæring af hul til vindue. B7 plader skæres præcist til vinduesstørrelse - Lægter tilskæres hvor nødvendigt - Spær tilpasses hvis påkrævet - Åbning kontrolleres for mål', 2, 1.0, 'per_piece', 1.0, 0),
(@velux_mont_pkg, 'Montering af vindue og inddækning', 'Fastgørelse af vindue med inddækningskit. Velux vindue monteres i åbning med montageskinner - Inddækningskit monteres trin for trin efter anvisning - Fastgøres med tagskruer - Vandtæthed sikres ved alle samlinger', 3, 1.5, 'per_piece', 1.5, 0),
(@velux_mont_pkg, 'Isolering og dampspærre', 'Indvendig finish omkring vindue. Isoleringskit monteres omkring vinduesramme - Dampspærre monteres og tapes lufttæt - Isoleringsbånd anbringes ved alle samlinger - Kvalitetskontrol af tæthed', 4, 0.5, 'per_piece', 0.5, 0);
-- ═══════════════════════════════════════════════════════════════════════════
-- PAKKE 13: Velux vindue - Udskiftning
-- ═══════════════════════════════════════════════════════════════════════════
INSERT INTO material_packages (name, description, category, difficulty_level, estimated_hours, hourly_rate, installation_notes, is_template, area_based, auto_calculated, created_by)
VALUES ('Udskiftning af eksisterende Velux', 'Udskiftning af gammelt Velux vindue til nyt med samme størrelse. Auto-beregner per vindue inkl. nedtagning og bortkørsel.', 'Tagarbejde', 'mellem', 2.5, 580, 'BEREGNING: 2.5 timer per vindue. Hurtigere end ny montering da åbning allerede eksisterer. Inkluderer ikke nyt vindue.', 1, 0, 1, 'system');
SET @velux_udsk_pkg = LAST_INSERT_ID();
INSERT INTO package_materials (package_id, material_name, material_category, quantity, unit, unit_price, total_price, supplier, notes, geometry_multiplier, base_quantity) VALUES
(@velux_udsk_pkg, 'VELUX INDDÆKNINGSKIT B7 78X98 [EDJ0000]', 'Tagvindue tilbehør', 1, 'stk', 785.00, 785.00, 'Velux', 'Per vindue - tilpas størrelse', 'antal_vinduer', 1),
(@velux_udsk_pkg, 'VELUX ISOLERINGSKIT [BDX0000]', 'Isolering', 1, 'stk', 385.00, 385.00, 'Velux', 'Nyt kit ved udskiftning', 'antal_vinduer', 1),
(@velux_udsk_pkg, 'VELUX DAMPSPÆRRE [BBX0000]', 'Isolering', 1, 'stk', 145.00, 145.00, 'Velux', 'Ny dampspærre', 'antal_vinduer', 1),
(@velux_udsk_pkg, 'ETERNIT TAGSKRUER 100 STK [144855]', 'Beslag', 0.15, 'pakke', 115.00, 17.25, 'Eternit', '15 skruer per vindue', 'antal_vinduer * 15 / 100', 0.15),
(@velux_udsk_pkg, 'SILIKONE NEUTRAL [050043]', 'Forsegling', 1, 'stk', 55.00, 55.00, 'Bygma', 'Til efterbehandling', 'antal_vinduer', 1);
INSERT INTO smart_package_tasks (package_id, name, description, task_order, hours, time_unit, time_per_unit, is_custom) VALUES
(@velux_udsk_pkg, 'Nedtagning af gammelt vindue', 'Forsigtigt nedtagning af eksisterende Velux. Gammelt inddækningskit afmonteres forsigtigt - Velux vindue løsnes og tages ned - Gammel isolering og dampspærre fjernes - Åbning rengøres og kontrolleres', 1, 0.8, 'per_piece', 0.8, 0),
(@velux_udsk_pkg, 'Kontrol og justering af åbning', 'Forberedelse af åbning til nyt vindue. Åbning kontrolleres for mål - Eventuelle justeringer af lægter foretages - Spær kontrolleres for skader - Område rengøres', 2, 0.3, 'per_piece', 0.3, 0),
(@velux_udsk_pkg, 'Montering af nyt vindue', 'Montering med nyt inddækningskit. Nyt Velux vindue monteres i eksisterende åbning - Nyt inddækningskit monteres efter anvisning - Fastgøres med tagskruer - Vandtæthed sikres', 3, 1.0, 'per_piece', 1.0, 0),
(@velux_udsk_pkg, 'Isolering og finish', 'Indvendig færdiggørelse. Nyt isoleringskit monteres - Dampspærre monteres lufttæt - Silikone påføres ved behov - Kvalitetskontrol - Gammelt vindue bortkøres', 4, 0.4, 'per_piece', 0.4, 0);
-- ═══════════════════════════════════════════════════════════════════════════
-- PAKKE 14: Trækbånd til spær
-- ═══════════════════════════════════════════════════════════════════════════
INSERT INTO material_packages (name, description, category, difficulty_level, estimated_hours, hourly_rate, installation_notes, is_template, area_based, auto_calculated, created_by)
VALUES ('Montering af trækbånd', 'Montering af trækbånd til forstærkning af tagkonstruktion. Auto-beregner per stk med beslag og monteringstid.', 'Tagarbejde', 'svær', 1.0, 580, 'BEREGNING: 1 time per trækbånd. Vigtigt for at optage trækkræfter i tagkonstruktionen.', 1, 0, 1, 'system');
SET @traekbaand_pkg = LAST_INSERT_ID();
INSERT INTO package_materials (package_id, material_name, material_category, quantity, unit, unit_price, total_price, supplier, notes, geometry_multiplier, base_quantity) VALUES
(@traekbaand_pkg, 'TRÆKBÅND GALVANISERET 40X5MM [TRKBND]', 'Beslag', 1, 'stk', 285.00, 285.00, 'Bygma', 'Standard længde 6 meter', 'antal_traekbaand', 1),
(@traekbaand_pkg, 'BÅNDJERN BESLAG ENDE [BESLAG_ENDE]', 'Beslag', 2, 'stk', 45.00, 90.00, 'Bygma', '2 per trækbånd', 'antal_traekbaand * 2', 2),
(@traekbaand_pkg, 'SPÆNDEBOLT M12X120MM [SPBOLT]', 'Beslag', 2, 'stk', 28.00, 56.00, 'Bygma', '2 per trækbånd', 'antal_traekbaand * 2', 2),
(@traekbaand_pkg, 'SKIVER OG MØTRIKKER M12 [SKIV12]', 'Beslag', 4, 'stk', 3.50, 14.00, 'Bygma', '4 per trækbånd', 'antal_traekbaand * 4', 4),
(@traekbaand_pkg, 'RUSTFRI SKRUER 6X50MM 100 STK [RFS650]', 'Beslag', 0.08, 'pakke', 145.00, 11.60, 'Bygma', '8 per trækbånd', 'antal_traekbaand * 8 / 100', 0.08);
INSERT INTO smart_package_tasks (package_id, name, description, task_order, hours, time_unit, time_per_unit, is_custom) VALUES
(@traekbaand_pkg, 'Opmåling og markering', 'Identifikation af monteringspunkter. Spærkonstruktion analyseres for optimal placering - Monteringspunkter markeres på bjælker - Afstande kontrolleres', 1, 0.2, 'per_piece', 0.2, 0),
(@traekbaand_pkg, 'Montering af endebeslag', 'Fastgørelse af beslag i begge ender. Endebeslag monteres på bjælker/murværk - Fastgøres med skruer og bolte - Sikres mod udrivning', 2, 0.3, 'per_piece', 0.3, 0),
(@traekbaand_pkg, 'Montering af trækbånd', 'Fastgørelse og spænding af bånd. Trækbånd monteres mellem endebeslag - Spændes med spændebolte - Korrekt spænding kontrolleres - Sikres med låsemøtrikker', 3, 0.4, 'per_piece', 0.4, 0),
(@traekbaand_pkg, 'Kontrol og justering', 'Kvalitetssikring af montering. Alle fastgørelser kontrolleres - Spænding verificeres - Dokumentation af placering - Rådgivning til kunde om vedligehold', 4, 0.1, 'per_piece', 0.1, 0);
-- ═══════════════════════════════════════════════════════════════════════════
-- PAKKE 15: Skortrende komplet
-- ═══════════════════════════════════════════════════════════════════════════
INSERT INTO material_packages (name, description, category, difficulty_level, estimated_hours, hourly_rate, installation_notes, is_template, area_based, auto_calculated, created_by)
VALUES ('Montering af skortrende', 'Komplet skortrende system i zink eller kobber inkl. beslag og tætning. Auto-beregner per løbende meter omkring skorsten.', 'Tagarbejde', 'svær', 0.8, 580, 'BEREGNING: 0.8 time per løbende meter. Kræver præcis loddearbejde og tætning for vandtæthed.', 1, 0, 1, 'system');
SET @skortrende_pkg = LAST_INSERT_ID();
INSERT INTO package_materials (package_id, material_name, material_category, quantity, unit, unit_price, total_price, supplier, notes, geometry_multiplier, base_quantity, waste_factor) VALUES
(@skortrende_pkg, 'ZINKSKORTRENDE PROFIL [ZINKSK]', 'Tætning', 1.0, 'lbm', 385.00, 385.00, 'Blikkenslager', 'Per løbende meter omkring skorsten', 'skorsten_omkreds', 1.0, 1.10),
(@skortrende_pkg, 'ZINKBESLAG HJØRNE [ZINKHJR]', 'Tætning', 4, 'stk', 95.00, 380.00, 'Blikkenslager', '4 hjørner per skorsten', NULL, 4, 1.00),
(@skortrende_pkg, 'BLYINDFATNING 30CM [BLYIND]', 'Tætning', 1.0, 'lbm', 285.00, 285.00, 'Blikkenslager', 'Per løbende meter', 'skorsten_omkreds', 1.0, 1.05),
(@skortrende_pkg, 'TAGSKRUER ZINK 100 STK [TSKZINK]', 'Beslag', 0.06, 'pakke', 125.00, 7.50, 'Blikkenslager', '6 per løbende meter', 'skorsten_omkreds * 6 / 100', 0.06, 1.00),
(@skortrende_pkg, 'LODDETIN OG FLUKS [LODDE]', 'Materiale', 1, 'sæt', 145.00, 145.00, 'Blikkenslager', 'Per skorsten', NULL, 1, 1.00),
(@skortrende_pkg, 'SILIKONE HØJTEMPERAT [SILHOJ]', 'Forsegling', 1, 'tube', 85.00, 85.00, 'Bygma', 'Per skorsten', NULL, 1, 1.00);
INSERT INTO smart_package_tasks (package_id, name, description, task_order, hours, time_unit, time_per_unit, is_custom) VALUES
(@skortrende_pkg, 'Opmåling af skorsten', 'Præcis opmåling omkring skorsten. Skorstenens omkreds og højde opmåles - Skortrende dimensioneres efter mål - Materialer tilskæres før montering', 1, 0.5, 'per_project', 0.5, 0),
(@skortrende_pkg, 'Montering af blyndfatning', 'Anbringelse af blyindfatning ved skorsten. Blyindfatning monteres ved skorstensfod - Presses ned mellem tagplader - Former sig efter overflade', 2, 0.3, 'per_meter', 0.3, 0),
(@skortrende_pkg, 'Montering af zinkskortrende', 'Fastgørelse af zinkprofiler. Zinkskortrende monteres omkring skorsten per løbende meter - Hjørnebeslag monteres ved alle hjørner - Fastgøres med zinkskruer - Samlinger loddes vandtæt', 3, 0.4, 'per_meter', 0.4, 0),
(@skortrende_pkg, 'Lodning og tætning', 'Færdiggørelse med vandtæt finish. Alle samlinger loddes professionelt - Højtemperatur silikone påføres ved skorsten - Tæthed testes med vand - Kvalitetskontrol af hele system', 4, 0.1, 'per_meter', 0.1, 0);
-- ═══════════════════════════════════════════════════════════════════════════
-- COMPLETION STATUS
-- ═══════════════════════════════════════════════════════════════════════════
SELECT '✅ BATCH 3 SMART PACKAGES OPRETTET!' as STATUS;
SELECT name as 'PAKKE NAVN', category as KATEGORI, difficulty_level as SVÆRHEDSGRAD, estimated_hours as 'BASE TIMER'
FROM material_packages
WHERE created_by = 'system' AND DATE(created_at) = CURDATE()
ORDER BY id DESC LIMIT 4;
SELECT '📦 TOTAL OVERSIGT' as INFO;
SELECT
mp.category as 'KATEGORI',
COUNT(DISTINCT mp.id) as 'ANTAL PAKKER',
SUM(CASE WHEN spt.id IS NOT NULL THEN 1 ELSE 0 END) as 'TOTAL OPGAVER',
SUM(CASE WHEN pm.id IS NOT NULL THEN 1 ELSE 0 END) as 'TOTAL MATERIALER'
FROM material_packages mp
LEFT JOIN smart_package_tasks spt ON mp.id = spt.package_id
LEFT JOIN package_materials pm ON pm.package_id = mp.id
WHERE mp.created_by = 'system'
GROUP BY mp.category
ORDER BY COUNT(DISTINCT mp.id) DESC;