MEGAINFRA-WEBAPP/docs/n8n/delivery-note-ocr-workflow.json
DEls 8dbf393446 feat(delivery-notes): enhance delivery note upload and processing
- Updated the delivery note upload API to include site ID for better project association.
- Modified the delivery note schema to support additional fields such as processing status, OCR confidence, and received by name.
- Enhanced the UI components for uploading delivery notes to allow file drop functionality and improved user feedback during file selection.
- Refactored purchasing page to integrate delivery notes that require review, providing better visibility and management of pending items.
- Added new KPIs to track active purchase orders and fulfillment percentages, improving overall dashboard insights.
2026-08-26 15:33:30 +02:00

358 lines
No EOL
10 KiB
JSON

{
"name": "Delivery Note OCR",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "seconds",
"secondsInterval": 30
}
]
}
},
"id": "uuid-schedule",
"name": "Poll every 30 s",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [
240,
300
],
"webhookId": ""
},
{
"parameters": {
"method": "GET",
"url": "={{ $env.MEGA_PB_URL }}/api/collections/delivery_notes/records",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "filter",
"value": "=processing_status='pending'"
},
{
"name": "fields",
"value": "id,delivery_note_number,site,project_code,slip_image,purchase_order,date_received"
},
{
"name": "perPage",
"value": "20"
},
{
"name": "sort",
"value": "created"
}
]
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ $credentials.pbToken }}"
}
]
},
"options": {}
},
"id": "uuid-list",
"name": "List pending DNs",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
460,
300
],
"credentials": {
"httpHeaderAuth": {
"id": "REPLACE_PB_TOKEN_ID",
"name": "PocketBase Admin Token"
}
}
},
{
"parameters": {
"batchSize": 1,
"options": {}
},
"id": "uuid-split",
"name": "For each pending DN",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
680,
300
]
},
{
"parameters": {
"method": "GET",
"url": "={{ $env.MEGA_PB_URL }}/api/files/delivery_notes/{{ $json.id }}/{{ $json.slip_image }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ $credentials.pbToken }}"
}
]
},
"options": {}
},
"id": "uuid-download",
"name": "Download slip image",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
900,
300
],
"credentials": {
"httpHeaderAuth": {
"id": "REPLACE_PB_TOKEN_ID",
"name": "PocketBase Admin Token"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://api.openai.com/v1/chat/completions",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer {{ $credentials.openaiKey }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"model\": \"gpt-4o-mini\",\n \"response_format\": { \"type\": \"json_object\" },\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"You are extracting structured data from a delivery note slip image. Return ONLY valid JSON matching this schema: { \\\"type\\\": \\\"object\\\", \\\"properties\\\": { \\\"po_number\\\": { \\\"type\\\": [\\\"string\\\", \\\"null\\\"] }, \\\"delivery_note_number\\\": { \\\"type\\\": [\\\"string\\\", \\\"null\\\"] }, \\\"date_received\\\": { \\\"type\\\": [\\\"string\\\", \\\"null\\\"] }, \\\"received_by_name\\\": { \\\"type\\\": [\\\"string\\\", \\\"null\\\"] }, \\\"confidence\\\": { \\\"type\\\": \\\"number\\\", \\\"minimum\\\": 0, \\\"maximum\\\": 1 }, \\\"items\\\": { \\\"type\\\": \\\"array\\\", \\\"items\\\": { \\\"type\\\": \\\"object\\\", \\\"properties\\\": { \\\"description\\\": { \\\"type\\\": \\\"string\\\" }, \\\"qty\\\": { \\\"type\\\": \\\"number\\\" }, \\\"unit\\\": { \\\"type\\\": [\\\"string\\\", \\\"null\\\"] }, \\\"unit_price\\\": { \\\"type\\\": [\\\"number\\\", \\\"null\\\"] } } } } } }. Rules: confidence is your honest assessment (0.9+ only if everything is clearly legible); quantities must be numbers; if the slip is unreadable return empty items and confidence below 0.3.\"\n },\n {\n \"role\": \"user\",\n \"content\": [\n {\n \"type\": \"text\",\n \"text\": \"Extract the delivery note data from this slip image.\"\n },\n {\n \"type\": \"image_url\",\n \"image_url\": { \"url\": \"data:image/jpeg;base64,{{ $binary.data }}\" }\n }\n ]\n }\n ]\n}",
"options": {}
},
"id": "uuid-ocr",
"name": "OCR via OpenAI Vision",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1120,
300
],
"credentials": {
"httpHeaderAuth": {
"id": "REPLACE_OPENAI_KEY_ID",
"name": "OpenAI API Key"
}
}
},
{
"parameters": {
"jsCode": "// OpenAI returns { choices: [{ message: { content: \"<json string>\" }] }\nconst raw = $json.choices?.[0]?.message?.content;\nif (!raw) throw new Error('No OCR content in response');\nconst parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;\nreturn [{ json: parsed }];"
},
"id": "uuid-parse",
"name": "Parse OCR JSON",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1340,
300
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $env.MEGA_NEXT_URL }}/api/delivery-notes/{{ $('List pending DNs').item.json.id }}/ocr-result",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "x-ingest-secret",
"value": "={{ $env.N8N_INGEST_SECRET }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"po_number\": {{ $json.po_number ? '\"' + $json.po_number + '\"' : 'null' }},\n \"delivery_note_number\": {{ $json.delivery_note_number ? '\"' + $json.delivery_note_number + '\"' : 'null' }},\n \"date_received\": {{ $json.date_received ? '\"' + $json.date_received + '\"' : 'null' }},\n \"received_by_name\": {{ $json.received_by_name ? '\"' + $json.received_by_name + '\"' : 'null' }},\n \"confidence\": {{ $json.confidence || 0 }},\n \"items\": {{ JSON.stringify($json.items || []) }},\n \"raw_ocr\": null\n}",
"options": {
"continueOnFail": true
}
},
"id": "uuid-callback",
"name": "POST result to app",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1560,
300
]
},
{
"parameters": {
"method": "PATCH",
"url": "={{ $env.MEGA_PB_URL }}/api/collections/delivery_notes/records/{{ $('List pending DNs').item.json.id }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ $credentials.pbToken }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={ \"processing_status\": \"ocr-complete\" }",
"options": {
"continueOnFail": true
}
},
"id": "uuid-mark",
"name": "Mark ocr-complete in PB",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1780,
300
],
"credentials": {
"httpHeaderAuth": {
"id": "REPLACE_PB_TOKEN_ID",
"name": "PocketBase Admin Token"
}
}
}
],
"connections": {
"Poll every 30 s": {
"main": [
[
{
"node": "List pending DNs",
"type": "main",
"index": 0
}
]
]
},
"List pending DNs": {
"main": [
[
{
"node": "For each pending DN",
"type": "main",
"index": 0
}
]
]
},
"For each pending DN": {
"main": [
[
{
"node": "Download slip image",
"type": "main",
"index": 0
}
]
]
},
"Download slip image": {
"main": [
[
{
"node": "OCR via OpenAI Vision",
"type": "main",
"index": 0
}
]
]
},
"OCR via OpenAI Vision": {
"main": [
[
{
"node": "Parse OCR JSON",
"type": "main",
"index": 0
}
]
]
},
"Parse OCR JSON": {
"main": [
[
{
"node": "POST result to app",
"type": "main",
"index": 0
}
]
]
},
"POST result to app": {
"main": [
[
{
"node": "Mark ocr-complete in PB",
"type": "main",
"index": 0
}
]
]
},
"Mark ocr-complete in PB": {
"main": [
[
{
"node": "For each pending DN",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"saveExecutionProgress": true,
"saveDataSuccessExecution": "all",
"saveDataErrorExecution": "all",
"saveManualExecutions": true
},
"versionId": "1",
"id": "delivery-note-ocr",
"meta": {
"templateCredsSetupCompleted": false,
"description": "Polls PocketBase every 30 s for delivery_notes with processing_status='pending', OCRs the slip via OpenAI Vision, and POSTs the result back to /api/delivery-notes/{id}/ocr-result."
},
"tags": [
{
"name": "ocr"
},
{
"name": "delivery-notes"
},
{
"name": "pocketbase"
}
]
}