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 <[email protected]>
133 lines
4.7 KiB
Python
133 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Test script to verify PDF generation works correctly
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
sys.path.append('/mnt/HC_Volume_103713257/tilbudgivern/backend')
|
|
|
|
from pdf_generator import TilbudgivernPDFGenerator
|
|
from datetime import datetime
|
|
import json
|
|
|
|
# Create test data that matches the expected structure in pdf_generator.py
|
|
test_data = {
|
|
'quote_number': f'TG-{datetime.now().strftime("%Y%m%d")}-TEST',
|
|
'project': {
|
|
'customer_name': 'Test Kunde A/S',
|
|
'customer_address': 'Testvej 123, 4000 Roskilde',
|
|
'project_description': 'udskiftning af tag på enfamiliehus'
|
|
},
|
|
'geometry': {
|
|
'total_area': 105.0,
|
|
'roof_type': 'Komplet tagudskiftning med B7 eternit',
|
|
'roof_pitch': 35
|
|
},
|
|
'labor': {
|
|
'total_work_hours': 85.5,
|
|
'carpenter_count': 2,
|
|
'hourly_rate': 580.0
|
|
},
|
|
'materials': [
|
|
{
|
|
'material_category': 'Eternit B7 tagplader 920x1320mm',
|
|
'quantity': 223,
|
|
'total_price': 54635.00
|
|
},
|
|
{
|
|
'material_category': 'Lindab Rainline tagrende 125mm',
|
|
'quantity': 14,
|
|
'total_price': 3430.00
|
|
},
|
|
{
|
|
'material_category': 'Trykimprægnerede lægter 30x50mm',
|
|
'quantity': 228,
|
|
'total_price': 4218.00
|
|
},
|
|
{
|
|
'material_category': 'Eternit rygning inkl. elementer',
|
|
'quantity': 12,
|
|
'total_price': 2040.00
|
|
},
|
|
{
|
|
'material_category': 'Lindab nedløb komplet system',
|
|
'quantity': 4,
|
|
'total_price': 2180.00
|
|
}
|
|
],
|
|
'calculation': {
|
|
'total_labor_cost': 49590.00, # 85.5 * 580
|
|
'total_material_cost': 66503.00,
|
|
'overhead_percentage': 15.0,
|
|
'profit_percentage': 20.0,
|
|
'vat_percentage': 25.0
|
|
}
|
|
}
|
|
|
|
# Output paths
|
|
output_pdf = '/mnt/HC_Volume_103713257/tilbudgivern/Test_Tilbud.pdf'
|
|
output_json = '/mnt/HC_Volume_103713257/tilbudgivern/test_data.json'
|
|
|
|
# Save test data to JSON
|
|
print("💾 Gemmer test data til JSON...")
|
|
with open(output_json, 'w', encoding='utf-8') as f:
|
|
json.dump(test_data, f, indent=2, ensure_ascii=False)
|
|
print(f"✅ JSON gemt: {output_json}")
|
|
|
|
# Generate PDF
|
|
print("\n🔨 GENERERER TEST PDF...")
|
|
print(f"📋 Kunde: {test_data['project']['customer_name']}")
|
|
print(f"📍 Adresse: {test_data['project']['customer_address']}")
|
|
print(f"📐 Tagareal: {test_data['geometry']['total_area']} m²")
|
|
print(f"⏱️ Arbejdstimer: {test_data['labor']['total_work_hours']} timer")
|
|
|
|
try:
|
|
generator = TilbudgivernPDFGenerator()
|
|
success = generator.generate_pdf(test_data, output_pdf)
|
|
|
|
if success:
|
|
# Check if file exists and has content
|
|
if os.path.exists(output_pdf):
|
|
file_size = os.path.getsize(output_pdf)
|
|
print(f"\n✅ PDF GENERERET SUCCESFULDT!")
|
|
print(f"📄 Fil: {output_pdf}")
|
|
print(f"📊 Størrelse: {file_size:,} bytes")
|
|
|
|
if file_size > 1000:
|
|
print(f"✓ PDF indeholder data (ikke tom)")
|
|
|
|
# Calculate expected values
|
|
labor = test_data['calculation']['total_labor_cost']
|
|
materials = test_data['calculation']['total_material_cost']
|
|
subtotal = labor + materials
|
|
overhead = subtotal * 0.15
|
|
profit = subtotal * 0.20
|
|
before_vat = subtotal + overhead + profit
|
|
vat = before_vat * 0.25
|
|
total_with_vat = before_vat + vat
|
|
|
|
print(f"\n💰 PRISBEREGNING:")
|
|
print(f" Arbejdsløn: {labor:>12,.2f} kr")
|
|
print(f" Materialer: {materials:>12,.2f} kr")
|
|
print(f" ─────────────────────────────")
|
|
print(f" Subtotal: {subtotal:>12,.2f} kr")
|
|
print(f" Overhead (15%): {overhead:>12,.2f} kr")
|
|
print(f" Profit (20%): {profit:>12,.2f} kr")
|
|
print(f" Moms (25%): {vat:>12,.2f} kr")
|
|
print(f" ═════════════════════════════")
|
|
print(f" TOTAL: {total_with_vat:>12,.2f} kr")
|
|
|
|
print(f"\n✓ PDF kan nu åbnes og verificeres")
|
|
else:
|
|
print(f"⚠️ ADVARSEL: PDF er meget lille ({file_size} bytes) - muligvis tom!")
|
|
else:
|
|
print(f"❌ FEJL: PDF fil blev ikke oprettet!")
|
|
else:
|
|
print(f"❌ FEJL: PDF generering returnerede False")
|
|
|
|
except Exception as e:
|
|
print(f"❌ FEJL ved generering: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|