{
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "tao-hop-dong",
        "responseMode": "lastNode",
        "options": {}
      },
      "name": "Webhook (FastAPI g\u1ecdi)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        -1712,
        144
      ],
      "id": "36e33033-82b9-4cf0-b8fc-8f90481dea5a"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const d = $input.item.json.body;\n\n// H\u00e0m format s\u1ed1 ti\u1ec1n th\u00e0nh chu\u1ea9n VN\nconst fmt = (n) => {\n    if (!n) return '....................';\n    return new Intl.NumberFormat('vi-VN').format(n);\n};\n\nd.giaThueFormatted = fmt(d.giaThue);\n\n// T\u00cdNH TI\u1ec0N & T\u1ea0O QR\nconst tienPhaiDong = Number(d.giaThue || 0);\nd.tienPhaiDongFormatted = fmt(tienPhaiDong);\n\n// \u01afU TI\u00caN D\u00d9NG CHU\u1ed6I VIETQR \u0110\u1ec2 QU\u00c9T B\u1eb0NG APP NG\u00c2N H\u00c0NG\nif (d.payosQr) {\n    d.qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=${encodeURIComponent(d.payosQr)}`;\n} else if (d.payosUrl) {\n    d.qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=${encodeURIComponent(d.payosUrl)}`;\n} else {\n    d.qrUrl = 'https://payos.vn/error';\n}\n\n\n// L\u1ea5y ng\u00e0y th\u00e1ng n\u0103m hi\u1ec7n t\u1ea1i\nconst today = new Date();\nconst day = String(today.getDate()).padStart(2, '0');\nconst month = String(today.getMonth() + 1).padStart(2, '0');\nconst year = today.getFullYear();\n\nd.soThangText = (d.soThang || 12) + ' th\u00e1ng';\n\n// Khung HTML gi\u1eef nguy\u00ean format MS Word c\u1ee7a \u00f4ng, ch\u1ec9 b\u1ecf d\u00f2ng Ti\u1ec1n c\u1ecdc\nd.htmlContract = `<!DOCTYPE html>\n<html lang=\"vi\">\n<head>\n<meta charset=\"UTF-8\">\n<style>\n  * { box-sizing: border-box; }\n  body { \n    font-family: \"Times New Roman\", Times, serif; \n    font-size: 14pt; \n    line-height: 1.3; \n    margin: 40px; \n    color: #000; \n  }\n  \n  /* C\u00e1c class ti\u1ec7n \u00edch */\n  .center { text-align: center !important; }\n  .right { text-align: right !important; }\n  .bold { font-weight: bold; }\n  .italic { font-style: italic; }\n  .uppercase { text-transform: uppercase; }\n  .underline { text-decoration: underline; }\n\n  /* FIX L\u1ed6I GI\u00c3N CH\u1eee: Chuy\u1ec3n m\u1eb7c \u0111\u1ecbnh v\u1ec1 c\u0103n tr\u00e1i, ch\u1ec9 justify \u0111o\u1ea1n v\u0103n d\u00e0i */\n  .justify-text { text-align: justify; } \n  \n  h1 { font-size: 14pt; margin: 0; padding: 0; }\n  h2 { font-size: 16pt; margin: 20px 0 15px 0; padding: 0; }\n  \n  /* \u00c9p t\u1ea5t c\u1ea3 th\u1ebb p c\u0103n tr\u00e1i \u0111\u1ec3 kh\u00f4ng b\u1ecb k\u00e9o gi\u00e3n ch\u1eef */\n  p { margin: 6px 0; text-align: left; } \n\n  .indent { padding-left: 20px; }\n  .indent-more { padding-left: 40px; }\n\n  /* CSS CHO PH\u1ea6N CH\u1eee K\u00dd */\n  .signatures { display: flex; justify-content: space-between; margin-top: 40px; }\n  .sig-box { width: 45%; text-align: center; }\n  .spacing-bottom { margin-bottom: 10px; }\n  \n  /* STYLE CH\u1eee K\u00dd VI\u1ebeT TAY (M\u1ef1c xanh, nghi\u00eang, \u0111\u1ed5 b\u00f3ng nh\u1eb9) */\n  .handwritten {\n    font-family: \"Brush Script MT\", \"Lucida Handwriting\", cursive;\n    font-size: 30pt;\n    color: #1e3a8a; \n    display: block;\n    transform: rotate(-4deg);\n    margin: 15px 0;\n    text-shadow: 1px 1px 2px rgba(0,0,0,0.1);\n  }\n  /* STYLE CON D\u1ea4U X\u00c1C TH\u1ef0C (M\u1ef1c \u0111\u1ecf m\u1ed9c) */\n  .admin-seal {\n    color: #dc2626;\n    font-size: 10pt;\n    font-weight: bold;\n    border: 2px solid #dc2626;\n    padding: 3px 8px;\n    display: inline-block;\n    border-radius: 4px;\n    transform: rotate(8deg);\n    margin-bottom: -10px;\n  }\n</style>\n</head>\n<body>\n  <div class=\"center\">\n    <h1 class=\"bold uppercase\">C\u1ed8NG H\u00d2A X\u00c3 H\u1ed8I CH\u1ee6 NGH\u0128A VI\u1ec6T NAM</h1>\n    <p class=\"bold underline center\">\u0110\u1ed9c l\u1eadp \u2013 T\u1ef1 do \u2013 H\u1ea1nh ph\u00fac</p>\n  </div>\n\n  <h2 class=\"center bold uppercase\">H\u1ee2P \u0110\u1ed2NG THU\u00ca PH\u00d2NG TR\u1ecc</h2>\n  <div class=\"indent\">\n    <p>H\u00f4m nay, ng\u00e0y ${day} th\u00e1ng ${month} n\u0103m ${year}; t\u1ea1i \u0111\u1ecba ch\u1ec9: ..........................</p>\n    <p>Ch\u00fang t\u00f4i g\u1ed3m:..........................................................................</p>\n  </div>\n  <p class=\"bold\">1. \u0110\u1ea1i di\u1ec7n b\u00ean cho thu\u00ea ph\u00f2ng tr\u1ecd (B\u00ean A):</p>\n  <div class=\"indent\">\n      <p>\u00d4ng/b\u00e0: <b>Hu\u1ef3nh Ng\u1ecdc Huy</b> <span style=\"float:right\">Sinh ng\u00e0y: ....................................</span></p>\n      <p>N\u01a1i \u0111\u0103ng k\u00fd HK:......................................................................................................................</p>\n      <p>CMND/CCCD s\u1ed1: .................................... c\u1ea5p ng\u00e0y .......................... t\u1ea1i: ...................................................</p>\n      <p>S\u1ed1 \u0111i\u1ec7n tho\u1ea1i: .......................................................................</p>\n  </div>\n\n  <p class=\"bold\" style=\"margin-top: 10px;\">2. B\u00ean thu\u00ea ph\u00f2ng tr\u1ecd (B\u00ean B):</p>\n  <div class=\"indent\">\n      <p>\u00d4ng/b\u00e0: <b>${d.hoTen || '........................................'}</b> <span style=\"float:right\">Sinh ng\u00e0y: ${d.ngaySinh || '....................................'}</span></p>\n      <p>N\u01a1i \u0111\u0103ng k\u00fd HK th\u01b0\u1eddng tr\u00fa: ${d.thuongTru || '...................................................................................................'}</p>\n      <p>S\u1ed1 CMND/CCCD: <b>${d.soCccd || '....................................'}</b> c\u1ea5p ng\u00e0y: ${d.ngayCap || '....................'} t\u1ea1i: ${d.noiCap || '....................'}</p>\n      <p>S\u1ed1 \u0111i\u1ec7n tho\u1ea1i: <b>${d.sdt || '................................................'}</b></p>\n  </div>\n\n  <div class=\"indent\" style=\"margin-top: 15px;\">\n      <p class=\"justify-text\">Sau khi b\u00e0n b\u1ea1c tr\u00ean tinh th\u1ea7n d\u00e2n ch\u1ee7, hai b\u00ean c\u00f9ng c\u00f3 l\u1ee3i, c\u00f9ng th\u1ed1ng nh\u1ea5t nh\u01b0 sau:</p>\n      <p class=\"justify-text\">B\u00ean A \u0111\u1ed3ng \u00fd cho b\u00ean B thu\u00ea 01 ph\u00f2ng \u1edf t\u1ea1i \u0111\u1ecba ch\u1ec9: ................................................................................</p>\n      \n      <div class=\"indent\">\n          <p>- Ph\u00f2ng s\u1ed1: <b>${d.phong || '...........'}</b></p>\n          <p>- Gi\u00e1 thu\u00ea: <b>${d.giaThueFormatted} \u0111/th\u00e1ng</b></p>\n          <p>- H\u00ecnh th\u1ee9c thanh to\u00e1n: ....................................................................................................................</p>\n          <p>- Ti\u1ec1n \u0111i\u1ec7n: 3.500 \u0111/kwh t\u00ednh theo ch\u1ec9 s\u1ed1 c\u00f4ng t\u01a1, thanh to\u00e1n v\u00e0o cu\u1ed1i c\u00e1c th\u00e1ng.</p>\n          <p>- Ti\u1ec1n n\u01b0\u1edbc: 100.000 \u0111/ng\u01b0\u1eddi thanh to\u00e1n v\u00e0o \u0111\u1ea7u c\u00e1c th\u00e1ng.</p>\n          <p>- Th\u1eddi h\u1ea1n thu\u00ea: <b>${d.soThangText}</b>, b\u1eaft \u0111\u1ea7u t\u00ednh ti\u1ec1n t\u1eeb ng\u00e0y: <b>${d.ngayBatDau || '....................'}</b></p>\n      </div>\n  </div>\n\n  <p class=\"bold uppercase underline\" style=\"margin-top: 20px;\">TR\u00c1CH NHI\u1ec6M C\u1ee6A C\u00c1C B\u00caN</p>\n\n  <p class=\"bold\">* Tr\u00e1ch nhi\u1ec7m c\u1ee7a b\u00ean A:</p>\n  <div class=\"indent\">\n      <p class=\"justify-text\">- T\u1ea1o m\u1ecdi \u0111i\u1ec1u ki\u1ec7n thu\u1eadn l\u1ee3i \u0111\u1ec3 b\u00ean B th\u1ef1c hi\u1ec7n theo h\u1ee3p \u0111\u1ed3ng.</p>\n      <p class=\"justify-text\">- Cung c\u1ea5p ngu\u1ed3n \u0111i\u1ec7n, n\u01b0\u1edbc, wifi cho b\u00ean B s\u1eed d\u1ee5ng.</p>\n  </div>\n\n  <p class=\"bold\" style=\"margin-top: 10px;\">* Tr\u00e1ch nhi\u1ec7m c\u1ee7a b\u00ean B:</p>\n  <div class=\"indent\">\n      <p class=\"justify-text\">- Thanh to\u00e1n \u0111\u1ea7y \u0111\u1ee7 c\u00e1c kho\u1ea3n ti\u1ec1n theo \u0111\u00fang th\u1ecfa thu\u1eadn.</p>\n      <p class=\"justify-text\">- B\u1ea3o qu\u1ea3n c\u00e1c trang thi\u1ebft b\u1ecb v\u00e0 c\u01a1 s\u1edf v\u1eadt ch\u1ea5t c\u1ee7a b\u00ean A trang b\u1ecb cho ban \u0111\u1ea7u (l\u00e0m h\u1ecfng ph\u1ea3i s\u1eeda, m\u1ea5t ph\u1ea3i \u0111\u1ec1n).</p>\n      <p class=\"justify-text\">- Kh\u00f4ng \u0111\u01b0\u1ee3c t\u1ef1 \u00fd s\u1eeda ch\u1eefa, c\u1ea3i t\u1ea1o c\u01a1 s\u1edf v\u1eadt ch\u1ea5t khi ch\u01b0a \u0111\u01b0\u1ee3c s\u1ef1 \u0111\u1ed3ng \u00fd c\u1ee7a b\u00ean A.</p>\n      <p class=\"justify-text\">- Gi\u1eef g\u00ecn v\u1ec7 sinh trong v\u00e0 ngo\u00e0i khu\u00f4n vi\u00ean c\u1ee7a ph\u00f2ng tr\u1ecd.</p>\n      <p class=\"justify-text\">- B\u00ean B ph\u1ea3i ch\u1ea5p h\u00e0nh m\u1ecdi quy \u0111\u1ecbnh c\u1ee7a ph\u00e1p lu\u1eadt Nh\u00e0 n\u01b0\u1edbc v\u00e0 quy \u0111\u1ecbnh c\u1ee7a \u0111\u1ecba ph\u01b0\u01a1ng.</p>\n      <p class=\"justify-text\">- N\u1ebfu b\u00ean B cho kh\u00e1ch \u1edf qua \u0111\u00eam th\u00ec ph\u1ea3i b\u00e1o v\u00e0 \u0111\u01b0\u1ee3c s\u1ef1 \u0111\u1ed3ng \u00fd c\u1ee7a ch\u1ee7 nh\u00e0 \u0111\u1ed3ng th\u1eddi ph\u1ea3i ch\u1ecbu tr\u00e1ch nhi\u1ec7m v\u1ec1 c\u00e1c h\u00e0nh vi vi ph\u1ea1m ph\u00e1p lu\u1eadt c\u1ee7a kh\u00e1ch trong th\u1eddi gian \u1edf l\u1ea1i.</p>\n  </div>\n\n  <p class=\"bold uppercase underline\" style=\"margin-top: 20px;\">TR\u00c1CH NHI\u1ec6M CHUNG</p>\n  <div class=\"indent\">\n      <p class=\"justify-text\">- Hai b\u00ean ph\u1ea3i t\u1ea1o \u0111i\u1ec1u ki\u1ec7n cho nhau th\u1ef1c hi\u1ec7n h\u1ee3p \u0111\u1ed3ng.</p>\n      <p class=\"justify-text\">- Trong th\u1eddi gian h\u1ee3p \u0111\u1ed3ng c\u00f2n hi\u1ec7u l\u1ef1c n\u1ebfu b\u00ean n\u00e0o vi ph\u1ea1m c\u00e1c \u0111i\u1ec1u kho\u1ea3n \u0111\u00e3 th\u1ecfa thu\u1eadn th\u00ec b\u00ean c\u00f2n l\u1ea1i c\u00f3 quy\u1ec1n \u0111\u01a1n ph\u01b0\u01a1ng ch\u1ea5m d\u1ee9t h\u1ee3p \u0111\u1ed3ng; n\u1ebfu s\u1ef1 vi ph\u1ea1m h\u1ee3p \u0111\u1ed3ng \u0111\u00f3 g\u00e2y t\u1ed5n th\u1ea5t cho b\u00ean b\u1ecb vi ph\u1ea1m h\u1ee3p \u0111\u1ed3ng th\u00ec b\u00ean vi ph\u1ea1m h\u1ee3p \u0111\u1ed3ng ph\u1ea3i b\u1ed3i th\u01b0\u1eddng thi\u1ec7t h\u1ea1i.</p>\n      <p class=\"justify-text\">- M\u1ed9t trong hai b\u00ean mu\u1ed1n ch\u1ea5m d\u1ee9t h\u1ee3p \u0111\u1ed3ng tr\u01b0\u1edbc th\u1eddi h\u1ea1n th\u00ec ph\u1ea3i b\u00e1o tr\u01b0\u1edbc cho b\u00ean kia \u00edt nh\u1ea5t 30 ng\u00e0y v\u00e0 hai b\u00ean ph\u1ea3i c\u00f3 s\u1ef1 th\u1ed1ng nh\u1ea5t.</p>\n      <p class=\"justify-text\">- B\u00ean n\u00e0o vi ph\u1ea1m \u0111i\u1ec1u kho\u1ea3n chung th\u00ec ph\u1ea3i ch\u1ecbu tr\u00e1ch nhi\u1ec7m tr\u01b0\u1edbc ph\u00e1p lu\u1eadt.</p>\n      <p class=\"justify-text\">- H\u1ee3p \u0111\u1ed3ng \u0111\u01b0\u1ee3c l\u1eadp th\u00e0nh 02 b\u1ea3n c\u00f3 gi\u00e1 tr\u1ecb ph\u00e1p l\u00fd nh\u01b0 nhau, m\u1ed7i b\u00ean gi\u1eef m\u1ed9t b\u1ea3n.</p>\n  </div>\n\n  <div class=\"signatures\">\n      <div class=\"sig-box\">\n          <p class=\"bold center\">\u0110\u1ea0I DI\u1ec6N B\u00caN B</p>\n          <p class=\"italic spacing-bottom center\">(K\u00fd, ghi r\u00f5 h\u1ecd t\u00ean)</p>\n          <span class=\"handwritten\">${d.hoTen || ''}</span>\n          <p class=\"bold center\">${d.hoTen || ''}</p>\n      </div>\n      <div class=\"sig-box\">\n          <p class=\"bold center\">\u0110\u1ea0I DI\u1ec6N B\u00caN A</p>\n          <p class=\"italic spacing-bottom center\">(K\u00fd, ghi r\u00f5 h\u1ecd t\u00ean)</p>\n          <div class=\"center\"><span class=\"admin-seal\">\u0110\u00c3 X\u00c1C TH\u1ef0C</span></div>\n          <span class=\"handwritten\">Huy Hu\u1ef3nh</span>\n          <p class=\"bold center\">Hu\u1ef3nh Ng\u1ecdc Huy</p>\n      </div>\n  </div>\n</body>\n</html>`;\n\nreturn d;"
      },
      "name": "Format & T\u1ea1o HTML H\u1ee3p \u0110\u1ed3ng",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1520,
        144
      ],
      "id": "5ff087d0-2dcb-4809-a5cc-62c20807c6bb"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// 1. L\u1ea5y chu\u1ed7i HTML t\u1eeb Node t\u1ea1o h\u1ee3p \u0111\u1ed3ng\nconst htmlContent = $json.htmlContract;\n\n// 2. \u00c9p n\u00f3 th\u00e0nh Buffer chu\u1ea9n c\u1ee7a Node.js\nconst buffer = Buffer.from(htmlContent, 'utf-8');\n\n// 3. \u0110\u00f3ng g\u00f3i th\u00e0nh file binary an to\u00e0n tuy\u1ec7t \u0111\u1ed1i b\u1eb1ng h\u00e0m prepareBinaryData\nconst binaryData = await this.helpers.prepareBinaryData(\n  buffer, \n  'index.html', \n  'text/html'\n);\n\n// 4. Truy\u1ec1n \u0111i ti\u1ebfp\nreturn {\n  json: $json,\n  binary: {\n    indexHtml: binaryData\n  }\n};"
      },
      "name": "HTML String \u2192 Binary File",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1328,
        144
      ],
      "id": "c9f74a39-eaf6-4591-9b7c-c1ebd250964c"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://gotenberg:3000/forms/chromium/convert/html",
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "parameterType": "formBinaryData",
              "name": "files",
              "inputDataFieldName": "indexHtml"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "name": "Gotenberg: HTML \u2192 PDF",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        -1104,
        144
      ],
      "id": "eac324f6-8cfc-4751-acfe-8986a611d4d0"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// 1. Ch\u1ec9 \u0111\u1ecbnh \u0110\u00cdCH DANH t\u00ean g\u00f3i h\u00e0ng m\u00e0 Gotenberg tr\u1ea3 v\u1ec1 (m\u1eb7c \u0111\u1ecbnh l\u00e0 'data')\nconst binaryKey = 'data';\n\n// Ki\u1ec3m tra xem Gotenberg c\u00f3 th\u1ef1c s\u1ef1 nh\u1ea3 file v\u00e0o \u0111\u00e2y ch\u01b0a\nif (!$input.item.binary || !$input.item.binary[binaryKey]) {\n  throw new Error(\"Gotenberg ch\u01b0a tr\u1ea3 file v\u1ec1 bi\u1ebfn 'data'. H\u00e3y ki\u1ec3m tra Node Gotenberg (Response Format ph\u1ea3i l\u00e0 File).\");\n}\n\n// 2. L\u1ea5y b\u1ed9 \u0111\u1ec7m (Buffer) c\u1ee7a \u0110\u00daNG file PDF \u0111\u00f3\nconst pdfBuffer = await this.helpers.getBinaryDataBuffer(0, binaryKey);\n\n// 3. Ki\u1ec3m tra ch\u00e9o xem c\u00f3 ph\u1ea3i PDF x\u1ecbn kh\u00f4ng\nconst pdfBase64 = pdfBuffer.toString('base64');\nif (!pdfBase64.startsWith('JVBERi0')) {\n    const errorText = pdfBuffer.toString('utf-8');\n    throw new Error(\"L\u1ed7i b\u1ed1c nh\u1ea7m file ho\u1eb7c file h\u1ecfng: \" + errorText.substring(0, 100));\n}\n\n// 4. L\u1ea5y l\u1ea1i th\u00f4ng tin kh\u00e1ch h\u00e0ng t\u1eeb Node t\u1ea1o h\u1ee3p \u0111\u1ed3ng\nconst prevData = $('Format & T\u1ea1o HTML H\u1ee3p \u0110\u1ed3ng').item.json;\n\n// 5. \u0110\u00f3ng g\u00f3i file PDF ho\u00e0n h\u1ea3o\nconst fileName = `Hop_Dong_Phong_${prevData.phong || 'X'}.pdf`;\nconst cleanPdfBinary = await this.helpers.prepareBinaryData(\n  pdfBuffer, \n  fileName, \n  'application/pdf'\n);\n\n// 6. X\u00f3a b\u1ecf file HTML c\u0169 \u0111i cho nh\u1eb9 m\u00e1y, ch\u1ec9 truy\u1ec1n PDF \u0111i ti\u1ebfp\nreturn {\n  json: {\n    ...prevData,\n    pdfBase64: pdfBase64\n  },\n  binary: {\n    data: cleanPdfBinary\n  }\n};"
      },
      "name": "L\u1ea5y PDF Base64",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -912,
        144
      ],
      "id": "f9e2a142-3d2d-45ab-9fd1-02cebe672dad"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://backend:8000/api/contracts/save-pdf",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "contract_id",
              "value": "={{ $json.contractId }}"
            },
            {
              "name": "pdf_base64",
              "value": "={{ $json.pdfBase64 }}"
            }
          ]
        },
        "options": {}
      },
      "name": "L\u01b0u PDF v\u00e0o Database",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        -704,
        144
      ],
      "id": "3dc949ef-cd26-4d74-b861-cfd7878d3124"
    },
    {
      "parameters": {
        "sendTo": "={{ $('Format & T\u1ea1o HTML H\u1ee3p \u0110\u1ed3ng').item.json.email }}",
        "subject": "=\ud83c\udf89 H\u1ee3p \u0111\u1ed3ng thu\u00ea ph\u00f2ng {{ $('Format & T\u1ea1o HTML H\u1ee3p \u0110\u1ed3ng').item.json.phong }} ",
        "message": "=<div style='font-family:Arial,sans-serif;max-width:600px'>\n  <div style='background:#1e40af;padding:24px;border-radius:12px 12px 0 0'>\n    <h2 style='color:white;margin:0'>\ud83c\udfe0 H\u1ee3p \u0111\u1ed3ng thu\u00ea ph\u00f2ng P.{{ $json.phong }}</h2>\n  </div>\n  <div style='background:#f8fafc;padding:24px;border-radius:0 0 12px 12px;border:1px solid #e2e8f0'>\n    <p>Xin ch\u00e0o <strong>{{ $json.hoTen }}</strong>,</p>\n    <p>Y\u00eau c\u1ea7u thu\u00ea ph\u00f2ng c\u1ee7a b\u1ea1n \u0111\u00e3 \u0111\u01b0\u1ee3c duy\u1ec7t. Vui l\u00f2ng ki\u1ec3m tra <b>File PDF h\u1ee3p \u0111\u1ed3ng \u0111i\u1ec7n t\u1eed \u0111\u00ednh k\u00e8m</b> (\u0111\u00e3 c\u00f3 ch\u1eef k\u00fd c\u1ee7a ch\u1ee7 nh\u00e0).</p>\n    \n    <div style='background:white;border:1px solid #e2e8f0;border-radius:8px;padding:16px;margin:16px 0;text-align:center;'>\n      <b style='color:#dc2626;font-size:18px;'>THANH TO\u00c1N \u0110\u1ec2 HO\u00c0N T\u1ea4T NH\u1eacN PH\u00d2NG</b><br>\n      <p>T\u1ed5ng ti\u1ec1n c\u1ea7n \u0111\u00f3ng (Th\u00e1ng \u0111\u1ea7u ti\u00ean): <b style='font-size:20px;'>{{ $json.tienPhaiDongFormatted }} \u0111</b></p>\n      <img src=\"{{ $json.qrUrl }}\" width=\"250\" style=\"border: 2px solid #1e40af; border-radius: 8px; margin-top: 10px;\">\n      <p style='font-size: 13px; color: #64748b;'>Qu\u00e9t m\u00e3 QR \u0111\u1ec3 thanh to\u00e1n. H\u1ec7 th\u1ed1ng s\u1ebd t\u1ef1 \u0111\u1ed9ng b\u00e0n giao ph\u00f2ng & g\u1eedi t\u00e0i kho\u1ea3n cho b\u1ea1n sau khi nh\u1eadn \u0111\u01b0\u1ee3c ti\u1ec1n.</p>\n    </div>\n  </div>\n</div>",
        "options": {
          "attachmentsUi": {
            "attachmentsBinary": [
              {}
            ]
          }
        }
      },
      "name": "G\u1eedi Mail + PDF H\u1ee3p \u0110\u1ed3ng",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        -240,
        144
      ],
      "id": "7a16405d-e44d-4867-a8a1-f76c92dc9a7e",
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "-1003790123986",
        "text": "=\u2705 X\u1eed l\u00fd h\u1ee3p \u0111\u1ed3ng xong!\n\ud83d\udc64 {{ $('Format & T\u1ea1o HTML H\u1ee3p \u0110\u1ed3ng').item.json.hoTen }}\n\ud83c\udfe0 Ph\u00f2ng: {{ $('Format & T\u1ea1o HTML H\u1ee3p \u0110\u1ed3ng').item.json.phong }}\n\ud83d\udce7 Email + PDF g\u1eedi t\u1edbi: {{ $('Format & T\u1ea1o HTML H\u1ee3p \u0110\u1ed3ng').item.json.email }}\n\ud83d\udcbe PDF \u0111\u00e3 l\u01b0u DB (Contract #{{ $('Format & T\u1ea1o HTML H\u1ee3p \u0110\u1ed3ng').item.json.contractId }})",
        "additionalFields": {}
      },
      "name": "B\u00e1o c\u00e1o Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.1,
      "position": [
        0,
        144
      ],
      "id": "68e09e28-6247-4ad9-939c-c835b80d1efb",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "{ \"status\": \"success\", \"message\": \"PDF l\u01b0u DB v\u00e0 Email \u0111\u00e3 g\u1eedi!\" }",
        "options": {}
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        0,
        0
      ],
      "id": "d9e22bea-7481-4ddb-98a5-7a4eb48ef7f0",
      "name": "Respond to Webhook"
    },
    {
      "parameters": {
        "jsCode": "// 1. L\u1ea5y to\u00e0n b\u1ed9 d\u1eef li\u1ec7u kh\u00e1ch h\u00e0ng (JSON) t\u1eeb node L\u1ea5y PDF Base64\nconst fullData = $('L\u1ea5y PDF Base64').item.json;\n\n// 2. L\u1ea5y file PDF (Binary) c\u0169ng t\u1eeb node L\u1ea5y PDF Base64 lu\u00f4n\n// V\u00ec node \"L\u01b0u PDF v\u00e0o Database\" ngay ph\u00eda tr\u01b0\u1edbc \u0111\u00e3 l\u00e0m m\u1ea5t binary r\u1ed3i\nconst pdfFile = $('L\u1ea5y PDF Base64').item.binary.data;\n\nreturn {\n  json: fullData,\n  binary: {\n    data: pdfFile\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -496,
        144
      ],
      "id": "0c122112-2685-43db-b098-5300b31d7da3",
      "name": "Code in JavaScript"
    },
    {
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "subject": "={{ '\ud83d\udd11 B\u00e0n giao ph\u00f2ng ' + $json.phong + ' & T\u00e0i kho\u1ea3n h\u1ec7 th\u1ed1ng' }}",
        "message": "=<div style='font-family:Arial,sans-serif;max-width:600px'><div style='background:#1e40af;padding:24px;border-radius:12px 12px 0 0'><h2 style='color:white;margin:0'>\ud83c\udfe0 Ch\u00fac m\u1eebng b\u1ea1n \u0111\u00e3 nh\u1eadn ph\u00f2ng!</h2></div><div style='background:#f8fafc;padding:24px;border-radius:0 0 12px 12px;border:1px solid #e2e8f0'><p>Xin ch\u00e0o <strong>{{ $json.hoTen }}</strong>,</p><p>Ph\u00f2ng <b>{{ $json.phong }}</b> c\u1ee7a b\u1ea1n \u0111\u00e3 \u0111\u01b0\u1ee3c b\u00e0n giao th\u00e0nh c\u00f4ng. Ch\u00fac b\u1ea1n c\u00f3 nh\u1eefng tr\u1ea3i nghi\u1ec7m tuy\u1ec7t v\u1eddi t\u1ea1i \u0111\u00e2y.</p><div style='background:white;border:1px solid #e2e8f0;border-radius:8px;padding:16px;margin:16px 0'><b style='color:#1e40af'>\ud83d\udccb Th\u00f4ng tin ti\u1ec7n \u00edch</b><br><br>\ud83d\udcf6 Wifi: <b>CLM_Network_5G</b><br>\ud83d\udd13 M\u1eadt kh\u1ea9u Wifi: <b>68686868</b><br>\ud83d\udcdd Qu\u00fd kh\u00e1ch nh\u1edb \u0111\u1ecdc k\u1ef9 N\u1ed9i quy ph\u00f2ng tr\u1ecd \u0111\u00ednh k\u00e8m tr\u00ean Website \u0111\u1ec3 tr\u00e1nh b\u1ecb ph\u1ea1t nh\u00e9!</div><div style='background:#fefce8;border:1px solid #fde047;border-radius:8px;padding:16px;margin:16px 0'><b style='color:#854d0e'>\ud83d\udd11 T\u00e0i kho\u1ea3n \u0111\u0103ng nh\u1eadp App</b><br><br>\ud83c\udf10 Website: <a href='http://localhost:3000'>http://localhost:3000</a><br>\ud83d\udce7 Email: <b>{{ $json.tenDangNhap }}</b><br>\ud83d\udd12 M\u1eadt kh\u1ea9u: <code style='background:#fff;padding:2px 8px;border-radius:4px'>{{ $json.matKhau }}</code><br><small style='color:#dc2626'>\u26a0\ufe0f Vui l\u00f2ng \u0111\u1ed5i m\u1eadt kh\u1ea9u ngay sau l\u1ea7n \u0111\u0103ng nh\u1eadp \u0111\u1ea7u ti\u00ean!</small></div><p style='color:#64748b;font-size:12px;margin-top:20px'>Tr\u00e2n tr\u1ecdng,<br><b>Ban Qu\u1ea3n L\u00fd CLM</b></p></div></div>",
        "options": {}
      },
      "id": "ec28242d-79b6-4ab0-8ccc-a2ac67acf6a7",
      "name": "Mail: C\u1ea5p Account",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        -1056,
        736
      ],
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "ban-giao-phong",
        "options": {}
      },
      "id": "a37f3dd8-352d-4c56-9b5f-1037e3a57491",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        -1488,
        576
      ]
    },
    {
      "parameters": {
        "jsCode": "// 1. L\u1ea5y d\u1eef li\u1ec7u th\u00f4 t\u1eeb Webhook\nconst raw = $input.item.json;\n\n// 2. T\u1ef1 \u0111\u1ed9ng t\u00ecm t\u1ea7ng s\u00e2u nh\u1ea5t ch\u1ee9a d\u1eef li\u1ec7u (x\u1eed l\u00fd l\u1ed7i double body)\nconst data = (raw.body && raw.body.body) ? raw.body.body : (raw.body || raw);\n\nconst binaryData = {};\n\nfunction cleanB64(s) { \n  return (s && typeof s === 'string' && s.includes(',')) ? s.split(',')[1] : s; \n}\n\n// 3. X\u1eed l\u00fd CCCD Tr\u01b0\u1edbc\nif (data.cccdTruoc) {\n  binaryData.cccdTruoc = await this.helpers.prepareBinaryData(\n    Buffer.from(cleanB64(data.cccdTruoc), 'base64'),\n    `CCCD_Truoc_P${data.phong || 'unknown'}.jpg`,\n    'image/jpeg'\n  );\n}\n\n// 4. X\u1eed l\u00fd CCCD Sau\nif (data.cccdSau) {\n  binaryData.cccdSau = await this.helpers.prepareBinaryData(\n    Buffer.from(cleanB64(data.cccdSau), 'base64'),\n    `CCCD_Sau_P${data.phong || 'unknown'}.jpg`,\n    'image/jpeg'\n  );\n}\n\n// 5. X\u1eed l\u00fd PDF H\u1ee3p \u0111\u1ed3ng\nif (data.pdfBase64) {\n  binaryData.pdfFile = await this.helpers.prepareBinaryData(\n    Buffer.from(cleanB64(data.pdfBase64), 'base64'),\n    `HopDong_P${data.phong || 'unknown'}.pdf`,\n    'application/pdf'\n  );\n}\n\n// 6. Tr\u1ea3 v\u1ec1 k\u1ebft qu\u1ea3 s\u1ea1ch s\u1ebd cho c\u00e1c node sau\nreturn { json: data, binary: binaryData };\n"
      },
      "id": "3458935c-6efc-46bd-8c88-c575b3d810fb",
      "name": "X\u1eed l\u00fd Base64 sang File",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1296,
        576
      ]
    },
    {
      "parameters": {
        "chatId": "6126814953",
        "text": "=\u2705 B\u00c0N GIAO PH\u00d2NG M\u1edaI:\n\ud83c\udfe0 Ph\u00f2ng: {{ $json.phong }}\n\ud83d\udc64 Kh\u00e1ch: {{ $json.hoTen }}\n\ud83d\udcde S\u0110T: {{ $json.sdt }}\n\ud83e\udd16 Bot \u0111ang g\u1eedi h\u1ed3 s\u01a1 \u0111\u00ednh k\u00e8m...",
        "additionalFields": {}
      },
      "id": "b232106e-9248-460a-a7fb-b18dc76c053f",
      "name": "Tele: B\u00e1o c\u00e1o",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1,
      "position": [
        -1088,
        384
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "sendPhoto",
        "chatId": "6126814953",
        "binaryData": true,
        "binaryPropertyName": "cccdTruoc",
        "additionalFields": {}
      },
      "id": "b986cf10-fdba-46e2-a1c9-a32b455f34c8",
      "name": "Tele: CCCD Tr\u01b0\u1edbc",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1,
      "position": [
        -896,
        480
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "sendPhoto",
        "chatId": "6126814953",
        "binaryData": true,
        "binaryPropertyName": "cccdSau",
        "additionalFields": {}
      },
      "id": "b7618dd8-1236-4cc5-a2fc-50205f7b5a1d",
      "name": "Tele: CCCD Sau",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1,
      "position": [
        -688,
        576
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "sendDocument",
        "chatId": "6126814953",
        "binaryData": true,
        "binaryPropertyName": "pdfFile",
        "additionalFields": {}
      },
      "id": "1e7e382a-b1b3-4c94-8259-e2c822e86a95",
      "name": "Tele: PDF H\u1ee3p \u0110\u1ed3ng",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1,
      "position": [
        -480,
        672
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT r.ma_phong AS phong, '{{ $json.chiSoDienMoi }}'::numeric AS \"chiSoDienMoi\", 'AI_OCR_ADMIN' AS nguon, c.gia_thue_chot AS \"tienPhong\", t.ho_ten AS \"hoTen\", t.email AS \"email\", COALESCE((SELECT chi_so_dien_moi FROM utility_bills WHERE room_id = r.id ORDER BY id DESC LIMIT 1), 0) AS \"chiSoDienCu\", COALESCE((SELECT chi_so_nuoc_moi FROM utility_bills WHERE room_id = r.id ORDER BY id DESC LIMIT 1), 0) AS \"chiSoNuocCu\" FROM rooms r JOIN contracts c ON r.id = c.room_id JOIN tenants t ON c.tenant_id = t.id WHERE r.ma_phong = '{{ $json.phong }}' AND c.trang_thai = 'Hi\u1ec7u l\u1ef1c' LIMIT 1;",
        "options": {}
      },
      "id": "17461247-ba7f-4362-acfe-57086bf6a418",
      "name": "L\u1ea5y Ti\u1ec1n Nh\u00e0 & \u0110i\u1ec7n C\u0169",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -1168,
        1216
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {
          "download": true
        }
      },
      "id": "0ef1c897-b0bb-4323-a4c3-ddb94e48b497",
      "name": "g\u1eedi \u1ea3nh Telegram",
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.1,
      "position": [
        -2048,
        1216
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Chuy\u1ec3n \u0111\u1ed5i Binary Telegram sang m\u00e3 Base64 chu\u1ea9n\nconst binaryData = await this.helpers.getBinaryDataBuffer(0, 'data');\nreturn {\n  json: {\n    imageBase64: binaryData.toString('base64')\n  }\n};"
      },
      "id": "3429c448-c337-4b6e-a202-e01d6dbca309",
      "name": "Chuy\u1ec3n \u1ea3nh sang Base64",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1840,
        1216
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.groq.com/openai/v1/chat/completions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer {{GROQ_API_KEY}}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"meta-llama/llama-4-scout-17b-16e-instruct\",\n  \"messages\": [\n    {\n      \"role\": \"user\",\n      \"content\": [\n        {\n          \"type\": \"text\",\n          \"text\": \"\u0110\u00e2y l\u00e0 \u1ea3nh ch\u1ee5p c\u00f4ng t\u01a1 \u0111i\u1ec7n. H\u00e3y nh\u00ecn v\u00e0o \u00f4 hi\u1ec3n th\u1ecb ch\u1ec9 s\u1ed1 ch\u00ednh (th\u01b0\u1eddng n\u1eb1m c\u1ea1nh ch\u1eef kWh) v\u00e0 cho t\u00f4i bi\u1ebft con s\u1ed1 hi\u1ec7n t\u1ea1i l\u00e0 bao nhi\u00eau. Tr\u1ea3 v\u1ec1 k\u1ebft qu\u1ea3 d\u01b0\u1edbi d\u1ea1ng JSON: {\\\"chiSoDien\\\": 123}\"\n        },\n        {\n          \"type\": \"image_url\",\n          \"image_url\": {\n            \"url\": \"data:image/jpeg;base64,{{ $json.imageBase64 }}\"\n          }\n        }\n      ]\n    }\n  ],\n  \"response_format\": { \"type\": \"json_object\" }\n}\n",
        "options": {}
      },
      "id": "e0c2fe40-022c-42a9-94bb-c195f8b223a2",
      "name": "HTTP Request",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        -1632,
        1216
      ]
    },
    {
      "parameters": {
        "jsCode": "\ntry {\n  // L\u1ea5y d\u1eef li\u1ec7u t\u1eeb Groq (N\u1eb1m trong choices[0].message.content)\n  const groqOutput = $json.choices[0].message.content;\n  \n  let chiSoMoi = 0;\n  try {\n    const jsonMatch = groqOutput.match(/\\{[\\s\\S]*\\}/);\n    if (jsonMatch) {\n      const parsed = JSON.parse(jsonMatch[0]);\n      chiSoMoi = parsed.chiSoDien || 0;\n    } else {\n      const match = groqOutput.match(/\\d+/);\n      chiSoMoi = match ? Number(match[0]) : 0;\n    }\n  } catch (e) {\n    const match = groqOutput.match(/\\d+/);\n    chiSoMoi = match ? Number(match[0]) : 0;\n  }\n  \n  const caption = $('g\u1eedi \u1ea3nh Telegram').item.json.message.caption || '';\n  // Thay \u0111o\u1ea1n regex c\u0169 b\u1eb1ng \u0111o\u1ea1n n\u00e0y \u0111\u1ec3 l\u1ea5y c\u1ea3 ch\u1eef c\u00e1i (v\u00ed d\u1ee5: P101)\n  const matchPhong = caption.match(/(?:#|Ph\u00f2ng\\s*)?([A-Z]*\\d{3,4})/i);\n  const phong = matchPhong ? matchPhong[1] : '???';\n\n  \n  if (phong === '???') throw new Error(\"L\u1ed7i: B\u1ea1n ch\u01b0a ghi m\u00e3 ph\u00f2ng \u1edf ph\u1ea7n Caption (v\u00ed d\u1ee5: P101)\");\n\n  return {\n    json: {\n      phong: phong,\n      chiSoDienMoi: chiSoMoi,\n      nguon: \"AI_GROQ_ADMIN\",\n      ai_raw: groqOutput\n    }\n  };\n} catch (error) {\n  throw new Error(\"L\u1ed7i x\u1eed l\u00fd d\u1eef li\u1ec7u AI Groq: \" + error.message);\n}\n"
      },
      "id": "63805421-d478-4274-b7ad-6f152f6509d8",
      "name": "B\u00f3c t\u00e1ch D\u1eef li\u1ec7u",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1392,
        1216
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "is_auto",
              "value": false,
              "type": "boolean"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "id": "88d07415-95ca-479a-898c-4d53ccb45a19",
      "name": "Manual: Set Flag",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -944,
        1152
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "chot-dien-nuoc",
        "options": {}
      },
      "id": "dd73b23c-2a63-4e07-9eb1-085b24bc57a0",
      "name": "Webhook (Ch\u1ed1t tay)1",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        -1440,
        1008
      ]
    },
    {
      "parameters": {
        "operation": "sendDocument",
        "chatId": "6126814953",
        "binaryData": true,
        "additionalFields": {
          "caption": "=\ud83d\ude80 [B\u00c1O C\u00c1O H\u00d3A \u0110\u01a0N G\u1ed8P]\n\ud83c\udfe0 Ph\u00f2ng: {{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.phong }}\n\ud83d\udcb8 T\u1ed5ng ti\u1ec1n: {{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.txt_tong }}\n\u2705 Tr\u1ea1ng th\u00e1i: Ho\u00e0n t\u1ea5t ch\u1ed1t s\u1ed5."
        }
      },
      "id": "ac44c3c8-097b-43a6-abdb-f2fdcd62cb7c",
      "name": "7. Tele: B\u00e1o c\u00e1o",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.1,
      "position": [
        720,
        1168
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://backend:8000/api/bills/save",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "phong",
              "value": "={{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.phong }}"
            },
            {
              "name": "tong_tien",
              "value": "={{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.val_tong }}"
            },
            {
              "name": "pdf_base64",
              "value": "={{ $json.pdfBase64 }}"
            },
            {
              "name": "qr_url",
              "value": "={{ $('API L\u1ea5y QR PayOS').item.json.qr_image_url || $('2. T\u00ednh To\u00e1n & VietQR').item.json.qrUrl }}"
            },
            {
              "name": "thang_nam",
              "value": "={{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.thangDisplay }}"
            },
            {
              "name": "chi_so_dien_moi",
              "value": "={{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.chi_so_dien_moi }}"
            },
            {
              "name": "chi_so_dien_cu",
              "value": "={{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.chi_so_dien_cu }}"
            },
            {
              "name": "chi_so_nuoc_cu",
              "value": "={{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.chi_so_nuoc_cu }}"
            },
            {
              "name": "chi_so_nuoc_moi",
              "value": "={{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.chi_so_nuoc_moi }}"
            },
            {
              "name": "payos_order_code",
              "value": "={{ $('API L\u1ea5y QR PayOS').item.json.payos_order_code }}"
            }
          ]
        },
        "options": {}
      },
      "id": "0a02fac0-ef69-411e-8d9f-964bd9103ce5",
      "name": "6. L\u01b0u Database",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        720,
        880
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const fileBuffer = await this.helpers.getBinaryDataBuffer(0, 'data');\nconst pdfBase64 = fileBuffer.toString('base64');\nconst info = $('2. T\u00ednh To\u00e1n & VietQR').item.json;\n\nreturn {\n  json: { ...info, pdfBase64 },\n  binary: {\n    data: await this.helpers.prepareBinaryData(fileBuffer, `HoaDon_P${info.phong}.pdf`, 'application/pdf')\n  }\n};\n"
      },
      "id": "cde59a26-63e5-4b86-9495-5da5633c52cb",
      "name": "5. \u0110\u00f3ng g\u00f3i PDF & Base64",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        464,
        1008
      ]
    },
    {
      "parameters": {
        "mode": "combine",
        "combinationMode": "multiplex",
        "options": {}
      },
      "id": "e0776f3f-4bc5-4fbb-a64e-f20b49a7b058",
      "name": "Merge Data",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 2.1,
      "position": [
        272,
        1008
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://gotenberg:3000/forms/chromium/convert/html",
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "parameterType": "formBinaryData",
              "name": "files",
              "inputDataFieldName": "indexHtml"
            }
          ]
        },
        "options": {}
      },
      "id": "eb562fee-54cb-48d3-b962-64d9914a8039",
      "name": "4. Gotenberg (PDF)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        64,
        864
      ]
    },
    {
      "parameters": {
        "jsCode": "const d = $('2. T\u00ednh To\u00e1n & VietQR').item.json;\nconst payos = $('API L\u1ea5y QR PayOS').item.json;\nconst qrUrl = payos.qr_image_url || d.qrUrl;\n\nconst html = `<!DOCTYPE html>\n<html lang=\"vi\">\n<head>\n    <meta charset=\"UTF-8\">\n    <style>\n        body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; margin: 0; padding: 40px; color: #333; }\n        .invoice-box { max-width: 800px; margin: auto; padding: 30px; border: 1px solid #eee; box-shadow: 0 0 10px rgba(0, 0, 0, 0.15); font-size: 16px; line-height: 24px; }\n        .header { display: flex; justify-content: space-between; border-bottom: 2px solid #1e40af; padding-bottom: 20px; margin-bottom: 20px; }\n        .header h1 { margin: 0; color: #1e40af; font-size: 32px; }\n        .info-table { width: 100%; margin-bottom: 30px; }\n        .info-table td { padding: 5px; }\n        .billing-table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }\n        .billing-table th, .billing-table td { padding: 12px; border: 1px solid #ddd; text-align: left; }\n        .billing-table th { background-color: #f8fafc; color: #1e40af; font-weight: bold; }\n        .total-row { font-size: 18px; font-weight: bold; background-color: #e2e8f0; }\n        .total-row td { color: #dc2626; text-align: right; }\n        .footer { text-align: center; margin-top: 40px; font-size: 14px; color: #666; }\n        .qr-container { text-align: center; margin-top: 20px; }\n        .qr-container img { border: 1px solid #ccc; border-radius: 8px; padding: 10px; background: white; }\n    </style>\n</head>\n<body>\n    <div class=\"invoice-box\">\n        <div class=\"header\">\n            <div>\n                <h1>H\u00d3A \u0110\u01a0N THANH TO\u00c1N</h1>\n                <p>K\u1ef3 c\u01b0\u1edbc: Th\u00e1ng ${d.thangDisplay}</p>\n            </div>\n            <div style=\"text-align: right;\">\n                <h2 style=\"margin: 0; color: #dc2626;\">Ph\u00f2ng: ${d.phong}</h2>\n            </div>\n        </div>\n        \n        <table class=\"info-table\">\n            <tr>\n                <td><strong>Kh\u00e1ch h\u00e0ng:</strong> ${d.hoTen}</td>\n                <td style=\"text-align: right;\"><strong>Ng\u00e0y l\u1eadp:</strong> ${new Date().toLocaleDateString('vi-VN')}</td>\n            </tr>\n        </table>\n\n        <table class=\"billing-table\">\n            <thead>\n                <tr>\n                    <th>H\u1ea1ng m\u1ee5c</th>\n                    <th>Ch\u1ec9 s\u1ed1 \u0111\u1ea7u</th>\n                    <th>Ch\u1ec9 s\u1ed1 cu\u1ed1i</th>\n                    <th style=\"text-align: right;\">Th\u00e0nh ti\u1ec1n</th>\n                </tr>\n            </thead>\n            <tbody>\n                <tr>\n                    <td>Ti\u1ec1n thu\u00ea ph\u00f2ng</td>\n                    <td>-</td>\n                    <td>-</td>\n                    <td style=\"text-align: right;\">${d.txt_phong}</td>\n                </tr>\n                <tr>\n                    <td>Ti\u1ec1n \u0111i\u1ec7n</td>\n                    <td>${d.chi_so_dien_cu}</td>\n                    <td>${d.chi_so_dien_moi}</td>\n                    <td style=\"text-align: right;\">${d.txt_dien || 'T\u00ednh g\u1ed9p'}</td>\n                </tr>\n                <tr>\n                    <td>Ti\u1ec1n n\u01b0\u1edbc</td>\n                    <td>${d.chi_so_nuoc_cu}</td>\n                    <td>${d.chi_so_nuoc_moi}</td>\n                    <td style=\"text-align: right;\">${d.txt_nuoc || 'T\u00ednh g\u1ed9p'}</td>\n                </tr>\n                <tr class=\"total-row\">\n                    <td colspan=\"3\" style=\"text-align: right;\">T\u1ed4NG C\u1ed8NG:</td>\n                    <td>${d.txt_tong}</td>\n                </tr>\n            </tbody>\n        </table>\n\n        <div class=\"qr-container\">\n            <h3>Qu\u00e9t m\u00e3 QR \u0111\u1ec3 thanh to\u00e1n nhanh</h3>\n            <img src=\"${qrUrl}\" alt=\"PayOS QR\" width=\"250\" />\n            <p>N\u1ed9i dung CK: <strong>P${d.phong}</strong></p>\n            ${payos.checkout_url ? `<p><a href=\"${payos.checkout_url}\" style=\"display: inline-block; padding: 10px 20px; background-color: #1e40af; color: white; text-decoration: none; border-radius: 5px; margin-top: 10px;\">M\u1edf trang thanh to\u00e1n</a></p>` : ''}\n        </div>\n\n        <div class=\"footer\">\n            <p>C\u1ea3m \u01a1n qu\u00fd kh\u00e1ch \u0111\u00e3 s\u1eed d\u1ee5ng d\u1ecbch v\u1ee5 c\u1ee7a ch\u00fang t\u00f4i!</p>\n            <p>M\u1ecdi th\u1eafc m\u1eafc xin vui l\u00f2ng li\u00ean h\u1ec7 Ban Qu\u1ea3n L\u00fd CLM.</p>\n        </div>\n    </div>\n</body>\n</html>`;\nreturn {\n  json: { ...d, qrUrl },\n  binary: {\n    indexHtml: await this.helpers.prepareBinaryData(Buffer.from(html), 'index.html', 'text/html')\n  }\n};\n"
      },
      "id": "ab2b6181-2fba-456a-89a9-2278bd25b1bd",
      "name": "3. D\u00e0n trang HTML",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -176,
        864
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const input = $input.item.json;\nconst isAI = input.nguon === \"AI_OCR_ADMIN\" || input.nguon === \"AI_GEMINI_ADMIN\";\n\nfunction getV(key) {\n    if (input[key] !== undefined && input[key] !== null) return input[key];\n    if (input.body && input.body[key] !== undefined) return input.body[key];\n    return 0; \n}\n\nconst phong = getV('phong') || getV('ma_phong') || \"?\";\nconst hoTen = getV('hoTen') || getV('ho_ten') || \"Kh\u00e1ch h\u00e0ng\";\nconst email = getV('email') || \"clm@example.com\";\n\nconst today = new Date();\nconst thangNam = getV('thangNam') || getV('thang_nam') || `${today.getMonth() + 1}/${today.getFullYear()}`;\n\nlet baseRent = Number(getV('tienPhong') || getV('tien_phong') || 0);\nlet tongTuReact = Number(getV('tongTien') || getV('tong_tien') || 0);\n\nlet val_dien = 0;\nlet val_nuoc = 0;\nlet finalTotal = 0;\n\nif (tongTuReact > 0 && !isAI) {\n    baseRent = tongTuReact;\n    finalTotal = tongTuReact;\n} else {\n    const sd = Number(getV('chiSoDienMoi')) - Number(getV('chiSoDienCu'));\n    const sn = Number(getV('chiSoNuocMoi')) - Number(getV('chiSoNuocCu'));\n\n    val_dien = Math.max(0, sd * 10); \n    val_nuoc = Math.max(0, sn * 100000); \n\n    finalTotal = baseRent + val_dien + val_nuoc;\n}\n\nconst fmt = (v) => new Intl.NumberFormat('vi-VN').format(v) + ' \u0111';\n\nreturn {\n    json: {\n        ...input, \n        phong, hoTen, email,\n        is_auto: false,\n        val_phong: baseRent,\n        val_tong: finalTotal,\n        txt_phong: fmt(baseRent),\n        txt_tong: fmt(finalTotal),\n        txt_dien: fmt(val_dien),\n        txt_nuoc: fmt(val_nuoc),\n        thangDisplay: thangNam,\n        qrUrl: `https://img.vietqr.io/image/MB-0905881525-compact2.png?amount=${finalTotal}&addInfo=ThanhToan_P${phong}&accountName=HUYNH%20NGOC%20HUY`,\n        chi_so_dien_cu: Number(getV('chiSoDienCu')),\n        chi_so_dien_moi: Number(getV('chiSoDienMoi')),\n        chi_so_nuoc_cu: Number(getV('chiSoNuocCu')),\n        chi_so_nuoc_moi: Number(getV('chiSoNuocMoi'))\n    }\n};\n"
      },
      "id": "9fece268-02c1-4063-9c63-40506a5b3219",
      "name": "2. T\u00ednh To\u00e1n & VietQR",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -608,
        992
      ]
    },
    {
      "parameters": {
        "sendTo": "={{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.email }}",
        "subject": "=H\u00f3a \u0111\u01a1n ti\u1ec1n nh\u00e0 P.{{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.phong }} - Th\u00e1ng {{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.thangDisplay }}",
        "message": "=<div style='font-family:Arial,sans-serif; max-width:600px; border:1px solid #e2e8f0; border-radius:12px; overflow:hidden;'>\n  <div style='background:#1e40af; padding:20px; color:white; text-align:center;'>\n    <h2 style='margin:0;'>H\u00d3A \u0110\u01a0N P.{{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.phong }}</h2>\n  </div>\n  \n  <div style='padding:24px; background:#f8fafc;'>\n    <p>Xin ch\u00e0o <strong>{{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.hoTen }}</strong>,</p>\n    \n    <p>Ban qu\u1ea3n l\u00fd g\u1eedi b\u1ea1n th\u00f4ng b\u00e1o thanh to\u00e1n cho k\u1ef3 <strong>{{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.thangDisplay }}</strong>, bao g\u1ed3m:</p>\n    <ul style='color:#334155;'>\n      <li><b>Ti\u1ec1n nh\u00e0:</b> {{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.txt_phong }}</li>\n      <li><b>Ti\u1ec1n \u0111i\u1ec7n:</b> {{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.txt_dien || 'T\u00ednh theo th\u1ef1c t\u1ebf' }}</li>\n      <li><b>Ti\u1ec1n n\u01b0\u1edbc:</b> {{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.txt_nuoc || 'T\u00ednh theo th\u1ef1c t\u1ebf' }}</li>\n    </ul>\n\n    <div style='background:white; border:2px dashed #1e40af; padding:15px; text-align:center; margin:20px 0;'>\n      <span style='font-size:14px; color:#64748b;'>T\u1ed4NG S\u1ed0 TI\u1ec0N C\u1ea6N THANH TO\u00c1N</span><br>\n      <b style='font-size:24px; color:#dc2626;'>{{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.txt_tong }}</b>\n    </div>\n\n    <p style='font-size:13px; color:#64748b; font-style:italic;'>* Chi ti\u1ebft ch\u1ec9 s\u1ed1 v\u00e0 QR thanh to\u00e1n, vui l\u00f2ng xem trong file PDF \u0111\u00ednh k\u00e8m.</p>\n    \n    <hr style='border:0; border-top:1px solid #e2e8f0; margin:20px 0;'>\n    <p style='color:#1e40af; font-weight:bold; margin:0;'>Tr\u00e2n tr\u1ecdng,<br>Ban Qu\u1ea3n L\u00fd CLM</p>\n  </div>\n</div>\n",
        "options": {
          "attachmentsUi": {
            "attachmentsBinary": [
              {}
            ]
          }
        }
      },
      "id": "a12e6013-473d-49a6-8cfa-bcb454141fe2",
      "name": "G\u1eedi Mail H\u00f3a \u0110\u01a1n",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        976,
        1008
      ],
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://backend:8000/api/bills/draft-and-qr",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "phong",
              "value": "={{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.phong }}"
            },
            {
              "name": "tong_tien",
              "value": "={{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.val_tong }}"
            },
            {
              "name": "thang_nam",
              "value": "={{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.thangDisplay }}"
            },
            {
              "name": "chi_so_dien_moi",
              "value": "={{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.chi_so_dien_moi }}"
            },
            {
              "name": "chi_so_dien_cu",
              "value": "={{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.chi_so_dien_cu }}"
            },
            {
              "name": "chi_so_nuoc_cu",
              "value": "={{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.chi_so_nuoc_cu }}"
            },
            {
              "name": "chi_so_nuoc_moi",
              "value": "={{ $('2. T\u00ednh To\u00e1n & VietQR').item.json.chi_so_nuoc_moi }}"
            }
          ]
        },
        "options": {}
      },
      "id": "30693559-918d-47c4-8f68-dea6e3a82f69",
      "name": "API L\u1ea5y QR PayOS",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        -384,
        992
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT \n    table_name, \n    column_name, \n    data_type \nFROM information_schema.columns \nWHERE table_schema = 'public' \nORDER BY table_name, ordinal_position;\n",
        "options": {}
      },
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        0,
        592
      ],
      "id": "86f2bd43-60e1-4927-a2b9-03027a9c1a66",
      "name": "Execute a SQL query",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Webhook (FastAPI g\u1ecdi)": {
      "main": [
        [
          {
            "node": "Format & T\u1ea1o HTML H\u1ee3p \u0110\u1ed3ng",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format & T\u1ea1o HTML H\u1ee3p \u0110\u1ed3ng": {
      "main": [
        [
          {
            "node": "HTML String \u2192 Binary File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTML String \u2192 Binary File": {
      "main": [
        [
          {
            "node": "Gotenberg: HTML \u2192 PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gotenberg: HTML \u2192 PDF": {
      "main": [
        [
          {
            "node": "L\u1ea5y PDF Base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "L\u1ea5y PDF Base64": {
      "main": [
        [
          {
            "node": "L\u01b0u PDF v\u00e0o Database",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "L\u01b0u PDF v\u00e0o Database": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "G\u1eedi Mail + PDF H\u1ee3p \u0110\u1ed3ng": {
      "main": [
        [
          {
            "node": "B\u00e1o c\u00e1o Telegram",
            "type": "main",
            "index": 0
          },
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "G\u1eedi Mail + PDF H\u1ee3p \u0110\u1ed3ng",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "X\u1eed l\u00fd Base64 sang File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "X\u1eed l\u00fd Base64 sang File": {
      "main": [
        [
          {
            "node": "Tele: B\u00e1o c\u00e1o",
            "type": "main",
            "index": 0
          },
          {
            "node": "Tele: CCCD Tr\u01b0\u1edbc",
            "type": "main",
            "index": 0
          },
          {
            "node": "Tele: CCCD Sau",
            "type": "main",
            "index": 0
          },
          {
            "node": "Tele: PDF H\u1ee3p \u0110\u1ed3ng",
            "type": "main",
            "index": 0
          },
          {
            "node": "Mail: C\u1ea5p Account",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "L\u1ea5y Ti\u1ec1n Nh\u00e0 & \u0110i\u1ec7n C\u0169": {
      "main": [
        [
          {
            "node": "Manual: Set Flag",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "g\u1eedi \u1ea3nh Telegram": {
      "main": [
        [
          {
            "node": "Chuy\u1ec3n \u1ea3nh sang Base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Chuy\u1ec3n \u1ea3nh sang Base64": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request": {
      "main": [
        [
          {
            "node": "B\u00f3c t\u00e1ch D\u1eef li\u1ec7u",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "B\u00f3c t\u00e1ch D\u1eef li\u1ec7u": {
      "main": [
        [
          {
            "node": "L\u1ea5y Ti\u1ec1n Nh\u00e0 & \u0110i\u1ec7n C\u0169",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual: Set Flag": {
      "main": [
        [
          {
            "node": "2. T\u00ednh To\u00e1n & VietQR",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook (Ch\u1ed1t tay)1": {
      "main": [
        [
          {
            "node": "Manual: Set Flag",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. \u0110\u00f3ng g\u00f3i PDF & Base64": {
      "main": [
        [
          {
            "node": "7. Tele: B\u00e1o c\u00e1o",
            "type": "main",
            "index": 0
          },
          {
            "node": "6. L\u01b0u Database",
            "type": "main",
            "index": 0
          },
          {
            "node": "G\u1eedi Mail H\u00f3a \u0110\u01a1n",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Data": {
      "main": [
        [
          {
            "node": "5. \u0110\u00f3ng g\u00f3i PDF & Base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Gotenberg (PDF)": {
      "main": [
        [
          {
            "node": "Merge Data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "3. D\u00e0n trang HTML": {
      "main": [
        [
          {
            "node": "4. Gotenberg (PDF)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. T\u00ednh To\u00e1n & VietQR": {
      "main": [
        [
          {
            "node": "API L\u1ea5y QR PayOS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "API L\u1ea5y QR PayOS": {
      "main": [
        [
          {
            "node": "Merge Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "3. D\u00e0n trang HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "meta": {
    "templateCredsSetupCompleted": true
  }
}