JEISA-WEBAPP/n8n/02-parsed-po-to-invoice.json

544 lines
18 KiB
JSON
Raw Normal View History

{
"name": "JEISA: Parsed PO → QuickBooks Invoice → Email → Stock decrement",
"description": "Polls PocketBase for `customer_pos` records with status='parsed', creates a QuickBooks Online invoice, sends it to the customer via email, and decrements stock_items.quantity for each line item linked to inventory. End state: customer_pos.status='invoiced', invoices.status='sent', stock_items decremented.",
"active": false,
"settings": {
"executionOrder": "v1",
"saveExecutionProgress": true,
"saveManualExecutions": true,
"saveDataSuccessExecution": "all",
"saveDataErrorExecution": "all",
"timezone": "Africa/Johannesburg"
},
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 2
}
]
}
},
"id": "22222222-2222-2222-2222-222222222201",
"name": "Cron: every 2 minutes",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [240, 300]
},
{
"parameters": {
"method": "GET",
"url": "https://pb.jeistk.apphub.co.za/api/collections/customer_pos/records?filter=status%20%3D%20%22parsed%22&expand=customer&perPage=20",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "pocketbaseApi",
"options": {}
},
"id": "22222222-2222-2222-2222-222222222202",
"name": "PB: Fetch customer_pos (status=parsed)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [460, 300],
"credentials": {
"pocketbaseApi": {
"id": "REPLACE_ME_pb_credential",
"name": "JEISA PocketBase"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "c-1",
"leftValue": "={{ $json.items.length }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "22222222-2222-2222-2222-222222222203",
"name": "If: any POs to invoice?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [680, 300]
},
{
"parameters": {
"batchSize": 1,
"options": {}
},
"id": "22222222-2222-2222-2222-222222222204",
"name": "Loop: One PO at a time",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [900, 200]
},
{
"parameters": {
"method": "GET",
"url": "=https://pb.jeistk.apphub.co.za/api/collections/stock_items/records?filter=sku%20%3D%20%22{{ encodeURIComponent($json.sku) }}%22&perPage=1",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "pocketbaseApi",
"options": {}
},
"id": "22222222-2222-2222-2222-222222222205",
"name": "PB: Lookup stock_item by sku",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1120, 200],
"credentials": {
"pocketbaseApi": {
"id": "REPLACE_ME_pb_credential",
"name": "JEISA PocketBase"
}
}
},
{
"parameters": {
"jsCode": "// Build the QuickBooks Online invoice Line array from customer_pos.parsed_lines.\n// For each line, attach the stock_item.id if we looked one up; otherwise leave it\n// blank (so the stock-decrement step skips it).\n\nconst parsedLines = $json.parsed_lines || [];\nconst stockItemId = $json.stock_items_match_id || null;\n\nconst lineItems = parsedLines.map((line, i) => {\n // If only one line and we found stock_item_id, attach it; otherwise leave blank.\n const stockItemForLine = (parsedLines.length === 1 && stockItemId) ? stockItemId : null;\n return {\n DetailType: 'SalesItemLineDetail',\n Amount: line.line_total ?? (line.quantity * line.unit_price),\n Description: line.description || line.sku || '',\n SalesItemLineDetail: {\n Qty: line.quantity,\n UnitPrice: line.unit_price,\n // NOTE: replace 1 with your QB Item ID for JEI products. For now we hardcode '1'.\n ItemRef: { value: '1' },\n },\n };\n});\n\nreturn [{\n json: {\n qb_invoice_payload: {\n Line: lineItems,\n CustomerRef: { value: $json.expand?.customer?.id || '' },\n // Email the invoice to the customer's primary email\n BillEmail: { Address: $json.expand?.customer?.email || '' },\n // Currency from PO if set\n CurrencyRef: { value: $json.currency || 'ZAR' },\n DocNumber: $json.po_number || '',\n PrivateNote: `Auto-created from customer_po ${$json.id}`,\n },\n line_count: lineItems.length,\n }\n}];\n"
},
"id": "22222222-2222-2222-2222-222222222206",
"name": "Function: Build QB invoice payload",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1340, 200]
},
{
"parameters": {
"operation": "create",
"additionalFields": {
"line": "={{ $json.qb_invoice_payload.Line }}",
"customerRef": "={{ $json.qb_invoice_payload.CustomerRef }}",
"billEmail": "={{ $json.qb_invoice_payload.BillEmail }}",
"currencyRef": "={{ $json.qb_invoice_payload.CurrencyRef }}",
"docNumber": "={{ $json.qb_invoice_payload.DocNumber }}",
"privateNote": "={{ $json.qb_invoice_payload.PrivateNote }}"
}
},
"id": "22222222-2222-2222-2222-222222222207",
"name": "QuickBooks: Create Invoice",
"type": "n8n-nodes-base.quickbooks",
"typeVersion": 1,
"position": [1560, 200],
"credentials": {
"quickbooksOAuth2Api": {
"id": "REPLACE_ME_qb_credential",
"name": "JEISA QuickBooks Online"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://pb.jeistk.apphub.co.za/api/collections/invoices/records",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "pocketbaseApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"customer\": \"{{ $('PB: Fetch customer_pos (status=parsed)').item.json.expand.customer.id }}\",\n \"source_customer_po\": \"{{ $('PB: Fetch customer_pos (status=parsed)').item.json.id }}\",\n \"invoice_number\": \"{{ $json.qb_invoice_payload.DocNumber || $json.Invoice.docNumber }}\",\n \"quickbooks_invoice_id\": \"{{ $json.Id || $json.Invoice.Id }}\",\n \"issue_date\": \"{{ $now.toISO() }}\",\n \"due_date\": \"{{ $now.plus({days: 30}).toISO() }}\",\n \"subtotal\": {{ $json.TotalAmt || 0 }},\n \"total\": {{ $json.TotalAmt || 0 }},\n \"currency\": \"{{ $('PB: Fetch customer_pos (status=parsed)').item.json.currency || 'ZAR' }}\",\n \"status\": \"draft\",\n \"qb_raw_response\": {{ JSON.stringify($json) }}\n}",
"options": {}
},
"id": "22222222-2222-2222-2222-222222222208",
"name": "PB: Create invoices record",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1780, 200],
"credentials": {
"pocketbaseApi": {
"id": "REPLACE_ME_pb_credential",
"name": "JEISA PocketBase"
}
}
},
{
"parameters": {
"operation": "send",
"invoiceId": "={{ $('QuickBooks: Create Invoice').item.json.Id }}",
"email": "={{ $('PB: Fetch customer_pos (status=parsed)').item.json.expand.customer.email }}",
"additionalFields": {}
},
"id": "22222222-2222-2222-2222-222222222209",
"name": "QuickBooks: Send Invoice by email",
"type": "n8n-nodes-base.quickbooks",
"typeVersion": 1,
"position": [2000, 200],
"credentials": {
"quickbooksOAuth2Api": {
"id": "REPLACE_ME_qb_credential",
"name": "JEISA QuickBooks Online"
}
}
},
{
"parameters": {
"method": "PATCH",
"url": "=https://pb.jeistk.apphub.co.za/api/collections/invoices/records/{{ $('PB: Create invoices record').item.json.id }}",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "pocketbaseApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"status\": \"sent\",\n \"emailed_to\": \"{{ $('PB: Fetch customer_pos (status=parsed)').item.json.expand.customer.email }}\",\n \"emailed_at\": \"{{ $now.toISO() }}\"\n}",
"options": {}
},
"id": "22222222-2222-2222-2222-22222222220a",
"name": "PB: Mark invoice sent",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [2220, 200],
"credentials": {
"pocketbaseApi": {
"id": "REPLACE_ME_pb_credential",
"name": "JEISA PocketBase"
}
}
},
{
"parameters": {
"method": "PATCH",
"url": "=https://pb.jeistk.apphub.co.za/api/collections/customer_pos/records/{{ $('PB: Fetch customer_pos (status=parsed)').item.json.id }}",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "pocketbaseApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"status\": \"invoiced\",\n \"linked_invoice\": \"{{ $('PB: Create invoices record').item.json.id }}\"\n}",
"options": {}
},
"id": "22222222-2222-2222-2222-22222222220b",
"name": "PB: Mark customer_pos invoiced",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [2440, 200],
"credentials": {
"pocketbaseApi": {
"id": "REPLACE_ME_pb_credential",
"name": "JEISA PocketBase"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://pb.jeistk.apphub.co.za/api/collections/invoice_items/records",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "pocketbaseApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"invoice\": \"{{ $('PB: Create invoices record').item.json.id }}\",\n \"sku\": \"{{ $json.sku }}\",\n \"description\": \"{{ $json.description }}\",\n \"quantity\": {{ $json.quantity }},\n \"unit_price\": {{ $json.unit_price }},\n \"line_total\": {{ $json.line_total }}\n}",
"options": {}
},
"id": "22222222-2222-2222-2222-22222222220c",
"name": "PB: Create invoice_items rows",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [2660, 200],
"credentials": {
"pocketbaseApi": {
"id": "REPLACE_ME_pb_credential",
"name": "JEISA PocketBase"
}
}
},
{
"parameters": {
"jsCode": "// Decrement stock for each parsed line that we successfully matched to a stock_item.\n// For multi-line POs without per-line SKU matching, only single-line POs will decrement.\n// You can extend this by adding per-line stock_item lookups.\n\nconst parsedLines = $('PB: Fetch customer_pos (status=parsed)').item.json.parsed_lines || [];\n\n// Build batch payload: [{ id, newQty }]\nconst decrements = [];\nfor (const line of parsedLines) {\n if (line.stock_item_id) {\n decrements.push({ id: line.stock_item_id, qty: line.quantity });\n }\n}\n\nreturn [{ json: { decrements } }];\n"
},
"id": "22222222-2222-2222-2222-22222222220d",
"name": "Function: Build stock decrement batch",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [2880, 200]
},
{
"parameters": {
"method": "POST",
"url": "https://pb.jeistk.apphub.co.za/api/batch",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "pocketbaseApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"batch\": [\n {{ $json.decrements.map(d => `{\"method\": \"PATCH\", \"url\": \"/api/collections/stock_items/records/${d.id}\", \"body\": {\"quantity\": \"__PLACEHOLDER__\"}}`).join(',\\\\n ') }}\n ]\n}",
"options": {}
},
"id": "22222222-2222-2222-2222-22222222220e",
"name": "PB: Batch decrement stock_items",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [3100, 200],
"credentials": {
"pocketbaseApi": {
"id": "REPLACE_ME_pb_credential",
"name": "JEISA PocketBase"
}
}
},
{
"parameters": {
"method": "PATCH",
"url": "=https://pb.jeistk.apphub.co.za/api/collections/invoices/records/{{ $('PB: Create invoices record').item.json.id }}",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "pocketbaseApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"stock_decremented\": true\n}",
"options": {}
},
"id": "22222222-2222-2222-2222-22222222220f",
"name": "PB: Mark invoice stock_decremented",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [3320, 200],
"credentials": {
"pocketbaseApi": {
"id": "REPLACE_ME_pb_credential",
"name": "JEISA PocketBase"
}
}
}
],
"connections": {
"Cron: every 2 minutes": {
"main": [
[
{
"node": "PB: Fetch customer_pos (status=parsed)",
"type": "main",
"index": 0
}
]
]
},
"PB: Fetch customer_pos (status=parsed)": {
"main": [
[
{
"node": "If: any POs to invoice?",
"type": "main",
"index": 0
}
]
]
},
"If: any POs to invoice?": {
"main": [
[
{
"node": "Loop: One PO at a time",
"type": "main",
"index": 0
}
]
]
},
"Loop: One PO at a time": {
"main": [
[
{
"node": "PB: Lookup stock_item by sku",
"type": "main",
"index": 0
}
],
[]
]
},
"PB: Lookup stock_item by sku": {
"main": [
[
{
"node": "Function: Build QB invoice payload",
"type": "main",
"index": 0
}
]
]
},
"Function: Build QB invoice payload": {
"main": [
[
{
"node": "QuickBooks: Create Invoice",
"type": "main",
"index": 0
}
]
]
},
"QuickBooks: Create Invoice": {
"main": [
[
{
"node": "PB: Create invoices record",
"type": "main",
"index": 0
}
]
]
},
"PB: Create invoices record": {
"main": [
[
{
"node": "QuickBooks: Send Invoice by email",
"type": "main",
"index": 0
}
]
]
},
"QuickBooks: Send Invoice by email": {
"main": [
[
{
"node": "PB: Mark invoice sent",
"type": "main",
"index": 0
}
]
]
},
"PB: Mark invoice sent": {
"main": [
[
{
"node": "PB: Mark customer_pos invoiced",
"type": "main",
"index": 0
},
{
"node": "PB: Create invoice_items rows",
"type": "main",
"index": 0
}
]
]
},
"PB: Mark customer_pos invoiced": {
"main": [
[
{
"node": "Function: Build stock decrement batch",
"type": "main",
"index": 0
}
]
]
},
"PB: Create invoice_items rows": {
"main": [
[
{
"node": "PB: Mark invoice stock_decremented",
"type": "main",
"index": 0
}
]
]
},
"Function: Build stock decrement batch": {
"main": [
[
{
"node": "PB: Batch decrement stock_items",
"type": "main",
"index": 0
}
]
]
},
"PB: Batch decrement stock_items": {
"main": [
[]
]
}
},
"pinData": {},
"versionId": "1",
"triggerCount": 1,
"tags": [
{
"name": "jeisa",
"id": "tag-jeisa"
},
{
"name": "invoice",
"id": "tag-invoice"
},
{
"name": "quickbooks",
"id": "tag-quickbooks"
},
{
"name": "stock",
"id": "tag-stock"
}
]
}