{
  "name": "CRM Audit \u2014 Bot vs Didar (Bale)",
  "nodes": [
    {
      "id": "trigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        250,
        500
      ],
      "parameters": {}
    },
    {
      "id": "config",
      "name": "Config",
      "type": "n8n-nodes-base.code",
      "typeVersion": 1,
      "position": [
        500,
        500
      ],
      "parameters": {
        "jsCode": "\nconst CONFIG = {\n  DIDAR_API_KEY: '1t31qjd4bl43cxej1yybhr2uf24ael2a',\n  PIPELINE_ID: '9b0e5024-4822-4833-abe6-8ca426a937ae',\n  COMPANY_ID: '00000000-0000-0000-0000-000000000000',\n\n  STAGES: {\n    register: 'b7e97097-ff9b-4207-a2e7-07dd2ea606af',\n    lesson_1: 'ffa64a67-02e0-462b-a0c2-60c85eee6af5',\n    lesson_2: 'bcfe1289-12bf-4ee6-88e7-2dcf0ed48469',\n    lesson_3: '4223a51c-544b-41e2-94bc-1e99016fbaba',\n    lesson_4: '09856491-6d78-40df-b3ed-c80083e77a8f',\n    lesson_5: '0fcb3769-e1ad-45d5-8847-e51e97065d85',\n    lesson_6: 'aee7d1c1-5f18-43e8-b1cb-050d59ce3517',\n    lesson_7: 'd9fc8133-052f-465b-9289-4211272b6e18',\n    lesson_8: '6faab0b4-be10-478d-9655-bf41eca744a8',\n    sales_wait: '6faab0b4-be10-478d-9655-bf41eca744a8',\n    followup_1: 'be5d2b7a-4cf6-4b5d-873f-a3a76ccf2310',\n    followup_2: 'daa24b66-5151-4e57-b08e-032a2961c339',\n    followup_3: '3d3cf5e9-0726-43db-9e01-885c50c3b2bd',\n    won: ''\n  },\n\n  CUSTOM_FIELDS: {\n    monthly_income: 'Field_996_0_26',\n    staff_count: 'Field_996_0_25',\n    job: 'J',\n    best_call_time: 'Field_996_0_31',\n    lead_score: 'Field_996_12_30',\n    city: 'Field_996_0_11',\n    income_class: ''\n  },\n\n  BOT_PANEL_URL: 'http://195.177.255.133:8080',\n  BOT_ADMIN_USER: 'admin',\n  BOT_ADMIN_PASS: 'BaleAdmin@2024Panel',\n};\n\n// Build reverse stage lookup: GUID \u2192 stage name\nconst stageToName = {};\nfor (const [name, guid] of Object.entries(CONFIG.STAGES)) {\n  stageToName[guid] = name;\n}\nCONFIG._stageToName = stageToName;\n\n// Build lesson_number \u2192 stage GUID mapping\nconst lessonToStage = {};\nfor (const [name, guid] of Object.entries(CONFIG.STAGES)) {\n  const m = name.match(/^lesson_(\\d+)$/);\n  if (m) lessonToStage[parseInt(m[1])] = guid;\n}\nCONFIG._lessonToStage = lessonToStage;\n\nreturn [{json: {CONFIG}}];\n",
        "mode": "runOnceForAllItems"
      }
    },
    {
      "id": "login",
      "name": "Login Bot Panel",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        750,
        500
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $json.CONFIG.BOT_PANEL_URL + '/api/auth/login' }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({username: $json.CONFIG.BOT_ADMIN_USER, password: $json.CONFIG.BOT_ADMIN_PASS}) }}",
        "options": {
          "timeout": 15000
        }
      }
    },
    {
      "id": "extract_token",
      "name": "Extract Token",
      "type": "n8n-nodes-base.code",
      "typeVersion": 1,
      "position": [
        1000,
        500
      ],
      "parameters": {
        "jsCode": "\nconst config = $('Config').first().json.CONFIG;\nconst resp = $input.first().json;\nconst token = resp.access_token || '';\nif (!token) throw new Error('Login failed \u2014 no access_token in response');\nreturn [{json: {CONFIG: config, token}}];\n",
        "mode": "runOnceForAllItems"
      }
    },
    {
      "id": "fetch_bot_users",
      "name": "Fetch Bot Users",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1250,
        500
      ],
      "parameters": {
        "method": "GET",
        "url": "={{ $json.CONFIG.BOT_PANEL_URL + '/api/audit/users' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $json.token }}"
            }
          ]
        },
        "options": {
          "timeout": 30000
        }
      }
    },
    {
      "id": "extract_bot_users",
      "name": "Extract Bot Users",
      "type": "n8n-nodes-base.code",
      "typeVersion": 1,
      "position": [
        1500,
        500
      ],
      "parameters": {
        "jsCode": "\nconst config = $('Config').first().json.CONFIG;\nconst token = $('Extract Token').first().json.token;\nconst resp = $input.first().json;\nconst botUsers = resp.items || [];\nreturn [{json: {CONFIG: config, token, botUsers, totalBotUsers: botUsers.length}}];\n",
        "mode": "runOnceForAllItems"
      }
    },
    {
      "id": "fetch_deals",
      "name": "Fetch Didar Deals",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1750,
        500
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://app.didar.me/api/deal/search_v2?apikey=' + $json.CONFIG.DIDAR_API_KEY }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({Criteria: {PipelineId: $json.CONFIG.PIPELINE_ID}, From: 0, Limit: 5000}) }}",
        "options": {
          "timeout": 60000
        }
      }
    },
    {
      "id": "compare",
      "name": "Compare & Audit",
      "type": "n8n-nodes-base.code",
      "typeVersion": 1,
      "position": [
        2100,
        500
      ],
      "parameters": {
        "jsCode": "\n// Gather inputs\nconst data = $('Extract Bot Users').first().json;\nconst CONFIG = data.CONFIG;\nconst botUsers = data.botUsers || [];\n\nconst dealsResp = $('Fetch Didar Deals').first().json;\nconst deals = dealsResp?.Response?.List || dealsResp?.search_respons?.List || [];\n\n// \u2500\u2500 Build lookup maps \u2500\u2500\n\n// Normalise phone: remove leading 0, keep last 10 digits\nfunction normalisePhone(raw) {\n  if (!raw) return '';\n  const digits = String(raw).replace(/\\D/g, '');\n  if (digits.length >= 10) return digits.slice(-10);\n  return digits;\n}\n\n// Build deal lookup by person phone (using embedded Person data)\nconst dealByPhone = {};\nconst personByPhone = {};\nfor (const d of deals) {\n  const person = d.Person || d.Contact || {};\n  const phones = [person.MobilePhone, person.WorkPhone, person.Phone, person.HomePhone].filter(Boolean);\n  for (const ph of phones) {\n    const norm = normalisePhone(ph);\n    if (norm) {\n      // Prefer Pending deals; if multiple, keep first pending\n      if (!dealByPhone[norm] || d.Status === 'Pending') {\n        dealByPhone[norm] = d;\n      }\n      if (!personByPhone[norm]) {\n        personByPhone[norm] = person;\n      }\n    }\n  }\n}\n\n// Stage name from GUID lookup\nconst stageToName = CONFIG._stageToName || {};\nconst lessonToStage = CONFIG._lessonToStage || {};\n\n// \u2500\u2500 Build expected stage for each user \u2500\u2500\nfunction expectedStage(user) {\n  if (user.is_completed) return {name: 'won', guid: CONFIG.STAGES.won};\n  const ln = user.current_lesson_number;\n  if (ln && lessonToStage[ln]) {\n    return {name: 'lesson_' + ln, guid: lessonToStage[ln]};\n  }\n  return {name: 'register', guid: CONFIG.STAGES.register};\n}\n\n// \u2500\u2500 Compare each bot user \u2500\u2500\nconst report = {\n  summary: {\n    total_bot_users: botUsers.length,\n    users_with_phone: 0,\n    users_without_phone: 0,\n    total_crm_deals: deals.length,\n    deal_found: 0,\n    deal_missing: 0,\n    stage_match: 0,\n    stage_mismatch: 0,\n    status_match: 0,\n    status_mismatch: 0,\n    lead_score_match: 0,\n    lead_score_mismatch: 0,\n    fully_synced: 0,\n  },\n  missing_deals: [],\n  stage_mismatches: [],\n  status_mismatches: [],\n  lead_score_mismatches: [],\n  orphan_deals: [],\n};\n\nconst matchedPhones = new Set();\n\nfor (const user of botUsers) {\n  const phone = normalisePhone(user.phone);\n  if (!phone) {\n    report.summary.users_without_phone++;\n    continue;\n  }\n  report.summary.users_with_phone++;\n\n  const deal = dealByPhone[phone];\n  const person = personByPhone[phone];\n  const issues = [];\n\n  if (!deal) {\n    report.summary.deal_missing++;\n    report.missing_deals.push({\n      bot_user_id: user.id,\n      name: (user.first_name || '') + ' ' + (user.last_name || ''),\n      phone: user.phone,\n      lesson: user.current_lesson_number,\n      completed: user.is_completed,\n    });\n    continue;\n  }\n\n  matchedPhones.add(phone);\n  report.summary.deal_found++;\n\n  // Check stage\n  const exp = expectedStage(user);\n  const actualStageGuid = deal.PipelineStageId || '';\n  const actualStageName = stageToName[actualStageGuid] || actualStageGuid;\n\n  if (actualStageGuid === exp.guid) {\n    report.summary.stage_match++;\n  } else {\n    report.summary.stage_mismatch++;\n    report.stage_mismatches.push({\n      bot_user_id: user.id,\n      name: (user.first_name || '') + ' ' + (user.last_name || ''),\n      phone: user.phone,\n      expected_stage: exp.name,\n      actual_stage: actualStageName,\n      deal_id: deal.Id,\n      lesson: user.current_lesson_number,\n      completed: user.is_completed,\n    });\n    issues.push('stage_mismatch');\n  }\n\n  // Check deal status\n  const expectedStatus = user.is_completed ? 'Won' : 'Pending';\n  const actualStatus = deal.Status || 'Unknown';\n  if (actualStatus === expectedStatus) {\n    report.summary.status_match++;\n  } else {\n    report.summary.status_mismatch++;\n    report.status_mismatches.push({\n      bot_user_id: user.id,\n      name: (user.first_name || '') + ' ' + (user.last_name || ''),\n      phone: user.phone,\n      expected_status: expectedStatus,\n      actual_status: actualStatus,\n      deal_id: deal.Id,\n    });\n    issues.push('status_mismatch');\n  }\n\n  // Check lead_score (on embedded Person custom fields)\n  const crmLeadScoreField = CONFIG.CUSTOM_FIELDS?.lead_score;\n  if (crmLeadScoreField && crmLeadScoreField !== 'FIELD_GUID' && person?.Fields) {\n    const crmScore = parseInt(person.Fields[crmLeadScoreField] || '0', 10);\n    const botScore = user.lead_score || 0;\n    if (crmScore === botScore) {\n      report.summary.lead_score_match++;\n    } else {\n      report.summary.lead_score_mismatch++;\n      report.lead_score_mismatches.push({\n        bot_user_id: user.id,\n        name: (user.first_name || '') + ' ' + (user.last_name || ''),\n        phone: user.phone,\n        bot_lead_score: botScore,\n        crm_lead_score: crmScore,\n        crm_person_id: person.Id,\n      });\n      issues.push('lead_score_mismatch');\n    }\n  }\n\n  if (issues.length === 0) {\n    report.summary.fully_synced++;\n  }\n}\n\n// Find orphan deals (in pipeline but no matching bot user)\nconst botPhones = new Set(botUsers.map(u => normalisePhone(u.phone)).filter(Boolean));\nfor (const deal of deals) {\n  const person = deal.Person || deal.Contact || {};\n  const phones = [person.MobilePhone, person.WorkPhone].filter(Boolean).map(normalisePhone);\n  const hasMatch = phones.some(ph => botPhones.has(ph));\n  if (!hasMatch) {\n    report.orphan_deals.push({\n      deal_id: deal.Id,\n      deal_title: deal.Title || '',\n      person_name: person.DisplayName || 'Unknown',\n      phone: person.MobilePhone || '',\n      stage: stageToName[deal.PipelineStageId] || deal.PipelineStageId,\n      status: deal.Status,\n    });\n  }\n}\n\nreturn [{json: report}];\n",
        "mode": "runOnceForAllItems"
      }
    },
    {
      "id": "format_report",
      "name": "Format Report",
      "type": "n8n-nodes-base.code",
      "typeVersion": 1,
      "position": [
        2350,
        500
      ],
      "parameters": {
        "jsCode": "\nconst r = $input.first().json;\nconst s = r.summary;\n\nconst lines = [];\nlines.push('\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');\nlines.push('  \ud83d\udcca \u06af\u0632\u0627\u0631\u0634 \u0622\u062f\u06cc\u062a \u0631\u0628\u0627\u062a \u2194 CRM \u062f\u06cc\u062f\u0627\u0631');\nlines.push('  CRM Sync Audit Report');\nlines.push('\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');\nlines.push('');\nlines.push('\ud83d\udccb \u062e\u0644\u0627\u0635\u0647 / Summary');\nlines.push('\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500');\nlines.push(`  \u06a9\u0627\u0631\u0628\u0631\u0627\u0646 \u0631\u0628\u0627\u062a (\u06a9\u0644)        : ${s.total_bot_users}`);\nlines.push(`  \u062f\u0627\u0631\u0627\u06cc \u0634\u0645\u0627\u0631\u0647 \u062a\u0644\u0641\u0646          : ${s.users_with_phone}`);\nlines.push(`  \u0628\u062f\u0648\u0646 \u0634\u0645\u0627\u0631\u0647 \u062a\u0644\u0641\u0646           : ${s.users_without_phone}`);\nlines.push(`  \u062f\u06cc\u0644\u200c\u0647\u0627\u06cc CRM               : ${s.total_crm_deals}`);\nlines.push('');\nlines.push('\ud83d\udd0d \u0646\u062a\u0627\u06cc\u062c \u0645\u0642\u0627\u06cc\u0633\u0647 / Comparison Results');\nlines.push('\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500');\nlines.push(`  \u2705 \u062f\u06cc\u0644 \u067e\u06cc\u062f\u0627 \u0634\u062f            : ${s.deal_found}`);\nlines.push(`  \u274c \u062f\u06cc\u0644 \u0646\u0627\u0645\u0648\u062c\u0648\u062f            : ${s.deal_missing}`);\nlines.push(`  \u2705 \u0627\u0633\u062a\u06cc\u062c \u0635\u062d\u06cc\u062d             : ${s.stage_match}`);\nlines.push(`  \u274c \u0627\u0633\u062a\u06cc\u062c \u0627\u0634\u062a\u0628\u0627\u0647           : ${s.stage_mismatch}`);\nlines.push(`  \u2705 \u0648\u0636\u0639\u06cc\u062a \u0635\u062d\u06cc\u062d             : ${s.status_match}`);\nlines.push(`  \u274c \u0648\u0636\u0639\u06cc\u062a \u0627\u0634\u062a\u0628\u0627\u0647           : ${s.status_mismatch}`);\nlines.push(`  \u2705 \u0627\u0645\u062a\u06cc\u0627\u0632 \u0644\u06cc\u062f \u0635\u062d\u06cc\u062d        : ${s.lead_score_match}`);\nlines.push(`  \u274c \u0627\u0645\u062a\u06cc\u0627\u0632 \u0644\u06cc\u062f \u0627\u0634\u062a\u0628\u0627\u0647      : ${s.lead_score_mismatch}`);\nlines.push('');\n\nconst syncRate = s.users_with_phone > 0\n  ? ((s.fully_synced / s.users_with_phone) * 100).toFixed(1)\n  : '0.0';\nlines.push(`  \ud83c\udfaf \u06a9\u0627\u0645\u0644\u0627\u064b \u0633\u06cc\u0646\u06a9 \u0634\u062f\u0647: ${s.fully_synced} / ${s.users_with_phone} (${syncRate}%)`);\nlines.push('');\n\n// \u2500\u2500 Details sections \u2500\u2500\nif (r.missing_deals.length > 0) {\n  lines.push('');\n  lines.push('\ud83d\udeab \u062f\u06cc\u0644 \u0646\u0627\u0645\u0648\u062c\u0648\u062f \u062f\u0631 CRM / Missing Deals');\n  lines.push('\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500');\n  for (const d of r.missing_deals.slice(0, 50)) {\n    lines.push(`  \u2022 ${d.name} | \ud83d\udcf1 ${d.phone} | \u062f\u0631\u0633 ${d.lesson || '-'} | ${d.completed ? '\u062a\u06a9\u0645\u06cc\u0644' : '\u0641\u0639\u0627\u0644'}`);\n  }\n  if (r.missing_deals.length > 50) {\n    lines.push(`  ... \u0648 ${r.missing_deals.length - 50} \u0645\u0648\u0631\u062f \u062f\u06cc\u06af\u0631`);\n  }\n}\n\nif (r.stage_mismatches.length > 0) {\n  lines.push('');\n  lines.push('\u26a0\ufe0f \u0639\u062f\u0645 \u062a\u0637\u0627\u0628\u0642 \u0627\u0633\u062a\u06cc\u062c / Stage Mismatches');\n  lines.push('\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500');\n  for (const m of r.stage_mismatches.slice(0, 50)) {\n    lines.push(`  \u2022 ${m.name} | \ud83d\udcf1 ${m.phone} | \u0645\u0648\u0631\u062f \u0627\u0646\u062a\u0638\u0627\u0631: ${m.expected_stage} | \u0648\u0627\u0642\u0639\u06cc: ${m.actual_stage}`);\n  }\n  if (r.stage_mismatches.length > 50) {\n    lines.push(`  ... \u0648 ${r.stage_mismatches.length - 50} \u0645\u0648\u0631\u062f \u062f\u06cc\u06af\u0631`);\n  }\n}\n\nif (r.status_mismatches.length > 0) {\n  lines.push('');\n  lines.push('\u26a0\ufe0f \u0639\u062f\u0645 \u062a\u0637\u0627\u0628\u0642 \u0648\u0636\u0639\u06cc\u062a \u062f\u06cc\u0644 / Status Mismatches');\n  lines.push('\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500');\n  for (const m of r.status_mismatches.slice(0, 50)) {\n    lines.push(`  \u2022 ${m.name} | \ud83d\udcf1 ${m.phone} | \u0645\u0648\u0631\u062f \u0627\u0646\u062a\u0638\u0627\u0631: ${m.expected_status} | \u0648\u0627\u0642\u0639\u06cc: ${m.actual_status}`);\n  }\n  if (r.status_mismatches.length > 50) {\n    lines.push(`  ... \u0648 ${r.status_mismatches.length - 50} \u0645\u0648\u0631\u062f \u062f\u06cc\u06af\u0631`);\n  }\n}\n\nif (r.lead_score_mismatches.length > 0) {\n  lines.push('');\n  lines.push('\u26a0\ufe0f \u0639\u062f\u0645 \u062a\u0637\u0627\u0628\u0642 \u0627\u0645\u062a\u06cc\u0627\u0632 \u0644\u06cc\u062f / Lead Score Mismatches');\n  lines.push('\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500');\n  for (const m of r.lead_score_mismatches.slice(0, 50)) {\n    lines.push(`  \u2022 ${m.name} | \ud83d\udcf1 ${m.phone} | \u0631\u0628\u0627\u062a: ${m.bot_lead_score} | CRM: ${m.crm_lead_score}`);\n  }\n  if (r.lead_score_mismatches.length > 50) {\n    lines.push(`  ... \u0648 ${r.lead_score_mismatches.length - 50} \u0645\u0648\u0631\u062f \u062f\u06cc\u06af\u0631`);\n  }\n}\n\nif (r.orphan_deals.length > 0) {\n  lines.push('');\n  lines.push('\ud83d\udc7b \u062f\u06cc\u0644\u200c\u0647\u0627\u06cc \u06cc\u062a\u06cc\u0645 (\u0628\u062f\u0648\u0646 \u06a9\u0627\u0631\u0628\u0631 \u0631\u0628\u0627\u062a) / Orphan Deals');\n  lines.push('\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500');\n  for (const d of r.orphan_deals.slice(0, 30)) {\n    lines.push(`  \u2022 ${d.person_name} | ${d.deal_title} | \u0627\u0633\u062a\u06cc\u062c: ${d.stage} | ${d.status}`);\n  }\n  if (r.orphan_deals.length > 30) {\n    lines.push(`  ... \u0648 ${r.orphan_deals.length - 30} \u0645\u0648\u0631\u062f \u062f\u06cc\u06af\u0631`);\n  }\n}\n\nlines.push('');\nlines.push('\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');\nlines.push(`  \u062a\u0627\u0631\u06cc\u062e \u0627\u062c\u0631\u0627: ${new Date().toLocaleString('fa-IR', {timeZone: 'Asia/Tehran'})}`);\nlines.push('\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');\n\nconst reportText = lines.join('\\n');\n\nreturn [{json: {\n  report_text: reportText,\n  report_data: r,\n  generated_at: new Date().toISOString(),\n}}];\n",
        "mode": "runOnceForAllItems"
      }
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "Login Bot Panel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Login Bot Panel": {
      "main": [
        [
          {
            "node": "Extract Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Token": {
      "main": [
        [
          {
            "node": "Fetch Bot Users",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Bot Users": {
      "main": [
        [
          {
            "node": "Extract Bot Users",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Bot Users": {
      "main": [
        [
          {
            "node": "Fetch Didar Deals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Didar Deals": {
      "main": [
        [
          {
            "node": "Compare & Audit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compare & Audit": {
      "main": [
        [
          {
            "node": "Format Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "tags": [
    {
      "name": "course-bot"
    },
    {
      "name": "audit"
    },
    {
      "name": "didar-crm"
    },
    {
      "name": "bale"
    }
  ]
}