{
  "id": "D6lfkWs2oDNbZlB7",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Task\u2011Based Billing via Time Tracking",
  "tags": [],
  "nodes": [
    {
      "id": "cf6f064d-c4a5-4ac3-81fb-b4fe8cbf110e",
      "name": "List tasks",
      "type": "n8n-nodes-dart.dart",
      "position": [
        336,
        0
      ],
      "parameters": {
        "resource": "Task",
        "operation": "List Tasks",
        "requestOptions": {},
        "additionalFields": {
          "dartboard_id": "yccHfZwasnhc"
        }
      },
      "credentials": {
        "dartApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a9a2c117-2981-4dc8-93d4-f7d811901503",
      "name": "Get row(s) in sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        720,
        0
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/13mDa4dCMkyq-rQPMCU3k883ZvHiq63RTlj4PzuuAfi0/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "13mDa4dCMkyq-rQPMCU3k883ZvHiq63RTlj4PzuuAfi0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/13mDa4dCMkyq-rQPMCU3k883ZvHiq63RTlj4PzuuAfi0/edit?usp=drivesdk",
          "cachedResultName": "Gsheets Timetracking"
        },
        "combineFilters": "OR"
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "c9112e57-9c89-4bb8-98c9-3bfffa550381",
      "name": "Aggregate",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        912,
        0
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "f6c98556-1d4e-414c-8281-c30009972a19",
      "name": "AI SCANNER",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1296,
        64
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-flash-latest"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a6b7a2e9-ed77-48df-810c-715fce778944",
      "name": "Assignee & Time Scanner",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1328,
        -96
      ],
      "parameters": {
        "text": "=You are an expert Payroll Calculator. Your goal is to calculate the total hours and final payout for employees based on a provided Rate Card and Task Log.\n\n### INSTRUCTIONS:\n\n1. **Analyze Input A (Rate Card):** Identify the authorized assignees and their specific Hourly Rate.\n\n2. **Analyze Input B (Task Logs):**\n   - Find all tasks assigned to the people listed in Input A (use fuzzy matching for names/emails).\n   - **Filter by Status:** Only include tasks where the \"Status\" is exactly \"Done\". Ignore any tasks with other statuses (e.g., \"In Progress\", \"Todo\").\n   - Extract the duration of each valid task.\n\n3. **Perform Calculations:**\n   - **Sum Duration:** Add up the time spent on the filtered \"Done\" tasks for each person. Convert the final sum into **HOURS** (e.g., if the log says \"90 minutes\", convert it to \"1.5\").\n   - **Calculate Pay:** Multiply the `TotalHours` by the person's `HourlyRate`.\n\n4. **Output Format:**\n   - Return a strictly valid JSON list.\n   - Do NOT list the individual tasks. Only output the aggregates.\n   - **Date Field:** Use the exact date provided in \"INPUT C\" below for the `DateCalculated` field.\n\n### INPUT A: Google Spreadsheet (Rate Card)\n\n\"\"\"\n{{ JSON.stringify($json.data) }}\n\"\"\"\n\n### INPUT B: List of Tasks (Dart Logs)\n\n\"\"\"\n{{ JSON.stringify($('List tasks').item.json.results) }}\n\"\"\"\n\n### INPUT C: Current Date (Use this exact date)\n\n\"\"\"\n{{ $now.toFormat('yyyy-MM-dd') }}\n\"\"\"\n\n### REQUIRED OUTPUT FORMAT (JSON ONLY):\n\n[\n  {\n    \"Name\": \"Employee Name\",\n    \"TotalHours\": 20.5,\n    \"TotalPay\": 10250,\n    \"DateCalculated\": \"2025-09-11\"\n  }\n]",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "a322cf36-724b-4c55-a4d7-f011dc93b551",
      "name": "Parse Output",
      "type": "n8n-nodes-base.code",
      "position": [
        1856,
        -64
      ],
      "parameters": {
        "jsCode": "// Get raw input\nlet rawString = $input.first().json.output;\n\n// Clean up markdown formatting if present\nrawString = rawString\n  .trim()\n  .replace(/^```(json)?/i, '')  // remove leading ``` or ```json\n  .replace(/```$/i, '')         // remove trailing ```\n  .trim();\n\n// Try parsing the cleaned string\nlet parsed;\ntry {\n  parsed = JSON.parse(rawString);\n} catch (error) {\n  throw new Error(`Failed to parse JSON. Cleaned input was:\\n${rawString}\\n\\nError: ${error.message}`);\n}\n\nreturn parsed;"
      },
      "typeVersion": 2
    },
    {
      "id": "3f4a509f-1763-4498-a629-e8365df50a33",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -560,
        -336
      ],
      "parameters": {
        "width": 544,
        "height": 608,
        "content": "## Task-YOUR_OPENAI_KEY_HERE Assignee billing via Time Tracking\nThis workflow automates billing by scanning a target Dartboard on schedule, aggregating time logs from completed tasks, cross\u2011referencing assignee rates in Google Sheets, calculating total pay, and updating the sheet with final billable hours and amounts.\n\n### Who's it for\nIndividuals, agencies, companies, and project managers automating payroll or client invoicing from task data.\n\n### How to setup\n- Link your [Dart](https://help.dartai.com/en/articles/12313191-n8n-integration) and Google accounts.\n- Replace the dummy ID in the **List tasks** node with your actual target Dartboard ID.\n- Set your preferred run frequency (e.g., Weekly).\n- Create a Google Sheet with these exact headers: `Name`, `HourlyRate`, `TotalHours`, `TotalPay`, `DateCalculated`.\n- Connect the Sheet nodes to your file. Pre-fill `Name` (matching Dart Assignees exactly) and `HourlyRate` in your Google Spreadsheet.\n- Optional: Add a last header column in the sheet as a Status header to track if the bill is paid or pending.\n\n### Customizing the workflow\n- Choose your AI model for your AI time tracking and assignee scanner\n- Use your own google sheet account and target spreadsheet document\n\n\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "75d9a88e-2c2a-4674-aa7f-f7642103101f",
      "name": "Append row in sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2304,
        16
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "HourlyRate",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "HourlyRate",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "TotalHours",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "TotalHours",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "TotalPay",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "TotalPay",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Currency",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Currency",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/13mDa4dCMkyq-rQPMCU3k883ZvHiq63RTlj4PzuuAfi0/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "13mDa4dCMkyq-rQPMCU3k883ZvHiq63RTlj4PzuuAfi0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/13mDa4dCMkyq-rQPMCU3k883ZvHiq63RTlj4PzuuAfi0/edit?usp=drivesdk",
          "cachedResultName": "Gsheets Timetracking"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "5667f473-6178-428c-a7f4-be807d1c3c6d",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        128,
        0
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtHour": 7
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "35165310-b834-448b-bbaa-1146e8a71a2b",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        64,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 432,
        "content": "## 1. Workflow launch through schedule\nTriggered by a preferred schedule (e.g. weekly, monthly, etc...)\n\n### Scanning of tasks\nThis step will scan and list all tasks in the target Dartboard. \n- Main goal is for the AI agent in this workflow to be able to identify tasks that are done and has a time tracking log."
      },
      "typeVersion": 1
    },
    {
      "id": "38bfedda-9295-4b83-b882-2562720f3c58",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 432,
        "content": "## 2. Gathering of data in the target Spreadsheet\nThis step scans all data in the spreadsheet. Goal is for the AI agent to able to scan for the pre-set column headers, assignee name, and hourly rate."
      },
      "typeVersion": 1
    },
    {
      "id": "1c5cf871-4d3a-4458-8551-8bf4a25a2c9b",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1216,
        -272
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 480,
        "content": "## 3. AI time tracking calculator agent\nThis agent calculates employee payroll by cross-referencing the connected spreadsheet with Dart task logs. It filters specifically for tasks marked \"Done\", sums the total hours, and calculates the final payout based on the pre-set hourly rates.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "c33ea72c-8b49-4cf4-8d07-c967704eea0f",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1760,
        -208
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 336,
        "content": "## 4. Parsing the AI output\nParse output into a usable format for the next node.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "a1c2c8ae-ebe5-4092-8b03-9cd137cefc68",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2160,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 480,
        "content": "## 5. Push output into the target Spreadsheet\nThis node will push the output data into the connected google spreadsheet.\n\n### Output each Assignee\n- Total time tracked\n- Total pay that is multiplied with the pre-set hourly rate\n- Date of calculation"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "b2d7e392-3cf5-4d18-910c-497dc5e3e2f1",
  "connections": {
    "Aggregate": {
      "main": [
        [
          {
            "node": "Assignee & Time Scanner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI SCANNER": {
      "ai_languageModel": [
        [
          {
            "node": "Assignee & Time Scanner",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "List tasks": {
      "main": [
        [
          {
            "node": "Get row(s) in sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Output": {
      "main": [
        [
          {
            "node": "Append row in sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "List tasks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get row(s) in sheet": {
      "main": [
        [
          {
            "node": "Aggregate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assignee & Time Scanner": {
      "main": [
        [
          {
            "node": "Parse Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}