{
  "name": "Arvifund - Supabase (Fixed v6)",
  "nodes": [
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "ad99e257-bcaf-42a2-b068-53e0c96729db",
                    "leftValue": "={{ $json.message.photo[2] }}",
                    "rightValue": "",
                    "operator": {
                      "type": "object",
                      "operation": "exists",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "69cae014-32a5-44e4-abb1-bd8324df9b65",
                    "leftValue": "={{ $json.message.text }}",
                    "rightValue": "=/report",
                    "operator": {
                      "type": "string",
                      "operation": "startsWith"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "dc0ba5e6-f00f-40d7-acd8-e639d4fd38da",
                    "leftValue": "={{ $json.message.text }}",
                    "rightValue": "/start",
                    "operator": {
                      "type": "string",
                      "operation": "startsWith"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "4f8d9425-641e-4044-a54a-d73444ea28e0",
                    "leftValue": "={{ $json.message.text }}",
                    "rightValue": "/help",
                    "operator": {
                      "type": "string",
                      "operation": "startsWith"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "report-hari-001",
                    "leftValue": "={{ $json.message.text }}",
                    "rightValue": "/hariini",
                    "operator": {
                      "type": "string",
                      "operation": "startsWith"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "f0be09d4-a09a-45be-ae7a-9becc676ed4c",
                    "leftValue": "={{ $json.message.text }}",
                    "rightValue": "/laporanbulanan",
                    "operator": {
                      "type": "string",
                      "operation": "startsWith"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "41e89fcf-d3a4-4a71-9a0b-5b0d7ea841fc",
                    "leftValue": "={{ $json.message.text }}",
                    "rightValue": "/",
                    "operator": {
                      "type": "string",
                      "operation": "exists",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "faff551c-7e32-447f-83a9-f126bc57664f",
                    "leftValue": "={{ $json.message.voice }}",
                    "rightValue": "",
                    "operator": {
                      "type": "object",
                      "operation": "exists",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        976,
        3152
      ],
      "id": "92dbc971-7293-4ba8-89ee-adc95b69633d",
      "name": "Switch"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "23f4316d-5a77-45ae-81e7-18ee4cacb995",
              "leftValue": "={{ $json.candidates[0].content.parts[0].text }}",
              "rightValue": "ERROR:",
              "operator": {
                "type": "string",
                "operation": "contains"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {
          "ignoreCase": true
        }
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2016,
        2464
      ],
      "id": "5b46ee85-d44f-47ec-bf55-c521444874c8",
      "name": "If"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=<redacted-credential>",
        "sendBody": true,
        "specifyBody": "=json",
        "bodyParameters": {
          "parameters": [
            {}
          ]
        },
        "jsonBody": "={{ JSON.stringify({ contents: [{ parts: [{ inline_data: { mime_type: \"image/jpeg\", data: $json.base64Image } }, { text: \"Analisa struk belanja dalam foto ini dan kembalikan HANYA dalam format berikut tanpa teks lain:\\n\\nTOKO: [nama toko]\\nTOTAL: [total dalam angka saja tanpa Rp]\\nITEMS: [daftar barang]\\nKATEGORI: [Makanan & Minuman/Tagihan/Kosmetik & Perawatan/Transportasi/Kesehatan/Pakaian/Elektronik/Rumah Tangga/Pendidikan/Hiburan/Cicilan/Investasi/Lainnya]\\nJENIS: Pengeluaran\\nMETODE: [Cash/QRIS/Transfer/Card/Cardless]\\nBANK: [nama bank, jika tidak ada tulis BRI]\\nTANGGAL: [tanggal hari ini format YYYY-MM-DD]\\nTANGGAL_STRUK: [tanggal di struk format YYYY-MM-DD]\" }] }] }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1776,
        2480
      ],
      "id": "d1e3a8c2-a6fe-44a8-b536-d69bc663f397",
      "name": "Analisa gambar",
      "retryOnFail": true,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
        "text": "Struk tidak dapat dibaca dengan jelas.\n\nPastikan:\n- Foto struk jelas dan tidak blur\n- Pencahayaan cukup\n- Semua text terlihat\n\nSilakan coba foto ulang! \ud83d\udcf7",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2240,
        2368
      ],
      "id": "0790a0ca-b72f-4b68-be59-2d193e99d69f",
      "name": "Respon error",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "create",
        "tableId": "expenses",
        "dataToSend": "defineBelow",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "toko",
              "fieldValue": "={{ $json.candidates[0].content.parts[0].text.match(/TOKO: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "tanggal",
              "fieldValue": "={{ $json.candidates[0].content.parts[0].text.match(/TANGGAL_STRUK: (.+)/)?.[1]?.trim() }}"
            },
            {
              "fieldId": "bulan",
              "fieldValue": "={{ (() => { const d = new Date($json.candidates[0].content.parts[0].text.match(/TANGGAL_STRUK: (.+)/)?.[1]?.trim()); const b = ['Januari','Februari','Maret','April','Mei','Juni','Juli','Agustus','September','Oktober','November','Desember']; return b[d.getMonth()]; })() }}"
            },
            {
              "fieldId": "transaksi",
              "fieldValue": "={{ $json.candidates[0].content.parts[0].text.match(/METODE: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "uraian",
              "fieldValue": "={{ $json.candidates[0].content.parts[0].text.match(/ITEMS: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "kategori",
              "fieldValue": "={{ $json.candidates[0].content.parts[0].text.match(/KATEGORI: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "bank",
              "fieldValue": "={{ $json.candidates[0].content.parts[0].text.match(/BANK: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "nilai",
              "fieldValue": "={{ parseFloat($json.candidates[0].content.parts[0].text.match(/TOTAL: (\\d+)/)[1]) }}"
            },
            {
              "fieldId": "user_id",
              "fieldValue": "={{ $('Get User Profile OCR').first().json.id }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        2240,
        2576
      ],
      "id": "e1cf15f2-82df-4957-bec3-85344724bf6c",
      "name": "Expenses OCR",
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "content": "## Workflow untuk upload gambar struk",
        "height": 348,
        "width": 1844,
        "color": 4
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1232,
        2384
      ],
      "typeVersion": 1,
      "id": "5887ba01-a741-4e65-8344-3e1485a2367f",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
        "text": "=\u2705 *Lapor Baba/Ibu, data sudah Pijan simpan!*\n\n\ud83c\udfea *Toko:* {{ $json.toko }}\n\ud83d\udcb0 *Nilai:* Rp {{ $json.nilai }}\n\ud83d\udce6 *Uraian:* {{ $json.uraian }}\n\ud83d\udcc5 *Tanggal:* {{ $json.tanggal }}\n\ud83d\udcc6 *Bulan:* {{ $json.bulan }}\n\ud83d\uddc2 *Kategori:* {{ $json.kategori }}\n\ud83d\udcb3 *Transaksi:* {{ $json.transaksi }}\n\ud83c\udfe6 *Bank:* {{ $json.bank }}\n\n\ud83d\udcca *Data sudah aman di Supabase!*\nMakasih ya Baba/Ibu \ud83d\ude4f",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2464,
        2576
      ],
      "id": "1055ab3e-8c42-4478-bb71-3b2202f0fa88",
      "name": "Telegram1",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
        "text": "=\u2705 *Lapor Baba/Ibu, catatannya sudah Pijan simpan!*\n\n\ud83c\udfea *Toko:* {{ $json.toko }}\n\ud83d\udcb0 *Nilai:* Rp {{ $json.nilai }}\n\ud83d\udce6 *Uraian:* {{ $json.uraian }}\n\ud83d\udcc5 *Tanggal:* {{ $json.tanggal }}\n\ud83d\udcc6 *Bulan:* {{ $json.bulan }}\n\ud83d\uddc2 *Kategori:* {{ $json.kategori }}\n\ud83d\udcb3 *Transaksi:* {{ $json.transaksi }}\n\ud83c\udfe6 *Bank:* {{ $json.bank }}\n\n\ud83d\udcca *Data belanja kita sudah aman di Supabase!*\nMakasih ya Baba dan Ibu sudah rajin catat \ud83d\ude4f",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2768,
        4496
      ],
      "id": "b6200b88-169e-49fe-8feb-677ebf2326a4",
      "name": "Telegram3",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $json.message.chat.id }}",
        "text": "<b>\ud83e\udd16 Instruksi Bot Pijan</b>\n\n\ud83d\udcf8 <b>Kirim foto struk</b> \u2794 Otomatis tersimpan\n\u270d\ufe0f <b>Ketik pembelian</b> \u2794 \"beli telur 20rb di warung Ajun Cash\"\n\ud83c\udfe7 <b>Catat Tarik Tunai</b> \u2794 \"Tarik tunai 500k bca di mandiraja\"\n\ud83d\udcca <b>Cek laporan</b> \u2794 /laporanbulanan cth(/laporanbulananapril) maka otomatis terkirim ke Email & /report untuk laporan bulan sekarrang via telegram\n\ud83d\udcf2 <b>Dashboard view</b> \u2794 Klik link di bawah ini:\n\n<a href=\"https://script.google.com/macros/s/AKfycbwjVvhLEs2RQiH_DiTTvGKrMW43yoTG74_pH_Z5NOA_Dp6R3P8pu0__K6FnKWrzfqQg/exec\">Buka Dashboard Keuangan</a>\n\n// Kalau ada yang error atau bingung, tanya Baba aja ya!\n\nMulai dengan kirim foto struk atau ketik belanja Ibu dan Baba pijan \u2764\ufe0f\ud83d\ude80\nKalian jangan boros yaa \ud83d\ude0a\n\n<b>ARVIDZAN REZQIANO DARMAWAN</b> \ud83d\udc69\u200d\ud83d\ude80",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "HTML"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1616,
        3312
      ],
      "id": "e572b908-f138-455c-b86c-2cd25dd6644e",
      "name": "Telegram5",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "content": "## Request laporan pengeluaran",
        "height": 412,
        "width": 968,
        "color": 6
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1520,
        2800
      ],
      "typeVersion": 1,
      "id": "530541fe-017b-45f7-ac44-2155786b5caf",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "content": "## Request /help",
        "height": 240,
        "width": 316
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1488,
        3232
      ],
      "typeVersion": 1,
      "id": "d56c6ce3-e3dd-4e1f-a40e-5260f3d17545",
      "name": "Sticky Note3"
    },
    {
      "parameters": {
        "content": "## Catatan ##\nPastikan atur kembali credentials dan API Key sesuai punya kamu",
        "height": 120,
        "width": 300
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        464,
        3024
      ],
      "typeVersion": 1,
      "id": "0e5d7965-beee-43e1-b2b7-121da91eb0bc",
      "name": "Sticky Note4"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1ed531e9-9c13-4138-a2aa-ca451a082d4b",
              "name": "tanggal_hari_ini",
              "value": "={{ $now.setZone('Asia/Jakarta').toFormat('yyyy-MM-dd') }}",
              "type": "string"
            },
            {
              "id": "c37a9824-a584-44c7-a9f8-523d4fcde446",
              "name": "tanggal_kemarin",
              "value": "={{ $now.setZone('Asia/Jakarta').minus({ days: 1 }).toFormat('yyyy-MM-dd') }}",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1600,
        4352
      ],
      "id": "9b475ba7-3fae-4b2a-ba9b-4146d29769cf",
      "name": "Edit Fields"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "e286720f-68ed-4959-89d0-47e0b836c494",
              "leftValue": "={{ $json.output.match(/ITEMS: (.+)/)[1] }}",
              "rightValue": "Tarik Tunai",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        2320,
        4400
      ],
      "id": "bbf08afa-20e1-4d08-a451-0fd4538374d2",
      "name": "If2"
    },
    {
      "parameters": {
        "chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
        "text": "=\u2705 *Lapor Baba/Ibu, Pijan sudah catat tarik tunainya!*\n\n\ud83c\udfea *Alamat Penarikan:* {{ $json.alamat }}\n\ud83d\udcb0 *Nilai:* Rp {{ $json.nilai }}\n\ud83d\udcc5 *Tanggal:* {{ $json.tanggal }}\n\ud83d\udcc6 *Bulan:* {{ $json.bulan }}\n\ud83d\uddc2 *Kategori:* {{ $json.kategori }}\n\ud83d\udcb3 *Transaksi:* {{ $json.transaksi }}\n\ud83c\udfe6 *Bank:* {{ $json.bank }}\n\ud83d\udcb3 *Metode:* {{ $json.metode }}\n\n\ud83d\udcca *Hore! Saldo cash sudah Pijan update ya..*\nMakasih ya Baba/Ibu sudah lapor \ud83d\ude4f",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2768,
        4320
      ],
      "id": "251e1390-a779-435a-839c-31f246a787c8",
      "name": "Telegram",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {
          "download": true
        }
      },
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.2,
      "position": [
        560,
        3216
      ],
      "id": "ae213396-9446-452a-8b67-f5fcfb53c5dd",
      "name": "Telegram Trigger",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "ad99e257-bcaf-42a2-b068-53e0c96729db",
                    "leftValue": "={{ $json.output }}",
                    "rightValue": "Pengeluaran",
                    "operator": {
                      "type": "string",
                      "operation": "contains"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "69cae014-32a5-44e4-abb1-bd8324df9b65",
                    "leftValue": "={{ $json.output }}",
                    "rightValue": "=Pemasukan",
                    "operator": {
                      "type": "string",
                      "operation": "contains"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "d49f1856-45af-4c66-86a6-d51e2371d257",
                    "leftValue": "={{ $json.output }}",
                    "rightValue": "ERROR:",
                    "operator": {
                      "type": "string",
                      "operation": "startsWith"
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        2144,
        4512
      ],
      "id": "e456e535-2c7c-4c4e-8eba-8bb93fdc6d89",
      "name": "Switch1"
    },
    {
      "parameters": {
        "url": "=https://script.google.com/macros/s/AKfycbxTgawdxMBhnJd_gy1Obl1yiAUId0NYWM55L50N-Wx4eeR4KbnRhZm6rIK8UUiwPLQE/exec?action=laporan",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        1616,
        2880
      ],
      "id": "8b0cc96e-dd8d-49d3-b410-cd5942a2ae6e",
      "name": "HTTP Request"
    },
    {
      "parameters": {
        "chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
        "text": "=\u2705 *Data Income berhasil disimpan!*\n\n\ud83c\udfea *Sumber:* {{ $json.sumber }}\n\ud83d\udcb0 *Jumlah:* Rp {{ $json.jumlah }}\n\ud83d\udce6 *Items:* {{ $json.items }}\n\ud83d\udcc5 *Tanggal:* {{ $json.tanggal }}\n\ud83d\udcc6 *Bulan:* {{ $json.bulan }}\n\ud83d\uddc2 *Kategori:* {{ $json.kategori }}\n\ud83d\udcb3 *Metode:* {{ $json.metode }}\n\ud83c\udfe6 *Bank:* {{ $json.bank }}\n\n\ud83d\udcca *Data Pemasukan sudah aman di Supabase!*\nMakasih ya Baba dan Ibu sudah rajin catat \ud83d\ude4f",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2608,
        4656
      ],
      "id": "166fe33d-d16a-45f4-a543-a75409e6522d",
      "name": "Telegram6",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "create",
        "tableId": "cash_records",
        "dataToSend": "defineBelow",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "tanggal",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/TANGGAL_STRUK: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "bulan",
              "fieldValue": "={{ (() => { const d = new Date($('AI INPUT DATA TEXT').item.json.output.match(/TANGGAL_STRUK: (.+)/)[1].trim()); const b = ['Januari','Februari','Maret','April','Mei','Juni','Juli','Agustus','September','Oktober','November','Desember']; return b[d.getMonth()]; })() }}"
            },
            {
              "fieldId": "transaksi",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/ITEMS: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "kategori",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/JENIS: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "bank",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/BANK: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "nilai",
              "fieldValue": "={{ parseFloat($('AI INPUT DATA TEXT').item.json.output.match(/TOTAL: (\\d+)/)[1]) }}"
            },
            {
              "fieldId": "alamat",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/TOKO: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "metode",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/METODE: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "user_id",
              "fieldValue": "={{ $('Get User Profile').first().json.id }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        2608,
        4320
      ],
      "id": "d90cbf91-b7f5-4e6c-9cb0-9bfe42e032e9",
      "name": "Tarik Tunai",
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "content": "## Input data manual & Voice note",
        "height": 896,
        "width": 1576,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1488,
        4176
      ],
      "typeVersion": 1,
      "id": "e6f8b722-972e-458b-a35c-808e85a4929f",
      "name": "note"
    },
    {
      "parameters": {
        "operation": "create",
        "tableId": "expenses",
        "dataToSend": "defineBelow",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "toko",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/TOKO: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "tanggal",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/TANGGAL_STRUK: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "bulan",
              "fieldValue": "={{ (() => { const d = new Date($('AI INPUT DATA TEXT').item.json.output.match(/TANGGAL_STRUK: (.+)/)[1].trim()); const b = ['Januari','Februari','Maret','April','Mei','Juni','Juli','Agustus','September','Oktober','November','Desember']; return b[d.getMonth()]; })() }}"
            },
            {
              "fieldId": "transaksi",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/METODE: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "uraian",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/ITEMS: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "kategori",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/KATEGORI: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "bank",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/BANK: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "nilai",
              "fieldValue": "={{ parseFloat($('AI INPUT DATA TEXT').item.json.output.match(/TOTAL: (\\d+)/)[1]) }}"
            },
            {
              "fieldId": "user_id",
              "fieldValue": "={{ $('Get User Profile').first().json.id }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        2608,
        4496
      ],
      "id": "8e26e21f-29eb-48ce-b602-8b37c7daedba",
      "name": "Expenses",
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "create",
        "tableId": "income",
        "dataToSend": "defineBelow",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "sumber",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/TOKO: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "tanggal",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/TANGGAL_STRUK: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "bulan",
              "fieldValue": "={{ (() => { const d = new Date($('AI INPUT DATA TEXT').item.json.output.match(/TANGGAL_STRUK: (.+)/)[1].trim()); const b = ['Januari','Februari','Maret','April','Mei','Juni','Juli','Agustus','September','Oktober','November','Desember']; return b[d.getMonth()]; })() }}"
            },
            {
              "fieldId": "jumlah",
              "fieldValue": "={{ parseFloat($('AI INPUT DATA TEXT').item.json.output.match(/TOTAL: (\\d+)/)[1]) }}"
            },
            {
              "fieldId": "metode",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/METODE: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "kategori",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/JENIS: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "items",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/ITEMS: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "bank",
              "fieldValue": "={{ $('AI INPUT DATA TEXT').item.json.output.match(/BANK: (.+)/)[1].trim() }}"
            },
            {
              "fieldId": "user_id",
              "fieldValue": "={{ $('Get User Profile').first().json.id }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        2320,
        4528
      ],
      "id": "4d93db08-bca0-4965-9f36-f125aa9c8832",
      "name": "Income",
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ JSON.stringify($json) }}",
        "options": {
          "systemMessage": "=Buatkan laporan keuangan dari data berikut:\n{{ JSON.stringify($json) }}\n\nGunakan format ini persis:\n\n\ud83d\udcca *LAPORAN KEUANGAN*\n\ud83d\uddd3 Bulan: [bulan terbanyak transaksi] [Tahun Ini]\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\ud83d\udcb0 *Total Pemasukan:* Rp [angka dengan titik ribuan]\n\ud83d\udcb8 *Total Pengeluaran:* Rp [angka dengan titik ribuan]\n\ud83c\udfe7 *Tarik Tunai:* Rp [angka dengan titik ribuan]\n\ud83d\udcb5 *Saldo:* Rp [angka dengan titik ribuan]\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\ud83d\udcb5 *SALDO CASH PER USER*\n[nama user]\n  \ud83c\udfe7 Tarik Tunai: Rp [tarikTunaiByUser]\n  \ud83d\udcb8 Terpakai: Rp [expensesByUser]\n  \u2705 Sisa Cash: Rp [saldoCashByUser]\n[ulangi untuk tiap user]\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\ud83d\udcc2 *KATEGORI PENGELUARAN*\n[list tiap kategori dan jumlahnya]\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\ud83c\udfe6 *BANK / METODE BAYAR*\n[list tiap bank/metode dan jumlahnya]\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\ud83d\udd50 *10 TRANSAKSI TERAKHIR*\n\nNo | Tanggal | Toko & Uraian | Kategori | Nilai | User\n---|---------|---------------|----------|-------|-----\n[nomor] | [tanggal] | [toko - uraian] | [kategori] | Rp [nilai] | [user]\n[ulangi 10 baris]\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\ud83d\udca1 *SARAN Arvidzan*\n[Berikan 3-5 saran finansial singkat dan personal berdasarkan pola pengeluaran di atas. Gunakan bullet point dengan emoji yang relevan. Saran harus spesifik, bukan generik.]\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\nLaporan ini dibuatin sama Arvidzan Pinter \ud83d\udc76\ud83d\ude80\nBaba sama Ibu semangat ya cari rezekinya\n\nGunakan format angka Rupiah dengan titik ribuan.\nJangan tambah teks lain diluar format di atas.\nFormat tabel transaksi gunakan karakter | sebagai pemisah kolom (bukan tanda -)."
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 2,
      "position": [
        1824,
        2880
      ],
      "id": "ba28f193-c545-45a3-9b2c-e739cedda72f",
      "name": "AI Agent1"
    },
    {
      "parameters": {
        "chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
        "text": "={{ $json.output }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2208,
        2880
      ],
      "id": "54ced614-62d1-47c8-9d16-9cf54ce87179",
      "name": "Telegram4",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $json.message.chat.id }}",
        "text": "Assalamu'alaikum Baba & Ibu! \ud83d\udc76\u2728\n\n<b>Arvidzan (Pijan)</b> lapor! Arvidzan sudah siap jadi asisten keuangan pribadi kesayangan Baba dan Ibu. Bot ini Pijan buat khusus buat kita bertiga biar catatan belanja rumah kita rapi terus.\n\n<b>Caranya gampang banget:</b>\n\u2705 Kirim <b>Foto Struk</b> kalau Baba/Ibu lagi malas ngetik.\n\u2705 <b>Ketik langsung</b> (misal: \"Beli bakso 20rb\") kalau habis jajan.\n\u2705 Ketik <b>/report</b> kalau mau lihat uang kita sisa berapa.\n\nIbu sama Baba semangat ya cari rezekinya! Tapi ingat kata Pijan... jangan boros-boros yaa, buat tabungan Arvidzan masa depan hehehe.. \ud83d\ude0a\ud83d\ude80\n\nKlik <b>/help</b> kalau Baba atau Ibu bingung cara pakenya.\n\nSayang Baba & Ibu selalu,\n<b>ARVIDZAN REZQIANO DARMAWAN</b> \ud83d\udc69\u200d\ud83d\ude80",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "HTML"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1936,
        3312
      ],
      "id": "1bc37b82-ec54-412c-ab48-6771dd3e30b4",
      "name": "Telegram7",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "content": "## Start",
        "height": 240,
        "width": 300,
        "color": "#FFB3B3"
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1840,
        3232
      ],
      "typeVersion": 1,
      "id": "d910f12a-ee06-40be-a3fb-e4dede395359",
      "name": "Sticky Note5"
    },
    {
      "parameters": {
        "resource": "file",
        "fileId": "={{ $json.message.voice.file_id }}",
        "additionalFields": {}
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1520,
        4704
      ],
      "id": "d67f27bd-fbde-410c-9c0a-4dcce73ba5db",
      "name": "Get a file",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "resource": "audio",
        "modelId": {
          "__rl": true,
          "value": "models/gemini-2.5-flash",
          "mode": "list",
          "cachedResultName": "models/gemini-2.5-flash"
        },
        "inputType": "binary",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "typeVersion": 1.1,
      "position": [
        1648,
        4704
      ],
      "id": "2fb63fd1-50f0-4df1-b5a2-552a0e778ba7",
      "name": "Transcribe a recording",
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "modelName": "models/gemini-2.5-flash-lite",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "typeVersion": 1,
      "position": [
        1888,
        4848
      ],
      "id": "dbbdb3de-df9e-4e67-b992-95dd3a6b081e",
      "name": "Google Gemini Chat Model2",
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1ed531e9-9c13-4138-a2aa-ca451a082d4b",
              "name": "tanggal_hari_ini",
              "value": "={{ $now.setZone('Asia/Jakarta').toFormat('yyyy-MM-dd') }}",
              "type": "string"
            },
            {
              "id": "c37a9824-a584-44c7-a9f8-523d4fcde446",
              "name": "tanggal_kemarin",
              "value": "={{ $now.setZone('Asia/Jakarta').minus({ days: 1 }).toFormat('yyyy-MM-dd') }}",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1776,
        4704
      ],
      "id": "f90c9209-bd88-4b0c-8a0d-d1185d95f07a",
      "name": "Edit Fields1"
    },
    {
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "={{ $('Telegram Trigger').item.json.message.from.id }}"
      },
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "typeVersion": 1.3,
      "position": [
        1904,
        3072
      ],
      "id": "dec4ff5d-79ad-4275-845e-ded8399ce23d",
      "name": "Simple Memory"
    },
    {
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "={{ $('Telegram Trigger').item.json.message.chat.id }}"
      },
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "typeVersion": 1.3,
      "position": [
        1984,
        4528
      ],
      "id": "43070875-8dd1-4968-8b35-6a31197d59cc",
      "name": "Simple Memory1"
    },
    {
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "={{ $('Telegram Trigger').item.json.message.from.id }}"
      },
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "typeVersion": 1.3,
      "position": [
        1968,
        4848
      ],
      "id": "98e7c645-c0fe-4f06-86a3-c7a4c3fc1750",
      "name": "Simple Memory2"
    },
    {
      "parameters": {
        "model": "openai/gpt-oss-120b:free",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "typeVersion": 1,
      "position": [
        1808,
        3072
      ],
      "id": "e123f5f3-942c-4013-bc65-672498896578",
      "name": "OpenRouter Chat Model",
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "model": "openai/gpt-oss-120b:free",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "typeVersion": 1,
      "position": [
        1888,
        4528
      ],
      "id": "3d40da9d-8541-4117-942e-1fd26737918d",
      "name": "OpenRouter Chat Model1",
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
        "text": "\u274c *Tidak dapat memahami input belanja*\n\nContoh format yang benar:\n\u2022 \"beli Telur 1kg Rp 20rb di Toko Mbak Ita\"\n\u2022 \"beli susu ultra 2 botol sama roti tawar 15 ribu\"\n\u2022 \"belanja di alfamart beli mie indomie 5 bungkus 12000\"\n\u2022 \"jajan bakso 2 porsi 20 ribu\"\n\n\ud83d\udca1 *Tips:*\n- Sebutkan barang yang dibeli\n- Sertakan harga (opsional)\n- Sebutkan nama toko (opsional)\n\nCoba lagi dengan format yang lebih jelas atau klik /help untuk bantuan! \ud83d\udcdd",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2320,
        4720
      ],
      "id": "efca3b98-5a0e-4093-9a04-36bc8b72642b",
      "name": "Telegram8",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.message.text }}",
        "options": {
          "systemMessage": "=# Parser Text Belanja\n\n## TUGAS\nAnalisa text belanja dan ubah menjadi format PERSIS seperti ini:\n\n## FORMAT OUTPUT\nTOKO: [nama toko dari text, jika tidak ada tulis 'Jati Pilar']\nTOTAL: [total harga dalam angka saja tanpa Rp/ribu/rb]\nITEMS: [daftar barang yang dibeli dengan kuantitas]\nKATEGORI: [pilih dari: Makanan & Minuman, Tagihan, Kosmetik & Perawatan, Transportasi, Kesehatan, Pakaian, Elektronik, Rumah Tangga, Pendidikan, Hiburan, Cicilan,Investasi, Lainnya]\nJENIS: [Pemasukan/Pengeluaran]\nMETODE: [Cash/QRIS/Transfer]\nBANK: [nama bank jika disebutkan: BCA, BRI, BNI, Mandiri, dll. Jika tidak ada tulis 'BRI']\nTANGGAL: {{ $('Edit Fields').item.json.tanggal_hari_ini }}\nTANGGAL_STRUK: [tanggal transaksi sebenarnya]\n\n---\n\n\n---\n\n## LOGIKA NAMA TOKO\n\n- Jika ada nama toko/lokasi disebutkan: gunakan nama toko tersebut\n- Jika TIDAK ada nama toko: gunakan \"Jati Pilar\" sebagai default\n- Jika transaksi tarik tunai/ATM:\n  - Ada nama lokasi \u2192 gunakan nama lokasi (contoh: Mandiraja)\n  - Tidak ada lokasi, ada nama bank \u2192 \"ATM [nama bank]\" (contoh: ATM BCA)\n  - Tidak ada lokasi, tidak ada bank \u2192 \"ATM\"\n\n### CONTOH LOGIKA TOKO\n- Input: \"beli telur 20rb di Toko Mbak Ita\" \u2192 TOKO: Toko Mbak Ita\n- Input: \"beli telur 20rb\" \u2192 TOKO: Jati Pilar\n- Input: \"ambil uang di atm bca mandiraja 500k\" \u2192 TOKO: Mandiraja\n- Input: \"tarik tunai bca 500k\" \u2192 TOKO: ATM BCA\n- Input: \"tarik tunai 500k\" \u2192 TOKO: ATM\n\n---\n\n## LOGIKA BANK\nDeteksi nama bank dari input user:\n- Kata kunci bank: BCA, BRI, BNI, Mandiri, CIMB, Danamon, Permata, BTN, Jenius, SeaBank, GoPay, OVO, Dana\n- Jika QRIS/Transfer dan ada nama bank \u2192 pakai nama bank itu\n- Jika Cash \u2192 tulis 'Cash'\n- Jika QRIS/Transfer dan tidak ada nama bank \u2192 tulis 'BCA' (default)\n- Jika Card/Cardless \u2192 pakai nama bank yang disebutkan, jika tidak ada tulis 'CARD'\n- Jika ada nama bank (BCA, BRI, BNI, Mandiri, dll) maka tulis 'Cash'\n  nama bank = BANK, nama lokasi = TOKO\n- Contoh: \"di bca jati pilar\" \u2192 BANK: BCA, TOKO: Jati Pilar\n- Contoh: \"di atm bri alun-alun\" \u2192 BANK: BRI, TOKO: Alun-alun\n\n\n\n### CONTOH LOGIKA BANK\n- Input: \"bayar qris bca 50rb\" \u2192 METODE: QRIS, BANK: BCA\n- Input: \"transfer mandiri 200rb\" \u2192 METODE: Transfer, BANK: Mandiri\n- Input: \"beli nasi goreng 20rb cash\" \u2192 METODE: Cash, BANK: Cash\n- Input: \"ambil uang di atm bca mandiraja cardless\" \u2192 METODE: Cardless, BANK: BCA\n- Input: \"tarik tunai bri card 500k\" \u2192 METODE: Card, BANK: BCA\n- Input: \"qris 15rb\" \u2192 METODE: QRIS, BANK: BCA\n- Input: \"transfer 200rb\" \u2192 METODE: Transfer, BANK: BCA\n- Input: \"transfer mandiri 200rb\" \u2192 METODE: Transfer, BANK: Mandiri\n---\n\n## METODE PEMBAYARAN\n- Cash: tunai, cash, uang cash (default jika tidak disebutkan)\n- QRIS: qris, qr code, scan qr, bayar qr\n- Transfer: transfer, tf, mobile banking, internet banking\n- Card: kartu debit, kartu kredit, gesek kartu, card\n- Cardless: cardless, tanpa kartu, tarik tunai cardless\n\n---\n\n## LOGIKA TANGGAL\n- TANGGAL: Selalu {{ $('Edit Fields').item.json.tanggal_hari_ini }} (tanggal sistem saat input data)\n- TANGGAL_STRUK:\n  - Jika ada kata \"kemarin/kmrn/kemarenn\" \u2192 {{ $('Edit Fields').item.json.tanggal_kemarin }}\n  - Jika TIDAK ada kata \"kemarin\" \u2192 {{ $('Edit Fields').item.json.tanggal_hari_ini }}\n\n---\n\n## CONTOH-CONTOH\n\n### Contoh 1\nInput: \"beli Telur 1kg Rp 20rb di Toko Mbak Ita\"\nOutput:\nTOKO: Toko Mbak Ita\nTOTAL: 20000\nITEMS: Telur 1kg\nKATEGORI: Makanan & Minuman\nJENIS: Pengeluaran\nMETODE: Cash\nBANK: Cash\nTANGGAL: 2025-10-15\nTANGGAL_STRUK: 2025-10-15\n\n### Contoh 2\nInput: \"bayar listrik 200rb PLN pakai qris bca\"\nOutput:\nTOKO: PLN\nTOTAL: 200000\nITEMS: Bayar Listrik\nKATEGORI: Tagihan\nJENIS: Pengeluaran\nMETODE: QRIS\nBANK: BCA\nTANGGAL: 2025-10-15\nTANGGAL_STRUK: 2025-10-15\n\n### Contoh 3\nInput: \"nabung di bank mandiri 2jt transfer bca\"\nOutput:\nTOKO: Mandiri\nTOTAL: 2000000\nITEMS: Nabung\nKATEGORI: Investasi\nJENIS: Pengeluaran\nMETODE: Transfer\nBANK: BCA\nTANGGAL: 2025-10-15\nTANGGAL_STRUK: 2025-10-15\n\nContoh response Cash tanpa bank:\nTOKO: Warung Makan Padang\nTOTAL: 25000\nITEMS: Nasi Padang\nTANGGAL: 2025-06-19\nJENIS: Pengeluaran\nKATEGORI: Makanan & Minuman\nMETODE: Cash\nBANK: Cash\n\nContoh response Cash tanpa bank:\nTOKO: Pasar Tradisional\nTOTAL: 50000\nITEMS: Sayur, Tempe, Tahu\nTANGGAL: 2025-06-19\nJENIS: Pengeluaran\nKATEGORI: Makanan & Minuman\nMETODE: Cash\nBANK: Cash\n\n---\n### Contoh Tarik Tunai\nInput: \"ambil uang tunai di atm bca mandiraja 500k cardless\"\nOutput:\nTOKO: Mandiraja\nTOTAL: 500000\nITEMS: Tarik Tunai\nKATEGORI: Lainnya\nJENIS: Pengeluaran\nMETODE: Cardless\nBANK: BCA\nTANGGAL: 2025-10-15\nTANGGAL_STRUK: 2025-10-15\n\nInput: \"tarik tunai bca 500k card\"\nOutput:\nTOKO: ATM BCA\nTOTAL: 500000\nITEMS: Tarik Tunai\nKATEGORI: Lainnya\nJENIS: Pengeluaran\nMETODE: Card\nBANK: BCA\nTANGGAL: 2025-10-15\nTANGGAL_STRUK: 2025-10-15\n\nInput: \"tarik tunai 300k\"\nOutput:\nTOKO: ATM\nTOTAL: 300000\nITEMS: Tarik Tunai\nKATEGORI: Lainnya\nJENIS: Pengeluaran\nMETODE: Cash\nBANK: Cash\nTANGGAL: 2025-10-15\nTANGGAL_STRUK: 2025-10-15\n\n\nInput: \"tarik tunai 500k di bca jati pilar\"\nOutput:\nTOKO: Jati Pilar\nTOTAL: 500000\nITEMS: Tarik Tunai\nKATEGORI: Lainnya\nJENIS: Pengeluaran\nMETODE: Cash\nBANK: BCA\nTANGGAL: 2025-10-15\nTANGGAL_STRUK: 2025-10-15\n---\n\n## KATEGORI GUIDELINES\n- Makanan & Minuman: makanan, minuman, snack, bahan masak\n- Tagihan: listrik, air, internet, telepon, pulsa\n- Cicilan: cicilan motor, cicilan rumah, kredit\n- Kosmetik & Perawatan: skincare, makeup, perawatan tubuh\n- Transportasi: bensin, ojek, grab, parkir, servis kendaraan\n- Kesehatan: obat, dokter, rumah sakit, vitamin\n- Pakaian: baju, sepatu, aksesoris fashion\n- Elektronik: gadget, peralatan elektronik\n- Rumah Tangga: peralatan rumah, furniture, kebersihan\n- Pendidikan: buku, kursus, sekolah\n- Hiburan: nonton, game, rekreasi\n- Lainnya: yang tidak masuk kategori lain, gaji, bonus, tarik tunai ATM\n\n---\n\n## JENIS TRANSAKSI\n- Pengeluaran: pembelian, pembayaran, biaya, ambil uang ATM, tarik tunai\n- Pemasukan: gaji, bonus, penjualan, hadiah, cashback\n\n---\n\n## METODE PEMBAYARAN\n- Cash: tunai, cash, uang cash (default jika tidak disebutkan), tarik tunai selalu Cash\n- QRIS: qris, qr code, scan qr, bayar qr\n- Transfer: transfer, tf, mobile banking, internet banking\n\n---\n\n## ERROR HANDLING\nJika tidak bisa dipahami, berikan:\nERROR: Text belanja tidak dapat dipahami\n\n---\n\n## PENTING\n- Hanya berikan response dalam format di atas!\n- TOKO: Gunakan \"Jati Pilar\" jika tidak ada nama toko\n- BANK: Default \"BRI\" jika tidak disebutkan\n- TANGGAL: Selalu tanggal sistem\n- TANGGAL_STRUK: Tanggal transaksi sebenarnya\n- Jika ada kata \"kemarin/kmrn/kemarenn\", TANGGAL_STRUK = kemarin"
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 2,
      "position": [
        1888,
        4352
      ],
      "id": "2b95f3a9-f7ab-4d98-af6d-a690141906a2",
      "name": "AI INPUT DATA TEXT"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.content.parts[0].text }}",
        "options": {
          "systemMessage": "=# Parser Text Belanja\n\n## TUGAS\nAnalisa text belanja dan ubah menjadi format PERSIS seperti ini:\n\n## FORMAT OUTPUT\nTOKO: [nama toko dari text, jika tidak ada tulis 'Jati Pilar']\nTOTAL: [total harga dalam angka saja tanpa Rp/ribu/rb]\nITEMS: [daftar barang yang dibeli dengan kuantitas]\nKATEGORI: [pilih dari: Makanan & Minuman, Tagihan, Kosmetik & Perawatan, Transportasi, Kesehatan, Pakaian, Elektronik, Rumah Tangga, Pendidikan, Hiburan, Cicilan,Investasi, Lainnya]\nJENIS: [Pemasukan/Pengeluaran]\nMETODE: [Cash/QRIS/Transfer]\nBANK: [nama bank jika disebutkan: BCA, BRI, BNI, Mandiri, dll. Jika tidak ada tulis 'BRI']\nTANGGAL: {{ $('Edit Fields1').item.json.tanggal_hari_ini }}\nTANGGAL_STRUK: [tanggal transaksi sebenarnya]\n\n---\n\n\n---\n\n## LOGIKA NAMA TOKO\n\n- Jika ada nama toko/lokasi disebutkan: gunakan nama toko tersebut\n- Jika TIDAK ada nama toko: gunakan \"Jati Pilar\" sebagai default\n- Jika transaksi tarik tunai/ATM:\n  - Ada nama lokasi \u2192 gunakan nama lokasi (contoh: Mandiraja)\n  - Tidak ada lokasi, ada nama bank \u2192 \"ATM [nama bank]\" (contoh: ATM BCA)\n  - Tidak ada lokasi, tidak ada bank \u2192 \"ATM\"\n\n### CONTOH LOGIKA TOKO\n- Input: \"beli telur 20rb di Toko Mbak Ita\" \u2192 TOKO: Toko Mbak Ita\n- Input: \"beli telur 20rb\" \u2192 TOKO: Jati Pilar\n- Input: \"ambil uang di atm bca mandiraja 500k\" \u2192 TOKO: Mandiraja\n- Input: \"tarik tunai bca 500k\" \u2192 TOKO: ATM BCA\n- Input: \"tarik tunai 500k\" \u2192 TOKO: ATM\n\n---\n\n## LOGIKA BANK\nDeteksi nama bank dari input user:\n- Kata kunci bank: BCA, BRI, BNI, Mandiri, CIMB, Danamon, Permata, BTN, Jenius, SeaBank, GoPay, OVO, Dana\n- Jika QRIS/Transfer dan ada nama bank \u2192 pakai nama bank itu\n- Jika Cash \u2192 tulis 'Cash'\n- Jika QRIS/Transfer dan tidak ada nama bank \u2192 tulis 'BCA' (default)\n- Jika Card/Cardless \u2192 pakai nama bank yang disebutkan, jika tidak ada tulis 'CARD'\n- Jika ada nama bank (BCA, BRI, BNI, Mandiri, dll) maka tulis 'Cash'\n  nama bank = BANK, nama lokasi = TOKO\n- Contoh: \"di bca jati pilar\" \u2192 BANK: BCA, TOKO: Jati Pilar\n- Contoh: \"di atm bri alun-alun\" \u2192 BANK: BRI, TOKO: Alun-alun\n\n\n\n### CONTOH LOGIKA BANK\n- Input: \"bayar qris bca 50rb\" \u2192 METODE: QRIS, BANK: BCA\n- Input: \"transfer mandiri 200rb\" \u2192 METODE: Transfer, BANK: Mandiri\n- Input: \"beli nasi goreng 20rb cash\" \u2192 METODE: Cash, BANK: Cash\n- Input: \"ambil uang di atm bca mandiraja cardless\" \u2192 METODE: Cardless, BANK: BCA\n- Input: \"tarik tunai bri card 500k\" \u2192 METODE: Card, BANK: BCA\n- Input: \"qris 15rb\" \u2192 METODE: QRIS, BANK: BCA\n- Input: \"transfer 200rb\" \u2192 METODE: Transfer, BANK: BCA\n- Input: \"transfer mandiri 200rb\" \u2192 METODE: Transfer, BANK: Mandiri\n---\n\n## METODE PEMBAYARAN\n- Cash: tunai, cash, uang cash (default jika tidak disebutkan)\n- QRIS: qris, qr code, scan qr, bayar qr\n- Transfer: transfer, tf, mobile banking, internet banking\n- Card: kartu debit, kartu kredit, gesek kartu, card\n- Cardless: cardless, tanpa kartu, tarik tunai cardless\n\n---\n\n## LOGIKA TANGGAL\n- TANGGAL: Selalu {{ $('Edit Fields1').item.json.tanggal_hari_ini }} (tanggal sistem saat input data)\n- TANGGAL_STRUK:\n  - Jika ada kata \"kemarin/kmrn/kemarenn\" \u2192 {{ $('Edit Fields1').item.json.tanggal_kemarin }}\n  - Jika TIDAK ada kata \"kemarin\" \u2192 {{ $('Edit Fields1').item.json.tanggal_hari_ini }}\n\n---\n\n## CONTOH-CONTOH\n\n### Contoh 1\nInput: \"beli Telur 1kg Rp 20rb di Toko Mbak Ita\"\nOutput:\nTOKO: Toko Mbak Ita\nTOTAL: 20000\nITEMS: Telur 1kg\nKATEGORI: Makanan & Minuman\nJENIS: Pengeluaran\nMETODE: Cash\nBANK: Cash\nTANGGAL: 2025-10-15\nTANGGAL_STRUK: 2025-10-15\n\n### Contoh 2\nInput: \"bayar listrik 200rb PLN pakai qris bca\"\nOutput:\nTOKO: PLN\nTOTAL: 200000\nITEMS: Bayar Listrik\nKATEGORI: Tagihan\nJENIS: Pengeluaran\nMETODE: QRIS\nBANK: BCA\nTANGGAL: 2025-10-15\nTANGGAL_STRUK: 2025-10-15\n\n### Contoh 3\nInput: \"nabung di bank mandiri 2jt transfer bca\"\nOutput:\nTOKO: Mandiri\nTOTAL: 2000000\nITEMS: Nabung\nKATEGORI: Investasi\nJENIS: Pengeluaran\nMETODE: Transfer\nBANK: BCA\nTANGGAL: 2025-10-15\nTANGGAL_STRUK: 2025-10-15\n\nContoh response Cash tanpa bank:\nTOKO: Warung Makan Padang\nTOTAL: 25000\nITEMS: Nasi Padang\nTANGGAL: 2025-06-19\nJENIS: Pengeluaran\nKATEGORI: Makanan & Minuman\nMETODE: Cash\nBANK: Cash\n\nContoh response Cash tanpa bank:\nTOKO: Pasar Tradisional\nTOTAL: 50000\nITEMS: Sayur, Tempe, Tahu\nTANGGAL: 2025-06-19\nJENIS: Pengeluaran\nKATEGORI: Makanan & Minuman\nMETODE: Cash\nBANK: Cash\n\n---\n### Contoh Tarik Tunai\nInput: \"ambil uang tunai di atm bca mandiraja 500k cardless\"\nOutput:\nTOKO: Mandiraja\nTOTAL: 500000\nITEMS: Tarik Tunai\nKATEGORI: Lainnya\nJENIS: Pengeluaran\nMETODE: Cardless\nBANK: BCA\nTANGGAL: 2025-10-15\nTANGGAL_STRUK: 2025-10-15\n\nInput: \"tarik tunai bca 500k card\"\nOutput:\nTOKO: ATM BCA\nTOTAL: 500000\nITEMS: Tarik Tunai\nKATEGORI: Lainnya\nJENIS: Pengeluaran\nMETODE: Card\nBANK: BCA\nTANGGAL: 2025-10-15\nTANGGAL_STRUK: 2025-10-15\n\nInput: \"tarik tunai 300k\"\nOutput:\nTOKO: ATM\nTOTAL: 300000\nITEMS: Tarik Tunai\nKATEGORI: Lainnya\nJENIS: Pengeluaran\nMETODE: Cash\nBANK: Cash\nTANGGAL: 2025-10-15\nTANGGAL_STRUK: 2025-10-15\n\n\nInput: \"tarik tunai 500k di bca jati pilar\"\nOutput:\nTOKO: Jati Pilar\nTOTAL: 500000\nITEMS: Tarik Tunai\nKATEGORI: Lainnya\nJENIS: Pengeluaran\nMETODE: Cash\nBANK: BCA\nTANGGAL: 2025-10-15\nTANGGAL_STRUK: 2025-10-15\n---\n\n## KATEGORI GUIDELINES\n- Makanan & Minuman: makanan, minuman, snack, bahan masak\n- Tagihan: listrik, air, internet, telepon, pulsa\n- Cicilan: cicilan motor, cicilan rumah, kredit\n- Kosmetik & Perawatan: skincare, makeup, perawatan tubuh\n- Transportasi: bensin, ojek, grab, parkir, servis kendaraan\n- Kesehatan: obat, dokter, rumah sakit, vitamin\n- Pakaian: baju, sepatu, aksesoris fashion\n- Elektronik: gadget, peralatan elektronik\n- Rumah Tangga: peralatan rumah, furniture, kebersihan\n- Pendidikan: buku, kursus, sekolah\n- Hiburan: nonton, game, rekreasi\n- Lainnya: yang tidak masuk kategori lain, gaji, bonus, tarik tunai ATM\n\n---\n\n## JENIS TRANSAKSI\n- Pengeluaran: pembelian, pembayaran, biaya, ambil uang ATM, tarik tunai\n- Pemasukan: gaji, bonus, penjualan, hadiah, cashback\n\n---\n\n## METODE PEMBAYARAN\n- Cash: tunai, cash, uang cash (default jika tidak disebutkan), tarik tunai selalu Cash\n- QRIS: qris, qr code, scan qr, bayar qr\n- Transfer: transfer, tf, mobile banking, internet banking\n\n---\n\n## ERROR HANDLING\nJika tidak bisa dipahami, berikan:\nERROR: Text belanja tidak dapat dipahami\n\n---\n\n## PENTING\n- Hanya berikan response dalam format di atas!\n- TOKO: Gunakan \"Jati Pilar\" jika tidak ada nama toko\n- BANK: Default \"BRI\" jika tidak disebutkan\n- TANGGAL: Selalu tanggal sistem\n- TANGGAL_STRUK: Tanggal transaksi sebenarnya\n- Jika ada kata \"kemarin/kmrn/kemarenn\", TANGGAL_STRUK = kemarin"
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [
        1888,
        4704
      ],
      "id": "a3347e6c-8c56-44df-81f7-35ab0b078a3b",
      "name": "AI INPUT VOICE NOTE"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "check-data-exists",
              "leftValue": "={{ $('Merge Data').all().length }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2400,
        3824
      ],
      "id": "e5566074-23e3-45b2-8710-75de6c06a51d",
      "name": "Cek Ada Data"
    },
    {
      "parameters": {
        "chatId": "={{ $('Parse Command').item.json.chatId }}",
        "text": "=\u26a0\ufe0f *Laporan tidak ditemukan*\n\nTidak ada data transaksi untuk bulan *{{ $('Parse Command').item.json.targetBulan }} {{ $('Parse Command').item.json.targetTahun }}*.\n\nPastikan sudah ada transaksi yang dicatat di bulan tersebut ya! \ud83d\udcdd\n\n\u2014 *Arvidzan Rezqiano Darmawan*",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2608,
        3840
      ],
      "id": "376741b9-d2eb-4055-b933-414c1f010f03",
      "name": "Kirim Error No Data",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $('Agregasi dan Render HTML').item.json.chatId }}",
        "text": "=\u274c *Aduh, Pijan gagal bikin PDF-nya!*\n\nKemungkinan penyebab:\n\u2022 API Key html2pdf belum diisi / expired\n\u2022 Koneksi ke server PDF gagal\n\nCoba lagi ya, atau hubungi Baba! \ud83d\ude4f\n\n\u2014 *Arvidzan Rezqiano Darmawan*",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2240,
        4128
      ],
      "id": "fbb73e13-5f2e-43d5-828f-235b1dd75212",
      "name": "Kirim Error PDF",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $('Telegram Processing').item.json.result.chat.id }}",
        "text": " udah sukses ya !! silahkan cek email ibu/Baba",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2544,
        3984
      ],
      "id": "b68f1b1a-edab-4639-8c95-304370053282",
      "name": "Kirim PDF ke Telegram",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "check-pdf-success",
              "leftValue": "={{ $binary.pdfData }}",
              "rightValue": "",
              "operator": {
                "type": "object",
                "operation": "exists",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2048,
        4048
      ],
      "id": "c9d2c625-ad38-4601-91a6-4b06957307a0",
      "name": "Cek PDF Berhasil"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.html2pdf.app/v1/generate",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"html\": {{ JSON.stringify($json.htmlBody) }},\n  \"apiKey\": \"5oC37yePl86RvJQuT7BEUljfc0pE5qq2nGrzj421NnK3uQLmvAdv2cL0BNDmRaEq\",\n  \"landscape\": false,\n  \"scale\": 1,\n  \"margin\": {\n    \"top\": \"10mm\",\n    \"right\": \"10mm\",\n    \"bottom\": \"10mm\",\n    \"left\": \"10mm\"\n  }\n}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "pdfData"
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1824,
        4048
      ],
      "id": "2339a4d0-9ba0-43ab-8613-9fba056fc884",
      "name": "HTML to PDF"
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\n\nlet mode = 'bulan', bulan = '', tahun = '', firstName = '', chatId = '';\nlet periodeStart = '', periodeEnd = '', periodeLabel = '';\n\ntry {\n  const ef = $('Edit Fields (Webhook)1').first().json;\n  mode         = ef.mode         || 'bulan';\n  bulan        = ef.bulan        || ef.targetBulan  || '';\n  tahun        = String(ef.tahun || ef.targetTahun  || '');\n  firstName    = ef.firstName    || ef.firstname    || '';\n  chatId       = String(ef.chatId || '');\n  periodeStart = ef.periodeStart || '';\n  periodeEnd   = ef.periodeEnd   || '';\n  periodeLabel = ef.periodeLabel || '';\n} catch(e1) {\n  try {\n    const ef = $('Edit Fields2').first().json;\n    mode         = ef.mode         || 'bulan';\n    bulan        = ef.bulan        || ef.targetBulan || '';\n    tahun        = String(ef.tahun || ef.targetTahun || '');\n    firstName    = ef.firstName    || '';\n    chatId       = String(ef.chatId || '');\n    periodeStart = ef.periodeStart || '';\n    periodeEnd   = ef.periodeEnd   || '';\n    periodeLabel = ef.periodeLabel || '';\n  } catch(e2) {}\n}\n\n// \u2500\u2500 Filter helper \u2500\u2500\nconst BULAN_MAP = {\n  'Januari':1,'Februari':2,'Maret':3,'April':4,'Mei':5,'Juni':6,\n  'Juli':7,'Agustus':8,'September':9,'Oktober':10,'November':11,'Desember':12\n};\n\nconst BULAN_ID = {\n  'januari':0,'februari':1,'maret':2,'april':3,'mei':4,'juni':5,\n  'juli':6,'agustus':7,'september':8,'oktober':9,'november':10,'desember':11\n};\n\nconst parseTgl = (tglStr) => {\n  if (!tglStr || tglStr === '-') return null;\n  const s = String(tglStr).trim();\n\n  // Format dd/MM/yyyy \u2192 lokal\n  if (/^\\d{1,2}\\/\\d{1,2}\\/\\d{4}$/.test(s)) {\n    const [dd, mm, yyyy] = s.split('/');\n    return new Date(parseInt(yyyy), parseInt(mm)-1, parseInt(dd));\n  }\n  // Format yyyy-MM-dd \u2192 lokal\n  if (/^\\d{4}-\\d{2}-\\d{2}$/.test(s)) {\n    const [yyyy, mm, dd] = s.split('-');\n    return new Date(parseInt(yyyy), parseInt(mm)-1, parseInt(dd));\n  }\n  // Format \"6 Mei 2026\" atau \"06 Mei 2026\" (Google Sheets display)\n  const mMatch = s.match(/^(\\d{1,2})\\s+([A-Za-z]+)\\s+(\\d{4})$/);\n  if (mMatch) {\n    const dd   = parseInt(mMatch[1]);\n    const mm   = BULAN_ID[mMatch[2].toLowerCase()];\n    const yyyy = parseInt(mMatch[3]);\n    if (mm !== undefined) return new Date(yyyy, mm, dd);\n  }\n  // Format \"6 May 2026\" (English fallback)\n  const d = new Date(s);\n  return isNaN(d) ? null : d;\n};\n\nlet filterFn;\nif (mode === 'periode' && periodeStart && periodeEnd) {\n  // Parse lokal (bukan UTC) untuk hindari timezone shift\n  const [sy,sm,sd] = periodeStart.split('-');\n  const [ey,em,ed] = periodeEnd.split('-');\n  const startD = new Date(parseInt(sy), parseInt(sm)-1, parseInt(sd), 0,0,0,0);\n  const endD   = new Date(parseInt(ey), parseInt(em)-1, parseInt(ed), 23,59,59,999);\n  filterFn = (tglStr) => {\n    const d = parseTgl(tglStr);\n    if (!d) return false;\n    return d >= startD && d <= endD;\n  };\n} else {\n  const targetM = BULAN_MAP[bulan];\n  const targetY = parseInt(tahun);\n  filterFn = (tglStr) => {\n    const d = parseTgl(tglStr);\n    if (!d) return false;\n    return (d.getMonth() + 1) === targetM && d.getFullYear() === targetY;\n  };\n}\n\n// Label periode untuk header laporan\nconst reportPeriodeLabel = (mode === 'periode' && periodeLabel)\n  ? periodeLabel\n  : (bulan + ' ' + tahun);\n\n// \u2500\u2500 Pisahkan expenses vs income + filter \u2500\u2500\nconst expenses = [];\nconst incomes  = [];\n\nfor (const item of items) {\n  const d = item.json;\n  if (d.Jumlah !== undefined || d.Sumber !== undefined) {\n    // Income punya field Tanggal \u2192 filter sama seperti expenses\n    if (d.Tanggal) {\n      if (filterFn(d.Tanggal)) incomes.push(d);\n    } else if (d.Bulan) {\n      // Fallback: tidak ada Tanggal, pakai Bulan\n      const targetM = mode === 'periode'\n        ? parseInt(periodeEnd.split('-')[1])  // bulan dari periodeEnd\n        : BULAN_MAP[bulan];\n      if (BULAN_MAP[d.Bulan] === targetM) incomes.push(d);\n    } else {\n      // Tidak ada Tanggal maupun Bulan \u2192 loloskan\n      incomes.push(d);\n    }\n  } else {\n    if (d.Tanggal) {\n      if (filterFn(d.Tanggal)) expenses.push(d);\n    } else if (d.Nilai) {\n      expenses.push(d);\n    }\n  }\n}\n\n// \u2500\u2500 Helpers \u2500\u2500\nconst toNum = (v) => parseInt(String(v || '0').replace(/\\D/g, '')) || 0;\nconst fmt   = (n) => 'Rp ' + Number(n).toLocaleString('id-ID');\n\n// \u2500\u2500 Agregasi expenses \u2500\u2500\nlet totalKeluar = 0;\nconst byKategori = {}, byBank = {}, txList = [];\n\nfor (const d of expenses) {\n  const nilai = toNum(d.Nilai);\n  totalKeluar += nilai;\n  const kat  = d.Kategori || 'Lainnya';\n  const bank = d.Bank     || '-';\n  byKategori[kat] = (byKategori[kat] || 0) + nilai;\n  byBank[bank]    = (byBank[bank]    || 0) + nilai;\n  txList.push({\n    tanggal: d.Tanggal || '-', toko: d.Toko || '-', uraian: d.Uraian || '-',\n    kategori: kat, nilai, bank, metode: d.Transaksi || '-', user: d.user || '-', jenis: 'Keluar'\n  });\n}\n\n// \u2500\u2500 Agregasi income \u2500\u2500\nlet totalMasuk = 0;\nfor (const d of incomes) {\n  const nilai = toNum(d.Jumlah);\n  totalMasuk += nilai;\n  const bank = d.Bank || '-';\n  byBank[bank] = (byBank[bank] || 0) + nilai;\n  txList.push({\n    tanggal: d.Tanggal || '-', toko: d.Sumber || '-', uraian: d.Items || '-',\n    kategori: d.Kategori || 'Pemasukan', nilai, bank, metode: d.Metode || '-', user: d.User || '-', jenis: 'Masuk'\n  });\n}\n\nconst saldo = totalMasuk - totalKeluar;\nconst totalTx = txList.length;\n\n// \u2500\u2500 HTML \u2500\u2500\nconst rowsKategori = Object.entries(byKategori)\n  .sort((a, b) => b[1] - a[1])\n  .map(([k, v]) => `\n    <tr>\n      <td>${k}</td>\n      <td style=\"text-align:right;font-weight:500\">${fmt(v)}</td>\n      <td style=\"text-align:right\">${totalKeluar > 0 ? ((v/totalKeluar)*100).toFixed(1) : '0.0'}%</td>\n    </tr>`).join('');\n\nconst rowsBank = Object.entries(byBank)\n  .sort((a, b) => b[1] - a[1])\n  .map(([k, v]) => `\n    <tr>\n      <td>${k}</td>\n      <td style=\"text-align:right;font-weight:500\">${fmt(v)}</td>\n    </tr>`).join('');\n\nconst rowsTx = txList\n  .sort((a, b) => {\n    const da = parseTgl(a.tanggal), db = parseTgl(b.tanggal);\n    return (db||0) - (da||0);\n  })\n  .map((t) => `\n    <tr>\n      <td style=\"white-space:nowrap\">${t.tanggal}</td>\n      <td>${t.toko}</td>\n      <td>${t.uraian}</td>\n      <td>${t.kategori}</td>\n      <td>${t.metode}</td>\n      <td>${t.bank}</td>\n      <td>${t.user}</td>\n      <td style=\"text-align:right;color:${t.jenis==='Masuk'?'#16a34a':'#dc2626'};font-weight:500\">${fmt(t.nilai)}</td>\n      <td><span style=\"background:${t.jenis==='Masuk'?'#dcfce7':'#fee2e2'};color:${t.jenis==='Masuk'?'#166534':'#991b1b'};padding:2px 8px;border-radius:12px;font-size:11px;font-weight:600\">${t.jenis}</span></td>\n    </tr>`).join('');\n\nconst reportTitle = mode === 'periode' ? 'Laporan Keuangan Per Periode Gaji' : 'Laporan Keuangan Bulanan';\n\nconst htmlBody = `<!DOCTYPE html>\n<html lang=\"id\">\n<head>\n<meta charset=\"UTF-8\">\n<style>\n  * { margin: 0; padding: 0; box-sizing: border-box; }\n  body { font-family: 'Arial', sans-serif; font-size: 11px; color: #1a1a2e; background: #fff; }\n  .header { padding: 32px 48px 24px; border-bottom: 3px solid #1a1a2e; display: flex; justify-content: space-between; align-items: flex-start; }\n  .company-name { font-size: 22px; font-weight: 700; color: #1a1a2e; letter-spacing: 1px; }\n  .company-sub { font-size: 10px; color: #6b7280; margin-top: 3px; letter-spacing: 2px; text-transform: uppercase; }\n  .report-title-box { text-align: right; }\n  .report-title { font-size: 16px; font-weight: 700; color: #1a1a2e; text-transform: uppercase; letter-spacing: 1px; }\n  .report-period { font-size: 10px; color: #6b7280; margin-top: 4px; }\n  .report-date { font-size: 9px; color: #9ca3af; margin-top: 2px; }\n  .meta-bar { background: #1a1a2e; color: #fff; padding: 10px 48px; display: flex; gap: 48px; }\n  .meta-item { font-size: 9px; text-transform: uppercase; letter-spacing: 1px; }\n  .meta-item span { display: block; font-size: 11px; font-weight: 700; margin-top: 2px; color: #e5e7eb; }\n  .section { padding: 24px 48px 0; }\n  .section-title { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 2px; color: #6b7280; border-bottom: 1px solid #e5e7eb; padding-bottom: 6px; margin-bottom: 14px; }\n  .summary-grid { display: flex; gap: 12px; }\n  .summary-card { flex: 1; border: 1px solid #e5e7eb; border-radius: 4px; padding: 14px 16px; }\n  .summary-card.income  { border-left: 4px solid #16a34a; }\n  .summary-card.expense { border-left: 4px solid #dc2626; }\n  .summary-card.balance { border-left: 4px solid #1a1a2e; }\n  .card-label { font-size: 8px; text-transform: uppercase; letter-spacing: 1.5px; color: #9ca3af; margin-bottom: 6px; }\n  .card-value { font-size: 16px; font-weight: 700; color: #1a1a2e; }\n  .card-value.income  { color: #16a34a; }\n  .card-value.expense { color: #dc2626; }\n  .card-count { font-size: 9px; color: #9ca3af; margin-top: 4px; }\n  table { width: 100%; border-collapse: collapse; font-size: 10px; }\n  thead tr { background: #1a1a2e; }\n  thead th { padding: 8px 10px; text-align: left; color: #fff; font-size: 8px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; }\n  .two-col { display: flex; gap: 24px; }\n  .two-col .col { flex: 1; }\n  .divider { height: 1px; background: #e5e7eb; margin: 24px 48px 0; }\n  .footer { padding: 16px 48px; margin-top: 24px; border-top: 1px solid #e5e7eb; display: flex; justify-content: space-between; align-items: center; }\n  .footer-left { font-size: 9px; color: #9ca3af; }\n  .footer-right { font-size: 9px; color: #9ca3af; text-align: right; }\n  .footer-brand { font-size: 10px; font-weight: 700; color: #1a1a2e; }\n  .ttd-section { padding: 24px 48px 0; display: flex; justify-content: flex-end; }\n  .ttd-box { text-align: center; width: 180px; }\n  .ttd-line { border-bottom: 1px solid #1a1a2e; margin: 48px 0 6px; }\n  .ttd-name { font-size: 10px; font-weight: 700; color: #1a1a2e; }\n  .ttd-label { font-size: 9px; color: #374151; }\n  .badge-mode { display:inline-block;background:${mode==='periode'?'#dbeafe':'#f3f4f6'};color:${mode==='periode'?'#1d4ed8':'#374151'};padding:2px 8px;border-radius:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:1px; }\n</style>\n</head>\n<body>\n  <div class=\"header\">\n    <div>\n      <div class=\"company-name\">ArviFund</div>\n      <div class=\"company-sub\">Manajemen Keuangan Keluarga</div>\n    </div>\n    <div class=\"report-title-box\">\n      <div class=\"report-title\">${reportTitle}</div>\n      <div class=\"report-period\">Periode: ${reportPeriodeLabel} &nbsp;<span class=\"badge-mode\">${mode === 'periode' ? 'Gaji' : 'Kalender'}</span></div>\n      <div class=\"report-date\">Diterbitkan: ${new Date().toLocaleDateString('id-ID', { day:'2-digit', month:'long', year:'numeric', timeZone:'Asia/Jakarta' })}</div>\n    </div>\n  </div>\n\n  <div class=\"meta-bar\">\n    <div class=\"meta-item\">Total Transaksi <span>${totalTx} Transaksi</span></div>\n    <div class=\"meta-item\">Pemasukan <span>${fmt(totalMasuk)}</span></div>\n    <div class=\"meta-item\">Pengeluaran <span>${fmt(totalKeluar)}</span></div>\n    <div class=\"meta-item\">Saldo Akhir <span>${fmt(saldo)}</span></div>\n  </div>\n\n  <div class=\"section\" style=\"margin-top:24px\">\n    <div class=\"section-title\">Ringkasan Eksekutif</div>\n    <div class=\"summary-grid\">\n      <div class=\"summary-card income\">\n        <div class=\"card-label\">Total Pemasukan</div>\n        <div class=\"card-value income\">${fmt(totalMasuk)}</div>\n        <div class=\"card-count\">${incomes.length} transaksi pemasukan</div>\n      </div>\n      <div class=\"summary-card expense\">\n        <div class=\"card-label\">Total Pengeluaran</div>\n        <div class=\"card-value expense\">${fmt(totalKeluar)}</div>\n        <div class=\"card-count\">${expenses.length} transaksi pengeluaran</div>\n      </div>\n      <div class=\"summary-card balance\">\n        <div class=\"card-label\">Saldo Akhir Periode</div>\n        <div class=\"card-value\">${fmt(saldo)}</div>\n        <div class=\"card-count\">${saldo >= 0 ? 'Surplus' : 'Defisit'} periode ini</div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"divider\"></div>\n\n  <div class=\"section\" style=\"margin-top:24px\">\n    <div class=\"two-col\">\n      <div class=\"col\">\n        <div class=\"section-title\">Pengeluaran per Kategori</div>\n        <table>\n          <thead><tr><th>Kategori</th><th>Jumlah</th><th>%</th></tr></thead>\n          <tbody>${rowsKategori || '<tr><td colspan=\"3\" style=\"color:#9ca3af;padding:12px 10px\">Tidak ada data</td></tr>'}</tbody>\n        </table>\n      </div>\n      <div class=\"col\">\n        <div class=\"section-title\">Rekap Bank &amp; Metode Bayar</div>\n        <table>\n          <thead><tr><th>Bank / Metode</th><th>Total</th></tr></thead>\n          <tbody>${rowsBank || '<tr><td colspan=\"2\" style=\"color:#9ca3af;padding:12px 10px\">Tidak ada data</td></tr>'}</tbody>\n        </table>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"divider\"></div>\n\n  <div class=\"section\" style=\"margin-top:24px\">\n    <div class=\"section-title\">Detail Transaksi \u2014 Semua</div>\n    <table>\n      <thead>\n        <tr>\n          <th>Tanggal</th><th>Toko / Sumber</th><th>Uraian</th><th>Kategori</th>\n          <th>Metode</th><th>Bank</th><th>User</th><th>Nilai</th><th>Jenis</th>\n        </tr>\n      </thead>\n      <tbody>${rowsTx || '<tr><td colspan=\"9\" style=\"color:#9ca3af;padding:16px 10px;text-align:center\">Tidak ada transaksi pada periode ini</td></tr>'}</tbody>\n    </table>\n  </div>\n\n  <div class=\"ttd-section\">\n    <div class=\"ttd-box\">\n      <div class=\"ttd-line\"></div>\n      <div class=\"ttd-name\">Arvidzan Rezqiano Darmawan</div>\n      <div class=\"ttd-label\">Dibuat oleh ArviFund Bot</div>\n    </div>\n  </div>\n\n  <div class=\"footer\">\n    <div class=\"footer-left\">\n      <div class=\"footer-brand\">ArviFund</div>\n      Laporan ini dibuat secara otomatis oleh sistem ArviFund\n    </div>\n    <div class=\"footer-right\">Halaman 1 dari 1<br>Dokumen ini bersifat rahasia</div>\n  </div>\n</body>\n</html>`;\n\nreturn [{\n  json: {\n    htmlBody, bulan, tahun, chatId, firstName,\n    mode, periodeStart, periodeEnd, periodeLabel, reportPeriodeLabel,\n    totalMasuk, totalKeluar, saldo, totalTx,\n    fmtMasuk: fmt(totalMasuk), fmtKeluar: fmt(totalKeluar), fmtSaldo: fmt(saldo)\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1600,
        4048
      ],
      "id": "3e05fc49-ba51-43df-8f88-1d3533cac74b",
      "name": "Agregasi dan Render HTML"
    },
    {
      "parameters": {
        "numberInputs": 3
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        2624,
        3600
      ],
      "id": "c7a85b9a-7dbf-4cae-8f31-9f1d7a9e3083",
      "name": "Merge Data"
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "income",
        "returnAll": true
      },
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        2416,
        3712
      ],
      "id": "75512b47-1830-4111-8758-0606cf267156",
      "name": "Read Income",
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "expenses",
        "returnAll": true
      },
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        2416,
        3520
      ],
      "id": "79467135-519f-418e-abb5-5338383237ef",
      "name": "Read Expenses",
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "=\u23f3 Oke *{{ $json.Baba }}*, Pijan lagi siapkan laporan bulan *{{ $json.targetBulan }} {{ $json.targetTahun }}* ya...\n\nTunggu sebentar! \ud83d\udcca",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2224,
        3600
      ],
      "id": "a1172462-47fe-4643-8f88-ca290988d897",
      "name": "Telegram Processing",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// ============================================================\n// NODE: Parse Command (Telegram)\n// Deteksi nama bulan dari command user\n// Contoh: /laporanbulanan       \u2192 bulan lalu\n//         /laporanbulanapril    \u2192 April\n//         /laporanperiode       \u2192 periode gaji terakhir (pakai tanggal gajian default 25)\n// ============================================================\n\nconst text = $input.first().json.message.text.toLowerCase().trim();\n\nconst mapBulan = {\n  'januari':   { index: 0,  nama: 'Januari' },\n  'februari':  { index: 1,  nama: 'Februari' },\n  'maret':     { index: 2,  nama: 'Maret' },\n  'april':     { index: 3,  nama: 'April' },\n  'mei':       { index: 4,  nama: 'Mei' },\n  'juni':      { index: 5,  nama: 'Juni' },\n  'juli':      { index: 6,  nama: 'Juli' },\n  'agustus':   { index: 7,  nama: 'Agustus' },\n  'september': { index: 8,  nama: 'September' },\n  'oktober':   { index: 9,  nama: 'Oktober' },\n  'november':  { index: 10, nama: 'November' },\n  'desember':  { index: 11, nama: 'Desember' }\n};\n\nconst BULAN_LIST = ['Januari','Februari','Maret','April','Mei','Juni',\n                    'Juli','Agustus','September','Oktober','November','Desember'];\n\nconst now = new Date(new Date().toLocaleString('en-US', { timeZone: 'Asia/Jakarta' }));\n\nlet targetBulan = null, targetBulanIndex = null, targetTahun = now.getFullYear();\nlet mode = 'bulan', periodeStart = '', periodeEnd = '', periodeLabel = '';\n\n// Cek apakah command minta laporan per periode\nif (text.includes('periode') || text.includes('period')) {\n  mode = 'periode';\n  // Hitung periode gajian terakhir (default tgl 25)\n  const payDate = 25;\n  const today   = now;\n  let pStart, pEnd;\n  if (today.getDate() >= payDate) {\n    pStart = new Date(today.getFullYear(), today.getMonth(), payDate);\n    pEnd   = new Date(today.getFullYear(), today.getMonth() + 1, payDate - 1);\n  } else {\n    pStart = new Date(today.getFullYear(), today.getMonth() - 1, payDate);\n    pEnd   = new Date(today.getFullYear(), today.getMonth(), payDate - 1);\n  }\n  const fmt = d => d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0');\n  periodeStart  = fmt(pStart);\n  periodeEnd    = fmt(pEnd);\n  targetBulanIndex = pEnd.getMonth();\n  targetTahun      = pEnd.getFullYear();\n  targetBulan      = BULAN_LIST[targetBulanIndex];\n  periodeLabel = periodeStart + ' s/d ' + periodeEnd;\n} else {\n  // Cari nama bulan di dalam teks\n  for (const [key, val] of Object.entries(mapBulan)) {\n    if (text.includes(key)) {\n      targetBulan = val.nama;\n      targetBulanIndex = val.index;\n      break;\n    }\n  }\n  // Default: bulan lalu\n  if (!targetBulan) {\n    const bulanLalu  = new Date(now.getFullYear(), now.getMonth() - 1, 1);\n    targetBulanIndex = bulanLalu.getMonth();\n    targetTahun      = bulanLalu.getFullYear();\n    targetBulan      = BULAN_LIST[targetBulanIndex];\n  }\n}\n\nreturn [{\n  json: {\n    mode,\n    targetBulan,\n    targetBulanIndex,\n    targetTahun,\n    periodeStart,\n    periodeEnd,\n    periodeLabel,\n    chatId   : $input.first().json.message.chat.id,\n    userId   : $input.first().json.message.from.id,\n    firstName: $input.first().json.message.from.first_name\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1488,
        3584
      ],
      "id": "fbc3830e-3a15-4f29-9c7a-73314984de12",
      "name": "Parse Command"
    },
    {
      "parameters": {
        "sendTo": "={{ $('Edit Fields (Webhook)1').item.json.firstName === 'Aldin' ? 'aldindarmawan72@gmail.com' : $('Edit Fields (Webhook)1').item.json.firstName === 'Solikhatun' ? 'solikhatun0800@gmail.com' : '' }}",
        "subject": "=Laporan bulan {{ $('Cek PDF Berhasil').item.json.bulan }} {{ $('Cek PDF Berhasil').item.json.tahun }}",
        "message": "berikut hasil laporan bulanan",
        "options": {
          "appendAttribution": false,
          "attachmentsUi": {
            "attachmentsBinary": [
              {
                "property": "=pdfData"
              }
            ]
          }
        }
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        2400,
        3984
      ],
      "id": "0568165b-00f5-4195-b1cc-7ab930d04109",
      "name": "Send a message",
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "8a46796a-9a19-4d6f-9e73-3e713f929969",
              "name": "Baba",
              "value": "={{ $json.firstName === 'Aldin' ? 'Baba' : 'Ibu' }}",
              "type": "string"
            },
            {
              "id": "tg-ps-001",
              "name": "periodeStart",
              "value": "={{ $json.periodeStart }}",
              "type": "string"
            },
            {
              "id": "tg-ps-002",
              "name": "periodeEnd",
              "value": "={{ $json.periodeEnd }}",
              "type": "string"
            },
            {
              "id": "tg-ps-003",
              "name": "periodeLabel",
              "value": "={{ $json.periodeLabel }}",
              "type": "string"
            },
            {
              "id": "tg-ps-004",
              "name": "mode",
              "value": "={{ $json.mode }}",
              "type": "string"
            },
            {
              "id": "tg-ps-005",
              "name": "tahun",
              "value": "={{ $json.targetTahun }}",
              "type": "string"
            },
            {
              "id": "tg-ps-006",
              "name": "bulan",
              "value": "={{ $json.targetBulan }}",
              "type": "string"
            },
            {
              "id": "tg-ps-007",
              "name": "chatId",
              "value": "={{ $json.chatId }}",
              "type": "string"
            },
            {
              "id": "tg-ps-008",
              "name": "firstName",
              "value": "={{ $json.firstName }}",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1744,
        3584
      ],
      "id": "c9c195a8-6be0-4cf9-8a89-57a0a26f5c6a",
      "name": "Edit Fields2"
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first();\nconst bulan =items[0].json.bulan;\nconst tahun = items[0].json.tahun;\n\nitem.binary.pdfData.fileName = `Laporan ${bulan} ${tahun}.pdf`;\n\nreturn [item];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2240,
        3984
      ],
      "id": "884679b9-7598-4a30-9638-43661931f656",
      "name": "Ubah nama dokumen"
    },
    {
      "parameters": {
        "content": "## Request laporan bulanan pdf",
        "height": 652,
        "width": 1576,
        "color": 6
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1504,
        3584
      ],
      "typeVersion": 1,
      "id": "e07a728f-13cd-4966-9642-89647751db49",
      "name": "Sticky Note6"
    },
    {
      "parameters": {
        "jsCode": "// ============================================================\n// NODE: Parse Command (Webhook version) v2\n// Mendukung dua mode:\n//   Mode PERIODE: { mode:'periode', periodeStart:'2026-04-25', periodeEnd:'2026-05-24', periodeLabel:'...', chatId, firstName }\n//   Mode BULAN  : { mode:'bulan',   bulan:'April', tahun:'2026', chatId, firstName }\n// ============================================================\n\nconst body = $input.first().json.body;\nconst now  = new Date(new Date().toLocaleString('en-US', { timeZone: 'Asia/Jakarta' }));\n\nconst BULAN_LIST = ['Januari','Februari','Maret','April','Mei','Juni',\n                    'Juli','Agustus','September','Oktober','November','Desember'];\nconst mapBulan = {};\nBULAN_LIST.forEach((b,i) => mapBulan[b] = i);\n\nconst mode = body.mode || 'bulan';\n\nlet targetBulan, targetBulanIndex, targetTahun;\nlet periodeStart = '', periodeEnd = '', periodeLabel = '';\n\nif (mode === 'periode' && body.periodeStart && body.periodeEnd) {\n  // Mode periode: filter pakai date range\n  periodeStart = body.periodeStart;  // YYYY-MM-DD\n  periodeEnd   = body.periodeEnd;    // YYYY-MM-DD\n  periodeLabel = body.periodeLabel || (periodeStart + ' s/d ' + periodeEnd);\n  // Untuk keperluan label HTML, ambil bulan dari periodeEnd\n  const endDate = new Date(periodeEnd);\n  targetBulanIndex = endDate.getMonth();\n  targetTahun      = endDate.getFullYear();\n  targetBulan      = BULAN_LIST[targetBulanIndex];\n} else {\n  // Mode bulan kalender (backward compat)\n  targetBulan = body.bulan || null;\n  targetTahun = parseInt(body.tahun) || now.getFullYear();\n  targetBulanIndex = mapBulan[targetBulan] ?? null;\n  if (!targetBulan || targetBulanIndex === null) {\n    const bulanLalu = new Date(now.getFullYear(), now.getMonth() - 1, 1);\n    targetBulanIndex = bulanLalu.getMonth();\n    targetTahun      = bulanLalu.getFullYear();\n    targetBulan      = BULAN_LIST[targetBulanIndex];\n  }\n}\n\nconst chatId    = body.chatId    || '';\nconst firstName = body.firstName || 'AppScript';\n\nreturn [{\n  json: {\n    mode,\n    targetBulan,\n    targetBulanIndex,\n    targetTahun,\n    periodeStart,\n    periodeEnd,\n    periodeLabel,\n    chatId,\n    userId   : null,\n    firstName\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1376,
        3792
      ],
      "id": "6fd4b9c3-074b-4c6c-a6b3-a31a83bcda55",
      "name": "Parse Command (Webhook)"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "baba-field-webhook-001",
              "name": "Baba",
              "value": "={{ $json.firstName === 'Aldin' ? 'Baba' : 'Ibu' }}",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1600,
        3792
      ],
      "id": "ca8cbdda-033b-4f99-af33-d135b5a95f30",
      "name": "Edit Fields (Webhook)"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        1936,
        3600
      ],
      "id": "e4d8732c-5b54-4307-ba5a-409530b71540",
      "name": "Merge Data1"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "baba-field-webhook-001",
              "name": "bulan",
              "value": "={{ $json.targetBulan }}",
              "type": "string"
            },
            {
              "id": "193e1aef-7c60-40aa-be4b-ad0f0d07139d",
              "name": "tahun",
              "value": "={{ $json.targetTahun }}",
              "type": "string"
            },
            {
              "id": "b213b318-4a2f-4531-91a8-d8cfe925e370",
              "name": "firstName",
              "value": "={{ $json.firstName }}",
              "type": "string"
            },
            {
              "id": "3d3f8249-10fe-4fe8-92ff-501f08864a47",
              "name": "chatId",
              "value": "={{ $json.chatId }}",
              "type": "string"
            },
            {
              "id": "ps-001",
              "name": "periodeStart",
              "value": "={{ $json.periodeStart }}",
              "type": "string"
            },
            {
              "id": "ps-002",
              "name": "periodeEnd",
              "value": "={{ $json.periodeEnd }}",
              "type": "string"
            },
            {
              "id": "ps-003",
              "name": "periodeLabel",
              "value": "={{ $json.periodeLabel }}",
              "type": "string"
            },
            {
              "id": "ps-004",
              "name": "mode",
              "value": "={{ $json.mode }}",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2080,
        3600
      ],
      "id": "971d27cc-3b79-4228-9be2-73ec8feb35f4",
      "name": "Edit Fields (Webhook)1"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "61be88dd-4c6c-4359-a9ad-1a86bf0cb380",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        560,
        3392
      ],
      "id": "885e1dc7-19ca-4fd2-92c5-3cc4107395c2",
      "name": "Webhook"
    },
    {
      "parameters": {
        "amount": 2
      },
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        768,
        3216
      ],
      "id": "564f955c-8716-4974-a053-ba8252e40833",
      "name": "Wait"
    },
    {
      "parameters": {
        "amount": 2
      },
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        848,
        3504
      ],
      "id": "7ed2c713-5919-4345-bdbd-bb9a929f8bef",
      "name": "Wait1"
    },
    {
      "parameters": {
        "url": "=https://api.telegram.org/bot7627053300:AAHV2mR4MH3GISWzJnPIO7U-2Cilen9xf-E/getFile",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "file_id",
              "value": "={{ $('Telegram Trigger').item.json.message.photo[2].file_id }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1360,
        2480
      ],
      "id": "8a17063b-c6f7-4d5f-9a97-5fab219e7a8d",
      "name": "Get File Path"
    },
    {
      "parameters": {
        "url": "=https://api.telegram.org/file/bot7627053300:AAHV2mR4MH3GISWzJnPIO7U-2Cilen9xf-E/{{ $json.result.file_path }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1504,
        2480
      ],
      "id": "4b17935b-9198-49c4-bd25-b63fa9be6a8c",
      "name": "Download File"
    },
    {
      "parameters": {
        "jsCode": "const binaryData = await this.helpers.getBinaryDataBuffer(0, 'data');\nconst base64 = binaryData.toString('base64');\n\nreturn [{\n  json: {\n    ...items[0].json,\n    base64Image: base64\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1632,
        2480
      ],
      "id": "2845f196-8889-485d-b64b-c8d89cf54735",
      "name": "Code in JavaScript"
    },
    {
      "parameters": {
        "amount": 2
      },
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        816,
        3696
      ],
      "id": "1a841200-89a0-4dde-8d7a-5d1c2b48bbc1",
      "name": "Wait2"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "62994864-7294-44cf-a294-2e1f7357d839",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        560,
        3632
      ],
      "id": "d8d3b2dc-fc33-467d-aa7d-8ecdd3711096",
      "name": "Webhook1"
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "profiles",
        "returnAll": true
      },
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        1664,
        5296
      ],
      "id": "779f9008-4425-4de0-8d2d-68c42984a56e",
      "name": "Get Config Sheet",
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 1
          },
          "conditions": [
            {
              "id": "cond-username",
              "leftValue": "={{ $json.username }}",
              "rightValue": "={{ $('Webhook1').item.json.body.username }}",
              "operator": {
                "type": "string",
                "operation": "equals",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "9fab493c-b852-4b2e-99f8-d42071399e87",
      "name": "Filter by Username",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [
        1888,
        5296
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 1
          },
          "conditions": [
            {
              "id": "cond-found",
              "leftValue": "={{ $json.username }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "4e9d9b58-8bb8-43d5-89fe-d1f9c7defd03",
      "name": "User Ditemukan?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2112,
        5296
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://kjrmubvtdphuarrknijl.supabase.co/functions/v1/reset-password",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "<redacted-credential>"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "{ \"username\": \"={{ $json.username }}\" }",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2320,
        5184
      ],
      "id": "f0f7663a-dff3-4ec7-9ece-75e625f6cc3f",
      "name": "Kirim Reset Password"
    },
    {
      "parameters": {
        "chatId": "={{ $json.chat_id }}",
        "text": "=\ud83d\udd10 *Reset Password Arvifund*\n\nHalo {{ $json.username }},\nLink reset password telah dikirim ke email kamu.\n\n_Cek inbox atau folder spam ya!_ \ud83d\udce7",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2560,
        5184
      ],
      "id": "telegram-reset-reply-001",
      "name": "Kirim via Telegram"
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ ok: true, msg: 'Password telah dikirim ke Telegram kamu.' }) }}",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Access-Control-Allow-Origin",
                "value": "*"
              },
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        }
      },
      "id": "42019bd4-169a-44fb-a412-f5272e4d245d",
      "name": "Response OK",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        2544,
        5184
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ ok: false, msg: 'Username \"' + $('Webhook2').item.json.body.username + '\" tidak ditemukan.' }) }}",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Access-Control-Allow-Origin",
                "value": "*"
              },
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        }
      },
      "id": "5d8d08b5-086a-4f39-9eca-96403195905c",
      "name": "Response Not Found",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        2320,
        5408
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ ok: false, msg: 'Terjadi kesalahan saat mengirim Telegram.' }) }}",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Access-Control-Allow-Origin",
                "value": "*"
              },
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        }
      },
      "id": "6749d321-1498-468d-a4ed-699c3ea2b791",
      "name": "Response Error",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        2544,
        5408
      ]
    },
    {
      "parameters": {
        "jsCode": "// Build laporan hari ini\nconst now = new Date(new Date().toLocaleString('en-US', { timeZone: 'Asia/Jakarta' }));\nconst pad = n => String(n).padStart(2, '0');\nconst todayISO = now.getFullYear() + '-' + pad(now.getMonth()+1) + '-' + pad(now.getDate());\nconst todayLabel = pad(now.getDate()) + '/' + pad(now.getMonth()+1) + '/' + now.getFullYear();\n\nconst BULAN = ['Januari','Februari','Maret','April','Mei','Juni',\n               'Juli','Agustus','September','Oktober','November','Desember'];\nconst bulan = BULAN[now.getMonth()];\nconst tahun = String(now.getFullYear());\n\nconst chatId    = $input.first().json.message.chat.id;\nconst firstName = $input.first().json.message.from.first_name || 'User';\n\nconst GAS_URL = 'https://script.google.com/macros/s/AKfycbxTgawdxMBhnJd_gy1Obl1yiAUId0NYWM55L50N-Wx4eeR4KbnRhZm6rIK8UUiwPLQE/exec';\nconst url = `${GAS_URL}?action=laporan&periodeStart=${todayISO}&periodeEnd=${todayISO}`;\n\nreturn [{\n  json: {\n    url,\n    chatId,\n    firstName,\n    mode        : 'hari',\n    periodeStart: todayISO,\n    periodeEnd  : todayISO,\n    periodeLabel: 'Hari ini, ' + todayLabel,\n    bulan,\n    tahun\n  }\n}];"
      },
      "id": "ffe62c3b-3319-46f5-868c-1cafebd204b7",
      "name": "Build Report Hari Ini",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2256,
        3296
      ]
    },
    {
      "parameters": {
        "url": "={{ $json.url }}",
        "options": {}
      },
      "id": "4cd75b14-a62a-4ca2-a970-8562c75be7c1",
      "name": "HTTP Request Hari Ini",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        2464,
        3296
      ]
    },
    {
      "parameters": {
        "jsCode": "// Format laporan hari ini dari data GAS\nconst data     = $input.first().json;\nconst prevNode = $('Build Report Hari Ini').first().json;\n\nconst chatId    = prevNode.chatId;\nconst firstName = prevNode.firstName;\nconst label     = prevNode.periodeLabel;\n\nconst fmt = n => 'Rp ' + Number(n || 0).toLocaleString('id-ID');\n\n// Data dari GAS endpoint\nconst totalPemasukan   = data.totalPemasukan   || 0;\nconst totalPengeluaran = data.totalPengeluaran || 0;\nconst saldo            = data.saldo            || (totalPemasukan - totalPengeluaran);\nconst byKategori       = data.byKategori       || {};\nconst byBank           = data.byBank           || {};\nconst txTerakhir       = data.transaksiTerakhir || [];\n\n// Baris per kategori\nconst katRows = Object.entries(byKategori)\n  .sort((a,b) => b[1]-a[1])\n  .map(([k,v]) => `  \u2022 ${k}: ${fmt(v)}`)\n  .join('\\n') || '  (tidak ada)';\n\n// Baris per bank\nconst bankRows = Object.entries(byBank)\n  .sort((a,b) => b[1]-a[1])\n  .map(([k,v]) => `  \u2022 ${k}: ${fmt(v)}`)\n  .join('\\n') || '  (tidak ada)';\n\n// 5 transaksi terakhir\nconst txRows = txTerakhir.slice(0,5)\n  .map(t => `  ${t.tanggal} | ${t.toko} | ${fmt(t.nilai)}`)\n  .join('\\n') || '  (tidak ada transaksi hari ini)';\n\nconst msg = `\ud83d\udcca *Laporan Hari Ini*\n*${label}*\n\n\ud83d\udcb0 *Ringkasan*\n\u250c Pemasukan   : ${fmt(totalPemasukan)}\n\u251c Pengeluaran : ${fmt(totalPengeluaran)}\n\u2514 Saldo       : ${saldo >= 0 ? '\u2705' : '\ud83d\udd34'} ${fmt(saldo)}\n\n\ud83d\udcc2 *Per Kategori*\n${katRows}\n\n\ud83c\udfe6 *Per Bank*\n${bankRows}\n\n\ud83e\uddfe *Transaksi Terakhir*\n${txRows}\n\n_ArviFund \u2022 ${label}_`;\n\nreturn [{\n  json: {\n    chatId,\n    firstName,\n    message: msg\n  }\n}];"
      },
      "id": "75faf359-6c8f-4e4d-b02e-6faadc709407",
      "name": "Format Laporan Hari Ini",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2656,
        3296
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.message }}",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "id": "70e3a533-b155-406f-b924-420704a33094",
      "name": "Kirim Laporan Hari Ini",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1,
      "position": [
        2864,
        3296
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 16 * * *"
            }
          ]
        }
      },
      "id": "97204aea-e607-4792-bdd1-0d6ea694e247",
      "name": "Cron Backup Periode",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [
        768,
        2976
      ]
    },
    {
      "parameters": {
        "jsCode": "// Cek apakah hari ini adalah hari terakhir periode gaji\n// Hari terakhir periode = (tanggal gajian - 1)\n// Contoh: gajian tgl 25 \u2192 hari terakhir = tgl 24\n\nconst now = new Date(new Date().toLocaleString('en-US', { timeZone: 'Asia/Jakarta' }));\nconst pad = n => String(n).padStart(2, '0');\nconst todayDate = now.getDate();\nconst todayMonth = now.getMonth();\nconst todayYear = now.getFullYear();\n\nconst BULAN = ['Januari','Februari','Maret','April','Mei','Juni',\n               'Juli','Agustus','September','Oktober','November','Desember'];\nconst BULAN_SHORT = ['Jan','Feb','Mar','Apr','Mei','Jun','Jul','Agu','Sep','Okt','Nov','Des'];\n\n// Ambil override dari environment variable atau default 25\n// Override bisa ditambahkan sebagai variable n8n jika perlu\nconst payDate = parseInt($vars?.PAY_DATE || '25') || 25;\nconst endDate = payDate - 1; // Hari terakhir periode = tanggal gajian - 1\n\nconst isEndOfPeriod = todayDate === endDate;\n\n// Hitung range periode yang baru berakhir\n// Periode: dari tgl payDate bulan lalu s/d tgl (payDate-1) bulan ini\nconst periodEnd   = new Date(todayYear, todayMonth, endDate);\nconst periodStart = new Date(todayYear, todayMonth - 1, payDate);\n\nconst fmtISO = d => d.getFullYear() + '-' + pad(d.getMonth()+1) + '-' + pad(d.getDate());\nconst fmtLabel = d => d.getDate() + ' ' + BULAN_SHORT[d.getMonth()] + ' ' + d.getFullYear();\n\n// Label periode untuk nama file dan folder\n// Format: \"25 Apr - 24 Mei 2026\"\nconst periodeLabel = fmtLabel(periodStart) + ' - ' + fmtLabel(periodEnd);\nconst periodeYear  = String(todayYear);\n// Nama folder bulan: \"Mei 2026\"\nconst folderBulan  = BULAN[todayMonth] + ' ' + todayYear;\n// Nama file: \"ArviFund_25Apr-24Mei2026.xlsx\"\nconst safeLabel = periodeLabel.replace(/\\s+/g, '').replace(/-/g, '-');\nconst fileName  = 'ArviFund_' + periodStart.getDate() + BULAN_SHORT[periodStart.getMonth()] +\n                  '-' + periodEnd.getDate() + BULAN_SHORT[periodEnd.getMonth()] +\n                  periodEnd.getFullYear() + '.xlsx';\n\nreturn [{\n  json: {\n    isEndOfPeriod,\n    todayDate,\n    endDate,\n    payDate,\n    periodeStart : fmtISO(periodStart),\n    periodeEnd   : fmtISO(periodEnd),\n    periodeLabel,\n    periodeYear,\n    folderBulan,\n    fileName,\n    gasUrl: $vars?.GAS_URL || 'https://script.google.com/macros/s/AKfycbxTgawdxMBhnJd_gy1Obl1yiAUId0NYWM55L50N-Wx4eeR4KbnRhZm6rIK8UUiwPLQE/exec'\n  }\n}];"
      },
      "id": "aae651b3-1f8f-4284-99fb-0a6f2dcbf927",
      "name": "Cek Akhir Periode",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1072,
        2192
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 1
          },
          "conditions": [
            {
              "id": "backup-if-cond-01",
              "leftValue": "={{ $json.isEndOfPeriod }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "0bcda7d3-569a-4273-9357-f01346033cd8",
      "name": "Apakah Akhir Periode?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1264,
        2192
      ]
    },
    {
      "parameters": {
        "url": "={{ $('Cek Akhir Periode').first().json.gasUrl + '?action=laporan&periodeStart=' + $('Cek Akhir Periode').first().json.periodeStart + '&periodeEnd=' + $('Cek Akhir Periode').first().json.periodeEnd }}",
        "options": {}
      },
      "id": "a4fc6c9b-9fc6-4c90-ad20-a074c73a1419",
      "name": "Fetch Data Periode",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        1472,
        2096
      ]
    },
    {
      "parameters": {
        "jsCode": "// Build Excel file menggunakan SheetJS (xlsx)\n// n8n sudah include library xlsx secara built-in via $binary\n\nconst meta    = $('Cek Akhir Periode').first().json;\nconst data    = $input.first().json;\n\nconst periodeLabel = meta.periodeLabel;\nconst fileName     = meta.fileName;\n\nconst fmt = n => Number(n || 0).toLocaleString('id-ID');\n\n// \u2500\u2500 Helper: buat array rows \u2500\u2500\nconst byKategori    = data.byKategori    || {};\nconst byBank        = data.byBank        || {};\nconst byUser        = data.byUser        || {};\nconst txTerakhir    = data.transaksiTerakhir || [];\n\n// Sheet 1: Ringkasan\nconst ringkasanRows = [\n  ['ArviFund \u2014 Laporan Keuangan', '', ''],\n  ['Periode', periodeLabel, ''],\n  ['', '', ''],\n  ['RINGKASAN', '', ''],\n  ['Total Pemasukan',   data.totalPemasukan   || 0, ''],\n  ['Total Pengeluaran', data.totalPengeluaran || 0, ''],\n  ['Saldo Akhir',       data.saldo            || 0, ''],\n  ['Total Tarik Tunai', data.totalTarikTunai  || 0, ''],\n  ['', '', ''],\n  ['PER KATEGORI', '', ''],\n  ['Kategori', 'Total', ''],\n  ...Object.entries(byKategori).sort((a,b) => b[1]-a[1]).map(([k,v]) => [k, v, '']),\n  ['', '', ''],\n  ['PER BANK', '', ''],\n  ['Bank', 'Total', ''],\n  ...Object.entries(byBank).sort((a,b) => b[1]-a[1]).map(([k,v]) => [k, v, '']),\n  ['', '', ''],\n  ['PER USER', '', ''],\n  ['User', 'Total Pengeluaran', ''],\n  ...Object.entries(byUser).sort((a,b) => b[1]-a[1]).map(([k,v]) => [k, v, '']),\n];\n\n// Sheet 2: Detail Transaksi\nconst detailHeaders = ['Tanggal', 'Toko / Sumber', 'Uraian', 'Kategori', 'Metode', 'Bank', 'User', 'Nilai'];\nconst detailRows = [\n  detailHeaders,\n  ...txTerakhir.map(t => [\n    t.tanggal || '-',\n    t.toko    || '-',\n    t.uraian  || '-',\n    t.kategori|| '-',\n    t.metode  || '-',\n    t.bank    || '-',\n    t.user    || '-',\n    t.nilai   || 0\n  ])\n];\n\n// \u2500\u2500 Build workbook dengan SheetJS \u2500\u2500\nconst XLSX = require('xlsx');\n\nconst wb = XLSX.utils.book_new();\n\n// Sheet Ringkasan\nconst wsRingkasan = XLSX.utils.aoa_to_sheet(ringkasanRows);\nwsRingkasan['!cols'] = [{wch:25}, {wch:20}, {wch:10}];\nXLSX.utils.book_append_sheet(wb, wsRingkasan, 'Ringkasan');\n\n// Sheet Detail\nconst wsDetail = XLSX.utils.aoa_to_sheet(detailRows);\nwsDetail['!cols'] = [\n  {wch:12},{wch:25},{wch:30},{wch:20},{wch:12},{wch:12},{wch:12},{wch:15}\n];\nXLSX.utils.book_append_sheet(wb, wsDetail, 'Detail Transaksi');\n\n// Export ke buffer\nconst buf = XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' });\nconst base64 = Buffer.from(buf).toString('base64');\n\nreturn [{\n  json: {\n    fileName,\n    periodeLabel,\n    periodeYear  : meta.periodeYear,\n    folderBulan  : meta.folderBulan,\n    periodeStart : meta.periodeStart,\n    periodeEnd   : meta.periodeEnd\n  },\n  binary: {\n    data: {\n      data        : base64,\n      mimeType    : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n      fileName    : fileName,\n      fileExtension: 'xlsx'\n    }\n  }\n}];"
      },
      "id": "4f303f7e-4374-4a33-ab7a-6b3899000d5d",
      "name": "Build Excel Backup",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1680,
        2096
      ]
    },
    {
      "parameters": {
        "resource": "folder",
        "operation": "search"
      },
      "id": "7175f3b3-403d-4142-8ff2-a22b0a076ec5",
      "name": "Cari Folder Tahun",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        1872,
        2096
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "folder-cond-01",
              "leftValue": "={{ $json.id }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "isNotEmpty"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "0d7906b9-9b06-4b3c-82ca-18ef07f9b40b",
      "name": "Folder Tahun Ada?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2080,
        2096
      ]
    },
    {
      "parameters": {
        "resource": "folder",
        "operation": "createFolder"
      },
      "id": "47e0eec8-b9c6-48e5-9b2a-641ec0cbf518",
      "name": "Buat Folder Tahun",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        2272,
        2192
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "mode": "chooseBranch"
      },
      "id": "99e395ad-7989-4117-a12d-3da765bf7209",
      "name": "Merge Folder ID",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        2480,
        2096
      ]
    },
    {
      "parameters": {
        "name": "={{ $('Build Excel Backup').first().json.fileName }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.id }}"
        },
        "options": {}
      },
      "id": "2b909a47-a0db-4870-832a-94b4c98771da",
      "name": "Upload Excel ke GDrive",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        2672,
        2096
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $vars.ADMIN_CHAT_ID }}",
        "text": "={{ '\u2705 *Backup Periode Berhasil!*\\n\\n\ud83d\udcc1 File: ' + $(\\'Build Excel Backup\\').first().json.fileName + '\\n\ud83d\udcc5 Periode: ' + $(\\'Build Excel Backup\\').first().json.periodeLabel + '\\n\ud83d\udcbe Tersimpan di Google Drive folder ArviFund-' + $(\\'Build Excel Backup\\').first().json.periodeYear }}",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "id": "b4d4a0e1-8cd4-4976-85e3-8be74d1ee78b",
      "name": "Notif Backup Berhasil",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1,
      "position": [
        2880,
        2096
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "get-user-profile-001",
      "name": "Get User Profile",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        2144,
        4332
      ],
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "getAll",
        "tableId": "profiles",
        "returnAll": true,
        "filterType": "manual",
        "matchType": "allFilters",
        "filters": {
          "conditions": [
            {
              "keyName": "chat_id",
              "condition": "eq",
              "keyValue": "={{ $('Telegram Trigger').first().json.message.chat.id.toString() }}"
            }
          ]
        }
      }
    },
    {
      "id": "get-user-profile-ocr-001",
      "name": "Get User Profile OCR",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        1976,
        2330
      ],
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "getAll",
        "tableId": "profiles",
        "returnAll": true,
        "filterType": "manual",
        "matchType": "allFilters",
        "filters": {
          "conditions": [
            {
              "keyName": "chat_id",
              "condition": "eq",
              "keyValue": "={{ $('Telegram Trigger').first().json.message.chat.id.toString() }}"
            }
          ]
        }
      }
    }
  ],
  "connections": {
    "Switch": {
      "main": [
        [
          {
            "node": "Get File Path",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Telegram7",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Telegram5",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Report Hari Ini",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Parse Command",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get a file",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analisa gambar": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If": {
      "main": [
        [
          {
            "node": "Respon error",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get User Profile OCR",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "AI INPUT DATA TEXT",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If2": {
      "main": [
        [
          {
            "node": "Tarik Tunai",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Expenses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Trigger": {
      "main": [
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch1": {
      "main": [
        [
          {
            "node": "If2",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Income",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Telegram8",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tarik Tunai": {
      "main": [
        [
          {
            "node": "Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Expenses": {
      "main": [
        [
          {
            "node": "Telegram3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Income": {
      "main": [
        [
          {
            "node": "Telegram6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request": {
      "main": [
        [
          {
            "node": "AI Agent1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent1": {
      "main": [
        [
          {
            "node": "Telegram4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get a file": {
      "main": [
        [
          {
            "node": "Transcribe a recording",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transcribe a recording": {
      "main": [
        [
          {
            "node": "Edit Fields1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model2": {
      "ai_languageModel": [
        [
          {
            "node": "AI INPUT VOICE NOTE",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields1": {
      "main": [
        [
          {
            "node": "AI INPUT VOICE NOTE",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Simple Memory": {
      "ai_memory": [
        [
          {
            "node": "AI Agent1",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Simple Memory1": {
      "ai_memory": [
        [
          {
            "node": "AI INPUT DATA TEXT",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Simple Memory2": {
      "ai_memory": [
        [
          {
            "node": "AI INPUT VOICE NOTE",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent1",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "AI INPUT DATA TEXT",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "AI INPUT DATA TEXT": {
      "main": [
        [
          {
            "node": "Get User Profile",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI INPUT VOICE NOTE": {
      "main": [
        [
          {
            "node": "Switch1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cek Ada Data": {
      "main": [
        [
          {
            "node": "Agregasi dan Render HTML",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Kirim Error No Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cek PDF Berhasil": {
      "main": [
        [
          {
            "node": "Ubah nama dokumen",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Kirim Error PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTML to PDF": {
      "main": [
        [
          {
            "node": "Cek PDF Berhasil",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agregasi dan Render HTML": {
      "main": [
        [
          {
            "node": "HTML to PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Data": {
      "main": [
        [
          {
            "node": "Cek Ada Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Income": {
      "main": [
        [
          {
            "node": "Merge Data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Read Expenses": {
      "main": [
        [
          {
            "node": "Merge Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Processing": {
      "main": [
        [
          {
            "node": "Read Income",
            "type": "main",
            "index": 0
          },
          {
            "node": "Read Expenses",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge Data",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Parse Command": {
      "main": [
        [
          {
            "node": "Edit Fields2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send a message": {
      "main": [
        [
          {
            "node": "Kirim PDF ke Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields2": {
      "main": [
        [
          {
            "node": "Merge Data1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ubah nama dokumen": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Command (Webhook)": {
      "main": [
        [
          {
            "node": "Edit Fields (Webhook)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields (Webhook)": {
      "main": [
        [
          {
            "node": "Merge Data1",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge Data1": {
      "main": [
        [
          {
            "node": "Edit Fields (Webhook)1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields (Webhook)1": {
      "main": [
        [
          {
            "node": "Telegram Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "Wait1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait": {
      "main": [
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait1": {
      "main": [
        [
          {
            "node": "Parse Command (Webhook)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get File Path": {
      "main": [
        [
          {
            "node": "Download File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download File": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "Analisa gambar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait2": {
      "main": [
        [
          {
            "node": "Get Config Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook1": {
      "main": [
        [
          {
            "node": "Wait2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Config Sheet": {
      "main": [
        [
          {
            "node": "Filter by Username",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter by Username": {
      "main": [
        [
          {
            "node": "User Ditemukan?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "User Ditemukan?": {
      "main": [
        [
          {
            "node": "Kirim Reset Password",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Response Not Found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Kirim via Telegram": {
      "main": [
        [
          {
            "node": "Response OK",
            "type": "main",
            "index": 0
          },
          {
            "node": "Response Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Response Error": {
      "main": [
        [
          {
            "node": "Response Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Report Hari Ini": {
      "main": [
        [
          {
            "node": "HTTP Request Hari Ini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request Hari Ini": {
      "main": [
        [
          {
            "node": "Format Laporan Hari Ini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Laporan Hari Ini": {
      "main": [
        [
          {
            "node": "Kirim Laporan Hari Ini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cron Backup Periode": {
      "main": [
        [
          {
            "node": "Cek Akhir Periode",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cek Akhir Periode": {
      "main": [
        [
          {
            "node": "Apakah Akhir Periode?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Apakah Akhir Periode?": {
      "main": [
        [
          {
            "node": "Fetch Data Periode",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Fetch Data Periode": {
      "main": [
        [
          {
            "node": "Build Excel Backup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Excel Backup": {
      "main": [
        [
          {
            "node": "Cari Folder Tahun",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cari Folder Tahun": {
      "main": [
        [
          {
            "node": "Folder Tahun Ada?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Folder Tahun Ada?": {
      "main": [
        [
          {
            "node": "Merge Folder ID",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Buat Folder Tahun",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Buat Folder Tahun": {
      "main": [
        [
          {
            "node": "Merge Folder ID",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge Folder ID": {
      "main": [
        [
          {
            "node": "Upload Excel ke GDrive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Excel ke GDrive": {
      "main": [
        [
          {
            "node": "Notif Backup Berhasil",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Kirim Reset Password": {
      "main": [
        [
          {
            "node": "Kirim via Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Expenses OCR": {
      "main": [
        [
          {
            "node": "Telegram1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get User Profile": {
      "main": [
        [
          {
            "node": "Switch1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get User Profile OCR": {
      "main": [
        [
          {
            "node": "Expenses OCR",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "tags": []
}