{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "87ce7a42-e246-464a-b466-f5f1f6a8c118",
      "name": "Gmail Trigger",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        -7120,
        752
      ],
      "parameters": {
        "simple": false,
        "filters": {
          "q": "=has:attachment (filename:timesheet OR subject:timesheet)",
          "readStatus": "unread"
        },
        "options": {
          "downloadAttachments": true
        },
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        }
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "0460076d-e261-420b-94c2-e20ab74c4be7",
      "name": "Split Binary Attachments",
      "type": "n8n-nodes-base.code",
      "position": [
        -6896,
        752
      ],
      "parameters": {
        "jsCode": "const newItems = [];\n\nconst binary = items[0].binary;\n\nfor (const key in binary) {\n  newItems.push({\n    json: {},\n    binary: {\n      [key]: binary[key]\n    }\n  });\n}\n\nreturn newItems;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "797d4d1d-87a3-4106-b30d-425f2678f973",
      "name": "Loop: Process Each Attachment",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -6672,
        752
      ],
      "parameters": {
        "options": {
          "reset": false
        }
      },
      "typeVersion": 3,
      "alwaysOutputData": false
    },
    {
      "id": "af7850b0-e5c8-448b-b399-d67b663d88fa",
      "name": "Extract Timesheet Data (OpenAI)",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -6128,
        768
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4",
          "cachedResultName": "GPT-4"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=Extract the following from the timesheet text:\n\n- Employee Name  \n- Week Starting Date  \n- Week Ending Date  \n- Total Working Hours  \n- Client Name\n\nInstructions:\n\n- For Week Starting Date:  \n  - If a phrase like \"Period from ... to ...\" is present, extract the **first date** from it. Use the date exactly as written.  \n  - If no such phrase is found, and there is a row with weekdays like \"Monday Tuesday...\" followed by short-form dates like \"Mar-17\", \"May-5\", etc., extract the **first date** in that row.  \n  - Convert the short-form date into full format: **MM/DD/YYYY**.  \n    Example: \"Mar-17\" \u2192 \"03/17/2025\", \"May-5\" \u2192 \"05/05/2025\".  \n  - Do not return \"Mar-17\", \"May-5\", or any partial date format. Always return the full date in MM/DD/YYYY.\n\n- For Week Ending Date:  \n  - If a phrase like \"Period from ... to ...\" is present, extract the **second date**.  \n  - If weekday headers with short-form dates are present, extract the **last date** in that row.  \n  - Again, convert to **MM/DD/YYYY** format.  \n    Example: \"May-11\" \u2192 \"05/11/2025\".\n\n- Do not calculate, assume, or guess dates. Only convert short-form dates to MM/DD/YYYY format using the correct month and year from the timesheet.\n- Look for a label such as:\n - \"Total Hours\",  \n   - \"Total Billable Hours\", \n  - \"Total Number of Hours Worked\"  \n   - or any clearly indicated **total row or cell** at the bottom of the table.\n\n- Do not sum individual daily entries.\n- Return numbers exactly as shown (e.g., 40.00).\n\nOutput the result in this exact JSON format:\n\n{{ $json.data[0].text }}\n"
            }
          ]
        },
        "simplify": false
      },
      "typeVersion": 1.8,
      "alwaysOutputData": true
    },
    {
      "id": "8c7aad74-4441-47c7-8156-a8a244d0bcd6",
      "name": "Get Customer Info From PO Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -5440,
        768
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $('Gmail Trigger').item.json.from.value[0].address }}",
              "lookupColumn": "Email"
            }
          ]
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "executeOnce": false,
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "cdcefae3-154c-4562-92b7-7506cdb271f3",
      "name": "Set Invoice Date & Due Date Days",
      "type": "n8n-nodes-base.set",
      "position": [
        -2464,
        272
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "c3ddd851-ac75-4900-b55e-95cc463788b5",
              "name": "Invoice Date",
              "type": "string",
              "value": "={{ $('Set Timesheet JSON Fields').item.json['Week End Date'].trim() }}\n"
            },
            {
              "id": "0e628019-2892-4b62-8635-4aa3f476a5bb",
              "name": "Due Date",
              "type": "string",
              "value": "={{ $('Set Timesheet JSON Fields').item.json['Week End Date'].toDateTime().plus($('Get Customer Info From PO Sheet').item.json[\"Due Date Calculation\"], 'days').format('MM/dd/yyyy').trim()}}\n"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "2c8cac15-9aec-464a-a820-7aefbe4f5618",
      "name": "Generate Sheet Name (Employee + Week Start to End Dates)",
      "type": "n8n-nodes-base.set",
      "position": [
        -2288,
        272
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a628947e-a7e6-489d-978f-d7662dcb3e64",
              "name": "Sheet Name ",
              "type": "string",
              "value": "={{ \n  $('Set Timesheet JSON Fields').item.json['Employee Name'].trim().replace(/\\s+/g, '') + '_' +\n  $('Set Timesheet JSON Fields').item.json['Week Start Date'].trim() +\n  '_to_' +\n  $('Set Timesheet JSON Fields').item.json['Week End Date'].trim()\n}}\n"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "6951b033-5547-4f23-a6b4-d140126e48af",
      "name": "Set: Invoice Range",
      "type": "n8n-nodes-base.set",
      "position": [
        -3264,
        640
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "798b8ca0-6192-47b5-b1c8-b716d7667f8c",
              "name": " Invoice range for debug",
              "type": "string",
              "value": "={{ $('Get Customer Info From PO Sheet').item.json['Invoice range'] }}"
            },
            {
              "id": "f04c0bd7-b5f7-4a86-84b9-164eebf887a2",
              "name": "Year Folder id",
              "type": "string",
              "value": "={{ $json.id ||$('Create Current Year Folder').item.json.id }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "dd1191dc-d72d-400e-8ffd-32775b6927a8",
      "name": "If: File Already Exists?",
      "type": "n8n-nodes-base.if",
      "position": [
        -1504,
        976
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "768e3b62-b460-4845-abce-3e2058826a7a",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.id}}",
              "rightValue": "={{ $('Set: File Name from Start & End Based Date Range').item.json['Sheet Name 1'] }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "031af0bc-68ad-49c4-8902-54fb547d792d",
      "name": " If: Invoice Range is 15 Days?",
      "type": "n8n-nodes-base.if",
      "position": [
        -2912,
        640
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "d064b0bc-6c78-4f05-a8e4-4e6a6be858e5",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $('Set: Invoice Range').item.json[' Invoice range for debug'] }}",
              "rightValue": "=15 days"
            }
          ]
        }
      },
      "typeVersion": 2.2,
      "alwaysOutputData": false
    },
    {
      "id": "418a0a3c-4233-48b1-9e0f-31f1b0017fa7",
      "name": "Set: File Name from Start & End Based Date Range",
      "type": "n8n-nodes-base.set",
      "position": [
        -2608,
        928
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a628947e-a7e6-489d-978f-d7662dcb3e64",
              "name": "File Name (Start Date Based)",
              "type": "string",
              "value": "={{\n(() => {\n  const emp = $('Set Timesheet JSON Fields').item.json['Employee Name']?.replace(/\\s+/g,'') ?? 'Unknown';\n\n  // Clean up and normalise date string\n  let raw = $('Set Timesheet JSON Fields').item.json['Week Start Date']?.trim();\n  raw = raw.replace(/\\//g, '-'); // 08/25/2025 -> 08-25-2025\n\n  const [mm, dd, yyyy] = raw.split('-');\n  const startDate = new Date(`${yyyy}-${mm}-${dd}T12:00:00`);\n  const endDate = new Date(startDate.getTime() + 13 * 86400000);\n\n  const pad = n => n.toString().padStart(2, '0');\n  const format = d => `${pad(d.getMonth() + 1)}-${pad(d.getDate())}-${d.getFullYear()}`;\n\n  return `${emp}_${format(startDate)}_to_${format(endDate)}`;\n})()\n}}\n"
            },
            {
              "id": "d60af835-8f88-4fa5-9132-36e9052c52c7",
              "name": "File Name (End Date Based)",
              "type": "string",
              "value": "={{\n(() => {\n  // clean employee name\n  const emp = $('Set Timesheet JSON Fields').item.json['Employee Name']?.replace(/\\s+/g,'') ?? 'Unknown';\n\n  // get end date string and normalise it\n  let rawEnd = $('Set Timesheet JSON Fields').item.json['Week End Date']?.trim() || '';\n  rawEnd = rawEnd.replace(/\\//g,'-');\n\n  // split into parts\n  const [mmE, ddE, yyyyE] = rawEnd.split('-');\n\n  // build Date object for end date\n  const endDate = new Date(`${yyyyE}-${mmE}-${ddE}T12:00:00`);\n\n  // subtract 13 days to get start date\n  const startDate = new Date(endDate.getTime() - 13 * 86400000);\n\n  // helper to pad numbers\n  const pad = n => n.toString().padStart(2,'0');\n  const format = d => `${pad(d.getMonth()+1)}-${pad(d.getDate())}-${d.getFullYear()}`;\n\n  return `${emp}_${format(startDate)}_to_${format(endDate)}`;\n})()\n}}\n\n"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "4c347d9a-c68a-499b-983e-a87a493ba18d",
      "name": " Merge: Combine Folder Search Results",
      "type": "n8n-nodes-base.merge",
      "position": [
        -1712,
        976
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineAll"
      },
      "typeVersion": 3.1,
      "alwaysOutputData": true
    },
    {
      "id": "a2701092-4d1a-48f3-b3bc-ade5bdfa00bb",
      "name": "Create New Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -1984,
        272
      ],
      "parameters": {
        "title": "={{ $json[\"Sheet Name \"] }}",
        "options": {},
        "resource": "spreadsheet"
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "f21db8a7-1a61-4046-abd6-6312203a37fa",
      "name": "Move Created Sheet to Final Folder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -1520,
        272
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Create New Sheet').item.json.spreadsheetId }}"
        },
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive",
          "cachedResultUrl": "https://drive.google.com/drive/my-drive",
          "cachedResultName": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set: Invoice Range').item.json['Year Folder id']||  $('Create Current Year Folder').item.json.id }}"
        },
        "operation": "move"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "7d293b58-2a76-4731-8103-5da138f1393e",
      "name": " Set: Row Data",
      "type": "n8n-nodes-base.set",
      "position": [
        -1328,
        272
      ],
      "parameters": {
        "mode": "raw",
        "options": {
          "dotNotation": false
        },
        "jsonOutput": "{\n  \"Customer Account Number\": \"\",\n   \"Invoice Date\": \"\",\n  \"Due Date\": \"\",\n   \"PO Number\": \"\",\n  \"Item Column Title\":\"\",\n  \"Quantity Column\":\"\",\n  \"Item Name\":\"\",\n  \"Quantity\" :\"\",\n  \"Unit Price\":\"\",\n  \"Description\" :\"\"\n  \n  }\n"
      },
      "executeOnce": false,
      "typeVersion": 3.4
    },
    {
      "id": "24eb5310-0fc2-47ef-a07f-5d54c39a987e",
      "name": " Sheets: Append Row",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -1152,
        272
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "Customer Account Number",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Customer Account Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Invoice Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Invoice Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Due Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Due Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "PO Number",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "PO Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Item Column Title",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Item Column Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Quantity Column",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Quantity Column",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Item Name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Item Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Quantity",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Quantity",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Unit Price",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Unit Price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Decription",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Decription",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Create New Sheet').item.json.sheets[0].properties.sheetId }}"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Create New Sheet').item.json.spreadsheetId }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "executeOnce": false,
      "typeVersion": 4.5,
      "alwaysOutputData": false
    },
    {
      "id": "3eec4b39-7b40-4f50-bf13-0f7ac00ce342",
      "name": " Sheets: Final Append",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -784,
        272
      ],
      "parameters": {
        "columns": {
          "value": {
            "Due Date": "={{ $('Set Invoice Date & Due Date Days').item.json[\"Due Date\"] }}",
            "Quantity": "={{ $('Set Timesheet JSON Fields').item.json[\"Total Billable hours\"] }}",
            "Item Name": "={{ $('Get Customer Info From PO Sheet').item.json.Item }}",
            "PO Number": "={{ $('Get Customer Info From PO Sheet').item.json[\"PO Number\"] }}",
            "Description": "=Week Period from {{ $('Set Timesheet JSON Fields').item.json['Week Start Date'] }} to {{ $('Set Timesheet JSON Fields').item.json['Week End Date'] }}",
            "Invoice Date": "={{ $('Set Invoice Date & Due Date Days').item.json[\"Invoice Date\"] }}",
            "Quantity Column": "Hours",
            "Item Column Title": "Items",
            "Customer Account Number": "={{ $('Get Customer Info From PO Sheet').item.json[\"Customer Account Number\"] }}"
          },
          "schema": [
            {
              "id": "Customer Account Number",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Customer Account Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Invoice Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Invoice Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Due Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Due Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "PO Number",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "PO Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Item Column Title",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Item Column Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Quantity Column",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Quantity Column",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Item Name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Item Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Quantity",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Quantity",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Unit Price",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Unit Price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Description",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Description",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Create New Sheet').item.json.sheets[0].properties.sheetId }}"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Create New Sheet').item.json.spreadsheetId }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "58c09537-c8c7-4e42-9691-90e6731c5838",
      "name": " Sheets: Create Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -1264,
        1280
      ],
      "parameters": {
        "title": "={{ $('Set: File Name from Start & End Based Date Range').item.json['File Name (Start Date Based)'] }}",
        "options": {},
        "resource": "spreadsheet",
        "sheetsUi": {
          "sheetValues": [
            {
              "title": "=Sheet1"
            }
          ]
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "9570c6e8-5c5c-45e3-9760-2058ac45a9ee",
      "name": " Sheets: Append Row1",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -528,
        1280
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "Customer Account Number",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Customer Account Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Invoice Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Invoice Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Due Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Due Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "PO Number",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "PO Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Item Column Title",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Item Column Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Quantity Column",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Quantity Column",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Item Name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Item Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Quantity",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Quantity",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Unit Price",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Unit Price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Decription",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Decription",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $(' Sheets: Create Sheet').item.json.sheets[0].properties.sheetId }}"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $(' Sheets: Create Sheet').item.json.spreadsheetId }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "executeOnce": false,
      "typeVersion": 4.5,
      "alwaysOutputData": false
    },
    {
      "id": "b3073fc2-5281-4d75-8a58-647ffbfaa8dc",
      "name": "Extract Text from Attachment",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -6400,
        768
      ],
      "parameters": {
        "url": "https://universal-file-to-text-extractor.vercel.app/extract",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "name": "mode",
              "value": "single"
            },
            {
              "name": "output_type",
              "value": "text"
            },
            {
              "name": "files",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "={{ Object.keys($binary)[0] }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "340eb3c0-7e59-4da5-a753-a1c0111bba0c",
      "name": "Search: Client Invoices Folder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -5040,
        768
      ],
      "parameters": {
        "filter": {
          "driveId": {
            "mode": "list",
            "value": "My Drive"
          }
        },
        "options": {},
        "resource": "fileFolder",
        "returnAll": true
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3,
      "alwaysOutputData": true
    },
    {
      "id": "2a853b8e-941b-4def-ab26-9ab7dcb7ba95",
      "name": "Search: Client Folder by Name",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -4832,
        768
      ],
      "parameters": {
        "filter": {
          "driveId": {
            "mode": "list",
            "value": "My Drive"
          },
          "folderId": {
            "__rl": true,
            "mode": "id",
            "value": "={{ $json.id }}"
          },
          "whatToSearch": "folders"
        },
        "options": {},
        "resource": "fileFolder",
        "returnAll": true,
        "queryString": "={{ $('Set Timesheet JSON Fields').item.json['Client Name'] }}"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3,
      "alwaysOutputData": true
    },
    {
      "id": "8e38f87b-707e-4e36-aa10-edb7067f6364",
      "name": "Search: File By Start Date Name",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -2176,
        816
      ],
      "parameters": {
        "filter": {},
        "options": {},
        "resource": "fileFolder",
        "returnAll": true,
        "queryString": "={{ $json['File Name (Start Date Based)'] }}"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3,
      "alwaysOutputData": true
    },
    {
      "id": "20d3361a-0e06-4f7d-af7a-84dd56e50ed5",
      "name": "Search: File By End Date Name",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -2160,
        1040
      ],
      "parameters": {
        "filter": {},
        "options": {},
        "resource": "fileFolder",
        "returnAll": true,
        "queryString": "={{ $json['File Name (End Date Based)'] }}"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3,
      "alwaysOutputData": true
    },
    {
      "id": "c7b82a70-5d5b-4448-85ee-2e6f9f420296",
      "name": "Drive: Move Sheet To Final Folder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -912,
        1280
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.spreadsheetId }}"
        },
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set: Invoice Range').item.json['Year Folder id']||  $('Create Current Year Folder').item.json.id }}"
        },
        "operation": "move"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "62b6b38c-d93a-4b31-bbaf-a14b80ed8de1",
      "name": "Set: Empty Row Structure",
      "type": "n8n-nodes-base.set",
      "position": [
        -736,
        1280
      ],
      "parameters": {
        "mode": "raw",
        "options": {
          "dotNotation": false
        },
        "jsonOutput": "{\n  \"Customer Account Number\": \"\",\n   \"Invoice Date\": \"\",\n  \"Due Date\": \"\",\n   \"PO Number\": \"\",\n  \"Item Column Title\":\"\",\n  \"Quantity Column\":\"\",\n  \"Item Name\":\"\",\n  \"Quantity\" :\"\",\n  \"Unit Price\":\"\",\n  \"Description\" :\"\"\n  \n  }\n"
      },
      "executeOnce": false,
      "typeVersion": 3.4
    },
    {
      "id": "a17b04d9-705c-4ed9-acc3-d4b971ad790d",
      "name": "Set: Spreadsheet  (ID & Name)",
      "type": "n8n-nodes-base.set",
      "position": [
        -336,
        1280
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "89af4fa2-04e9-4e86-90cd-805437cb96c4",
              "name": "id",
              "type": "string",
              "value": "={{ $(' Sheets: Create Sheet').item.json.spreadsheetId }}"
            },
            {
              "id": "6625391b-df04-4775-976d-baeb91ff083c",
              "name": "name",
              "type": "string",
              "value": "={{ $(' Sheets: Create Sheet').item.json.properties.title }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "058a5baf-6f32-4822-ae06-d0c67f644ca6",
      "name": "Append: Final Row to Existing Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -32,
        1280
      ],
      "parameters": {
        "columns": {
          "value": {
            "Due Date": "={{ $json.name.split(\"_to_\")[1].trim().toDateTime('MM-dd-yyyy').plus($('Get Customer Info From PO Sheet').item.json['Due Date Calculation'],'days').format('MM-dd-yyyy').replaceAll('-', '/').trim() }}",
            "Quantity": "={{ $('Set Timesheet JSON Fields').item.json['Total Billable hours'].trim() }}",
            "Item Name": "={{ $('Get Customer Info From PO Sheet').item.json.Item }}",
            "PO Number": "={{ $('Get Customer Info From PO Sheet').item.json['PO Number'] }}",
            "Description": "=Week ending {{ $('Set Timesheet JSON Fields').item.json[\"Week End Date\"].trim() }}",
            "Invoice Date": "={{ $json.name.split(\"_to_\")[1].replaceAll('-', '/').trim() }}",
            "Quantity Column": "Hours",
            "Item Column Title": "Items",
            "Customer Account Number": "={{ $('Get Customer Info From PO Sheet').item.json['Customer Account Number'] }}"
          },
          "schema": [
            {
              "id": "Customer Account Number",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Customer Account Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Invoice Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Invoice Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Due Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Due Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "PO Number",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "PO Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Item Column Title",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Item Column Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Quantity Column",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Quantity Column",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Item Name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Item Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Quantity",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Quantity",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Unit Price",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Unit Price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Description",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Description",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Sheet1"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "d546cefb-f8cb-4385-81f7-02b08f627e13",
      "name": "Check if Year Folder Exists",
      "type": "n8n-nodes-base.if",
      "position": [
        -3664,
        656
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "daebe4a7-edcf-4902-a5d7-6c6b4ad995c8",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.id }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "a5141e72-4f6c-4334-aac7-1e836af18b3c",
      "name": "Create Client Name Folder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -4384,
        912
      ],
      "parameters": {
        "name": "={{ $('Set Timesheet JSON Fields').item.json['Client Name'] }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Search: Client Invoices Folder').item.json.id }}"
        },
        "resource": "folder"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "7384fb2c-b9a7-4054-a751-1b872d581848",
      "name": "Check Client Name Folder",
      "type": "n8n-nodes-base.if",
      "position": [
        -4592,
        768
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e4d2d3f6-4fff-473b-9dd6-27bb19e8ce67",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.id }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "64995379-b50c-4412-92fc-02101b06bd18",
      "name": "Search: Employee Name Folder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -4320,
        544
      ],
      "parameters": {
        "filter": {
          "driveId": {
            "mode": "list",
            "value": "My Drive"
          },
          "folderId": {
            "__rl": true,
            "mode": "id",
            "value": "={{ $json.id }}"
          },
          "whatToSearch": "folders"
        },
        "options": {},
        "resource": "fileFolder",
        "returnAll": true,
        "queryString": "={{ $('Get Customer Info From PO Sheet').item.json['Folder Name'] }}"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3,
      "alwaysOutputData": true
    },
    {
      "id": "43f1592f-8461-433d-bb8a-4533373328bb",
      "name": "Check Employee Name Folder",
      "type": "n8n-nodes-base.if",
      "position": [
        -4128,
        544
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "2582c0dd-ec40-421a-8246-e5ecb32c9d0f",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.id }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "18aedc4f-54af-4531-9bb5-0462fcba562c",
      "name": "Create Employee Name Folder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -4080,
        912
      ],
      "parameters": {
        "name": "={{ $('Get Customer Info From PO Sheet').item.json['Folder Name'] }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Check Client Name Folder').item.json.id || $('Create Client Name Folder').item.json.id }}"
        },
        "resource": "folder"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "d394c96e-5a7b-4fc1-864d-d3b661133347",
      "name": "Search: Year Folder ",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -3856,
        528
      ],
      "parameters": {
        "filter": {
          "folderId": {
            "__rl": true,
            "mode": "id",
            "value": "={{ $json.id }}"
          },
          "whatToSearch": "folders"
        },
        "options": {},
        "resource": "fileFolder",
        "queryString": "={{ $('Set Timesheet JSON Fields').item.json['Current Year'] }}"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3,
      "alwaysOutputData": true
    },
    {
      "id": "c3ae7f46-6e1a-4e04-a983-1c67840ef848",
      "name": "Create Year Folder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -3840,
        912
      ],
      "parameters": {
        "name": "={{ $('Set Timesheet JSON Fields').item.json['Current Year'] }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.id }}"
        },
        "resource": "folder"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "b4ac8818-d950-4f59-b320-40e6ae181170",
      "name": "Create Current Year Folder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -3472,
        928
      ],
      "parameters": {
        "name": "={{ $('Set Timesheet JSON Fields').item.json['Current Year'] }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Search: Employee Name Folder').item.json.id }}"
        },
        "resource": "folder"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "ac3907f4-1e21-44bc-8707-0196c641264c",
      "name": "Set Timesheet JSON Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        -5696,
        768
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "12c3738c-4b46-44fe-a482-efeb1beda924",
              "name": "=Employee Name",
              "type": "string",
              "value": "={{ JSON.parse($json[\"choices\"][0][\"message\"][\"content\"])[\"Employee Name\"].replace(/[\\n\\r]/g, '').trim() }}\n\n\n"
            },
            {
              "id": "b6bdd6c3-f57d-4695-9863-68e37b3bf10b",
              "name": "=Total Billable hours",
              "type": "string",
              "value": "={{ JSON.parse($json[\"choices\"][0][\"message\"][\"content\"])[\"Total Working Hours\"].replace(/[\\n\\r]/g, '').trim() }}\n\n"
            },
            {
              "id": "f826dc43-34ca-47bf-a823-9e21c0a46df8",
              "name": "Week Start Date",
              "type": "string",
              "value": "={{ JSON.parse($json[\"choices\"][0][\"message\"][\"content\"])[\"Week Starting Date\"].replaceAll('-', '/').trim()}}\n"
            },
            {
              "id": "faf34b88-5e61-4c15-97b6-7c933d69c279",
              "name": "Week End Date",
              "type": "string",
              "value": "={{ JSON.parse($json[\"choices\"][0][\"message\"][\"content\"])[\"Week Ending Date\"].replaceAll('-', '/').trim().replace(/[\\n\\r]/g, '')}}\n"
            },
            {
              "id": "95dd93b1-8e25-422f-b6fb-73bf65aa792e",
              "name": "Client Name",
              "type": "string",
              "value": "={{ JSON.parse($json[\"choices\"][0][\"message\"][\"content\"].replaceAll('\\n', '').replaceAll('\\r', ''))[\"Client Name\"].trim() }}\n"
            },
            {
              "id": "8c92f891-1393-4199-b9ba-3683750455f1",
              "name": "Current Year",
              "type": "string",
              "value": "={{ JSON.parse($json[\"choices\"][0][\"message\"][\"content\"])[\"Week Starting Date\"].split('/')[2] }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a9f7449f-e724-451f-8e6b-330104591223",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -6480,
        608
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 304,
        "content": "## OCR extraction\nSends each attachment to the OCR API and returns plain text. This turns PDFs and images into machine readable text before AI parsing.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "98a98ac1-856a-4f5d-97d2-645375e4874f",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2512,
        128
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 272,
        "content": "## Build invoice dates\nCalculates the invoice date and due date using the timesheet's week end date and PO due date rules. Outputs clean fields used to name and populate the invoice sheet.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "3b7abbc0-cd8e-4cd8-b34f-3fd2c2d052d5",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3568,
        864
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 224,
        "content": "## create new year folder"
      },
      "typeVersion": 1
    },
    {
      "id": "ec7a2360-1081-4527-9456-a0068c0f56a2",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2016,
        128
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 256,
        "content": "## Create invoice sheet\nGenerates the sheet name, creates a new Google Sheet, updates its timezone, and moves it into the correct year folder based on the employee and date range.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "db62fec3-3831-478c-be6d-0ea4b132ad45",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1200,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 256,
        "content": "## Write invoice row\nInitializes the row structure, maps customer and timesheet fields, and appends the invoice row into the newly created sheet to complete the entry.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "19828bb4-04c0-4b73-a56e-0befdab104f7",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1760,
        816
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 336,
        "content": "## Decide reuse or create sheet\nChecks the merged search results. If a matching sheet exists, route to append a new invoice row. If nothing is found, route to create a fresh sheet for this employee and date range.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "4030985b-8b57-40ae-a6a1-864d4def5ace",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -7152,
        608
      ],
      "parameters": {
        "color": 7,
        "width": 624,
        "height": 320,
        "content": "## Email intake and loop\nListens to Gmail for unread timesheet emails, splits all attachments, and processes each one in a loop so multiple timesheets in the same email are handled independently.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "b71ffe42-26c6-42b5-850a-835de9182923",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -8240,
        64
      ],
      "parameters": {
        "width": 976,
        "height": 992,
        "content": "Timesheet to Invoice Automation\n\n## How it works\nThe workflow automates converting emailed timesheets into organized invoice sheets.\n\n**1. Email intake**\nPulls unread Gmail messages with timesheet attachments. Each file is separated and processed individually.\n\n**2. Text extraction**\nAttachments are sent to an OCR API to convert PDFs or images into clean text.\n\n**3. AI parsing**\nOpenAI extracts key fields:\n- Employee name  \n- Client name  \n- Week start and end dates  \n- Total billable hours  \n- Timesheet year\n\n**4. Customer and PO lookup**\nYour Customer POs Google Sheet provides:\n- Account and PO numbers  \n- Item name  \n- Folder naming rules  \n- Invoice range  \n- Due date offset\n\n**5. Drive folder logic**\nThe workflow locates or creates:\n- Client folder  \n- Employee folder  \n- Year folder  \ninside the 01-ClientInvoices directory.\n\n**6. Sheet handling**\nBuilds a file name from employee and dates, checks if a sheet already exists, then:\n- Appends a row to an existing sheet, or  \n- Creates a new sheet, sets timezone, writes the invoice row, and moves it to the correct folder.\n\n## Setup steps\nConnect Gmail, Drive, Sheets, OpenAI, and OCR.  \nUpdate PO Sheet ID and column names.  \nConfirm Gmail filter.  \nEnsure the 01-ClientInvoices folder exists.  \nTest with one sample email.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "fbf95800-fcc9-4776-8141-68dd2c886f0f",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5536,
        608
      ],
      "parameters": {
        "color": 7,
        "width": 352,
        "height": 320,
        "content": "## Customer and PO lookup\nLooks up the sender in the Customer POs sheet and pulls account number, PO number, item name, folder name, invoice range, and due date offset that drive the invoice logic.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "6c262c74-70ca-403d-9887-73fd8630c5bc",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -6144,
        608
      ],
      "parameters": {
        "color": 7,
        "width": 352,
        "height": 304,
        "content": "## AI timesheet parsing\nOpenAI reads the timesheet text and extracts employee name, client name, week start and end dates, total hours, and current year into clean JSON fields for later use.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "d1dd448a-edfa-4a62-ad33-8a3e4fdbf605",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5104,
        624
      ],
      "parameters": {
        "color": 7,
        "width": 464,
        "height": 304,
        "content": "## Client folder discovery\nSearch Client Invoices folder, finds or creates the client level folder based on the timesheet client name and PO configuration.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "1eb23006-723d-4afb-8be2-85702d63c6e7",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4384,
        432
      ],
      "parameters": {
        "color": 7,
        "width": 704,
        "height": 256,
        "content": "## ## File naming and search\n searches Drive for existing sheets with the employee and if it exist get that year's folder"
      },
      "typeVersion": 1
    },
    {
      "id": "feec06a8-cd41-4a32-88cd-87738b916622",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4416,
        816
      ],
      "parameters": {
        "color": 7,
        "width": 704,
        "height": 224,
        "content": "## Create new \ncreate new client name, employee and year named folder\n"
      },
      "typeVersion": 1
    },
    {
      "id": "5f10a262-9c27-4002-b637-31098a8b73da",
      "name": "HTTP Request(new sheet)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1744,
        272
      ],
      "parameters": {
        "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{$json.spreadsheetId}}:batchUpdate\n",
        "method": "POST",
        "options": {},
        "jsonBody": "{\n  \"requests\": [\n    {\n      \"updateSpreadsheetProperties\": {\n        \"properties\": {\n          \"timeZone\": \"America/New_York\"\n        },\n        \"fields\": \"timeZone\"\n      }\n    }\n  ]\n}\n",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "googleSheetsOAuth2Api"
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "7ed7ba40-2732-48f3-87eb-5374d850e38e",
      "name": "Sticky Note14",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2624,
        704
      ],
      "parameters": {
        "color": 7,
        "width": 784,
        "height": 544,
        "content": "## Build file names and search\nGenerates start based and end based file names from employee and week dates, then searches Google Drive for existing spreadsheets that match either name. Both search results are merged to see if this invoice period is already stored.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "5ce8ea5e-3b9b-4800-8b6a-f43d3f1d03b3",
      "name": "Sticky Note15",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1328,
        1136
      ],
      "parameters": {
        "color": 7,
        "width": 528,
        "height": 288,
        "content": "## Create invoice sheet\nGenerates the sheet name, creates a new Google Sheet, updates its timezone, and moves it into the correct year folder based on the employee and date range.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "9c95568f-4f66-428a-a4e4-deae2201b462",
      "name": "HTTP Request1(create sheet)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1088,
        1280
      ],
      "parameters": {
        "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{$json.spreadsheetId}}:batchUpdate\n",
        "method": "POST",
        "options": {},
        "jsonBody": "{\n  \"requests\": [\n    {\n      \"updateSpreadsheetProperties\": {\n        \"properties\": {\n          \"timeZone\": \"America/New_York\"\n        },\n        \"fields\": \"timeZone\"\n      }\n    }\n  ]\n}\n",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "googleSheetsOAuth2Api"
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "a04d99d7-e658-4738-9d3b-99b921d26689",
      "name": "Sticky Note16",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -560,
        1120
      ],
      "parameters": {
        "color": 7,
        "width": 752,
        "height": 288,
        "content": "## Write invoice row\n append invoice rows\nFor new sheets, creates the sheet, sets timezone, moves it to the correct year folder, prepares an empty row structure, and writes the first invoice row. For existing sheets, appends a new invoice line using the same column structure.\n\n\n\n\n\n\n\n"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Gmail Trigger": {
      "main": [
        [
          {
            "node": "Split Binary Attachments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    " Set: Row Data": {
      "main": [
        [
          {
            "node": " Sheets: Append Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create New Sheet": {
      "main": [
        [
          {
            "node": "HTTP Request(new sheet)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Year Folder": {
      "main": [
        [
          {
            "node": "Check if Year Folder Exists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set: Invoice Range": {
      "main": [
        [
          {
            "node": " If: Invoice Range is 15 Days?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    " Sheets: Append Row": {
      "main": [
        [
          {
            "node": " Sheets: Final Append",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    " Sheets: Append Row1": {
      "main": [
        [
          {
            "node": "Set: Spreadsheet  (ID & Name)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search: Year Folder ": {
      "main": [
        [
          {
            "node": "Check if Year Folder Exists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    " Sheets: Create Sheet": {
      "main": [
        [
          {
            "node": "HTTP Request1(create sheet)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    " Sheets: Final Append": {
      "main": [
        [
          {
            "node": "Loop: Process Each Attachment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request(new sheet)": {
      "main": [
        [
          {
            "node": "Move Created Sheet to Final Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Client Name Folder": {
      "main": [
        [
          {
            "node": "Search: Employee Name Folder",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Client Name Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If: File Already Exists?": {
      "main": [
        [
          {
            "node": "Append: Final Row to Existing Sheet",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": " Sheets: Create Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set: Empty Row Structure": {
      "main": [
        [
          {
            "node": " Sheets: Append Row1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Binary Attachments": {
      "main": [
        [
          {
            "node": "Loop: Process Each Attachment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Client Name Folder": {
      "main": [
        [
          {
            "node": "Create Employee Name Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Timesheet JSON Fields": {
      "main": [
        [
          {
            "node": "Get Customer Info From PO Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Employee Name Folder": {
      "main": [
        [
          {
            "node": "Search: Year Folder ",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Employee Name Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Current Year Folder": {
      "main": [
        [
          {
            "node": "Set: Invoice Range",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if Year Folder Exists": {
      "main": [
        [
          {
            "node": "Set: Invoice Range",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Current Year Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Employee Name Folder": {
      "main": [
        [
          {
            "node": "Create Year Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request1(create sheet)": {
      "main": [
        [
          {
            "node": "Drive: Move Sheet To Final Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Text from Attachment": {
      "main": [
        [
          {
            "node": "Extract Timesheet Data (OpenAI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search: Employee Name Folder": {
      "main": [
        [
          {
            "node": "Check Employee Name Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop: Process Each Attachment": {
      "main": [
        [],
        [
          {
            "node": "Extract Text from Attachment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search: Client Folder by Name": {
      "main": [
        [
          {
            "node": "Check Client Name Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search: File By End Date Name": {
      "main": [
        [
          {
            "node": " Merge: Combine Folder Search Results",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Set: Spreadsheet  (ID & Name)": {
      "main": [
        [
          {
            "node": "Append: Final Row to Existing Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    " If: Invoice Range is 15 Days?": {
      "main": [
        [
          {
            "node": "Set Invoice Date & Due Date Days",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set: File Name from Start & End Based Date Range",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search: Client Invoices Folder": {
      "main": [
        [
          {
            "node": "Search: Client Folder by Name",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Timesheet Data (OpenAI)": {
      "main": [
        [
          {
            "node": "Set Timesheet JSON Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Customer Info From PO Sheet": {
      "main": [
        [
          {
            "node": "Search: Client Invoices Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search: File By Start Date Name": {
      "main": [
        [
          {
            "node": " Merge: Combine Folder Search Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Invoice Date & Due Date Days": {
      "main": [
        [
          {
            "node": "Generate Sheet Name (Employee + Week Start to End Dates)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Drive: Move Sheet To Final Folder": {
      "main": [
        [
          {
            "node": "Set: Empty Row Structure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Move Created Sheet to Final Folder": {
      "main": [
        [
          {
            "node": " Set: Row Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append: Final Row to Existing Sheet": {
      "main": [
        [
          {
            "node": "Loop: Process Each Attachment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    " Merge: Combine Folder Search Results": {
      "main": [
        [
          {
            "node": "If: File Already Exists?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set: File Name from Start & End Based Date Range": {
      "main": [
        [
          {
            "node": "Search: File By Start Date Name",
            "type": "main",
            "index": 0
          },
          {
            "node": "Search: File By End Date Name",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Sheet Name (Employee + Week Start to End Dates)": {
      "main": [
        [
          {
            "node": "Create New Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}