diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..1f67719 Binary files /dev/null and b/.DS_Store differ diff --git a/Invoice_Processing/[PENNYLANE] Auto Invoice from Webhook.json b/Invoice_Processing/[PENNYLANE] Auto Invoice from Webhook.json new file mode 100644 index 0000000..583160a --- /dev/null +++ b/Invoice_Processing/[PENNYLANE] Auto Invoice from Webhook.json @@ -0,0 +1,746 @@ +{ + "name": "[PENNYLANE] Auto Invoice from Webhook", + "nodes": [ + { + "parameters": { + "content": "# [PENNYLANE] Auto Invoice from Webhook\n\n**Trigger:** Webhook POST `/pennylane-invoice`\n**Source:** Any CRM, form, or manual call\n\n---\n\n### Flow\n\n`WH Receive Invoice Data`\n → `Code Validate Payload`\n → `PL Search Customer`\n → `IF Customer Exists`\n → ✅ `Set Customer ID`\n → ❌ `PL Create Customer` → `Set Customer ID`\n → `Code Build Invoice Payload`\n → `PL Create Invoice`\n → `IF Send Email`\n → ✅ `Wait PDF Generation` → `PL Send Invoice Email`\n → `Code Build Notification`\n → `SL Send Notification`\n → `IF Has Notification Email`\n → ✅ `GM Send Notification`\n → `Set Output Response`\n\n---\n\n### Services & Credentials\n\n| Service | Credential |\n|---------|-----------|\n| Pennylane API v2 | Pennylane API (HTTP Header Auth) |\n| Slack (optional) | Slack OAuth |\n| Gmail (optional) | Gmail OAuth |\n\n---\n\n### Node refs for $()\n\n- WH Receive Invoice Data\n- Code Validate Payload\n- PL Search Customer\n- IF Customer Exists\n- Set Customer ID\n- PL Create Customer\n- Code Build Invoice Payload\n- PL Create Invoice\n- IF Send Email\n- Wait PDF Generation\n- PL Send Invoice Email\n- Code Build Notification\n- SL Send Notification\n- IF Has Notification Email\n- GM Send Notification\n- Set Output Response", + "height": 752, + "width": 480 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -352, + -272 + ], + "typeVersion": 1, + "id": "2432a919-ea99-4529-969d-2f94a9f44110", + "name": "Sticky Note" + }, + { + "parameters": { + "httpMethod": "POST", + "path": "pennylane-invoice", + "responseMode": "lastNode", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + 272, + 32 + ], + "id": "402c4d14-ab30-4baf-85b5-daec4ceb06f4", + "name": "WH Receive Invoice Data", + "webhookId": "00e37d2c-4cff-46d2-8216-7e2c2006ec47" + }, + { + "parameters": { + "jsCode": "// Refs: 'WH Receive Invoice Data'\nconst input = $input.first().json;\n\nconst errors = [];\n\nif (!input.customer_name || input.customer_name.trim() === '') {\n errors.push('customer_name is required');\n}\n\nif (!input.customer_email || !input.customer_email.includes('@')) {\n errors.push('customer_email is required and must be valid');\n}\n\nif (!input.items || !Array.isArray(input.items) || input.items.length === 0) {\n errors.push('items array is required and must contain at least one item');\n} else {\n input.items.forEach((item, i) => {\n if (!item.label) errors.push(`items[${i}].label is required`);\n if (!item.quantity || item.quantity <= 0) errors.push(`items[${i}].quantity must be > 0`);\n if (!item.unit_price || item.unit_price <= 0) errors.push(`items[${i}].unit_price must be > 0`);\n });\n}\n\nif (errors.length > 0) {\n throw new Error('Validation failed: ' + errors.join('; '));\n}\n\nreturn [{\n json: {\n customer_name: input.customer_name.trim(),\n customer_email: input.customer_email.trim().toLowerCase(),\n customer_address: input.customer_address || '',\n customer_postal_code: input.customer_postal_code || '00000',\n customer_city: input.customer_city || 'Non renseigné',\n customer_country: input.customer_country || 'FR',\n items: input.items.map(item => ({\n label: item.label,\n quantity: item.quantity,\n unit_price: item.unit_price,\n vat_rate: item.vat_rate || 'FR_200'\n })),\n due_date: input.due_date || new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],\n currency: input.currency || 'EUR',\n payment_conditions: input.payment_conditions || '30_days',\n reference: input.reference || null,\n send_email: input.send_email || false,\n notification_email: input.notification_email || null\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 528, + 32 + ], + "id": "585218e8-449a-40da-8ce7-d8157998c2f7", + "name": "Code Validate Payload" + }, + { + "parameters": { + "url": "https://app.pennylane.com/api/external/v2/customers", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "filter", + "value": "=[{\"field\":\"emails\",\"operator\":\"in\",\"value\":\"{{ $json.customer_email }}\"}]" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 832, + 32 + ], + "id": "5440a265-819f-42d4-8c26-d83cb51eb219", + "name": "PL Search Customer", + "credentials": { + "httpHeaderAuth": { + "id": "", + "name": "" + } + } + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "85be8afd-6833-43ba-bb7b-305dd91e9a73", + "leftValue": "={{ $json.items.length }}", + "rightValue": 0, + "operator": { + "type": "number", + "operation": "gt" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 1040, + 32 + ], + "id": "8e9e52fa-654e-4b14-b72f-8c57b70744f7", + "name": "IF Customer Exists" + }, + { + "parameters": { + "method": "POST", + "url": "https://app.pennylane.com/api/external/v2/company_customers", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"name\": \"{{ $('Code Validate Payload').item.json.customer_name }}\",\n \"emails\": [\"{{ $('Code Validate Payload').item.json.customer_email }}\"],\n \"billing_address\": {\n \"address\": \"{{ $('Code Validate Payload').item.json.customer_address }}\",\n \"postal_code\": \"{{ $('Code Validate Payload').item.json.customer_postal_code }}\",\n \"city\": \"{{ $('Code Validate Payload').item.json.customer_city }}\",\n \"country_alpha2\": \"{{ $('Code Validate Payload').item.json.customer_country }}\"\n }\n}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1248, + 128 + ], + "id": "106b531c-143f-4de7-ba98-8ede691a0d01", + "name": "PL Create Customer", + "credentials": { + "httpHeaderAuth": { + "id": "", + "name": "" + } + } + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "86d02b7a-27bd-4791-a4aa-02b7429dc7bd", + "name": "=customer_id", + "value": "={{ $json.id ?? $json.items?.[0]?.id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 1424, + 16 + ], + "id": "49c0e376-9f63-4ded-8d0c-917cc7ddf652", + "name": "Set Customer ID" + }, + { + "parameters": { + "method": "POST", + "url": "https://app.pennylane.com/api/external/v2/customer_invoices", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify($json) }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1824, + 16 + ], + "id": "c33bd39f-5043-418c-88f9-0aaa8bc25c9d", + "name": "PL Create Invoice", + "credentials": { + "httpHeaderAuth": { + "id": "", + "name": "" + } + } + }, + { + "parameters": { + "jsCode": "// Refs: 'Set Customer ID', 'Code Validate Payload'\nconst data = $('Code Validate Payload').first().json;\nconst customerId = $('Set Customer ID').first().json.customer_id;\n\nconst invoiceLines = data.items.map(item => ({\n label: item.label,\n quantity: item.quantity,\n unit: 'piece',\n raw_currency_unit_price: String(item.unit_price.toFixed(2)),\n vat_rate: item.vat_rate\n}));\n\nconst today = new Date().toISOString().split('T')[0];\n\nconst payload = {\n customer_id: customerId,\n date: today,\n deadline: data.due_date,\n currency: data.currency,\n invoice_lines: invoiceLines,\n draft: false\n};\n\nif (data.reference) {\n payload.external_reference = data.reference;\n}\n\nreturn [{ json: payload }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1648, + 16 + ], + "id": "964e6cc9-ff3c-49ab-bda0-ca842e6b380e", + "name": "Code Build Invoice Payload" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "de5a2cc8-def5-4777-8369-51caea43256f", + "leftValue": "={{ $('Code Validate Payload').item.json.send_email }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 2064, + 16 + ], + "id": "041e2680-0585-43e8-8df3-4a04b3c32fc3", + "name": "IF Send Email" + }, + { + "parameters": { + "jsCode": "// Refs: 'PL Create Invoice', 'Code Validate Payload'\nconst invoice = $('PL Create Invoice').first().json;\nconst data = $('Code Validate Payload').first().json;\n\nconst message = [\n `New invoice created in Pennylane`,\n ``,\n `Customer: ${data.customer_name}`,\n `Invoice: ${invoice.invoice_number}`,\n `Amount: ${invoice.currency_amount_before_tax} EUR HT`,\n `Total TTC: ${invoice.currency_amount} EUR`,\n `Due date: ${data.due_date}`,\n `Status: ${invoice.status}`,\n data.reference ? `Reference: ${data.reference}` : '',\n ``,\n `View: ${invoice.public_file_url || 'N/A'}`\n].filter(Boolean).join('\\n');\n\nreturn [{\n json: {\n message,\n invoice_id: invoice.id,\n invoice_number: invoice.invoice_number,\n amount: invoice.currency_amount,\n customer_name: data.customer_name,\n public_url: invoice.public_file_url || null\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2752, + 32 + ], + "id": "797f431f-2ffd-42fc-afbc-f76b5806af67", + "name": "Code Build Notification" + }, + { + "parameters": { + "authentication": "oAuth2", + "select": "channel", + "channelId": { + "__rl": true, + "value": "", + "mode": "list", + "cachedResultName": "" + }, + "text": "={{ $json.message }}", + "otherOptions": {} + }, + "type": "n8n-nodes-base.slack", + "typeVersion": 2.4, + "position": [ + 3296, + -112 + ], + "id": "d5f88019-04c7-43e7-a813-efbb52b954a5", + "name": "SL Send Notification", + "webhookId": "f2c28848-283c-4ca1-b9c7-e47d6da805c3", + "credentials": { + "slackOAuth2Api": { + "id": "", + "name": "" + } + } + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "c5fe5a2e-f759-4afd-907f-eacb7251481b", + "name": "success", + "value": true, + "type": "boolean" + }, + { + "id": "03e80656-7f61-46cd-98d4-0e7daf6636d3", + "name": "invoice_id", + "value": "={{ $('Code Build Notification').item.json.invoice_id }}", + "type": "string" + }, + { + "id": "0f54ae5d-87d0-473f-9076-3b17bc24ec0d", + "name": "invoice_number", + "value": "={{ $('Code Build Notification').item.json.invoice_number }}", + "type": "string" + }, + { + "id": "878b0587-95ee-4b69-8853-f51c1c356dfa", + "name": "amount", + "value": "={{ $('Code Build Notification').item.json.amount }}", + "type": "string" + }, + { + "id": "ef1c38e7-d845-4604-bb8f-908fcbbe4146", + "name": "customer", + "value": "={{ $('Code Build Notification').item.json.customer_name }}", + "type": "string" + }, + { + "id": "93d69fa3-86ca-4f0b-8db3-94d7177c10cf", + "name": "public_url", + "value": "={{ $('Code Build Notification').item.json.public_url }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 3648, + 32 + ], + "id": "8d44a126-0a95-4283-ab89-2ebf6e40baa7", + "name": "Set Output Response" + }, + { + "parameters": { + "amount": 30 + }, + "type": "n8n-nodes-base.wait", + "typeVersion": 1.1, + "position": [ + 2288, + -48 + ], + "id": "f12591be-0ef5-4cc5-8232-a9058b77d8b6", + "name": "Wait PDF Generation", + "webhookId": "a4b7c8a5-7345-417e-8c05-4519039a97b7" + }, + { + "parameters": { + "method": "POST", + "url": "=https://app.pennylane.com/api/external/v2/customer_invoices/{{ $('PL Create Invoice').item.json.id }}/send_by_email", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 2512, + -48 + ], + "id": "892471ed-1b8a-427c-a310-0f40ec49b632", + "name": "PL Send Invoice Email", + "credentials": { + "httpHeaderAuth": { + "id": "", + "name": "" + } + } + }, + { + "parameters": { + "sendTo": "={{ $('Code Validate Payload').item.json.notification_email }}", + "subject": "=[Pennylane] Invoice {{ $('Code Build Notification').item.json.invoice_number }} created for {{ $('Code Build Notification').item.json.customer_name }}", + "message": "=
\n
\n

New Invoice Created

\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Customer{{ $('Code Build Notification').item.json.customer_name }}
Invoice{{ $('Code Build Notification').item.json.invoice_number }}
Amount HT{{ $('PL Create Invoice').item.json.currency_amount_before_tax }} EUR
Total TTC{{ $('Code Build Notification').item.json.amount }} EUR
Due date{{ $('Code Validate Payload').item.json.due_date }}
Status{{ $('PL Create Invoice').item.json.status }}
\n
\n
\n View Invoice PDF\n
\n

Sent automatically by n8n-pennylane-auto-invoicing

\n
", + "options": {} + }, + "type": "n8n-nodes-base.gmail", + "typeVersion": 2.2, + "position": [ + 3216, + 176 + ], + "id": "b73e78af-3604-4d70-a583-f0074877a411", + "name": "GM Send Notification", + "webhookId": "33b592da-5502-4eb7-ad16-3735811be9dc", + "credentials": { + "gmailOAuth2": { + "id": "", + "name": "" + } + } + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "94e188dc-d883-4a77-aac1-06b5bed2dbff", + "leftValue": "={{ $('Code Validate Payload').item.json.notification_email }}", + "rightValue": "", + "operator": { + "type": "string", + "operation": "notEmpty", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 2992, + 96 + ], + "id": "88c6cf59-4ad7-4872-a7b1-116590237115", + "name": "IF Has Notification Email" + }, + { + "parameters": { + "content": "## 1. Input\n\nReceives invoice data via POST webhook.\nAny CRM, form, or script can call this endpoint.\n\nSee `examples/` folder in the GitHub repo\nfor sample payloads.", + "height": 624, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 192, + -272 + ], + "typeVersion": 1, + "id": "adf964cd-ccdd-44c7-aaa3-4b035df2fa44", + "name": "Sticky Note1" + }, + { + "parameters": { + "content": "## 2. Validation\n\nChecks required fields:\n- customer_name\n- customer_email\n- items[] with label, quantity, unit_price\n\nApplies defaults:\n- country: FR\n- currency: EUR\n- vat_rate: FR_200\n- due_date: +30 days\n- payment_conditions: 30_days", + "height": 624, + "width": 304, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 432, + -272 + ], + "typeVersion": 1, + "id": "50f9b391-c5ba-4b8e-8738-687added6648", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "## 3. Customer lookup\n\nSearches Pennylane for an existing customer\nby email. Creates one if not found.\n\nEndpoint: GET /customers (filter by emails)\nEndpoint: POST /company_customers\n\nRequired billing_address fields:\naddress, postal_code, city, country_alpha2", + "height": 624, + "width": 832, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 736, + -272 + ], + "typeVersion": 1, + "id": "6e1dd244-3c60-405c-ae59-f1dc8a60a63c", + "name": "Sticky Note3" + }, + { + "parameters": { + "content": "## 4. Invoice creation\n\nBuilds the invoice payload with line items,\nVAT, and deadline, then sends it to Pennylane.\n\nEndpoint: POST /customer_invoices\n\nAmounts must be strings (e.g. \"1500.00\").\nVAT codes: FR_200 (20%), FR_100 (10%),\nFR_055 (5.5%), exempt (0%).\n\nSet draft: true to create without finalizing.", + "height": 624, + "width": 432, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1568, + -272 + ], + "typeVersion": 1, + "id": "4a4c4708-448f-4b1c-9a21-ba0bcdf1f6ca", + "name": "Sticky Note4" + }, + { + "parameters": { + "content": "## 5. Email sending (optional)\n\nIf send_email is true in the payload,\nwaits 30s for PDF generation, then sends\nthe invoice to the customer via Pennylane.\n\nEndpoint: POST /customer_invoices/{id}/send_by_email\n\nA 409 means the PDF is not ready yet.", + "height": 624, + "width": 672, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2000, + -272 + ], + "typeVersion": 1, + "id": "53f73bb0-8e6f-4f4d-bf16-89af06cfa1bc", + "name": "Sticky Note5" + }, + { + "parameters": { + "content": "## 6. Notifications\n\nSlack: always sends a summary to your channel.\nGmail: sends an HTML notification if\nnotification_email is provided in the payload.\n\nBoth are optional. Remove or replace\nwith your preferred channel (Telegram, etc.).", + "height": 624, + "width": 800, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2672, + -272 + ], + "typeVersion": 1, + "id": "83320de8-8149-4bc1-b58d-c20c20b7b549", + "name": "Sticky Note6" + }, + { + "parameters": { + "content": "## 7. Webhook response\n\nReturns a JSON response to the caller with:\n- success: true/false\n- invoice_id\n- invoice_number\n- amount\n- customer\n- public_url (link to PDF)\n\nConfigure Error Workflow in Settings\nfor production error handling.", + "height": 624, + "width": 368, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3472, + -272 + ], + "typeVersion": 1, + "id": "bfbe45e4-cacc-4784-a309-b378ed99a2ce", + "name": "Sticky Note7" + } + ], + "pinData": {}, + "connections": { + "WH Receive Invoice Data": { + "main": [ + [ + { + "node": "Code Validate Payload", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code Validate Payload": { + "main": [ + [ + { + "node": "PL Search Customer", + "type": "main", + "index": 0 + } + ] + ] + }, + "PL Search Customer": { + "main": [ + [ + { + "node": "IF Customer Exists", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF Customer Exists": { + "main": [ + [ + { + "node": "Set Customer ID", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "PL Create Customer", + "type": "main", + "index": 0 + } + ] + ] + }, + "PL Create Customer": { + "main": [ + [ + { + "node": "Set Customer ID", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Customer ID": { + "main": [ + [ + { + "node": "Code Build Invoice Payload", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code Build Invoice Payload": { + "main": [ + [ + { + "node": "PL Create Invoice", + "type": "main", + "index": 0 + } + ] + ] + }, + "PL Create Invoice": { + "main": [ + [ + { + "node": "IF Send Email", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF Send Email": { + "main": [ + [ + { + "node": "Wait PDF Generation", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code Build Notification", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code Build Notification": { + "main": [ + [ + { + "node": "SL Send Notification", + "type": "main", + "index": 0 + }, + { + "node": "IF Has Notification Email", + "type": "main", + "index": 0 + } + ] + ] + }, + "SL Send Notification": { + "main": [ + [ + { + "node": "Set Output Response", + "type": "main", + "index": 0 + } + ] + ] + }, + "Wait PDF Generation": { + "main": [ + [ + { + "node": "PL Send Invoice Email", + "type": "main", + "index": 0 + } + ] + ] + }, + "PL Send Invoice Email": { + "main": [ + [ + { + "node": "Code Build Notification", + "type": "main", + "index": 0 + } + ] + ] + }, + "GM Send Notification": { + "main": [ + [ + { + "node": "Set Output Response", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF Has Notification Email": { + "main": [ + [ + { + "node": "GM Send Notification", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Set Output Response", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "timeSavedMode": "fixed", + "callerPolicy": "workflowsFromSameOwner", + "availableInMCP": true, + "timeSavedPerExecution": 5 + }, + "versionId": "91238c54-efda-4f81-b8d0-6b2fee1f78e8", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "70ec3bed341af97283a0a6dbf0b48c20f5e82d1a117b40451ad8016f7992090c" + }, + "id": "UI00dYBcdYiWLc5Y", + "tags": [] +} diff --git a/Invoice_Processing/[PENNYLANE] Overdue Reminder.json b/Invoice_Processing/[PENNYLANE] Overdue Reminder.json new file mode 100644 index 0000000..e9391ce --- /dev/null +++ b/Invoice_Processing/[PENNYLANE] Overdue Reminder.json @@ -0,0 +1,331 @@ +{ + "name": "[PENNYLANE] Overdue Reminder", + "nodes": [ + { + "parameters": { + "content": "# [PENNYLANE] Overdue Reminder\n\n**Trigger:** Schedule (daily at 9:00 AM)\n**Source:** Pennylane API v2 - customer invoices\n\n---\n\n### Flow\n\n`Schedule Trigger`\n → `PL Fetch Invoices`\n → `Code Filter Overdue`\n → `IF Has Overdue`\n → ✅ `Code Build Reminder`\n → `SL Send Notification`\n → `Set Done`\n\n---\n\n### Services & Credentials\n\n| Service | Credential |\n|---------|-----------|\n| Pennylane API v2 | Pennylane API (HTTP Header Auth) |\n| Slack (optional) | Slack OAuth |\n\n---\n\n### Node refs for $()\n\n- Schedule Trigger\n- PL Fetch Invoices\n- Code Filter Overdue\n- IF Has Overdue\n- Code Build Reminder\n- SL Send Notification\n- Set Done", + "height": 608, + "width": 416 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -448, + -80 + ], + "typeVersion": 1, + "id": "7709720d-5bd0-419b-9a73-d63f4baf97fe", + "name": "Sticky Note" + }, + { + "parameters": { + "rule": { + "interval": [ + { + "triggerAtHour": 9 + } + ] + } + }, + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.3, + "position": [ + 80, + 224 + ], + "id": "7c7a9506-5ec1-460d-a014-7d6db37c3a35", + "name": "Schedule Trigger" + }, + { + "parameters": { + "url": "https://app.pennylane.com/api/external/v2/customer_invoices", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 320, + 224 + ], + "id": "bf00125c-bc60-4c7e-8649-bf3d9faa2f42", + "name": "PL Fetch Invoices", + "credentials": { + "httpHeaderAuth": { + "id": "", + "name": "" + } + } + }, + { + "parameters": { + "jsCode": "// Refs: 'PL Fetch Invoices'\nconst invoices = $input.first().json.items;\nconst today = new Date().toISOString().split('T')[0];\nconst THRESHOLD_DAYS = 7;\n\nconst overdue = [];\n\nfor (const inv of invoices) {\n if (inv.draft || inv.paid) continue;\n if (inv.deadline >= today) continue;\n\n const deadlineDate = new Date(inv.deadline);\n const todayDate = new Date(today);\n const daysOverdue = Math.floor((todayDate - deadlineDate) / (1000 * 60 * 60 * 24));\n\n if (daysOverdue >= THRESHOLD_DAYS) {\n overdue.push({\n id: inv.id,\n invoice_number: inv.invoice_number,\n label: inv.label,\n currency_amount: inv.currency_amount,\n currency: inv.currency,\n deadline: inv.deadline,\n days_overdue: daysOverdue,\n customer_id: inv.customer.id,\n public_file_url: inv.public_file_url\n });\n }\n}\n\nreturn [{\n json: {\n overdue_count: overdue.length,\n overdue,\n has_overdue: overdue.length > 0\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 560, + 224 + ], + "id": "d25af660-3af7-4869-b149-7b9909ffbc0f", + "name": "Code Filter Overdue" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "1a032dfe-0694-4d96-8c75-c891a86339b2", + "leftValue": "={{ $json.has_overdue }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 736, + 224 + ], + "id": "28c82e3c-1d77-4137-8396-3d79ef392cd8", + "name": "IF Has Overdue" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "7c77345e-ab79-4816-aa14-d62c9e9b61a9", + "name": "status", + "value": "no overdue invoices", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 1552, + 240 + ], + "id": "00fca357-c089-4a78-a7eb-9bbf9a1f76b9", + "name": "Set Done" + }, + { + "parameters": { + "jsCode": "// Refs: 'Code Filter Overdue'\nconst data = $input.first().json;\nconst lines = ['🚨 Overdue Invoice Reminder', ''];\n\nlines.push(`${data.overdue_count} invoice(s) require attention:`);\nlines.push('');\n\nfor (const inv of data.overdue) {\n lines.push(`• ${inv.invoice_number} | ${inv.currency_amount} ${inv.currency}`);\n lines.push(` Due: ${inv.deadline} (${inv.days_overdue} days overdue)`);\n lines.push(` ${inv.public_file_url}`);\n lines.push('');\n}\n\nreturn [{\n json: {\n message: lines.join('\\n'),\n overdue_count: data.overdue_count\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 976, + 144 + ], + "id": "73f0f390-b353-487b-ad7e-98a87ca7d4ca", + "name": "Code Build Reminder" + }, + { + "parameters": { + "authentication": "oAuth2", + "select": "channel", + "channelId": { + "__rl": true, + "value": "", + "mode": "list", + "cachedResultName": "" + }, + "text": "={{ $json.message }}", + "otherOptions": {} + }, + "type": "n8n-nodes-base.slack", + "typeVersion": 2.4, + "position": [ + 1168, + 144 + ], + "id": "04edc672-c812-4b77-8643-d68f58bbc2c1", + "name": "SL Send Notification", + "webhookId": "6355e179-fd0a-4eed-8311-43954f4330ea", + "credentials": { + "slackOAuth2Api": { + "id": "", + "name": "" + } + } + }, + { + "parameters": { + "content": "## 1. Trigger\n\nRuns once daily at 9:00 AM.\nChecks for overdue invoices that need\nfollow-up.\n\nAdjust schedule in the Schedule Trigger\nnode to fit your needs.", + "height": 480, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 16, + -80 + ], + "typeVersion": 1, + "id": "4ca9d854-033b-49bf-bd18-7fac67c6e674", + "name": "Sticky Note1" + }, + { + "parameters": { + "content": "## 2. Fetch invoices\n\nRetrieves all customer invoices from Pennylane.\n\nEndpoint: GET /customer_invoices\n\nSame approach as WF2: no server-side\nstatus filtering available, all logic\nis handled in the Code node.", + "height": 480, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 256, + -80 + ], + "typeVersion": 1, + "id": "000b18e0-894f-4d7a-aeaa-efd9b9106545", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "## 3. Filter overdue\n\nIdentifies unpaid invoices past their\ndeadline by at least 7 days (THRESHOLD_DAYS).\n\nAdjust the threshold in Code Filter Overdue\nto change the sensitivity.\n\nSkips draft and paid invoices.\nCalculates exact days overdue for each.", + "height": 480, + "width": 432, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 496, + -80 + ], + "typeVersion": 1, + "id": "ba078fad-ae3c-4ebb-b045-384adf189fa5", + "name": "Sticky Note3" + }, + { + "parameters": { + "content": "## 4. Notify\n\nBuilds a detailed reminder with:\n- Invoice number and amount\n- Due date and days overdue\n- Direct link to the invoice PDF\n\nSends to Slack. Replace with your preferred\nnotification channel (Telegram, Email, etc.).", + "height": 480, + "width": 480, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 928, + -80 + ], + "typeVersion": 1, + "id": "c4d123ec-9c7d-41de-b22a-e93380e7e64a", + "name": "Sticky Note4" + }, + { + "parameters": { + "content": "## 5. Done\n\nNo overdue invoices detected.\nWorkflow ends silently.\n\nThis node ensures a clean execution log\nin n8n. No notification is sent.", + "height": 480, + "width": 368, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1408, + -80 + ], + "typeVersion": 1, + "id": "a8a9b804-edbb-4123-beec-1f937e50eee6", + "name": "Sticky Note5" + } + ], + "pinData": {}, + "connections": { + "Schedule Trigger": { + "main": [ + [ + { + "node": "PL Fetch Invoices", + "type": "main", + "index": 0 + } + ] + ] + }, + "PL Fetch Invoices": { + "main": [ + [ + { + "node": "Code Filter Overdue", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code Filter Overdue": { + "main": [ + [ + { + "node": "IF Has Overdue", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF Has Overdue": { + "main": [ + [ + { + "node": "Code Build Reminder", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Set Done", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code Build Reminder": { + "main": [ + [ + { + "node": "SL Send Notification", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "timeSavedMode": "fixed", + "callerPolicy": "workflowsFromSameOwner", + "availableInMCP": true + }, + "versionId": "a2cf0709-0d9b-4746-a6ca-ed2dadb03ada", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "70ec3bed341af97283a0a6dbf0b48c20f5e82d1a117b40451ad8016f7992090c" + }, + "id": "hihBb5dvnUpjT1Ti", + "tags": [] +} diff --git a/Invoice_Processing/[PENNYLANE] Track Payments.json b/Invoice_Processing/[PENNYLANE] Track Payments.json new file mode 100644 index 0000000..c59048a --- /dev/null +++ b/Invoice_Processing/[PENNYLANE] Track Payments.json @@ -0,0 +1,340 @@ +{ + "name": "[PENNYLANE] Track Payments", + "nodes": [ + { + "parameters": { + "content": "# [PENNYLANE] Track Payments\n\n**Trigger:** Schedule (every 15 minutes)\n**Source:** Pennylane API v2 - customer invoices\n\n---\n\n### Flow\n\n`Schedule Trigger`\n → `PL Fetch Invoices`\n → `Code Filter Status Changes`\n → `IF Has Updates`\n → ✅ `Code Build Payment Notification`\n → `SL Send Notification`\n → `Set Done`\n\n---\n\n### Services & Credentials\n\n| Service | Credential |\n|---------|-----------|\n| Pennylane API v2 | Pennylane API (HTTP Header Auth) |\n| Slack (optional) | Slack OAuth |\n\n---\n\n### Node refs for $()\n\n- Schedule Trigger\n- PL Fetch Invoices\n- Code Filter Status Changes\n- IF Has Updates\n- Code Build Payment Notification\n- SL Send Notification\n- Set Done", + "height": 608, + "width": 544 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -688, + -272 + ], + "typeVersion": 1, + "id": "6ea2a45d-df0a-4ec7-a38e-740c40782606", + "name": "Sticky Note" + }, + { + "parameters": { + "rule": { + "interval": [ + { + "field": "minutes", + "minutesInterval": 15 + } + ] + } + }, + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.3, + "position": [ + -16, + 32 + ], + "id": "d45398dc-a8b8-4c63-a872-ea829ecfc92c", + "name": "Schedule Trigger" + }, + { + "parameters": { + "url": "https://app.pennylane.com/api/external/v2/customer_invoices", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendQuery": true, + "queryParameters": { + "parameters": [] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 256, + 32 + ], + "id": "3472cf87-8215-4bd0-9e6d-fa78ecaed39a", + "name": "PL Fetch Invoices", + "credentials": { + "httpHeaderAuth": { + "id": "", + "name": "" + } + } + }, + { + "parameters": { + "jsCode": "// Refs: 'PL Fetch Invoices'\nconst invoices = $input.first().json.items;\nconst today = new Date().toISOString().split('T')[0];\n\nconst results = {\n paid: [],\n overdue: [],\n upcoming: []\n};\n\nfor (const inv of invoices) {\n if (inv.draft) continue;\n\n if (inv.paid) {\n results.paid.push(inv);\n } else if (inv.deadline < today) {\n results.overdue.push(inv);\n } else {\n results.upcoming.push(inv);\n }\n}\n\nreturn [{\n json: {\n total_invoices: invoices.length,\n paid_count: results.paid.length,\n overdue_count: results.overdue.length,\n upcoming_count: results.upcoming.length,\n paid: results.paid,\n overdue: results.overdue,\n has_updates: results.paid.length > 0 || results.overdue.length > 0\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 496, + 32 + ], + "id": "d2b7e251-96c3-4228-b08c-2c147dcda8b7", + "name": "Code Filter Status Changes" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "f6d3fe78-bb28-4193-b4c0-7d04e963d48f", + "leftValue": "={{ $json.has_updates }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "looseTypeValidation": true, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 672, + 32 + ], + "id": "023f3a34-1a30-4ab2-b8f4-14227dac655c", + "name": "IF Has Updates" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "aee99e96-e05f-4770-b8c4-53a574358d7b", + "name": "Status", + "value": "no updates", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 1504, + 48 + ], + "id": "ac4d9578-3a5b-43cc-ab0a-8673987a377a", + "name": "Set Done" + }, + { + "parameters": { + "jsCode": "// Refs: 'Code Filter Status Changes'\nconst data = $input.first().json;\nconst lines = ['📊 Pennylane Invoice Status Report', ''];\n\nif (data.paid_count > 0) {\n lines.push(`✅ ${data.paid_count} invoice(s) paid:`);\n for (const inv of data.paid) {\n lines.push(` - ${inv.invoice_number} | ${inv.currency_amount} ${inv.currency} | ${inv.label}`);\n }\n lines.push('');\n}\n\nif (data.overdue_count > 0) {\n lines.push(`⚠️ ${data.overdue_count} invoice(s) overdue:`);\n for (const inv of data.overdue) {\n lines.push(` - ${inv.invoice_number} | ${inv.currency_amount} ${inv.currency} | Due: ${inv.deadline} | ${inv.label}`);\n }\n lines.push('');\n}\n\nlines.push(`📋 ${data.upcoming_count} invoice(s) upcoming`);\nlines.push(`Total tracked: ${data.total_invoices}`);\n\nreturn [{\n json: {\n message: lines.join('\\n'),\n paid_count: data.paid_count,\n overdue_count: data.overdue_count,\n upcoming_count: data.upcoming_count\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 912, + -48 + ], + "id": "66da8691-e7a6-4c3d-92f6-701fa2fdcc18", + "name": "Code Build Payment Notification" + }, + { + "parameters": { + "authentication": "oAuth2", + "select": "channel", + "channelId": { + "__rl": true, + "value": "", + "mode": "list", + "cachedResultName": "" + }, + "text": "={{ $json.message }}", + "otherOptions": {} + }, + "type": "n8n-nodes-base.slack", + "typeVersion": 2.4, + "position": [ + 1152, + -48 + ], + "id": "8284d459-6b14-428c-84c2-208511b97546", + "name": "SL Send Notification", + "webhookId": "763cb5da-2c6f-4311-9c3c-a652d891af6e", + "credentials": { + "slackOAuth2Api": { + "id": "", + "name": "" + } + } + }, + { + "parameters": { + "content": "## 1. Trigger\n\nRuns every 15 minutes.\nPolls Pennylane for all customer invoices.\n\nAdjust the schedule interval in the\nSchedule Trigger node to fit your needs.", + "height": 560, + "width": 272, + "color": 6 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -112, + -272 + ], + "typeVersion": 1, + "id": "8110087c-5802-4805-bedb-151a9261bb02", + "name": "Sticky Note1" + }, + { + "parameters": { + "content": "## 2. Fetch invoices\n\nRetrieves all customer invoices from Pennylane.\n\nEndpoint: GET /customer_invoices\n\nNote: the Pennylane API does not support\nfiltering by status or paid fields.\nAll filtering is done in the next Code node.", + "height": 560, + "width": 272, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 160, + -272 + ], + "typeVersion": 1, + "id": "07df7bfc-ded9-4119-b697-706ed1710194", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "## 3. Classify & filter\n\nClassifies each invoice into 3 categories:\n- paid: invoice has been settled\n- overdue: past deadline and still unpaid\n- upcoming: not yet due\n\nOnly triggers a notification if there are\npaid or overdue invoices (has_updates = true).\n\nIf all invoices are upcoming, the workflow\nends silently via Set Done.", + "height": 560, + "width": 416, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 432, + -272 + ], + "typeVersion": 1, + "id": "f4e10525-59ed-46a2-ad6b-1b2b8e4bb789", + "name": "Sticky Note3" + }, + { + "parameters": { + "content": "## 4. Notify\n\nBuilds a summary message with:\n- List of newly paid invoices\n- List of overdue invoices\n- Count of upcoming invoices\n\nSends to Slack. Replace with your preferred\nnotification channel (Telegram, Email, etc.).", + "height": 560, + "width": 512, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 848, + -272 + ], + "typeVersion": 1, + "id": "ee1f614f-fc39-4d68-b1e6-74dc303b0297", + "name": "Sticky Note4" + }, + { + "parameters": { + "content": "## 5. Done\n\nNo paid or overdue invoices detected.\nWorkflow ends silently.\n\nThis node ensures a clean execution log\nin n8n. No notification is sent.", + "height": 560, + "width": 368, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1360, + -272 + ], + "typeVersion": 1, + "id": "4f5a9ddb-f537-4282-8c6a-47a3b36f30b5", + "name": "Sticky Note5" + } + ], + "pinData": {}, + "connections": { + "Schedule Trigger": { + "main": [ + [ + { + "node": "PL Fetch Invoices", + "type": "main", + "index": 0 + } + ] + ] + }, + "PL Fetch Invoices": { + "main": [ + [ + { + "node": "Code Filter Status Changes", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code Filter Status Changes": { + "main": [ + [ + { + "node": "IF Has Updates", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF Has Updates": { + "main": [ + [ + { + "node": "Code Build Payment Notification", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Set Done", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code Build Payment Notification": { + "main": [ + [ + { + "node": "SL Send Notification", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "timeSavedMode": "fixed", + "callerPolicy": "workflowsFromSameOwner", + "executionTimeout": -1, + "availableInMCP": true + }, + "versionId": "b182dbdb-49af-4e9b-abb5-964fbb5f6171", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "70ec3bed341af97283a0a6dbf0b48c20f5e82d1a117b40451ad8016f7992090c" + }, + "id": "iiAZtgzpKqekwA9W", + "tags": [] +}