- 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.
358 lines
No EOL
10 KiB
JSON
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"
|
|
}
|
|
]
|
|
} |