{
  "name": "INV 03 - Approval Fallback and Cash-Flow Radar",
  "nodes": [
    {
      "name": "Monitor Purpose",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1100,
        -430
      ],
      "parameters": {
        "content": "## Approval fallback + weekly cash-flow radar\nEvery hour: emails the owner once when a Telegram approval has waited 4 hours.\nEvery Monday at 08:30: sends cash-flow exposure to Telegram and the owner email.\nSet the owner email before activation.",
        "height": 300,
        "width": 740,
        "color": 5
      }
    },
    {
      "name": "Hourly Fallback Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -1040,
        -60
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 1
            }
          ]
        }
      }
    },
    {
      "name": "Monday Radar Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -1040,
        220
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "30 8 * * 1"
            }
          ]
        }
      }
    },
    {
      "name": "Owner Settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -800,
        60
      ],
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "own1",
              "name": "owner_email",
              "value": "SET_OWNER_EMAIL_BEFORE_ACTIVATION",
              "type": "string"
            },
            {
              "id": "own2",
              "name": "installation_currency",
              "value": "USD",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "name": "Read Queue for Fallback",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        -560,
        -100
      ],
      "parameters": {
        "authentication": "oAuth2",
        "resource": "sheet",
        "operation": "read",
        "documentId": {
          "mode": "id",
          "value": "SPREADSHEET_ID"
        },
        "sheetName": {
          "mode": "id",
          "value": "APPROVAL_QUEUE_SHEET_ID"
        },
        "filtersUI": {},
        "combineFilters": "AND",
        "options": {}
      },
      "alwaysOutputData": true
    },
    {
      "name": "Find Due Fallback Notices",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -300,
        -100
      ],
      "parameters": {
        "jsCode": "const now=new Date(); return $input.all().map(i=>i.json).filter(q=>String(q.status).toUpperCase()==='PENDING'&&q.fallback_due_at&&new Date(q.fallback_due_at)<=now&&!q.fallback_sent_at).map(q=>({json:{...q,route:'FALLBACK',owner_email:$('Owner Settings').first().json.owner_email,email_subject:`Approval waiting: invoice ${q.invoice_number}`,email_body:`An invoice reminder is waiting for your approval in Telegram.\\n\\nInvoice: ${q.invoice_number}\\nCustomer: ${q.customer_name}\\nStage: ${q.stage}\\nBalance: ${q.currency} ${q.balance_snapshot}\\nWaiting since: ${q.created_at}\\n\\nOpen Telegram and use Approve, Edit, Skip, or Defer.`}}));"
      }
    },
    {
      "name": "Read Invoices for Radar",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        -560,
        260
      ],
      "parameters": {
        "authentication": "oAuth2",
        "resource": "sheet",
        "operation": "read",
        "documentId": {
          "mode": "id",
          "value": "SPREADSHEET_ID"
        },
        "sheetName": {
          "mode": "id",
          "value": "INVOICES_SHEET_ID"
        },
        "filtersUI": {},
        "combineFilters": "AND",
        "options": {}
      }
    },
    {
      "name": "Read Log for Radar",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        -300,
        260
      ],
      "parameters": {
        "authentication": "oAuth2",
        "resource": "sheet",
        "operation": "read",
        "documentId": {
          "mode": "id",
          "value": "SPREADSHEET_ID"
        },
        "sheetName": {
          "mode": "id",
          "value": "REMINDER_LOG_SHEET_ID"
        },
        "filtersUI": {},
        "combineFilters": "AND",
        "options": {}
      },
      "alwaysOutputData": true
    },
    {
      "name": "Build Weekly Cash Flow Radar",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -60,
        260
      ],
      "parameters": {
        "jsCode": "const invoices=$('Read Invoices for Radar').all().map(i=>i.json).slice(0,500); const logs=$input.all().map(i=>i.json); const cfg=$('Owner Settings').first().json; const now=new Date(); const day=86400000; const open=invoices.filter(x=>Number(x.balance_due)>0&&!['PAID','VOID','CANCELLED','DISPUTED'].includes(String(x.status).toUpperCase())&&String(x.is_disputed).toLowerCase()!=='true');\nconst bucket={current:0,d1_7:0,d8_14:0,d15_30:0,d31:0}; for(const x of open){const d=Math.floor((now-new Date(x.due_date))/day),v=Number(x.balance_due)||0;if(d<=0)bucket.current+=v;else if(d<=7)bucket.d1_7+=v;else if(d<=14)bucket.d8_14+=v;else if(d<=30)bucket.d15_30+=v;else bucket.d31+=v;}\nconst total=open.reduce((a,x)=>a+Number(x.balance_due||0),0); const sent7=logs.filter(x=>String(x.event_status).toUpperCase()==='SENT'&&new Date(x.created_at)>=new Date(now-7*day)).length; const fmt=v=>new Intl.NumberFormat('en',{style:'currency',currency:cfg.installation_currency}).format(v);\nconst text=`📊 <b>WEEKLY CASH-FLOW RADAR</b>\\n\\nOpen invoices: ${open.length}\\nTotal outstanding: ${fmt(total)}\\n\\nCurrent/not due: ${fmt(bucket.current)}\\n1–7 days overdue: ${fmt(bucket.d1_7)}\\n8–14 days overdue: ${fmt(bucket.d8_14)}\\n15–30 days overdue: ${fmt(bucket.d15_30)}\\n31+ days overdue: ${fmt(bucket.d31)}\\n\\nReminders sent in 7 days: ${sent7}`;\nreturn [{json:{route:'RADAR',owner_email:cfg.owner_email,telegram_text:text,email_subject:`Weekly cash-flow radar — ${fmt(total)} outstanding`,email_body:text.replace(/<[^>]+>/g,'')}}];"
      }
    },
    {
      "name": "Combine Monitor Routes",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        60
      ],
      "parameters": {
        "jsCode": "return $input.all();"
      }
    },
    {
      "name": "Is Fallback Notice?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        720,
        60
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "condition-6",
              "leftValue": "={{ $json.route }}",
              "rightValue": "FALLBACK",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "name": "Email Owner Fallback",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        980,
        -120
      ],
      "parameters": {
        "resource": "email",
        "operation": "send",
        "fromEmail": "SENDER_NAME <VERIFIED_FROM_ADDRESS>",
        "toEmail": "={{ $json.owner_email }}",
        "subject": "={{ $json.email_subject }}",
        "emailFormat": "text",
        "text": "={{ $json.email_body }}",
        "options": {
          "appendAttribution": false
        }
      }
    },
    {
      "name": "Mark Fallback Sent",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1220,
        -120
      ],
      "parameters": {
        "jsCode": "return [{json:{...$('Find Due Fallback Notices').item.json,fallback_sent_at:new Date().toISOString(),last_updated_at:new Date().toISOString(),delivery_detail:'Owner fallback email sent'}}];"
      }
    },
    {
      "name": "Update Fallback Timestamp",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 3,
      "position": [
        1460,
        -120
      ],
      "parameters": {
        "authentication": "oAuth2",
        "resource": "sheet",
        "operation": "appendOrUpdate",
        "documentId": {
          "mode": "id",
          "value": "SPREADSHEET_ID"
        },
        "sheetName": {
          "mode": "id",
          "value": "APPROVAL_QUEUE_SHEET_ID"
        },
        "dataMode": "autoMapInputData",
        "columnToMatchOn": "approval_id",
        "options": {
          "handlingExtraData": "ignoreIt"
        }
      }
    },
    {
      "name": "Send Weekly Radar to Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        980,
        220
      ],
      "parameters": {
        "resource": "message",
        "operation": "sendMessage",
        "chatId": "OWNER_CHAT_ID",
        "text": "={{ $json.telegram_text }}",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      }
    },
    {
      "name": "Email Weekly Radar to Owner",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        1220,
        220
      ],
      "parameters": {
        "resource": "email",
        "operation": "send",
        "fromEmail": "SENDER_NAME <VERIFIED_FROM_ADDRESS>",
        "toEmail": "={{ $json.owner_email }}",
        "subject": "={{ $json.email_subject }}",
        "emailFormat": "text",
        "text": "={{ $json.email_body }}",
        "options": {
          "appendAttribution": false
        }
      }
    }
  ],
  "connections": {
    "Hourly Fallback Trigger": {
      "main": [
        [
          {
            "node": "Owner Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Monday Radar Trigger": {
      "main": [
        [
          {
            "node": "Owner Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Owner Settings": {
      "main": [
        [
          {
            "node": "Read Queue for Fallback",
            "type": "main",
            "index": 0
          },
          {
            "node": "Read Invoices for Radar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Queue for Fallback": {
      "main": [
        [
          {
            "node": "Find Due Fallback Notices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Due Fallback Notices": {
      "main": [
        [
          {
            "node": "Combine Monitor Routes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Invoices for Radar": {
      "main": [
        [
          {
            "node": "Read Log for Radar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Log for Radar": {
      "main": [
        [
          {
            "node": "Build Weekly Cash Flow Radar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Weekly Cash Flow Radar": {
      "main": [
        [
          {
            "node": "Combine Monitor Routes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Monitor Routes": {
      "main": [
        [
          {
            "node": "Is Fallback Notice?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Fallback Notice?": {
      "main": [
        [
          {
            "node": "Email Owner Fallback",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Weekly Radar to Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Owner Fallback": {
      "main": [
        [
          {
            "node": "Mark Fallback Sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Fallback Sent": {
      "main": [
        [
          {
            "node": "Update Fallback Timestamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Weekly Radar to Telegram": {
      "main": [
        [
          {
            "node": "Email Weekly Radar to Owner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "timezone": "TIMEZONE",
    "saveManualExecutions": true,
    "saveExecutionProgress": true,
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all",
    "executionTimeout": 300
  },
  "pinData": {},
  "tags": []
}
