{
  "id": "U7FRP9wgf1KgBBQY",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Bulk Certificate and Contract Generator with Carbone, Excel and OneDrive",
  "tags": [],
  "nodes": [
    {
      "id": "b02a48be-78eb-4744-a765-80a6a14156bc",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        8656,
        -1440
      ],
      "parameters": {
        "width": 614,
        "height": 1008,
        "content": "## Bulk Certificate and Contract Generator: Carbone PDF Batch with OneDrive Archive and Excel Status Update\n\n### Weekly Scheduled Document Generation from Excel via Carbone and Microsoft 365\n\n### How it works\nThis workflow runs every Monday at 08:00, reads pending rows from a Microsoft\nExcel workbook, generates batches of PDF certificates and contracts using\nCarbone's renderDocument operation, archives each file to OneDrive, notifies\na Teams channel with a summary Adaptive Card, and batch-updates the Excel\nstatus rows to Completed via the Graph API.\n\n### Setup steps\n- [ ] Open the Config node and replace all placeholder IDs: workbookId,\n      worksheetId, certificateTemplateFileId, contractTemplateFileId,\n      certificatesFolderId, contractsFolderId, teamsTeamId, teamsChannelId.\n- [ ] Connect your Microsoft Excel credential to the Read Pending Rows from\n      Excel node.\n- [ ] Connect your Microsoft Graph OAuth2 credential to the Fetch Header Row\n      and Batch Update Excel via Graph API nodes.\n- [ ] Connect your Microsoft SharePoint credential to both Fetch Template\n      nodes and configure the file IDs from the Config node.\n- [ ] Connect your Carbone credential to both Generate nodes. Ensure your\n      Carbone templates use the field names output by the Build Carbone Batch\n      Arrays node: certificates array uses name, email, company, role,\n      courseTitle, completionDate, score, certificateId, issuedBy, issuerName.\n      Contracts array uses name, email, company, role, startDate, contractRef,\n      issuedBy, issuerName.\n- [ ] Connect your Microsoft OneDrive credential to both Upload nodes and\n      configure the target folder IDs from the Config node.\n- [ ] Connect your Microsoft Teams credential to the Notify Teams Channel\n      node and configure the target team and channel IDs from the Config node.\n- [ ] Update issuedBy and issuerName in the Build Carbone Batch Arrays node\n      to match your organization name and authorized signatory.\n\n### Required credentials\n- **Microsoft Excel** (row read)\n- **Microsoft Graph OAuth2** (header row fetch + Excel batch status update)\n- **Microsoft SharePoint** (template fetch)\n- **Carbone** (PDF batch generation via renderDocument)\n- **Microsoft OneDrive** (file archive)\n- **Microsoft Teams** (completion notification)\n\n### Required Excel columns\n`Name` \u00b7 `Email` \u00b7 `Company` \u00b7 `Role` \u00b7 `DocumentType` \u00b7 `CourseTitle` \u00b7\n`CompletionDate` \u00b7 `Score` \u00b7 `Status` \u00b7 `ProcessedAt`\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "fd443d56-99b4-4f3d-9e59-80281b126fd8",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9344,
        -1008
      ],
      "parameters": {
        "color": 7,
        "width": 1360,
        "height": 440,
        "content": "### Trigger \u00b7 Config \u00b7 Excel Read\nFires every Monday 08:00. All IDs loaded from Config. Header row fetched via Graph API to resolve column letters at runtime. Reads only Pending rows."
      },
      "typeVersion": 1
    },
    {
      "id": "090f86db-afc1-4853-b5e5-15b2b3d2c91b",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        10800,
        -1200
      ],
      "parameters": {
        "color": 7,
        "width": 1320,
        "height": 540,
        "content": "### Certificate Generation & Upload\nFetches template from SharePoint, generates batch PDF ZIP via Carbone, extracts files, uploads each to OneDrive, aggregates before continuing."
      },
      "typeVersion": 1
    },
    {
      "id": "8f7f4106-6431-40bd-a547-8682ab80b087",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        12208,
        -1200
      ],
      "parameters": {
        "color": 7,
        "width": 1360,
        "height": 540,
        "content": "### Contract Generation & Upload\nFetches template from SharePoint, generates batch PDF ZIP via Carbone, extracts files, uploads each to OneDrive, aggregates before notification."
      },
      "typeVersion": 1
    },
    {
      "id": "fe41ade8-08ed-46bc-98ec-e164f8b40def",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        13648,
        -1040
      ],
      "parameters": {
        "color": 7,
        "width": 1300,
        "height": 440,
        "content": "### Notify & Status Update\nBuilds Teams Adaptive Card with batch summary and posts to configured channel. Batch-updates Excel rows to Completed via Graph API using dynamically resolved column letters, chunked at 20 requests per call."
      },
      "typeVersion": 1
    },
    {
      "id": "6691bf1a-ae61-4657-b61a-85367c51a210",
      "name": "Schedule Trigger (Monday 8am)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        9440,
        -800
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "d98f2fed-a95f-416f-96a0-f42b4a7ff2cd",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "notes": "Replace all YOUR_* values before activating. workbookId and worksheetId: open the workbook in Excel Online \u2014 workbookId is after /items/, worksheetId is after /worksheets/. SharePoint file IDs: from the file URL, value between sourcedoc=%7B and %7D. OneDrive folder IDs: right-click folder \u2192 Details.",
      "position": [
        9664,
        -800
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-001",
              "name": "workbookId",
              "type": "string",
              "value": "YOUR_EXCEL_WORKBOOK_ID"
            },
            {
              "id": "cfg-002",
              "name": "worksheetId",
              "type": "string",
              "value": "YOUR_WORKSHEET_ID"
            },
            {
              "id": "cfg-003",
              "name": "certificateTemplateFileId",
              "type": "string",
              "value": "YOUR_CERTIFICATE_TEMPLATE_FILE_ID"
            },
            {
              "id": "cfg-004",
              "name": "contractTemplateFileId",
              "type": "string",
              "value": "YOUR_CONTRACT_TEMPLATE_FILE_ID"
            },
            {
              "id": "cfg-005",
              "name": "certificatesFolderId",
              "type": "string",
              "value": "YOUR_ONEDRIVE_CERTIFICATES_FOLDER_ID"
            },
            {
              "id": "cfg-006",
              "name": "contractsFolderId",
              "type": "string",
              "value": "YOUR_ONEDRIVE_CONTRACTS_FOLDER_ID"
            },
            {
              "id": "cfg-007",
              "name": "teamsTeamId",
              "type": "string",
              "value": "YOUR_TEAMS_TEAM_ID"
            },
            {
              "id": "cfg-008",
              "name": "teamsChannelId",
              "type": "string",
              "value": "YOUR_TEAMS_CHANNEL_ID"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "94bbe570-c425-4b0c-995d-3dac8b952c57",
      "name": "Fetch Header Row",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        9888,
        -800
      ],
      "parameters": {
        "url": "={{ `https://graph.microsoft.com/v1.0/me/drive/items/${$('Config').first().json.workbookId}/workbook/worksheets/${$('Config').first().json.worksheetId}/rows/itemAt(index=0)` }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftExcelOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "07caa82d-1d67-4c84-b62d-e8e7cbd21f86",
      "name": "Read Pending Rows from Excel",
      "type": "n8n-nodes-base.microsoftExcel",
      "position": [
        10112,
        -800
      ],
      "parameters": {
        "operation": "readRows"
      },
      "typeVersion": 2
    },
    {
      "id": "2561472a-5eec-4d56-8280-b975d5fc7271",
      "name": "Check Rows Exist",
      "type": "n8n-nodes-base.if",
      "position": [
        10336,
        -800
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $input.all().length }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "36459af1-b3d6-4081-afe1-a3121b10642b",
      "name": "Log No Rows",
      "type": "n8n-nodes-base.noOp",
      "position": [
        10560,
        -688
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "f861e1e6-732c-415f-be33-6e344844556a",
      "name": "Build Carbone Batch Arrays",
      "type": "n8n-nodes-base.code",
      "position": [
        10560,
        -896
      ],
      "parameters": {
        "jsCode": "const allItems = $input.all();\n\nif (!allItems || allItems.length === 0) {\n  return [{ json: { certificates: [], contracts: [], totalCertificates: 0, totalContracts: 0, totalDocuments: 0, batchId: `BATCH-${Date.now()}`, processedAt: new Date().toISOString(), originalRows: [] } }];\n}\n\nconst rows = allItems.map((item, index) => ({\n  ...item.json,\n  _originalRowIndex: index + 2\n}));\n\nconst certificates = rows\n  .filter(r => (r.DocumentType || '').toLowerCase().includes('certificate'))\n  .map(r => ({\n    name: r.Name || '',\n    email: r.Email || '',\n    company: r.Company || '',\n    role: r.Role || '',\n    courseTitle: r.CourseTitle || '',\n    completionDate: r.CompletionDate || new Date().toISOString().split('T')[0],\n    score: r.Score || '',\n    certificateId: `CERT-${Date.now()}-${Math.random().toString(36).substring(2,6).toUpperCase()}`,\n    issuedBy: 'Your Organization Name',\n    issuerName: 'Your Name'\n  }));\n\nconst contracts = rows\n  .filter(r => (r.DocumentType || '').toLowerCase().includes('contract'))\n  .map(r => ({\n    name: r.Name || '',\n    email: r.Email || '',\n    company: r.Company || '',\n    role: r.Role || '',\n    startDate: r.CompletionDate || new Date().toISOString().split('T')[0],\n    contractRef: `CTR-${Date.now()}-${Math.random().toString(36).substring(2,6).toUpperCase()}`,\n    issuedBy: 'Your Organization Name',\n    issuerName: 'Your Name'\n  }));\n\nreturn [{\n  json: {\n    certificates,\n    contracts,\n    totalCertificates: certificates.length,\n    totalContracts: contracts.length,\n    totalDocuments: certificates.length + contracts.length,\n    batchId: `BATCH-${Date.now()}`,\n    processedAt: new Date().toISOString(),\n    originalRows: rows\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "5dfd171f-8314-46f3-88ee-0f4d902781c0",
      "name": "Has Certificates?",
      "type": "n8n-nodes-base.if",
      "position": [
        10848,
        -896
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.totalCertificates }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "344c966d-03b3-492c-a066-b84dd2041110",
      "name": "Fetch Certificate Template",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        11072,
        -976
      ],
      "parameters": {
        "operation": "getFile",
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "edf6bff5-39e6-40b9-85c1-6d656815370a",
      "name": "Generate Certificates Batch PDF",
      "type": "n8n-nodes-carbone.carbone",
      "position": [
        11296,
        -976
      ],
      "parameters": {
        "data": "={{ JSON.stringify({ d: { certificates: $('Build Carbone Batch Arrays').first().json.certificates } }) }}",
        "resource": "renderDocument",
        "convertTo": "pdf",
        "templateSource": "file",
        "generateAdditionalOptions": {
          "batchOutput": "zip",
          "batchSplitBy": "d.certificates"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "e75b852f-378f-484e-a9d6-7eaf9f741283",
      "name": "Extract Certificate Files from ZIP",
      "type": "n8n-nodes-base.compression",
      "position": [
        11520,
        -976
      ],
      "parameters": {
        "binaryPropertyName": "certificates_zip"
      },
      "typeVersion": 1
    },
    {
      "id": "e54ddc2c-12b5-4717-918f-ee0cedb30f55",
      "name": "Upload Certificate to OneDrive",
      "type": "n8n-nodes-base.microsoftOneDrive",
      "position": [
        11744,
        -976
      ],
      "settings": {
        "continueOnFail": true
      },
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "c3465e6a-306f-446e-b721-d7459d8c4033",
      "name": "Aggregate Certificate Uploads",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        11968,
        -976
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "9753af79-c2f9-42bf-8984-51c02e379ec8",
      "name": "Has Contracts?",
      "type": "n8n-nodes-base.if",
      "position": [
        12240,
        -880
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $('Build Carbone Batch Arrays').first().json.totalContracts }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "b683b812-9090-4882-bfb5-a5a71525c810",
      "name": "Fetch Contract Template",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        12368,
        -1040
      ],
      "parameters": {
        "operation": "getFile",
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "3ae1b04a-b813-49c0-a156-c87f18e50e8c",
      "name": "Generate Contracts Batch PDF",
      "type": "n8n-nodes-carbone.carbone",
      "position": [
        12592,
        -1040
      ],
      "parameters": {
        "data": "={{ JSON.stringify({ d: { contracts: $('Build Carbone Batch Arrays').first().json.contracts } }) }}",
        "resource": "renderDocument",
        "convertTo": "pdf",
        "templateSource": "file",
        "generateAdditionalOptions": {
          "batchOutput": "zip",
          "batchSplitBy": "d.contracts"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "44ff36f1-5982-4e30-bc8a-ac3cbdbba4b3",
      "name": "Extract Contract Files from ZIP",
      "type": "n8n-nodes-base.compression",
      "position": [
        12816,
        -1040
      ],
      "parameters": {
        "binaryPropertyName": "contracts_zip"
      },
      "typeVersion": 1
    },
    {
      "id": "9da9804a-934e-4d75-90a9-04754457b85d",
      "name": "Upload Contract to OneDrive",
      "type": "n8n-nodes-base.microsoftOneDrive",
      "position": [
        13040,
        -1040
      ],
      "settings": {
        "continueOnFail": true
      },
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "94e72e08-b9d2-4e1f-9679-09662904633b",
      "name": "Aggregate Contract Uploads",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        13264,
        -1040
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "d0f08d2a-2d38-4ac2-981c-1c6adbe0e201",
      "name": "Build Teams Adaptive Card",
      "type": "n8n-nodes-base.code",
      "position": [
        13952,
        -864
      ],
      "parameters": {
        "jsCode": "const batchData = $('Build Carbone Batch Arrays').first().json;\n\nreturn [{\n  json: {\n    attachments: [\n      {\n        contentType: 'application/vnd.microsoft.card.adaptive',\n        content: {\n          '$schema': 'http://adaptivecards.io/schemas/adaptive-card.json',\n          type: 'AdaptiveCard',\n          version: '1.4',\n          body: [\n            {\n              type: 'TextBlock',\n              text: 'Bulk Document Generation Complete',\n              weight: 'Bolder',\n              size: 'Large',\n              color: 'Good'\n            },\n            {\n              type: 'FactSet',\n              facts: [\n                { title: 'Batch ID',               value: batchData.batchId },\n                { title: 'Certificates Generated', value: String(batchData.totalCertificates) },\n                { title: 'Contracts Generated',    value: String(batchData.totalContracts) },\n                { title: 'Total Documents',        value: String(batchData.totalDocuments) },\n                { title: 'Processed At',           value: batchData.processedAt }\n              ]\n            },\n            {\n              type: 'TextBlock',\n              text: 'All files uploaded to OneDrive. Excel rows updated to Completed.',\n              wrap: true,\n              color: 'Default'\n            }\n          ]\n        }\n      }\n    ]\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c972a888-24b8-40aa-9534-6417c79e2f8c",
      "name": "Notify Teams Channel",
      "type": "n8n-nodes-base.microsoftTeams",
      "position": [
        14176,
        -864
      ],
      "parameters": {
        "operation": "sendMessage"
      },
      "typeVersion": 2
    },
    {
      "id": "e17c269f-61b5-4e10-ab10-57173b17d540",
      "name": "Build Graph Batch Payload",
      "type": "n8n-nodes-base.code",
      "position": [
        14400,
        -864
      ],
      "parameters": {
        "jsCode": "const headerValues = $('Fetch Header Row').first().json.values[0];\n\nconst colIndex = {};\nheaderValues.forEach((name, i) => {\n  if (typeof name === 'string' && name.trim()) {\n    colIndex[name.trim()] = i;\n  }\n});\n\nconst statusIdx      = colIndex['Status'];\nconst processedAtIdx = colIndex['ProcessedAt'];\n\nif (statusIdx === undefined || processedAtIdx === undefined) {\n  throw new Error(\n    `Required columns missing. Found: ${JSON.stringify(colIndex)}. Need: Status, ProcessedAt.`\n  );\n}\n\nfunction colLetter(idx) {\n  let letter = '';\n  let n = idx;\n  while (n >= 0) {\n    letter = String.fromCharCode(65 + (n % 26)) + letter;\n    n = Math.floor(n / 26) - 1;\n  }\n  return letter;\n}\n\nconst statusCol    = colLetter(statusIdx);\nconst processedCol = colLetter(processedAtIdx);\n\nconst rows        = $('Build Carbone Batch Arrays').first().json.originalRows;\nconst workbookId  = $('Config').first().json.workbookId;\nconst worksheetId = $('Config').first().json.worksheetId;\nconst now         = new Date().toISOString();\n\nconst requests = [];\nrows.forEach((row, i) => {\n  const excelRow = row._originalRowIndex;\n  requests.push({\n    id: `status_${i}`,\n    method: 'PATCH',\n    url: `/me/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${statusCol}${excelRow}')`,\n    headers: { 'Content-Type': 'application/json' },\n    body: { values: [[ 'Completed' ]] }\n  });\n  requests.push({\n    id: `time_${i}`,\n    method: 'PATCH',\n    url: `/me/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${processedCol}${excelRow}')`,\n    headers: { 'Content-Type': 'application/json' },\n    body: { values: [[ now ]] }\n  });\n});\n\n// 2 requests per row \u2014 chunk at 10 rows to stay under Graph $batch limit of 20\nconst chunks = [];\nfor (let i = 0; i < requests.length; i += 20) {\n  chunks.push({ json: { requests: requests.slice(i, i + 20) } });\n}\n\nreturn chunks;"
      },
      "typeVersion": 2
    },
    {
      "id": "7f2fab32-6d9a-407d-b5ff-162afef8fc53",
      "name": "Batch Update Excel via Graph API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        14624,
        -864
      ],
      "parameters": {
        "url": "https://graph.microsoft.com/v1.0/$batch",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "jsonBody": "={{ $json }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "microsoftExcelOAuth2Api"
      },
      "typeVersion": 4.2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "c25540c0-a406-48f6-87d1-40053c48ce8e",
  "nodeGroups": [],
  "connections": {
    "Config": {
      "main": [
        [
          {
            "node": "Fetch Header Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Contracts?": {
      "main": [
        [
          {
            "node": "Fetch Contract Template",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Teams Adaptive Card",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Rows Exist": {
      "main": [
        [
          {
            "node": "Build Carbone Batch Arrays",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log No Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Header Row": {
      "main": [
        [
          {
            "node": "Read Pending Rows from Excel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Certificates?": {
      "main": [
        [
          {
            "node": "Fetch Certificate Template",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Has Contracts?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify Teams Channel": {
      "main": [
        [
          {
            "node": "Build Graph Batch Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Contract Template": {
      "main": [
        [
          {
            "node": "Generate Contracts Batch PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Graph Batch Payload": {
      "main": [
        [
          {
            "node": "Batch Update Excel via Graph API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Teams Adaptive Card": {
      "main": [
        [
          {
            "node": "Notify Teams Channel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Contract Uploads": {
      "main": [
        [
          {
            "node": "Build Teams Adaptive Card",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Carbone Batch Arrays": {
      "main": [
        [
          {
            "node": "Has Certificates?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Certificate Template": {
      "main": [
        [
          {
            "node": "Generate Certificates Batch PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Contract to OneDrive": {
      "main": [
        [
          {
            "node": "Aggregate Contract Uploads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Contracts Batch PDF": {
      "main": [
        [
          {
            "node": "Extract Contract Files from ZIP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Pending Rows from Excel": {
      "main": [
        [
          {
            "node": "Check Rows Exist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Certificate Uploads": {
      "main": [
        [
          {
            "node": "Has Contracts?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger (Monday 8am)": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Certificate to OneDrive": {
      "main": [
        [
          {
            "node": "Aggregate Certificate Uploads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Contract Files from ZIP": {
      "main": [
        [
          {
            "node": "Upload Contract to OneDrive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Certificates Batch PDF": {
      "main": [
        [
          {
            "node": "Extract Certificate Files from ZIP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Certificate Files from ZIP": {
      "main": [
        [
          {
            "node": "Upload Certificate to OneDrive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}