Splits the quote attachment model along the seam that actually matters:
'operator-only references' (drawings the operator consulted, supplier
emails they filed away) vs 'customer-facing files' (supplier quotes,
drawings, NDAs that need to ride along on the email).
Schema (PB): new `internal_attachments` + `outbound_attachments` fields
on the `quotes` collection, maxSelect 10 each. Existing `attachments`
field kept for back-compat but is no longer the source of truth for
outbound emails.
API:
- GET/POST/DELETE /api/v1/quotes/[id]/internal-attachments[/{filename}]
- GET/POST/DELETE /api/v1/quotes/[id]/outbound-attachments[/{filename}]
- POST /api/v1/quotes/[id]/attachments/upload kept for legacy `attachments`
MCP: `quotes.send_email` description now correctly states
`selectedAttachmentIds` are PB file tokens from `outbound_attachments`
(supplier quotes, drawings, NDAs), not IDs of the legacy `attachments`
collection.
Frontend: `ProcessOutboundAttachmentsCard` component lets the operator
upload/remove customer-facing files during the process page; approval +
process pages now read the new fields.
Outbound Send workflow (deployed as `PIEAkPYYYZLGl6Pj`):
- Old workflow (id `WF_Email_Send_SMTP`) and stale repo file
(`WFDASH — Quote Outbound Send.json`) replaced.
- New revision intersects `quotes.outbound_attachments` with
`email_outbound_attachment_ids`, downloads each from
`/api/files/quotes/<id>/<token>`, and emits
`options.attachments: [{name, content, type}]` consumed by
`Send Email (SMTP)` (PO Send pattern).
- Failure path flipped from `send_failed` to `mail_failed` so
`retryQuoteEmail` can find failed sends (it only accepts the
latter). Previously failed sends were stranded.
Email Monitor (v5) workflow fixes:
- Reply-branch auth: any authenticated request now passes through;
pre-fix bug rejected branches that the IMAP trigger hadn't seen yet.
- Reply detection: bulletproof subject regex
(/Q-?\d{4}-?\d{2}-?\d{3}/) accepts both legacy `Q-2026-09-246`
and current `Q202609246` formats; canonicalised digits-only for
PB `~` substring match.
Delivery notes: new pricing endpoint at
/api/v1/delivery-notes/items/[id]/pricing + service-layer methods.
Snapshots kept for forensics: PRE-FIX-REPLY-{BRANCH-AUTH,DETECTION}.json
and push-payload.baseline.json are the previously-deployed workflow
shapes, retained so we can diff/rollback if regression hits.
Verified:
- PUT workflow PIEAkPYYYZLGl6Pj -> 200, active, 25 nodes
- quotes-service.test.ts: 117 pass (4 pre-existing failures unchanged)
- mcp/route.test.ts: 118 pass (2 pre-existing failures unchanged)
- node --check on new Code node JS passes
Docs: docs/quotes-outbound-attachments-deploy.md covers the deploy
procedure; docs/api-mcp-layer.md description sync.
1650 lines
No EOL
47 KiB
JSON
1650 lines
No EOL
47 KiB
JSON
{
|
|
"updatedAt": "2026-08-27T07:05:09.146Z",
|
|
"createdAt": "2026-06-23T19:49:21.957Z",
|
|
"id": "PIEAkPYYYZLGl6Pj",
|
|
"name": "WFDASH \u2014 Quote Outbound Send (push-payload)",
|
|
"description": "WFDASH \u2014 Quote Outbound Send (push-payload)",
|
|
"active": true,
|
|
"isArchived": false,
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"fromEmail": "=quotes@wfsales.co.za",
|
|
"toEmail": "={{ $('View customers').item.json.email }}",
|
|
"subject": "=QUOTE-{{ $('Search quotes').item.json.quote_number }} REF - {{ $('Search quotes').item.json.quote_reference }}",
|
|
"html": "={{ $('Search quotes').item.json.email_message }}",
|
|
"options": {
|
|
"appendAttribution": false,
|
|
"fileAttachments": "=data",
|
|
"ccEmail": "={{ $('View users1').item.json.email }}",
|
|
"bccEmail": "=",
|
|
"replyTo": "quotes@wfsales.co.za"
|
|
}
|
|
},
|
|
"id": "8c00fc59-3492-42d3-8f3f-66a86eddfc31",
|
|
"name": "Send Email (SMTP)",
|
|
"type": "n8n-nodes-base.emailSend",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
-464,
|
|
288
|
|
],
|
|
"retryOnFail": true,
|
|
"maxTries": 3,
|
|
"waitBetweenTries": 5000,
|
|
"webhookId": "d03c93e2-7bf8-4006-8542-5d94a87e68d1",
|
|
"credentials": {
|
|
"smtp": {
|
|
"id": "R1iZHg4Q5J32w0tA",
|
|
"name": "QUOTES@WFSALES"
|
|
}
|
|
},
|
|
"onError": "continueErrorOutput"
|
|
},
|
|
{
|
|
"parameters": {},
|
|
"type": "n8n-nodes-base.manualTrigger",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-2176,
|
|
288
|
|
],
|
|
"id": "81e80622-3c74-4aa5-b94e-638c0ba42507",
|
|
"name": "When clicking \u2018Execute workflow\u2019"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "quotes",
|
|
"parameters": {
|
|
"elementsPerPage": 1,
|
|
"filter": "status = 'sending'"
|
|
},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-1808,
|
|
288
|
|
],
|
|
"id": "9ffcb69c-e88c-477a-8b1b-d65f7b2db527",
|
|
"name": "Search quotes",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "customers",
|
|
"operation": "view",
|
|
"elementId": "={{ $('Search quotes').item.json.customer }}",
|
|
"parameters": {},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-1584,
|
|
288
|
|
],
|
|
"id": "eba6b537-d696-4d72-a794-85149c645fbd",
|
|
"name": "View customers",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://pocketbase-gnqc88suy8fqk6wyryjjdpvl.169.239.180.130.sslip.io/api/files/{{ $json.collectionId }}/{{ $json.id }}/{{ $json.file[0] }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.5,
|
|
"position": [
|
|
-688,
|
|
288
|
|
],
|
|
"id": "d8fbb522-14b6-493a-ad78-9e0ae4b02eb2",
|
|
"name": "HTTP Request"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "quote_attachments",
|
|
"parameters": {
|
|
"filter": "=quote = '{{ $('Search quotes').item.json.id }}' && kind = 'pdf'"
|
|
},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-1136,
|
|
288
|
|
],
|
|
"id": "14fb9665-84b5-49f3-9f68-3d2d3e2a6167",
|
|
"name": "Search quote_attachments",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// 1. Only keep attachments whose filename matches allowed quote prefixes\nconst allowed = ['Quote-CNL', 'Quote-ISHERWOOD'];\n\nconst items = $input.all();\n\nconst filtered = items.filter(item => {\n const filename = item.json.filename || '';\n return allowed.some(prefix => filename.includes(prefix));\n});\n\n// 2. Keep only the latest PDF attachment row per quote\nconst byQuote = {};\nfor (const item of filtered) {\n const quoteId = item.json.quote;\n const uploaded = new Date(item.json.uploaded_at).getTime();\n\n if (!byQuote[quoteId] || uploaded > byQuote[quoteId].uploaded) {\n byQuote[quoteId] = { item, uploaded };\n }\n}\n\nreturn Object.values(byQuote).map(entry => entry.item);"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-912,
|
|
288
|
|
],
|
|
"id": "17c9488c-6567-45fb-af45-2ecb841542a7",
|
|
"name": "Code in JavaScript"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "quotes",
|
|
"operation": "update",
|
|
"elementId": "={{ $('Search quotes').item.json.id }}",
|
|
"parameters": {},
|
|
"fields": {
|
|
"assignments": [
|
|
{
|
|
"id": "200ad8f1-0104-439f-a8c4-56476dcf0e48",
|
|
"name": "status",
|
|
"value": "emailed",
|
|
"type": "string"
|
|
}
|
|
]
|
|
},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-240,
|
|
272
|
|
],
|
|
"id": "22f9730d-a5e2-44aa-b93f-587b8e37848c",
|
|
"name": "Update quotes",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "users",
|
|
"operation": "view",
|
|
"elementId": "={{ $('View customers').item.json.rep }}",
|
|
"parameters": {},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
208,
|
|
272
|
|
],
|
|
"id": "1d675ee9-9027-4fc9-ba82-a9b9ff83681c",
|
|
"name": "View users",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"deviceIdOverride": "4ce3633b-f2e1-4732-9127-7efe46711b45",
|
|
"operation": "sendMedia",
|
|
"phoneNumber": "27828524633",
|
|
"mediaType": "file",
|
|
"caption": "={{ $('HTTP Request').item.json.filename }} \n{{ $('View customers').item.json.customer }}\n{{ $('View customers').item.json.email }}\n\nCLAIMED BY - {{ $('VIEW claimed').item.json.name }}\nQUOTE PROCESS TIME - {{ $('Code in JavaScript2').item.json.work_days_duration }}"
|
|
},
|
|
"type": "@aldinokemal2104/n8n-nodes-gowa.gowa",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1552,
|
|
256
|
|
],
|
|
"id": "0b6b950d-13b0-4d53-8acb-5d427afe40c6",
|
|
"name": "Send media files",
|
|
"credentials": {
|
|
"goWhatsappApi": {
|
|
"id": "NcclW50L0w20LhlV",
|
|
"name": "GOWA account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://pocketbase-gnqc88suy8fqk6wyryjjdpvl.169.239.180.130.sslip.io/api/files/{{ $json.collectionId }}/{{ $json.id }}/{{ $json.file[0] }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.5,
|
|
"position": [
|
|
1104,
|
|
272
|
|
],
|
|
"id": "230c9ea7-8fcd-4373-901b-b1def618ff17",
|
|
"name": "HTTP Request1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "quote_attachments",
|
|
"parameters": {
|
|
"filter": "=quote = '{{ $('Search quotes').item.json.id }}' && kind = 'pdf'"
|
|
},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
656,
|
|
272
|
|
],
|
|
"id": "79596ab4-97e5-46d9-960f-96e0392321a7",
|
|
"name": "Search quote_attachments1",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// 1. Only keep attachments whose filename matches allowed quote prefixes\nconst allowed = ['Quote-CNL', 'Quote-ISHERWOOD'];\n\nconst items = $input.all();\n\nconst filtered = items.filter(item => {\n const filename = item.json.filename || '';\n return allowed.some(prefix => filename.includes(prefix));\n});\n\n// 2. Keep only the latest PDF attachment row per quote\nconst byQuote = {};\nfor (const item of filtered) {\n const quoteId = item.json.quote;\n const uploaded = new Date(item.json.uploaded_at).getTime();\n\n if (!byQuote[quoteId] || uploaded > byQuote[quoteId].uploaded) {\n byQuote[quoteId] = { item, uploaded };\n }\n}\n\nreturn Object.values(byQuote).map(entry => entry.item);"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
880,
|
|
272
|
|
],
|
|
"id": "01009e23-7df8-4b01-a203-0148373b36d1",
|
|
"name": "Code in JavaScript1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "users",
|
|
"operation": "view",
|
|
"elementId": "={{ $('Search quotes').item.json.claimed_by }}",
|
|
"parameters": {},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
432,
|
|
272
|
|
],
|
|
"id": "d312acc9-3b1b-4804-be96-47e386cf9feb",
|
|
"name": "VIEW claimed",
|
|
"alwaysOutputData": true,
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
},
|
|
"onError": "continueRegularOutput"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "users",
|
|
"operation": "view",
|
|
"elementId": "={{ $json.rep || 'eu0xus9z8le7n6v' }}",
|
|
"parameters": {},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-1360,
|
|
288
|
|
],
|
|
"id": "217ab67d-8bbd-4fd8-bea7-1f2e936c0913",
|
|
"name": "View users1",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Compute work-day duration between created and sending_started_at\nfunction workDaysBetween(startStr, endStr) {\n const start = new Date(startStr);\n const end = new Date(endStr);\n if (isNaN(start) || isNaN(end) || end < start) return 'N/A';\n\n const cur = new Date(start);\n cur.setHours(0, 0, 0, 0);\n const endDay = new Date(end);\n endDay.setHours(0, 0, 0, 0);\n\n let count = 0;\n while (cur < endDay) {\n cur.setDate(cur.getDate() + 1);\n const day = cur.getDay();\n if (day !== 0 && day !== 6) count++; // skip Sat/Sun\n }\n return count;\n}\n\nreturn $input.all().map(item => {\n const created = $('Search quotes').item.json.created;\n const processed = $('Search quotes').item.json.sending_started_at;\n const days = workDaysBetween(created, processed);\n return {\n json: {\n ...item.json,\n work_days_duration: days === 'N/A' ? 'N/A' : `${days} work day${days === 1 ? '' : 's'}`\n }\n };\n});"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-16,
|
|
272
|
|
],
|
|
"id": "49aa5965-281f-467c-9616-413d4de54fb7",
|
|
"name": "Code in JavaScript2"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"rule": {
|
|
"interval": [
|
|
{
|
|
"field": "minutes",
|
|
"minutesInterval": 4
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.scheduleTrigger",
|
|
"typeVersion": 1.3,
|
|
"position": [
|
|
-2176,
|
|
544
|
|
],
|
|
"id": "e33773d4-da92-49e7-8e1a-a68854cd3150",
|
|
"name": "Schedule Trigger"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "quotes",
|
|
"operation": "update",
|
|
"elementId": "={{ $('Search quotes').item.json.id }}",
|
|
"parameters": {},
|
|
"fields": {
|
|
"assignments": [
|
|
{
|
|
"id": "status-send-failed",
|
|
"name": "status",
|
|
"type": "string",
|
|
"value": "send_failed"
|
|
}
|
|
]
|
|
},
|
|
"requestOptions": {}
|
|
},
|
|
"id": "3e2816ef-26fe-4e77-9033-725c1989c78c",
|
|
"name": "Update quote - Send Failed",
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-240,
|
|
480
|
|
],
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "quote_attachments",
|
|
"parameters": {
|
|
"filter": "=quote = '{{ $('Search quotes').item.json.id }}' && kind = 'pdf'"
|
|
},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-16,
|
|
480
|
|
],
|
|
"id": "221aefad-c648-4e1f-9511-487b4a17f468",
|
|
"name": "SEARCH FAILED QUOTES",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// 1. Only keep attachments whose filename matches allowed quote prefixes\nconst allowed = ['Quote-CNL', 'Quote-ISHERWOOD'];\n\nconst items = $input.all();\n\nconst filtered = items.filter(item => {\n const filename = item.json.filename || '';\n return allowed.some(prefix => filename.includes(prefix));\n});\n\n// 2. Keep only the latest PDF attachment row per quote\nconst byQuote = {};\nfor (const item of filtered) {\n const quoteId = item.json.quote;\n const uploaded = new Date(item.json.uploaded_at).getTime();\n\n if (!byQuote[quoteId] || uploaded > byQuote[quoteId].uploaded) {\n byQuote[quoteId] = { item, uploaded };\n }\n}\n\nreturn Object.values(byQuote).map(entry => entry.item);"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
208,
|
|
480
|
|
],
|
|
"id": "cacf85ba-3b38-4ddd-9a6b-aa9b6e7d290d",
|
|
"name": "FILTER FAILED QUOTES"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://pocketbase-gnqc88suy8fqk6wyryjjdpvl.169.239.180.130.sslip.io/api/files/{{ $json.collectionId }}/{{ $json.id }}/{{ $json.file[0] }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.5,
|
|
"position": [
|
|
432,
|
|
480
|
|
],
|
|
"id": "7243dd8c-33b7-4b44-8f39-35d3ee5617e6",
|
|
"name": "DOWNLOAD FAILED QUOTE"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"deviceIdOverride": "4ce3633b-f2e1-4732-9127-7efe46711b45",
|
|
"operation": "sendMedia",
|
|
"phoneNumber": "={{ $('View users1').item.json.tel }}",
|
|
"mediaType": "file",
|
|
"caption": "=QUOTE FAILDED TO SEND VIA EMAIL\n{{ $('HTTP Request').item.json.filename }} \n{{ $('View customers').item.json.customer }}\n{{ $('View customers').item.json.email }}\n"
|
|
},
|
|
"type": "@aldinokemal2104/n8n-nodes-gowa.gowa",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
656,
|
|
480
|
|
],
|
|
"id": "2639682b-2714-400f-8a57-fe4d7f7823be",
|
|
"name": "SEND FAILED MESSAGE",
|
|
"credentials": {
|
|
"goWhatsappApi": {
|
|
"id": "NcclW50L0w20LhlV",
|
|
"name": "GOWA account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"deviceIdOverride": "4ce3633b-f2e1-4732-9127-7efe46711b45",
|
|
"operation": "sendMedia",
|
|
"phoneNumber": "={{ $('View users1').item.json.tel }}",
|
|
"mediaType": "file",
|
|
"caption": "={{ $('HTTP Request').item.json.filename }} \n{{ $('View customers').item.json.customer }}\n{{ $('View customers').item.json.email }}\n\nCLAIMED BY - {{ $('VIEW claimed').item.json.name }}\nQUOTE PROCESS TIME - {{ $('Code in JavaScript2').item.json.work_days_duration }}"
|
|
},
|
|
"type": "@aldinokemal2104/n8n-nodes-gowa.gowa",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1552,
|
|
464
|
|
],
|
|
"id": "9d9f79b3-9598-427d-8602-958e769e8529",
|
|
"name": "Send media files1",
|
|
"credentials": {
|
|
"goWhatsappApi": {
|
|
"id": "NcclW50L0w20LhlV",
|
|
"name": "GOWA account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 3
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "a2abe785-2f93-4e24-b0e2-baf9909ea940",
|
|
"leftValue": "={{ $('View users1').item.json.name }}",
|
|
"rightValue": "Gary",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.3,
|
|
"position": [
|
|
1296,
|
|
272
|
|
],
|
|
"id": "db6ed47b-8a63-4c65-9260-87ab30683752",
|
|
"name": "If"
|
|
}
|
|
],
|
|
"connections": {
|
|
"Send Email (SMTP)": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Update quotes",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Update quote - Send Failed",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"When clicking \u2018Execute workflow\u2019": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Search quotes",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Search quotes": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "View customers",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"View customers": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "View users1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"HTTP Request": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Send Email (SMTP)",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Search quote_attachments": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Code in JavaScript",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Code in JavaScript": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "HTTP Request",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Update quotes": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Code in JavaScript2",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"View users": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "VIEW claimed",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"HTTP Request1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "If",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Search quote_attachments1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Code in JavaScript1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Code in JavaScript1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "HTTP Request1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"VIEW claimed": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Search quote_attachments1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"View users1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Search quote_attachments",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Code in JavaScript2": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "View users",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Schedule Trigger": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Search quotes",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Update quote - Send Failed": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "SEARCH FAILED QUOTES",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"SEARCH FAILED QUOTES": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "FILTER FAILED QUOTES",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"FILTER FAILED QUOTES": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "DOWNLOAD FAILED QUOTE",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"DOWNLOAD FAILED QUOTE": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "SEND FAILED MESSAGE",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"If": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Send media files",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Send media files1",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Send media files",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"settings": {
|
|
"executionOrder": "v1",
|
|
"binaryMode": "separate",
|
|
"availableInMCP": true
|
|
},
|
|
"staticData": {
|
|
"node:Schedule Trigger": {
|
|
"recurrenceRules": [
|
|
null
|
|
],
|
|
"recurrenceRuleSignatures": [
|
|
null
|
|
]
|
|
}
|
|
},
|
|
"meta": {
|
|
"templateCredsSetupCompleted": true,
|
|
"aiBuilderAssisted": true,
|
|
"builderVariant": "mcp"
|
|
},
|
|
"nodeGroups": [],
|
|
"pinData": {
|
|
"When clicking \u2018Execute workflow\u2019": [
|
|
{
|
|
"json": {},
|
|
"pairedItem": {
|
|
"item": 0
|
|
}
|
|
}
|
|
],
|
|
"Schedule Trigger": [
|
|
{
|
|
"json": {
|
|
"timestamp": "2026-08-13T08:16:23.008+00:00",
|
|
"Readable date": "August 13th 2026, 8:16:23 am",
|
|
"Readable time": "8:16:23 am",
|
|
"Day of week": "Thursday",
|
|
"Year": "2026",
|
|
"Month": "August",
|
|
"Day of month": "13",
|
|
"Hour": "08",
|
|
"Minute": "16",
|
|
"Second": "23",
|
|
"Timezone": "UTC (UTC+00:00)"
|
|
},
|
|
"pairedItem": {
|
|
"item": 0
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"versionId": "1d2b9715-aaa0-405e-8915-0b7c6ed5360b",
|
|
"activeVersionId": "1d2b9715-aaa0-405e-8915-0b7c6ed5360b",
|
|
"versionCounter": 501,
|
|
"triggerCount": 1,
|
|
"sourceWorkflowId": null,
|
|
"shared": [
|
|
{
|
|
"updatedAt": "2026-06-23T19:49:21.957Z",
|
|
"createdAt": "2026-06-23T19:49:21.957Z",
|
|
"role": "workflow:owner",
|
|
"workflowId": "PIEAkPYYYZLGl6Pj",
|
|
"projectId": "UhHq8eAZFxiYmQxH",
|
|
"project": {
|
|
"updatedAt": "2026-06-11T15:00:27.533Z",
|
|
"createdAt": "2026-06-11T14:30:25.543Z",
|
|
"id": "UhHq8eAZFxiYmQxH",
|
|
"name": "David Els <david.els1@outlook.com>",
|
|
"type": "personal",
|
|
"icon": null,
|
|
"description": null,
|
|
"customTelemetryTags": [],
|
|
"creatorId": "4f770df2-bd2f-4436-b153-b27004efc7b6"
|
|
}
|
|
}
|
|
],
|
|
"tags": [
|
|
{
|
|
"updatedAt": "2026-06-24T04:53:13.397Z",
|
|
"createdAt": "2026-06-24T04:53:13.397Z",
|
|
"id": "9xXe3C8wcftEDwDj",
|
|
"name": "wfdash"
|
|
}
|
|
],
|
|
"activeVersion": {
|
|
"updatedAt": "2026-08-27T07:05:11.394Z",
|
|
"createdAt": "2026-08-27T07:05:09.154Z",
|
|
"versionId": "1d2b9715-aaa0-405e-8915-0b7c6ed5360b",
|
|
"workflowId": "PIEAkPYYYZLGl6Pj",
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"fromEmail": "=quotes@wfsales.co.za",
|
|
"toEmail": "={{ $('View customers').item.json.email }}",
|
|
"subject": "=QUOTE-{{ $('Search quotes').item.json.quote_number }} REF - {{ $('Search quotes').item.json.quote_reference }}",
|
|
"html": "={{ $('Search quotes').item.json.email_message }}",
|
|
"options": {
|
|
"appendAttribution": false,
|
|
"fileAttachments": "=data",
|
|
"ccEmail": "={{ $('View users1').item.json.email }}",
|
|
"bccEmail": "=",
|
|
"replyTo": "quotes@wfsales.co.za"
|
|
}
|
|
},
|
|
"id": "8c00fc59-3492-42d3-8f3f-66a86eddfc31",
|
|
"name": "Send Email (SMTP)",
|
|
"type": "n8n-nodes-base.emailSend",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
-464,
|
|
288
|
|
],
|
|
"retryOnFail": true,
|
|
"maxTries": 3,
|
|
"waitBetweenTries": 5000,
|
|
"webhookId": "d03c93e2-7bf8-4006-8542-5d94a87e68d1",
|
|
"credentials": {
|
|
"smtp": {
|
|
"id": "R1iZHg4Q5J32w0tA",
|
|
"name": "QUOTES@WFSALES"
|
|
}
|
|
},
|
|
"onError": "continueErrorOutput"
|
|
},
|
|
{
|
|
"parameters": {},
|
|
"type": "n8n-nodes-base.manualTrigger",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-2176,
|
|
288
|
|
],
|
|
"id": "81e80622-3c74-4aa5-b94e-638c0ba42507",
|
|
"name": "When clicking \u2018Execute workflow\u2019"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "quotes",
|
|
"parameters": {
|
|
"elementsPerPage": 1,
|
|
"filter": "status = 'sending'"
|
|
},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-1808,
|
|
288
|
|
],
|
|
"id": "9ffcb69c-e88c-477a-8b1b-d65f7b2db527",
|
|
"name": "Search quotes",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "customers",
|
|
"operation": "view",
|
|
"elementId": "={{ $('Search quotes').item.json.customer }}",
|
|
"parameters": {},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-1584,
|
|
288
|
|
],
|
|
"id": "eba6b537-d696-4d72-a794-85149c645fbd",
|
|
"name": "View customers",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://pocketbase-gnqc88suy8fqk6wyryjjdpvl.169.239.180.130.sslip.io/api/files/{{ $json.collectionId }}/{{ $json.id }}/{{ $json.file[0] }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.5,
|
|
"position": [
|
|
-688,
|
|
288
|
|
],
|
|
"id": "d8fbb522-14b6-493a-ad78-9e0ae4b02eb2",
|
|
"name": "HTTP Request"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "quote_attachments",
|
|
"parameters": {
|
|
"filter": "=quote = '{{ $('Search quotes').item.json.id }}' && kind = 'pdf'"
|
|
},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-1136,
|
|
288
|
|
],
|
|
"id": "14fb9665-84b5-49f3-9f68-3d2d3e2a6167",
|
|
"name": "Search quote_attachments",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// 1. Only keep attachments whose filename matches allowed quote prefixes\nconst allowed = ['Quote-CNL', 'Quote-ISHERWOOD'];\n\nconst items = $input.all();\n\nconst filtered = items.filter(item => {\n const filename = item.json.filename || '';\n return allowed.some(prefix => filename.includes(prefix));\n});\n\n// 2. Keep only the latest PDF attachment row per quote\nconst byQuote = {};\nfor (const item of filtered) {\n const quoteId = item.json.quote;\n const uploaded = new Date(item.json.uploaded_at).getTime();\n\n if (!byQuote[quoteId] || uploaded > byQuote[quoteId].uploaded) {\n byQuote[quoteId] = { item, uploaded };\n }\n}\n\nreturn Object.values(byQuote).map(entry => entry.item);"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-912,
|
|
288
|
|
],
|
|
"id": "17c9488c-6567-45fb-af45-2ecb841542a7",
|
|
"name": "Code in JavaScript"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "quotes",
|
|
"operation": "update",
|
|
"elementId": "={{ $('Search quotes').item.json.id }}",
|
|
"parameters": {},
|
|
"fields": {
|
|
"assignments": [
|
|
{
|
|
"id": "200ad8f1-0104-439f-a8c4-56476dcf0e48",
|
|
"name": "status",
|
|
"value": "emailed",
|
|
"type": "string"
|
|
}
|
|
]
|
|
},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-240,
|
|
272
|
|
],
|
|
"id": "22f9730d-a5e2-44aa-b93f-587b8e37848c",
|
|
"name": "Update quotes",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "users",
|
|
"operation": "view",
|
|
"elementId": "={{ $('View customers').item.json.rep }}",
|
|
"parameters": {},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
208,
|
|
272
|
|
],
|
|
"id": "1d675ee9-9027-4fc9-ba82-a9b9ff83681c",
|
|
"name": "View users",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"deviceIdOverride": "4ce3633b-f2e1-4732-9127-7efe46711b45",
|
|
"operation": "sendMedia",
|
|
"phoneNumber": "27828524633",
|
|
"mediaType": "file",
|
|
"caption": "={{ $('HTTP Request').item.json.filename }} \n{{ $('View customers').item.json.customer }}\n{{ $('View customers').item.json.email }}\n\nCLAIMED BY - {{ $('VIEW claimed').item.json.name }}\nQUOTE PROCESS TIME - {{ $('Code in JavaScript2').item.json.work_days_duration }}"
|
|
},
|
|
"type": "@aldinokemal2104/n8n-nodes-gowa.gowa",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1552,
|
|
256
|
|
],
|
|
"id": "0b6b950d-13b0-4d53-8acb-5d427afe40c6",
|
|
"name": "Send media files",
|
|
"credentials": {
|
|
"goWhatsappApi": {
|
|
"id": "NcclW50L0w20LhlV",
|
|
"name": "GOWA account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://pocketbase-gnqc88suy8fqk6wyryjjdpvl.169.239.180.130.sslip.io/api/files/{{ $json.collectionId }}/{{ $json.id }}/{{ $json.file[0] }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.5,
|
|
"position": [
|
|
1104,
|
|
272
|
|
],
|
|
"id": "230c9ea7-8fcd-4373-901b-b1def618ff17",
|
|
"name": "HTTP Request1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "quote_attachments",
|
|
"parameters": {
|
|
"filter": "=quote = '{{ $('Search quotes').item.json.id }}' && kind = 'pdf'"
|
|
},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
656,
|
|
272
|
|
],
|
|
"id": "79596ab4-97e5-46d9-960f-96e0392321a7",
|
|
"name": "Search quote_attachments1",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// 1. Only keep attachments whose filename matches allowed quote prefixes\nconst allowed = ['Quote-CNL', 'Quote-ISHERWOOD'];\n\nconst items = $input.all();\n\nconst filtered = items.filter(item => {\n const filename = item.json.filename || '';\n return allowed.some(prefix => filename.includes(prefix));\n});\n\n// 2. Keep only the latest PDF attachment row per quote\nconst byQuote = {};\nfor (const item of filtered) {\n const quoteId = item.json.quote;\n const uploaded = new Date(item.json.uploaded_at).getTime();\n\n if (!byQuote[quoteId] || uploaded > byQuote[quoteId].uploaded) {\n byQuote[quoteId] = { item, uploaded };\n }\n}\n\nreturn Object.values(byQuote).map(entry => entry.item);"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
880,
|
|
272
|
|
],
|
|
"id": "01009e23-7df8-4b01-a203-0148373b36d1",
|
|
"name": "Code in JavaScript1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "users",
|
|
"operation": "view",
|
|
"elementId": "={{ $('Search quotes').item.json.claimed_by }}",
|
|
"parameters": {},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
432,
|
|
272
|
|
],
|
|
"id": "d312acc9-3b1b-4804-be96-47e386cf9feb",
|
|
"name": "VIEW claimed",
|
|
"alwaysOutputData": true,
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
},
|
|
"onError": "continueRegularOutput"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "users",
|
|
"operation": "view",
|
|
"elementId": "={{ $json.rep || 'eu0xus9z8le7n6v' }}",
|
|
"parameters": {},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-1360,
|
|
288
|
|
],
|
|
"id": "217ab67d-8bbd-4fd8-bea7-1f2e936c0913",
|
|
"name": "View users1",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Compute work-day duration between created and sending_started_at\nfunction workDaysBetween(startStr, endStr) {\n const start = new Date(startStr);\n const end = new Date(endStr);\n if (isNaN(start) || isNaN(end) || end < start) return 'N/A';\n\n const cur = new Date(start);\n cur.setHours(0, 0, 0, 0);\n const endDay = new Date(end);\n endDay.setHours(0, 0, 0, 0);\n\n let count = 0;\n while (cur < endDay) {\n cur.setDate(cur.getDate() + 1);\n const day = cur.getDay();\n if (day !== 0 && day !== 6) count++; // skip Sat/Sun\n }\n return count;\n}\n\nreturn $input.all().map(item => {\n const created = $('Search quotes').item.json.created;\n const processed = $('Search quotes').item.json.sending_started_at;\n const days = workDaysBetween(created, processed);\n return {\n json: {\n ...item.json,\n work_days_duration: days === 'N/A' ? 'N/A' : `${days} work day${days === 1 ? '' : 's'}`\n }\n };\n});"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-16,
|
|
272
|
|
],
|
|
"id": "49aa5965-281f-467c-9616-413d4de54fb7",
|
|
"name": "Code in JavaScript2"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"rule": {
|
|
"interval": [
|
|
{
|
|
"field": "minutes",
|
|
"minutesInterval": 4
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.scheduleTrigger",
|
|
"typeVersion": 1.3,
|
|
"position": [
|
|
-2176,
|
|
544
|
|
],
|
|
"id": "e33773d4-da92-49e7-8e1a-a68854cd3150",
|
|
"name": "Schedule Trigger"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "quotes",
|
|
"operation": "update",
|
|
"elementId": "={{ $('Search quotes').item.json.id }}",
|
|
"parameters": {},
|
|
"fields": {
|
|
"assignments": [
|
|
{
|
|
"id": "status-send-failed",
|
|
"name": "status",
|
|
"type": "string",
|
|
"value": "send_failed"
|
|
}
|
|
]
|
|
},
|
|
"requestOptions": {}
|
|
},
|
|
"id": "3e2816ef-26fe-4e77-9033-725c1989c78c",
|
|
"name": "Update quote - Send Failed",
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-240,
|
|
480
|
|
],
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "quote_attachments",
|
|
"parameters": {
|
|
"filter": "=quote = '{{ $('Search quotes').item.json.id }}' && kind = 'pdf'"
|
|
},
|
|
"requestOptions": {}
|
|
},
|
|
"type": "n8n-nodes-pocketbase.pocketbaseHttp",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-16,
|
|
480
|
|
],
|
|
"id": "221aefad-c648-4e1f-9511-487b4a17f468",
|
|
"name": "SEARCH FAILED QUOTES",
|
|
"credentials": {
|
|
"pocketbaseHttpApi": {
|
|
"id": "hLwB1QPIJEWsuVyW",
|
|
"name": "PocketBase HTTP account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// 1. Only keep attachments whose filename matches allowed quote prefixes\nconst allowed = ['Quote-CNL', 'Quote-ISHERWOOD'];\n\nconst items = $input.all();\n\nconst filtered = items.filter(item => {\n const filename = item.json.filename || '';\n return allowed.some(prefix => filename.includes(prefix));\n});\n\n// 2. Keep only the latest PDF attachment row per quote\nconst byQuote = {};\nfor (const item of filtered) {\n const quoteId = item.json.quote;\n const uploaded = new Date(item.json.uploaded_at).getTime();\n\n if (!byQuote[quoteId] || uploaded > byQuote[quoteId].uploaded) {\n byQuote[quoteId] = { item, uploaded };\n }\n}\n\nreturn Object.values(byQuote).map(entry => entry.item);"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
208,
|
|
480
|
|
],
|
|
"id": "cacf85ba-3b38-4ddd-9a6b-aa9b6e7d290d",
|
|
"name": "FILTER FAILED QUOTES"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://pocketbase-gnqc88suy8fqk6wyryjjdpvl.169.239.180.130.sslip.io/api/files/{{ $json.collectionId }}/{{ $json.id }}/{{ $json.file[0] }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.5,
|
|
"position": [
|
|
432,
|
|
480
|
|
],
|
|
"id": "7243dd8c-33b7-4b44-8f39-35d3ee5617e6",
|
|
"name": "DOWNLOAD FAILED QUOTE"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"deviceIdOverride": "4ce3633b-f2e1-4732-9127-7efe46711b45",
|
|
"operation": "sendMedia",
|
|
"phoneNumber": "={{ $('View users1').item.json.tel }}",
|
|
"mediaType": "file",
|
|
"caption": "=QUOTE FAILDED TO SEND VIA EMAIL\n{{ $('HTTP Request').item.json.filename }} \n{{ $('View customers').item.json.customer }}\n{{ $('View customers').item.json.email }}\n"
|
|
},
|
|
"type": "@aldinokemal2104/n8n-nodes-gowa.gowa",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
656,
|
|
480
|
|
],
|
|
"id": "2639682b-2714-400f-8a57-fe4d7f7823be",
|
|
"name": "SEND FAILED MESSAGE",
|
|
"credentials": {
|
|
"goWhatsappApi": {
|
|
"id": "NcclW50L0w20LhlV",
|
|
"name": "GOWA account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"deviceIdOverride": "4ce3633b-f2e1-4732-9127-7efe46711b45",
|
|
"operation": "sendMedia",
|
|
"phoneNumber": "={{ $('View users1').item.json.tel }}",
|
|
"mediaType": "file",
|
|
"caption": "={{ $('HTTP Request').item.json.filename }} \n{{ $('View customers').item.json.customer }}\n{{ $('View customers').item.json.email }}\n\nCLAIMED BY - {{ $('VIEW claimed').item.json.name }}\nQUOTE PROCESS TIME - {{ $('Code in JavaScript2').item.json.work_days_duration }}"
|
|
},
|
|
"type": "@aldinokemal2104/n8n-nodes-gowa.gowa",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1552,
|
|
464
|
|
],
|
|
"id": "9d9f79b3-9598-427d-8602-958e769e8529",
|
|
"name": "Send media files1",
|
|
"credentials": {
|
|
"goWhatsappApi": {
|
|
"id": "NcclW50L0w20LhlV",
|
|
"name": "GOWA account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 3
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "a2abe785-2f93-4e24-b0e2-baf9909ea940",
|
|
"leftValue": "={{ $('View users1').item.json.name }}",
|
|
"rightValue": "Gary",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.3,
|
|
"position": [
|
|
1296,
|
|
272
|
|
],
|
|
"id": "db6ed47b-8a63-4c65-9260-87ab30683752",
|
|
"name": "If"
|
|
}
|
|
],
|
|
"connections": {
|
|
"Send Email (SMTP)": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Update quotes",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Update quote - Send Failed",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"When clicking \u2018Execute workflow\u2019": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Search quotes",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Search quotes": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "View customers",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"View customers": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "View users1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"HTTP Request": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Send Email (SMTP)",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Search quote_attachments": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Code in JavaScript",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Code in JavaScript": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "HTTP Request",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Update quotes": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Code in JavaScript2",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"View users": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "VIEW claimed",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"HTTP Request1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "If",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Search quote_attachments1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Code in JavaScript1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Code in JavaScript1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "HTTP Request1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"VIEW claimed": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Search quote_attachments1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"View users1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Search quote_attachments",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Code in JavaScript2": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "View users",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Schedule Trigger": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Search quotes",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Update quote - Send Failed": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "SEARCH FAILED QUOTES",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"SEARCH FAILED QUOTES": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "FILTER FAILED QUOTES",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"FILTER FAILED QUOTES": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "DOWNLOAD FAILED QUOTE",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"DOWNLOAD FAILED QUOTE": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "SEND FAILED MESSAGE",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"If": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Send media files",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Send media files1",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Send media files",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"nodeGroups": [],
|
|
"authors": "David Els",
|
|
"name": "Version 1d2b9715",
|
|
"description": "",
|
|
"autosaved": true,
|
|
"workflowPublishHistory": [
|
|
{
|
|
"createdAt": "2026-08-27T07:05:11.381Z",
|
|
"id": 849,
|
|
"workflowId": "PIEAkPYYYZLGl6Pj",
|
|
"versionId": "1d2b9715-aaa0-405e-8915-0b7c6ed5360b",
|
|
"event": "activated",
|
|
"userId": "4f770df2-bd2f-4436-b153-b27004efc7b6"
|
|
}
|
|
]
|
|
}
|
|
} |