Files
tilbudgivern/migrations/create_tomrer_smart_packages_batch_2.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

157 lines
19 KiB
SQL

-- ═══════════════════════════════════════════════════════════════════════════
-- TØMRER SMART PACKAGES - BATCH 2 (Pakker 6-11)
-- Oprettet: 2025-10-31
-- Matcher eksisterende database struktur 100%
-- ═══════════════════════════════════════════════════════════════════════════
USE tilbudgivern;
-- ═══════════════════════════════════════════════════════════════════════════
-- PAKKE 6: Taghætte B7
-- ═══════════════════════════════════════════════════════════════════════════
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 taghætte B7', 'Komplet montering af taghætte til B7 eternit tag inkl. tætning og blyfals. Auto-beregner per stk taghætte med præcis tidsestimat.', 'Tagarbejde', 'mellem', 1.5, 580, 'BEREGNING: 1.5 time per taghætte. Vigtig korrekt tætning omkring krave for at undgå vandindtrængning.', 1, 0, 1, 'system');
SET @taghaette_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
(@taghaette_pkg, 'ETERNIT TAGHÆTTE B7 [087521]', 'Tagmateriale', 1, 'stk', 385.00, 385.00, 'Eternit', 'Per taghætte', 'antal_taghaetter', 1),
(@taghaette_pkg, 'TAGHÆTTEKRAVE UNIVERSAL [145678]', 'Tagmateriale', 1, 'stk', 225.00, 225.00, 'Eternit', 'Per taghætte', 'antal_taghaetter', 1),
(@taghaette_pkg, 'BLYFALS 30CM BRED [092134]', 'Tætning', 2, 'meter', 145.00, 290.00, 'Bygma', '2 meter per taghætte', 'antal_taghaetter * 2', 2),
(@taghaette_pkg, 'ETERNIT TAGSKRUER 100 STK [144855]', 'Beslag', 0.08, 'pakke', 115.00, 9.20, 'Eternit', '8 skruer per taghætte', 'antal_taghaetter * 8 / 100', 0.08),
(@taghaette_pkg, 'SILIKONE NEUTRAL [050043]', 'Forsegling', 1, 'stk', 55.00, 55.00, 'Bygma', 'Per taghætte', 'antal_taghaetter', 1);
INSERT INTO smart_package_tasks (package_id, name, description, task_order, hours, time_unit, time_per_unit, is_custom) VALUES
(@taghaette_pkg, 'Udskæring af hul i tagdækning', 'Præcis udskæring i B7 plader til taghætte. Måler og markerer placering af taghætte på tag - Udskærer hul med præcision i B7 plader - Sikrer korrekt størrelse til taghættekrave', 1, 0.3, 'per_piece', 0.3, 0),
(@taghaette_pkg, 'Montering af taghættekrave', 'Fastgørelse af universal taghættekrave. Taghættekrave monteres omkring udskæring - Fastgøres til lægter med skruer - Sikres mod vindpåvirkning', 2, 0.4, 'per_piece', 0.4, 0),
(@taghaette_pkg, 'Anbringelse af blyfals', 'Tætning med blyfals omkring krave. Blyfals tilskæres og formes (2 meter per taghætte) - Anbringes korrekt omkring taghættekrave - Presses ned mellem tagplader for optimal tætning - Sikrer vandtæthed ved alle samlinger', 3, 0.5, 'per_piece', 0.5, 0),
(@taghaette_pkg, 'Montering af taghætte og tætning', 'Færdiggørelse med taghætte og silikone. Taghætte monteres på krave og fastgøres - Silikone påføres ved alle samlinger - Kontrol af tæthed og funktion - Alt affald bortkøres', 4, 0.3, 'per_piece', 0.3, 0);
-- ═══════════════════════════════════════════════════════════════════════════
-- PAKKE 7: Nedrivning betontegl
-- ═══════════════════════════════════════════════════════════════════════════
INSERT INTO material_packages (name, description, category, difficulty_level, estimated_hours, hourly_rate, installation_notes, is_template, area_based, time_per_sqm, auto_calculated, created_by)
VALUES ('Nedrivning af betontegl', 'Professionel nedrivning af eksisterende betontagsten inkl. sortering og bortkørsel. Auto-beregner per m² med containerleje.', 'Nedrivning', 'mellem', 0.3, 580, 'BEREGNING: 0.3 time per m². Inkluderer sikkerhed, nedtagning, sortering og bortkørsel til godkendt modtagestation.', 1, 1, 0.3, 1, 'system');
SET @nedrivning_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
(@nedrivning_pkg, 'CONTAINER 10M3 LEJE UGE [CONT10]', 'Affaldshåndtering', 0.1, 'stk', 1250.00, 125.00, 'Lokal renovator', 'Ca. 10m² per container', 'tagareal / 100', 0.1, 1.00),
(@nedrivning_pkg, 'AFFALDSGEBYR BETONTEGL TON [AFGIFT]', 'Affaldshåndtering', 0.15, 'ton', 450.00, 67.50, 'Genbrugsstation', 'Ca. 15kg per m²', 'tagareal * 15 / 1000', 0.15, 1.00),
(@nedrivning_pkg, 'SIKKERHEDSNET 3X6M [SIKNET]', 'Sikkerhed', 0.33, 'stk', 385.00, 128.00, 'Bygma', 'Per 100m² ca. 2 net', 'tagareal / 300', 0.33, 1.00);
INSERT INTO smart_package_tasks (package_id, name, description, task_order, hours, time_unit, time_per_unit, is_custom) VALUES
(@nedrivning_pkg, 'Sikring af arbejdsområde', 'Etablering af sikkerhed og afspærring. Sikkerhedsnet og stillads etableres - Arbejdsområde afspærres og markeres - Adgangsveje sikres - Beskyttelse af vinduer og have', 1, 2.0, 'per_project', 2.0, 0),
(@nedrivning_pkg, 'Nedtagning af betontagsten', 'Systematisk nedrivning af tag per m². Tagsten løsnes forsigtigt række for række - Nedkastes i container eller sækkes - Hensyn tages til undertag for ikke at beskadige', 2, 0.2, 'per_sqm', 0.2, 0),
(@nedrivning_pkg, 'Fjernelse af lægter og søm', 'Nedrivning af gamle lægter. Lægter afmonteres og søm fjernes - Materialer sorteres til genbrug eller affald - Spær rengøres for gamle søm og splinter', 3, 0.08, 'per_sqm', 0.08, 0),
(@nedrivning_pkg, 'Sortering og bortkørsel', 'Affaldshåndtering og oprydning. Alt tagaffald sorteres korrekt - Betontegl læsses i container - Container bestilles til afhentning - Arbejdsområde fejes og ryddes', 4, 0.02, 'per_sqm', 0.02, 0);
-- ═══════════════════════════════════════════════════════════════════════════
-- PAKKE 8: Fast undertag
-- ═══════════════════════════════════════════════════════════════════════════
INSERT INTO material_packages (name, description, category, difficulty_level, estimated_hours, hourly_rate, installation_notes, is_template, area_based, time_per_sqm, auto_calculated, created_by)
VALUES ('Montering af fast undertag', 'Komplet undertag system med tagpap, lægter og membran. Auto-beregner per m² med præcis materialeliste og arbejdstid.', 'Tagarbejde', 'mellem', 0.15, 580, 'BEREGNING: 0.15 time per m². Vigtigt med korrekt overlap (minimum 10cm) for vandtæthed.', 1, 1, 0.15, 1, 'system');
SET @undertag_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
(@undertag_pkg, 'UNDERTAGS PAP S-120 10M [UTP120]', 'Tagmateriale', 0.1, 'rulle', 425.00, 42.50, 'Bygma', 'Per m² (10m per rulle)', 'tagareal / 10', 0.1, 1.10),
(@undertag_pkg, 'UNDERTAG MEMBRAN DIFFUSERBAR [UTMEM]', 'Tagmateriale', 1.0, '', 35.00, 35.00, 'Bygma', 'Per m²', 'tagareal', 1.0, 1.10),
(@undertag_pkg, 'LÆGTE 30X50MM TRYKIMP. 420CM [1632038073]', 'Tømmer', 0.5, 'lbm', 18.50, 9.25, 'Trælast', 'Kontralægter hver 2 meter', 'tagareal * 0.5', 0.5, 1.05),
(@undertag_pkg, 'TÆTNINGSBÅND UNDERTAG 30M [TBAAND]', 'Tætning', 0.033, 'rulle', 185.00, 6.11, 'Bygma', 'Ved overlap', 'tagareal / 30', 0.033, 1.00),
(@undertag_pkg, 'KLAMMER UNDERTAG 5000 STK [KLAM]', 'Beslag', 0.002, 'pakke', 145.00, 0.29, 'Bygma', '10 per m²', 'tagareal * 10 / 5000', 0.002, 1.00);
INSERT INTO smart_package_tasks (package_id, name, description, task_order, hours, time_unit, time_per_unit, is_custom) VALUES
(@undertag_pkg, 'Montering af kontralægter', 'Fastgørelse af lægter på spær. Kontralægter monteres på spær med ca. 2m afstand - Sikrer luftspalte under membran - Fastgøres med søm eller skruer', 1, 0.05, 'per_sqm', 0.05, 0),
(@undertag_pkg, 'Udlægning af undertags membran', 'Anbringelse af diffuserbar membran. Membran rulles ud fra tagfod mod ryg - Minimum 10cm overlap mellem baner - Fastgøres midlertidigt med klammer', 2, 0.06, 'per_sqm', 0.06, 0),
(@undertag_pkg, 'Anbringelse af tætningsbånd', 'Tætning ved overlap og samlinger. Tætningsbånd påføres ved alle overlap - Sikrer vandtæthed ved samlinger - Anbringes korrekt efter fabrikantens anvisning', 3, 0.02, 'per_sqm', 0.02, 0),
(@undertag_pkg, 'Fastgørelse med lægter', 'Final sikring af undertag. Lægter monteres ovenpå membran - Fastgøres gennem til kontralægter - Sikrer membran mod vindpåvirkning - Kvalitetskontrol af overlap og tæthed', 4, 0.02, 'per_sqm', 0.02, 0);
-- ═══════════════════════════════════════════════════════════════════════════
-- PAKKE 9: Vindskeder
-- ═══════════════════════════════════════════════════════════════════════════
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 vindskeder', 'Komplet montering af vindskeder i trykimprægneret eller mahogni. Auto-beregner per løbende meter med skruer og plugs.', 'Tagarbejde', 'mellem', 0.25, 580, 'BEREGNING: 0.25 time per løbende meter. Vindskeder beskytter gavlene mod vejr og vind.', 1, 0, 1, 'system');
SET @vindskeder_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
(@vindskeder_pkg, 'VINDSKED 22X200MM TRYKIMP. 480CM [VSD22200]', 'Tømmer', 1.0, 'lbm', 58.00, 58.00, 'Trælast', 'Per løbende meter', 'vindskeder_laengde', 1.0, 1.05),
(@vindskeder_pkg, 'RUSTFRI SKRUER 5X70MM 200 STK [RFS570]', 'Beslag', 0.015, 'pakke', 165.00, 2.48, 'Bygma', '3 skruer per meter', 'vindskeder_laengde * 3 / 200', 0.015, 1.00),
(@vindskeder_pkg, 'MALING TRÆBESKYTTELSE 5L [TRMALING]', 'Finish', 0.033, 'spand', 385.00, 12.71, 'Bygma', '0.15L per meter', 'vindskeder_laengde * 0.15 / 5', 0.033, 1.00),
(@vindskeder_pkg, 'PLUGS 8MM 100 STK [PLUG8]', 'Beslag', 0.03, 'pakke', 45.00, 1.35, 'Bygma', '3 per meter', 'vindskeder_laengde * 3 / 100', 0.03, 1.00);
INSERT INTO smart_package_tasks (package_id, name, description, task_order, hours, time_unit, time_per_unit, is_custom) VALUES
(@vindskeder_pkg, 'Opmåling og tilskæring', 'Præcis opmåling af gavllængde. Vindskeder opmåles til korrekt længde - Vinkelsnit ved top og bund udføres - Alle skæringer kontrolleres for præcision', 1, 0.08, 'per_meter', 0.08, 0),
(@vindskeder_pkg, 'Grundbehandling af træ', 'Maling eller imprægnering før montering. Vindskeder grundbehandles med træbeskyttelse - Særlig fokus på endeskæringer - Tørretid overholdes', 2, 0.05, 'per_meter', 0.05, 0),
(@vindskeder_pkg, 'Montering på gavl', 'Fastgørelse af vindskeder. Vindskeder monteres lodret på gavl - Fastgøres med rustfri skruer og plugs (3 per meter) - Kontrol med vaterpas - Sikres mod vindpåvirkning', 3, 0.10, 'per_meter', 0.10, 0),
(@vindskeder_pkg, 'Finish og efterbehandling', 'Endelig behandling og kontrol. Skruehuller spartles - Yderligere lag maling påføres - Kvalitetskontrol af fastgørelse - Alt affald bortkøres', 4, 0.02, 'per_meter', 0.02, 0);
-- ═══════════════════════════════════════════════════════════════════════════
-- PAKKE 10: Udhængsbrædder
-- ═══════════════════════════════════════════════════════════════════════════
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 udhængsbrædder', 'Komplet montering af udhængsbrædder ved tagfod. Auto-beregner per løbende meter inkl. maling og beslag.', 'Tagarbejde', 'mellem', 0.20, 580, 'BEREGNING: 0.20 time per løbende meter. Udhængsbrædder giver flot finish ved tagfod.', 1, 0, 1, 'system');
SET @udhaengs_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
(@udhaengs_pkg, 'UDHÆNGSBRÆT 22X150MM TRYKIMP. 480CM [UHB22150]', 'Tømmer', 1.0, 'lbm', 42.00, 42.00, 'Trælast', 'Per løbende meter', 'udhaengs_laengde', 1.0, 1.05),
(@udhaengs_pkg, 'RUSTFRI SKRUER 4,5X60MM 200 STK [RFS4560]', 'Beslag', 0.02, 'pakke', 155.00, 3.10, 'Bygma', '4 skruer per meter', 'udhaengs_laengde * 4 / 200', 0.02, 1.00),
(@udhaengs_pkg, 'MALING UDVENDIGT 5L [UDMALING]', 'Finish', 0.02, 'spand', 425.00, 8.50, 'Bygma', '0.1L per meter', 'udhaengs_laengde * 0.1 / 5', 0.02, 1.00),
(@udhaengs_pkg, 'SPARTEL UDENDØRS [SPARTEL]', 'Finish', 0.05, 'tube', 45.00, 2.25, 'Bygma', 'Efter behov', NULL, 0.05, 1.00);
INSERT INTO smart_package_tasks (package_id, name, description, task_order, hours, time_unit, time_per_unit, is_custom) VALUES
(@udhaengs_pkg, 'Opmåling og forberedelse', 'Måling af tagfod til præcis længde. Udhængsbrædder opmåles langs tagfod - Tilskæres til korrekt længde - Grundbehandles med træbeskyttelse', 1, 0.06, 'per_meter', 0.06, 0),
(@udhaengs_pkg, 'Montering ved tagfod', 'Fastgørelse af brædder ved tagfod. Udhængsbrædder monteres præcist ved tagfod - Fastgøres med rustfri skruer (4 per meter) - Vandret linje kontrolleres med vaterpas - Sikres mod vindpåvirkning', 2, 0.10, 'per_meter', 0.10, 0),
(@udhaengs_pkg, 'Spartling og finish', 'Efterbehandling for flot resultat. Skruehuller og samlinger spartles - Overflade slibes glat - 2 lag maling påføres - Kvalitetskontrol', 3, 0.04, 'per_meter', 0.04, 0);
-- ═══════════════════════════════════════════════════════════════════════════
-- PAKKE 11: Sternbrædder
-- ═══════════════════════════════════════════════════════════════════════════
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 sternbrædder', 'Komplet montering af sternbrædder i kvalitetstræ. Auto-beregner per løbende meter med beslag og finish.', 'Tagarbejde', 'mellem', 0.30, 580, 'BEREGNING: 0.30 time per løbende meter. Sternbrædder er vigtige for tagets beskyttelse og æstetik.', 1, 0, 1, 'system');
SET @stern_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
(@stern_pkg, 'STERNBRÆT 22X225MM TRYKIMP. 480CM [STB22225]', 'Tømmer', 1.0, 'lbm', 68.00, 68.00, 'Trælast', 'Per løbende meter', 'stern_laengde', 1.0, 1.05),
(@stern_pkg, 'STERNBESLAG GALVANISERET [STBSL]', 'Beslag', 0.5, 'stk', 24.00, 12.00, 'Bygma', '1 beslag per 2 meter', 'stern_laengde / 2', 0.5, 1.00),
(@stern_pkg, 'RUSTFRI SKRUER 5X80MM 200 STK [RFS580]', 'Beslag', 0.02, 'pakke', 175.00, 3.50, 'Bygma', '4 skruer per meter', 'stern_laengde * 4 / 200', 0.02, 1.00),
(@stern_pkg, 'MALING TRÆBESKYTTELSE 5L [TRMALING]', 'Finish', 0.04, 'spand', 385.00, 15.40, 'Bygma', '0.2L per meter', 'stern_laengde * 0.2 / 5', 0.04, 1.00),
(@stern_pkg, 'VINKEL STERNBRÆT [VINKST]', 'Beslag', 2, 'stk', 35.00, 70.00, 'Bygma', '2 vinkler per projekt (hjørner)', NULL, 2, 1.00);
INSERT INTO smart_package_tasks (package_id, name, description, task_order, hours, time_unit, time_per_unit, is_custom) VALUES
(@stern_pkg, 'Opmåling og tilskæring', 'Præcis opmåling langs tagkant. Sternbrædder opmåles til korrekt længde - Vinkelsnit ved hjørner udføres præcist - Grundbehandles med træbeskyttelse', 1, 0.10, 'per_meter', 0.10, 0),
(@stern_pkg, 'Montering af beslag', 'Fastgørelse af sternbeslag. Sternbeslag monteres på spær (1 per 2 meter) - Sikrer solid fastgørelse af sternbrædder - Kontrol af afstande og placering', 2, 0.05, 'per_meter', 0.05, 0),
(@stern_pkg, 'Montering af sternbrædder', 'Fastgørelse af brædder på beslag. Sternbrædder monteres i beslag - Fastgøres med rustfri skruer (4 per meter) - Vinkler monteres ved hjørner - Vandret linje kontrolleres', 3, 0.12, 'per_meter', 0.12, 0),
(@stern_pkg, 'Finish og kvalitetskontrol', 'Efterbehandling og kontrol. Skruehuller spartles - 2 lag maling påføres for beskyttelse - Kvalitetskontrol af fastgørelse - Alt affald bortkøres', 4, 0.03, 'per_meter', 0.03, 0);
-- ═══════════════════════════════════════════════════════════════════════════
-- COMPLETION STATUS
-- ═══════════════════════════════════════════════════════════════════════════
SELECT '✅ BATCH 2 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 6;
SELECT '📦 DETALJER BATCH 2' as INFO;
SELECT
mp.name as 'PAKKE',
COUNT(DISTINCT spt.id) as 'ANTAL OPGAVER',
COUNT(DISTINCT pm.id) as 'ANTAL 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' AND DATE(mp.created_at) = CURDATE()
GROUP BY mp.id
ORDER BY mp.id DESC LIMIT 6;