[
  {
    "id": "invoiceApproval01",
    "name": "Invoice approval workflow",
    "nodes": [
      {
        "parameters": {
          "httpMethod": "POST",
          "path": "submit-invoice",
          "responseMode": "responseNode",
          "options": {}
        },
        "id": "f1",
        "name": "Submit invoice (webhook)",
        "type": "n8n-nodes-base.webhook",
        "typeVersion": 2,
        "position": [
          0,
          0
        ],
        "webhookId": "inv-submit"
      },
      {
        "parameters": {
          "jsCode": "// Validate the invoice, check for duplicates and decide who must approve it\nconst body = $input.first().json.body || {};\nconst missing = ['invoice_no', 'vendor', 'amount', 'submitted_by'].filter(k => !body[k]);\nconst amount = Number(body.amount);\n\n// Approval matrix: edit these limits and addresses to match your policy\nconst AUTO_LIMIT = 500;        // up to this amount: approved automatically\nconst MANAGER_LIMIT = 5000;    // up to this amount: manager approves; above it: finance director\nconst MANAGER = 'manager@360automate.test';\nconst DIRECTOR = 'director@360automate.test';\n\nconst sd = $getWorkflowStaticData('global');\nsd.invoices = sd.invoices || {};\n\nlet route;\nif (missing.length || !(amount > 0)) route = 'invalid';\nelse if (sd.invoices[body.invoice_no]) route = 'duplicate';\nelse if (amount <= AUTO_LIMIT) route = 'auto';\nelse route = 'approval';\n\nconst approver = amount <= MANAGER_LIMIT ? MANAGER : DIRECTOR;\nif (route === 'auto' || route === 'approval') {\n  sd.invoices[body.invoice_no] = {\n    invoice_no: body.invoice_no, vendor: body.vendor, amount, submitted_by: body.submitted_by,\n    status: route === 'auto' ? 'approved' : 'pending',\n    approver: route === 'auto' ? 'automatic (under limit)' : approver,\n    log: [{ at: new Date().toISOString(), event: route === 'auto' ? `Auto-approved: ${amount} is within the ${AUTO_LIMIT} limit` : `Sent to ${approver} for approval` }],\n  };\n}\nreturn [{ json: { invoice_no: body.invoice_no, vendor: body.vendor, amount, currency: body.currency || 'USD', description: body.description || '',\n  submitted_by: body.submitted_by, route, approver_email: approver, missing: missing.join(', ') } }];"
        },
        "id": "f2",
        "name": "Route by amount",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          250,
          0
        ]
      },
      {
        "parameters": {
          "respondWith": "json",
          "responseBody": "={{ { \"status\": $json.route, \"invoice_no\": $json.invoice_no, \"approver\": $json.route === 'approval' ? $json.approver_email : null } }}",
          "options": {}
        },
        "id": "f3",
        "name": "Respond to submitter",
        "type": "n8n-nodes-base.respondToWebhook",
        "typeVersion": 1.4,
        "position": [
          500,
          0
        ]
      },
      {
        "parameters": {
          "rules": {
            "values": [
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "s1",
                      "leftValue": "={{ $json.route }}",
                      "rightValue": "auto",
                      "operator": {
                        "type": "string",
                        "operation": "equals"
                      }
                    }
                  ],
                  "combinator": "and"
                },
                "renameOutput": true,
                "outputKey": "Auto-approve"
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "s2",
                      "leftValue": "={{ $json.route }}",
                      "rightValue": "approval",
                      "operator": {
                        "type": "string",
                        "operation": "equals"
                      }
                    }
                  ],
                  "combinator": "and"
                },
                "renameOutput": true,
                "outputKey": "Needs approval"
              }
            ]
          },
          "options": {
            "fallbackOutput": "extra",
            "renameFallbackOutput": "Rejected at intake"
          }
        },
        "id": "f4",
        "name": "Which route?",
        "type": "n8n-nodes-base.switch",
        "typeVersion": 3.2,
        "position": [
          750,
          0
        ]
      },
      {
        "parameters": {
          "fromEmail": "finance-bot@360automate.test",
          "toEmail": "ap@360automate.test",
          "subject": "=Approved automatically: {{ $json.invoice_no }} ({{ $json.vendor }}, {{ $json.currency }} {{ $json.amount }})",
          "emailFormat": "text",
          "text": "=Invoice {{ $json.invoice_no }} from {{ $json.vendor }} for {{ $json.currency }} {{ $json.amount }} is within the automatic approval limit and is ready for payment.\n\nSubmitted by: {{ $json.submitted_by }}",
          "options": {
            "appendAttribution": false
          }
        },
        "id": "f5",
        "name": "Tell AP: auto-approved",
        "type": "n8n-nodes-base.emailSend",
        "typeVersion": 2.1,
        "position": [
          1050,
          -220
        ],
        "credentials": {
          "smtp": {
            "id": "mailpitSmtp01",
            "name": "Local Mailpit SMTP"
          }
        }
      },
      {
        "parameters": {
          "operation": "sendAndWait",
          "fromEmail": "finance-bot@360automate.test",
          "toEmail": "={{ $json.approver_email }}",
          "subject": "=Approval needed: {{ $json.invoice_no }} ({{ $json.currency }} {{ $json.amount }})",
          "message": "=Vendor: {{ $json.vendor }}\nAmount: {{ $json.currency }} {{ $json.amount }}\nDescription: {{ $json.description }}\nSubmitted by: {{ $json.submitted_by }}",
          "responseType": "approval",
          "approvalOptions": {
            "values": {
              "approvalType": "double",
              "approveLabel": "Approve",
              "disapproveLabel": "Reject"
            }
          },
          "options": {
            "appendAttribution": false
          }
        },
        "id": "f6",
        "name": "Ask approver",
        "type": "n8n-nodes-base.emailSend",
        "typeVersion": 2.1,
        "position": [
          1050,
          0
        ],
        "credentials": {
          "smtp": {
            "id": "mailpitSmtp01",
            "name": "Local Mailpit SMTP"
          }
        }
      },
      {
        "parameters": {
          "conditions": {
            "options": {
              "caseSensitive": true,
              "leftValue": "",
              "typeValidation": "strict",
              "version": 2
            },
            "conditions": [
              {
                "id": "i1",
                "leftValue": "={{ $json.data.approved }}",
                "rightValue": true,
                "operator": {
                  "type": "boolean",
                  "operation": "true",
                  "singleValue": true
                }
              }
            ],
            "combinator": "and"
          },
          "options": {}
        },
        "id": "f7",
        "name": "Approved?",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          1300,
          0
        ]
      },
      {
        "parameters": {
          "jsCode": "// Record the decision in the audit log\nconst inv = $('Route by amount').item.json;\nconst sd = $getWorkflowStaticData('global');\nconst rec = (sd.invoices || {})[inv.invoice_no];\nif (rec) {\n  rec.status = 'approved';\n  rec.log.push({ at: new Date().toISOString(), event: 'Approved by ' + inv.approver_email });\n}\nreturn [{ json: { ...inv, status: 'approved' } }];"
        },
        "id": "f8",
        "name": "Record approval",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1550,
          -110
        ]
      },
      {
        "parameters": {
          "fromEmail": "finance-bot@360automate.test",
          "toEmail": "ap@360automate.test",
          "subject": "=Approved: {{ $json.invoice_no }} ({{ $json.vendor }}, {{ $json.currency }} {{ $json.amount }})",
          "emailFormat": "text",
          "text": "=Invoice {{ $json.invoice_no }} was approved by {{ $json.approver_email }} and is ready for payment.",
          "options": {
            "appendAttribution": false
          }
        },
        "id": "f9",
        "name": "Tell AP: approved",
        "type": "n8n-nodes-base.emailSend",
        "typeVersion": 2.1,
        "position": [
          1800,
          -110
        ],
        "credentials": {
          "smtp": {
            "id": "mailpitSmtp01",
            "name": "Local Mailpit SMTP"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "// Record the decision in the audit log\nconst inv = $('Route by amount').item.json;\nconst sd = $getWorkflowStaticData('global');\nconst rec = (sd.invoices || {})[inv.invoice_no];\nif (rec) {\n  rec.status = 'rejected';\n  rec.log.push({ at: new Date().toISOString(), event: 'Rejected by ' + inv.approver_email });\n}\nreturn [{ json: { ...inv, status: 'rejected' } }];"
        },
        "id": "f10",
        "name": "Record rejection",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1550,
          110
        ]
      },
      {
        "parameters": {
          "fromEmail": "finance-bot@360automate.test",
          "toEmail": "={{ $json.submitted_by }}",
          "subject": "=Invoice {{ $json.invoice_no }} was rejected",
          "emailFormat": "text",
          "text": "=Your invoice {{ $json.invoice_no }} ({{ $json.vendor }}, {{ $json.currency }} {{ $json.amount }}) was rejected by {{ $json.approver_email }}. Please contact them for details.",
          "options": {
            "appendAttribution": false
          }
        },
        "id": "f11",
        "name": "Tell submitter: rejected",
        "type": "n8n-nodes-base.emailSend",
        "typeVersion": 2.1,
        "position": [
          1800,
          110
        ],
        "credentials": {
          "smtp": {
            "id": "mailpitSmtp01",
            "name": "Local Mailpit SMTP"
          }
        }
      },
      {
        "parameters": {
          "fromEmail": "finance-bot@360automate.test",
          "toEmail": "={{ $json.submitted_by }}",
          "subject": "=Invoice {{ $json.invoice_no || '(no number)' }} was not accepted",
          "emailFormat": "text",
          "text": "={{ $json.route === 'duplicate' ? 'An invoice with this number was already submitted.' : 'The invoice is missing required fields: ' + $json.missing + '.' }}",
          "options": {
            "appendAttribution": false
          }
        },
        "id": "f12",
        "name": "Tell submitter: not accepted",
        "type": "n8n-nodes-base.emailSend",
        "typeVersion": 2.1,
        "position": [
          1050,
          240
        ],
        "credentials": {
          "smtp": {
            "id": "mailpitSmtp01",
            "name": "Local Mailpit SMTP"
          }
        }
      },
      {
        "parameters": {
          "httpMethod": "GET",
          "path": "invoice-status",
          "responseMode": "responseNode",
          "options": {}
        },
        "id": "f13",
        "name": "Invoice status (webhook)",
        "type": "n8n-nodes-base.webhook",
        "typeVersion": 2,
        "position": [
          0,
          420
        ],
        "webhookId": "inv-status"
      },
      {
        "parameters": {
          "jsCode": "// Return the status and audit log of one invoice\nconst no = $input.first().json.query.invoice_no;\nconst rec = (($getWorkflowStaticData('global').invoices) || {})[no];\nreturn [{ json: rec || { error: 'Invoice not found', invoice_no: no } }];"
        },
        "id": "f14",
        "name": "Look up invoice",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          250,
          420
        ]
      },
      {
        "parameters": {
          "respondWith": "json",
          "responseBody": "={{ $json }}",
          "options": {}
        },
        "id": "f15",
        "name": "Respond with audit log",
        "type": "n8n-nodes-base.respondToWebhook",
        "typeVersion": 1.4,
        "position": [
          500,
          420
        ]
      }
    ],
    "connections": {
      "Submit invoice (webhook)": {
        "main": [
          [
            {
              "node": "Route by amount",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Route by amount": {
        "main": [
          [
            {
              "node": "Respond to submitter",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Respond to submitter": {
        "main": [
          [
            {
              "node": "Which route?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Ask approver": {
        "main": [
          [
            {
              "node": "Approved?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Record approval": {
        "main": [
          [
            {
              "node": "Tell AP: approved",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Record rejection": {
        "main": [
          [
            {
              "node": "Tell submitter: rejected",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Invoice status (webhook)": {
        "main": [
          [
            {
              "node": "Look up invoice",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Look up invoice": {
        "main": [
          [
            {
              "node": "Respond with audit log",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Which route?": {
        "main": [
          [
            {
              "node": "Tell AP: auto-approved",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Ask approver",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Tell submitter: not accepted",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Approved?": {
        "main": [
          [
            {
              "node": "Record approval",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Record rejection",
              "type": "main",
              "index": 0
            }
          ]
        ]
      }
    },
    "settings": {
      "executionOrder": "v1"
    },
    "active": false
  }
]