499 lines
12 KiB
Markdown
499 lines
12 KiB
Markdown
# Ordrestyring GraphQL Integration
|
|
|
|
## Oversigt
|
|
|
|
Denne integration sender komplette tilbud til Ordrestyring via GraphQL API med detaljeret beskrivelse i professionelt format.
|
|
|
|
## Features
|
|
|
|
✅ **Detaljeret tilbudsbeskrivelse** med alle sektioner:
|
|
- ARBEJDE DER UDFØRES (geometri, arbejdstimer, opgavebeskrivelse)
|
|
- MATERIALER (komplet liste med priser)
|
|
- PRISSPECIFIKATION (arbejde, materialer, moms, totaler)
|
|
- VORES LØFTE TIL DIG (firma kvalitetsløfter)
|
|
- GARANTIER OG SERVICE (garantier og support)
|
|
- Standard tekster og disclaimers
|
|
|
|
✅ **Interne beregninger i bemærkninger** (ikke synlige for kunde):
|
|
- Overhead og fortjeneste gemmes i `notes` feltet
|
|
- Kun synligt internt i Ordrestyring systemet
|
|
- Fuld transparens internt uden at afsløre fortjeneste til kunde
|
|
|
|
✅ **Automatisk status**: Sætter "Tilbudsgiver Oprettet Tilbud" når tilbud oprettes
|
|
|
|
✅ **Dansk prisformatering** med tusindtalsseparator (195.906,94 kr)
|
|
|
|
✅ **GraphQL API integration** via https://graphql.ordrestyring.dk/graphql
|
|
|
|
✅ **Automatisk synkronisering** mellem lokal database og Ordrestyring
|
|
|
|
✅ **Status tracking** af tilbud i begge systemer
|
|
|
|
## API Endpoints
|
|
|
|
### 1. Preview Tilbudsbeskrivelse
|
|
|
|
**Endpoint:** `POST /api/ordrestyring/preview-description`
|
|
|
|
Preview tilbudsbeskrivelse uden at sende til Ordrestyring.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"projectId": 1,
|
|
"calculationId": 1
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"description": "Tilbud på udskiftning af tag...\n\nARBEJDE DER UDFØRES:\n...",
|
|
"internalNotes": "INTERNE BEREGNINGER (kun synlig internt):\n\nSubtotal: 116.093,00 kr\nOverhead (15%): 17.413,95 kr\nFortjeneste (20%): 23.218,60 kr\n...",
|
|
"priceExVat": 156725.55,
|
|
"priceInclVat": 195906.94,
|
|
"materials": [...]
|
|
}
|
|
```
|
|
|
|
### 2. Send Tilbud til Ordrestyring
|
|
|
|
**Endpoint:** `POST /api/ordrestyring/send-quote-graphql`
|
|
|
|
Send tilbud til Ordrestyring med komplet beskrivelse via GraphQL API.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"projectId": 1,
|
|
"calculationId": 1,
|
|
"customerId": 5358
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Tilbud sendt til Ordrestyring via GraphQL",
|
|
"offer": {
|
|
"id": 12345,
|
|
"number": "TIL-2024-00123",
|
|
"createdAt": "2024-01-15T10:30:00Z",
|
|
"customer": {
|
|
"id": 5358,
|
|
"name": "Anders Jensen"
|
|
},
|
|
"totals": {
|
|
"salesPrice": 156725.55,
|
|
"salesPriceWithVat": 195906.94
|
|
},
|
|
"status": {
|
|
"id": 999926,
|
|
"text": "Tilbudsgiver Oprettet Tilbud"
|
|
}
|
|
},
|
|
"description": "..."
|
|
}
|
|
```
|
|
|
|
**Funktionalitet:**
|
|
- Sender komplet tilbudsbeskrivelse til kunde (uden overhead/fortjeneste)
|
|
- Gemmer interne beregninger i `notes` feltet (kun synligt internt i Ordrestyring)
|
|
- Sætter automatisk status til "Tilbudsgiver Oprettet Tilbud"
|
|
- Opdaterer lokal database med Ordrestyring reference
|
|
|
|
### 3. Hent Tilbud fra Ordrestyring
|
|
|
|
**Endpoint:** `GET /api/ordrestyring/offer/:offerId`
|
|
|
|
Hent detaljer om tilbud fra Ordrestyring.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"offer": {
|
|
"id": 12345,
|
|
"number": "TIL-2024-00123",
|
|
"description": "...",
|
|
"customer": {
|
|
"id": 5358,
|
|
"name": "Anders Jensen",
|
|
"email": "[email protected]"
|
|
},
|
|
"lines": [
|
|
{
|
|
"id": 1,
|
|
"description": "Eternit B7 tagplader",
|
|
"quantity": 223,
|
|
"unit": "stk",
|
|
"unitPrice": 245.00,
|
|
"total": 54635.00
|
|
}
|
|
],
|
|
"totals": {...},
|
|
"status": {...}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Opdater Tilbud Status
|
|
|
|
**Endpoint:** `POST /api/ordrestyring/offer/:offerId/status`
|
|
|
|
Opdater status på tilbud i Ordrestyring.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"statusId": 2
|
|
}
|
|
```
|
|
|
|
Status IDs:
|
|
- 1: Draft
|
|
- 2: Sent
|
|
- 3: Accepted
|
|
- 4: Rejected
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Tilbud status opdateret",
|
|
"offer": {
|
|
"id": 12345,
|
|
"number": "TIL-2024-00123",
|
|
"status": {
|
|
"id": 2,
|
|
"text": "Sent"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Database Schema
|
|
|
|
Nye kolonner i `generated_quotes` tabel:
|
|
|
|
```sql
|
|
ALTER TABLE generated_quotes
|
|
ADD COLUMN ordrestyring_offer_id INT NULL COMMENT 'Offer ID fra Ordrestyring GraphQL API',
|
|
ADD COLUMN ordrestyring_offer_number VARCHAR(50) NULL COMMENT 'Offer nummer fra Ordrestyring',
|
|
ADD COLUMN ordrestyring_sent_at DATETIME NULL COMMENT 'Tidspunkt for afsendelse til Ordrestyring',
|
|
ADD COLUMN ordrestyring_status VARCHAR(50) NULL COMMENT 'Status fra Ordrestyring';
|
|
```
|
|
|
|
## Service Arkitektur
|
|
|
|
### OrdrestyringQuoteService
|
|
|
|
**Location:** `backend/src/services/ordrestyringQuoteService.js`
|
|
|
|
**Metoder:**
|
|
|
|
1. **generateDetailedQuoteDescription(projectId, calculationId)**
|
|
- Henter projekt, geometri, arbejdstimer, materialer, beregninger
|
|
- Formaterer komplet beskrivelse med alle sektioner (ARBEJDE, MATERIALER, PRISER, LØFTER, GARANTIER)
|
|
- Genererer interne noter med overhead og fortjeneste (gemmes i `notes`)
|
|
- Beregner priser med overhead og fortjeneste inkluderet
|
|
- Returnerer beskrivelse, internalNotes og pris data
|
|
|
|
2. **getOfferStatusId(statusText)**
|
|
- Henter alle tilgængelige statusser fra Ordrestyring
|
|
- Finder status ID baseret på tekst (case-insensitive)
|
|
- Fallback til "Nyt tilbud" hvis status ikke findes
|
|
- Bruges til at sætte "Tilbudsgiver Oprettet Tilbud" status
|
|
|
|
3. **sendQuoteToOrdrestyring(projectId, calculationId, customerId)**
|
|
- Genererer beskrivelse via generateDetailedQuoteDescription()
|
|
- Henter status ID for "Tilbudsgiver Oprettet Tilbud"
|
|
- Sender GraphQL createOffer mutation med:
|
|
* `description`: Komplet tilbudsbeskrivelse (til kunde)
|
|
* `notes`: Interne beregninger med overhead/fortjeneste (kun internt)
|
|
* `statusId`: "Tilbudsgiver Oprettet Tilbud"
|
|
* `lines`: Materialer som offer linjer
|
|
- Gemmer Ordrestyring reference i lokal database
|
|
- Returnerer offer data fra Ordrestyring
|
|
|
|
3. **getOfferFromOrdrestyring(offerId)**
|
|
- Henter tilbud via GraphQL query
|
|
- Returnerer komplet offer med linjer og status
|
|
|
|
4. **updateOfferStatus(offerId, statusId)**
|
|
- Opdaterer status via GraphQL mutation
|
|
- Logger status ændring
|
|
|
|
## Interne Beregninger
|
|
|
|
Overhead og fortjeneste beregnes men vises **KUN** i interne noter:
|
|
|
|
### Kunde Beskrivelse (description)
|
|
```
|
|
PRISSPECIFIKATION:
|
|
|
|
Arbejdsløn: 49.590,00 kr
|
|
Materialer: 66.503,00 kr
|
|
─────────────────────────────
|
|
Subtotal: 116.093,00 kr
|
|
Moms (25%): 39.181,39 kr
|
|
─────────────────────────────
|
|
|
|
SAMLET PRIS EKS. MOMS: 156.725,55 kr
|
|
INKL. MOMS (25%): 195.906,94 kr
|
|
```
|
|
|
|
### Interne Noter (notes - kun synligt internt)
|
|
```
|
|
INTERNE BEREGNINGER (kun synlig internt):
|
|
|
|
Subtotal (arbejde + materialer): 116.093,00 kr
|
|
Overhead (15%): 17.413,95 kr
|
|
Fortjeneste (20%): 23.218,60 kr
|
|
─────────────────────────────
|
|
Pris ex. moms: 156.725,55 kr
|
|
Moms (25%): 39.181,39 kr
|
|
Pris inkl. moms: 195.906,94 kr
|
|
|
|
Projektinfo:
|
|
- Projekt ID: 1
|
|
- Beregning ID: 1
|
|
- Oprettet: 2024-01-15T10:30:00Z
|
|
- Timepris: 580,00 kr/time
|
|
- Timer total: 85.5 timer
|
|
```
|
|
|
|
Dette sikrer:
|
|
- ✅ Kunden ser kun arbejde, materialer og totaler
|
|
- ✅ Interne medarbejdere kan se fuld kalkulation i Ordrestyring
|
|
- ✅ Transparens internt uden at afsløre fortjeneste eksternt
|
|
|
|
## Tilbudsstatus
|
|
|
|
Når tilbud sendes til Ordrestyring sættes automatisk status:
|
|
|
|
**"Tilbudsgiver Oprettet Tilbud"**
|
|
|
|
Servicen finder automatisk det korrekte status ID ved at:
|
|
1. Hente alle tilgængelige statusser fra Ordrestyring API
|
|
2. Søge efter "Tilbudsgiver Oprettet Tilbud" (case-insensitive)
|
|
3. Bruge det fundne status ID i createOffer mutation
|
|
4. Fallback til "Nyt tilbud" hvis status ikke findes
|
|
|
|
Status kan senere opdateres via `updateOfferStatus()` metoden.
|
|
|
|
## Beskrivelsesformat
|
|
|
|
Genereret beskrivelse følger dette format:
|
|
|
|
```
|
|
Tilbud på [projekt beskrivelse].
|
|
|
|
ARBEJDE DER UDFØRES:
|
|
|
|
[Tag type / arbejdstype]
|
|
|
|
Opmåling og forberedelse
|
|
• Samlet areal: X m²
|
|
• Taghældning: X°
|
|
• Taghøjde: X m
|
|
|
|
Arbejdsudførelse
|
|
• Arbejdstimer: X timer med X tømrere
|
|
• Timepris: X kr/time
|
|
• Alt affald bortkøres
|
|
• Området ryddes og efterlades rent
|
|
|
|
MATERIALER:
|
|
|
|
• Material 1: X stk - XX.XXX,XX kr
|
|
• Material 2: X stk - XX.XXX,XX kr
|
|
...
|
|
|
|
Vi indhenter tilbud fra flere leverandører...
|
|
|
|
PRISSPECIFIKATION:
|
|
|
|
Arbejdsløn: XX.XXX,XX kr
|
|
Materialer: XX.XXX,XX kr
|
|
─────────────────────────────
|
|
Subtotal: XX.XXX,XX kr
|
|
Moms (25%): XX.XXX,XX kr
|
|
─────────────────────────────
|
|
|
|
SAMLET PRIS EKS. MOMS: XXX.XXX,XX kr
|
|
|
|
INKL. MOMS (25%): XXX.XXX,XX kr
|
|
|
|
*Hvis der bruges kortere tid...
|
|
|
|
VORES LØFTE TIL DIG:
|
|
|
|
• Præcision og ordentlighed i alt hvad vi laver
|
|
• Tradition og transformation går hånd i hånd
|
|
• Meningsfuldt, ordentligt og bæredygtigt håndværk
|
|
• Vi kommer til tiden og står inde for vores arbejde
|
|
|
|
Vi leverer altid det aftalte...
|
|
|
|
GARANTIER OG SERVICE:
|
|
|
|
• Gratis tagtjek tilbydes
|
|
• Professionel rådgivning i valg af løsninger
|
|
• Korrekt dokumentation og tryghed
|
|
• Kvalitet der holder i mange år
|
|
|
|
TILBUDDET ER GYLDIGT I 30 DAGE
|
|
|
|
Der tages forbehold for rød og svamp...
|
|
|
|
Med venlig hilsen
|
|
Tømrer- og Snedkermester Mikael Holck ApS
|
|
...
|
|
```
|
|
|
|
## Test Script
|
|
|
|
**Location:** `test_ordrestyring_integration.py`
|
|
|
|
Kør test:
|
|
```bash
|
|
python3 test_ordrestyring_integration.py
|
|
```
|
|
|
|
Test scriptet:
|
|
1. Genererer preview af beskrivelse
|
|
2. Spørger om tilladelse til at sende
|
|
3. Sender tilbud til Ordrestyring
|
|
4. Henter tilbuddet tilbage for verifikation
|
|
|
|
## Frontend Integration
|
|
|
|
### FinalReview Component
|
|
|
|
Tilføj "Send til Ordrestyring" knap:
|
|
|
|
```javascript
|
|
const sendToOrdrestyring = async () => {
|
|
try {
|
|
setLoading(true);
|
|
|
|
const response = await fetch('/api/ordrestyring/send-quote-graphql', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
projectId: project.id,
|
|
calculationId: calculation.id,
|
|
customerId: project.ordrestyring_customer_id // Fra kunde record
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
setOfferNumber(data.offer.number);
|
|
showSuccess(`Tilbud sendt til Ordrestyring: ${data.offer.number}`);
|
|
} else {
|
|
showError(data.error);
|
|
}
|
|
} catch (error) {
|
|
showError('Fejl ved afsendelse til Ordrestyring');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
{/* Eksisterende UI */}
|
|
|
|
<button
|
|
onClick={sendToOrdrestyring}
|
|
disabled={loading || !project.ordrestyring_customer_id}
|
|
className="btn-primary"
|
|
>
|
|
📤 Send til Ordrestyring
|
|
</button>
|
|
|
|
{offerNumber && (
|
|
<div className="success-message">
|
|
✅ Sendt til Ordrestyring: {offerNumber}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### Environment Variables
|
|
|
|
```bash
|
|
# .env
|
|
ORDRESTYRING_GRAPHQL_URL=https://graphql.ordrestyring.dk/graphql
|
|
ORDRESTYRING_API_KEY=<ORDRESTYRING_API_TOKEN>
|
|
```
|
|
|
|
### Dependencies
|
|
|
|
Tilføjet i `backend/package.json`:
|
|
```json
|
|
{
|
|
"dependencies": {
|
|
"graphql": "^16.8.1",
|
|
"graphql-request": "^6.1.0"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Workflow
|
|
|
|
1. **Kunde opretter projekt** → Projekt gemt i customer_projects
|
|
2. **AI/Manual beregning** → Beregning gemt i project_calculations
|
|
3. **Final Review** → Bruger ser komplet tilbud
|
|
4. **Send til Ordrestyring** → Click button
|
|
- generateDetailedQuoteDescription() genererer beskrivelse
|
|
- sendQuoteToOrdrestyring() sender via GraphQL
|
|
- Offer ID og nummer gemt i database
|
|
5. **Status tracking** → Synkroniser status mellem systemer
|
|
6. **Kunde godkender** → updateOfferStatus(offerId, 3)
|
|
|
|
## Error Handling
|
|
|
|
Service håndterer fejl gracefully:
|
|
|
|
- **Database fejl**: Logger og returnerer specifik fejl
|
|
- **GraphQL fejl**: Parser fejl fra Ordrestyring API
|
|
- **Network fejl**: Timeout efter 10 sekunder
|
|
- **Validation fejl**: Checker required fields før API kald
|
|
|
|
Alle fejl logges via Winston logger:
|
|
```javascript
|
|
logger.error('Error sending quote to Ordrestyring:', error);
|
|
```
|
|
|
|
## Security
|
|
|
|
- ✅ API key gemt i environment variable
|
|
- ✅ Authorization header med Bearer token
|
|
- ✅ Input validation på alle endpoints
|
|
- ✅ SQL injection protection via parameterized queries
|
|
- ✅ HTTPS til Ordrestyring API
|
|
|
|
## Fremtidige Forbedringer
|
|
|
|
1. **Webhook integration**: Modtag status opdateringer fra Ordrestyring
|
|
2. **Batch sending**: Send flere tilbud på én gang
|
|
3. **Automatic sync**: Periodisk synkronisering af status
|
|
4. **Customer mapping**: Automatisk match mellem lokale kunder og Ordrestyring kunder
|
|
5. **Template system**: Tilpassede beskrivelser per kunde type
|
|
|
|
## Support
|
|
|
|
Ved fejl eller spørgsmål, se:
|
|
- GraphQL dokumentation: `apitest/API_INTEGRATION_GUIDE.md`
|
|
- Service implementation: `backend/src/services/ordrestyringQuoteService.js`
|
|
- Test examples: `apitest/examples/`
|