{
  "id": "qsqmbdaDKHfpuVGd",
  "name": "Weekly Timesheet Report + Pending Submissions",
  "tags": [],
  "nodes": [
    {
      "id": "676e445e-8fe6-432d-9371-0e06c3a115d2",
      "name": "Timesheet",
      "type": "n8n-nodes-base.salesforce",
      "position": [
        -752,
        352
      ],
      "parameters": {
        "options": {
          "fields": [
            "Name",
            "dbt__Employee__c",
            "dbt__Start_Date__c",
            "dbt__End_Date__c",
            "dbt__Status__c",
            "Id",
            "dbt__Absence_Hours__c",
            "dbt__Billable_Hours__c",
            "dbt__Non_Billable_Hours__c",
            "dbt__Met_Weekly_Hours__c",
            "dbt__Total_Hours__c"
          ],
          "conditionsUi": {
            "conditionValues": [
              {
                "field": "Name",
                "value": "={{    \"Timesheet for \" +   DateTime.now().minus({ weeks: 1 }).startOf('week').toISODate() +   \" to \" +   DateTime.now().minus({ weeks: 1 }).endOf('week').toISODate() }}"
              }
            ]
          }
        },
        "resource": "customObject",
        "operation": "getAll",
        "returnAll": true,
        "customObject": "dbt__Timesheet__c"
      },
      "credentials": {
        "salesforceOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "06b42733-6505-4874-998a-df63693fe09f",
      "name": "Send a message",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1552,
        544
      ],
      "parameters": {
        "sendTo": "={{ $json.managerEmail }}",
        "message": "={{ $json.emailBody }}",
        "options": {},
        "subject": "={{ $json.emailSubject }}",
        "emailType": "text"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "041ae955-a28b-4c5e-8c8b-c46f07588396",
      "name": "Salesforce - Get Employee Details",
      "type": "n8n-nodes-base.salesforce",
      "position": [
        -256,
        416
      ],
      "parameters": {
        "recordId": "={{ $json[\"dbt__Employee__c\"] }}",
        "resource": "customObject",
        "operation": "get",
        "customObject": "dbt__Employee__c"
      },
      "credentials": {
        "salesforceOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "6547e5bb-b552-4643-848a-29631cc54618",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -240,
        -80
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "28ede689-c8e9-46e5-acaa-61028ea0872d",
      "name": "OpenAI1",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        432,
        -64
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1",
          "cachedResultName": "GPT-4.1"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=Analyze the timesheet activities in the HTML below and provide the JSON response as instructed.\n\nEmployee ID:{{ $json.employeeId }}\n\nHTML:\n{{ $json[\"html\"] }}\n"
            },
            {
              "role": "system",
              "content": "=You are an AI assistant that extracts and summarizes timesheet line items.\n\nReturn a valid JSON object only:\n{\n  \"employeeId\": \"{{ $json.employeeId }}\",\n  \"summary\": [\n    \"Maximum 4 short points\",\n    \"Only project activities and tasks\",\n    \"Include clear positive and negative notes\",\n    \"No hours, no dates, no billable mention, no meetings-only work\"\n  ]\n}\n\nRules:\n- JSON ONLY\n- No extra text\n- No markdown\n- No bullet symbols like - or *\n- Keep each summary point short and simple\n- Prefer work done, avoid lengthy explanations\n"
            }
          ]
        },
        "jsonOutput": "={{ true }}"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "74b7ce82-ae9e-462e-b171-5b765408022d",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        64,
        368
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "advanced": true,
        "mergeByFields": {
          "values": [
            {
              "field1": "dbt__Employee__c",
              "field2": "Id"
            }
          ]
        }
      },
      "typeVersion": 3.2,
      "alwaysOutputData": false
    },
    {
      "id": "82f0c792-da55-4da6-b6d6-55234ea3188a",
      "name": "Salesforce - Get Employee Details1",
      "type": "n8n-nodes-base.salesforce",
      "position": [
        0,
        800
      ],
      "parameters": {
        "recordId": "={{ $json[\"dbt__Employee__c\"] }}",
        "resource": "customObject",
        "operation": "get",
        "customObject": "dbt__Employee__c"
      },
      "credentials": {
        "salesforceOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "908909ee-1431-4554-8c4e-2715be9bf473",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -960,
        352
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                5
              ]
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "a3fdc924-5c85-4f34-bb23-201c2c29d0fb",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1552,
        80
      ],
      "parameters": {
        "width": 464,
        "height": 656,
        "content": "# Weekly Timesheet Automation \u2014 Overview \n\n## How it works\nThis workflow runs every week to fetch employee timesheet status and work details from Salesforce. It identifies who has submitted and who has not, collects all timesheet line items for submitted users, and uses AI to generate a clear summary of their work and hours. It prepares two sections \u2014 one for employees who submitted with detailed breakdown, and one for missing submissions with employee contact details. Both sections are structured into a single professional summary email and automatically delivered to the manager. This automation helps HR and management review productivity and compliance without any manual effort.\n\n## Setup steps\n1. Install the Timesheet App from Salesforce AppExchange and start entering timesheet. [https://appexchange.salesforce.com/appxListingDetail?listingId=a077704c-2e99-4653-8bde-d32e1fafd8c6](https://appexchange.salesforce.com/appxListingDetail?listingId=a077704c-2e99-4653-8bde-d32e1fafd8c6)\n2. Configure Salesforce OAuth in n8n\n3. Configure Gmail OAuth for sending weekly reports\n4. Set the OpenAI model for timesheet summarization\n5. Update the manager recipient list\n6. Activate the weekly schedule\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "9c61b1df-4d7e-4351-9c93-e5c683c2019f",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -512,
        -176
      ],
      "parameters": {
        "color": 6,
        "width": 1360,
        "height": 352,
        "content": "## Submitted Timesheet Processing & AI Summary\nThis section processes only submitted timesheets. It collects detailed project line items, converts them into a readable structure, and uses AI to generate a short, meaningful summary of the employee\u2019s weekly work."
      },
      "typeVersion": 1
    },
    {
      "id": "0126bcd5-a7cd-457f-8e3e-a5721f3b56b2",
      "name": "Get Timesheet Line Items",
      "type": "n8n-nodes-base.salesforce",
      "position": [
        -16,
        -64
      ],
      "parameters": {
        "options": {
          "fields": [
            "dbt__Date__c",
            "dbt__Type__c",
            "dbt__Activity__c",
            "dbt__Duration__c",
            "dbt__Description__c",
            "dbt__Project__c",
            "dbt__Employee__c",
            "Id",
            "dbt__Timesheet__c"
          ],
          "conditionsUi": {
            "conditionValues": [
              {
                "field": "dbt__Timesheet__c",
                "value": "={{$json[\"Id\"]}}"
              }
            ]
          }
        },
        "resource": "customObject",
        "operation": "getAll",
        "returnAll": true,
        "customObject": "dbt__Timesheet_Line_Item__c"
      },
      "credentials": {
        "salesforceOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "d9f0a4a3-f460-45d2-ac6e-0a64b9f86fb0",
      "name": "Generate HTML Table",
      "type": "n8n-nodes-base.code",
      "position": [
        208,
        -64
      ],
      "parameters": {
        "jsCode": "// Input: Array of Salesforce Line Items\nconst items = $input.all();\n\nlet html = `\n<h2>Timesheet Line Items Summary</h2>\n<table border=\"1\" cellspacing=\"0\" cellpadding=\"5\" style=\"border-collapse: collapse; width: 100%;\">\n<tr>\n  <th>Activity</th>\n  <th>Type</th>\n  <th>Billable</th>\n  <th>Billable Amount</th>\n  <th>Date</th>\n  <th>Duration (hrs)</th>\n  <th>Description</th>\n</tr>\n`;\n\nfor (const item of items) {\n  const row = item.json;\n  html += `\n  <tr>\n    <td>${row.dbt__Activity__c || ''}</td>\n    <td>${row.dbt__Type__c || ''}</td>\n    <td>${row.dbt__Billable__c || ''}</td>\n    <td>${row.dbt__Billable_Amount__c || ''}</td>\n    <td>${row.dbt__Date__c || ''}</td>\n    <td>${row.dbt__Duration__c || ''}</td>\n    <td>${row.dbt__Description__c || ''}</td>\n  </tr>\n  `;\n}\n\nhtml += `</table>`;\n\nreturn [\n  {\n    json: {\n      employeeId:$input.first().json.dbt__Employee__c, // <-- employee Id\n      html\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "18613e5b-dbda-4c3f-8aa9-3cf4c8c63bf7",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -336,
        272
      ],
      "parameters": {
        "color": 6,
        "width": 1056,
        "height": 304,
        "content": "## Employee Data & Submitted Summary Preparation\nThis section attaches employee profile details to each submitted timesheet, links the AI-generated summary to the correct employee, and prepares the full submitted employees report section."
      },
      "typeVersion": 1
    },
    {
      "id": "59314e3b-5b44-49ab-bcbc-b3fe0c012ef7",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -480,
        672
      ],
      "parameters": {
        "color": 6,
        "width": 1328,
        "height": 304,
        "content": "## Pending Timesheet Tracking\nThis section identifies employees who have not submitted their timesheet. It fetches their contact details and prepares a clean list of pending submissions for manager visibility."
      },
      "typeVersion": 1
    },
    {
      "id": "d99d6315-d6ab-4366-9f5c-210a3c0a572d",
      "name": "Sticky Note15",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1024,
        400
      ],
      "parameters": {
        "color": 6,
        "width": 704,
        "height": 304,
        "content": "## Final Report Creation & Manager Notification\nThis section merges both submitted and pending timesheet sections into one final weekly report and automatically sends the combined email to managers."
      },
      "typeVersion": 1
    },
    {
      "id": "ccce948b-94b8-42b7-abc1-0dc0dccd7aee",
      "name": "Merge AI Summary",
      "type": "n8n-nodes-base.merge",
      "position": [
        352,
        352
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "advanced": true,
        "mergeByFields": {
          "values": [
            {
              "field1": "message.content.employeeId",
              "field2": "dbt__Employee__c"
            }
          ]
        }
      },
      "typeVersion": 3.2,
      "alwaysOutputData": false
    },
    {
      "id": "27ce803b-ab38-454e-94cd-58a6b1c580df",
      "name": "Prepare Submitted Section",
      "type": "n8n-nodes-base.code",
      "position": [
        576,
        352
      ],
      "parameters": {
        "jsCode": "let body = \"Hi, \\n\\nPlease find the weekly timesheet summary for all employees:\\n\\n\";\n\nitems.forEach(item => {\n  const t = item.json;\n\n  body += `\ud83d\udc64 Employee: ${t.Name}\\n`;\n  body += `\ud83d\udcc5 Period: ${t.dbt__Start_Date__c} \u2192 ${t.dbt__End_Date__c}\\n`;\n  body += `\ud83d\udccc Status: ${t.dbt__Status__c}\\n`;\n  body += `\ud83d\udd52 Total Hours: ${t.dbt__Total_Hours__c} hrs\\n`;\n  body += ` - Billable: ${t.dbt__Billable_Hours__c} hrs\\n`;\n  body += ` - Non-Billable: ${t.dbt__Non_Billable_Hours__c} hrs\\n`;\n  body += ` - Absence: ${t.dbt__Absence_Hours__c} hrs\\n`;\n  body += `Weekly Requirement Met: ${t.dbt__Met_Weekly_Hours__c ? \"\u2714\ufe0f Yes\" : \"\u274c No\"}\\n\\n`;\n  \n  const summary = t.message?.content?.summary || [];\n\n  if (summary.length > 0) {\n    body += \"\ud83d\udcc2 Timesheet Line Items Breakdown:\\n\";\n    summary.forEach(line => {\n      body += ` - ${line}\\n`;\n    });\n  } else {\n    body += \"\ud83d\udcc2 Timesheet Line Items Breakdown: No project details available\\n\";\n  }\n\n  body += `\\n-------------------------------------------\\n\\n`;\n});\n\nreturn [{\n  json: {\n    emailSubject: \"\ud83d\udcca Weekly Timesheet Summary \u2013 All Employees\",\n    emailBody: body,\n    managerEmail: [\n      \"user@example.com\",\n      \"user@example.com\",\n      \"user@example.com\"\n    ].join(\",\") // \ud83d\udc48 convert array to string\n  }\n}];\n\n"
      },
      "typeVersion": 2
    },
    {
      "id": "335825d9-4842-49c5-a5e4-3ee1e56de579",
      "name": "Merge Pending Info",
      "type": "n8n-nodes-base.merge",
      "position": [
        352,
        784
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "advanced": true,
        "mergeByFields": {
          "values": [
            {
              "field1": "dbt__Employee__c",
              "field2": "Id"
            }
          ]
        }
      },
      "typeVersion": 3.2,
      "alwaysOutputData": false
    },
    {
      "id": "e30a18f0-812e-4025-915b-0a27f649b07f",
      "name": "Merge Submitted + Pending",
      "type": "n8n-nodes-base.merge",
      "position": [
        1136,
        544
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "69f58122-d857-49dd-a977-43200c3264ba",
      "name": "Prepare Pending Section",
      "type": "n8n-nodes-base.code",
      "position": [
        576,
        784
      ],
      "parameters": {
        "jsCode": "// Get employees from input\nconst items = $input.all();\n\n// Get week period from the first item\nconst first = items[0].json;\nconst startDate = first.dbt__Start_Date__c;\nconst endDate = first.dbt__End_Date__c;\n\nlet body =`The following employees did not submit their timesheet for the previous week :\\n`;\nbody += `\ud83d\uddd3\ufe0f ${startDate} \u2192 ${endDate}\\n\\n`;\n\nitems.forEach((employee, index) => {\n  const e = employee.json;\n  \n  body += `${index + 1}. ${e.Name} \\n`;\n  body += `\ud83d\udce7 Email: ${e.dbt__Email__c}\\n\\n`;\n});\n\n\nreturn [{\n  json: {\n    emailSubject: \"\u26a0\ufe0f Timesheet Not Submitted \u2013 Previous Week\",\n    emailBody: body,\n    managerEmail: [\n      \"user@example.com\",\n      \"user@example.com\",\n      \"user@example.com\"\n    ].join(\",\") // \ud83d\udc48 convert array to string\n  }\n}];\n\n"
      },
      "typeVersion": 2
    },
    {
      "id": "4fb96899-e8df-4773-a5d3-72123034166b",
      "name": "Create Final Email",
      "type": "n8n-nodes-base.code",
      "position": [
        1328,
        544
      ],
      "parameters": {
        "jsCode": "// items[0] => Code1 output (All employees summary)\n// items[1] => Code2 output (Not submitted list)\n\nconst allEmployees = items.find(i => i.json.emailSubject.includes(\"Weekly Timesheet Summary\"));\nconst notSubmitted = items.find(i => i.json.emailSubject.includes(\"Not Submitted\"));\n\nlet body = \"\";\n\n// Add first email\nif (allEmployees) {\n  body += allEmployees.json.emailBody.trim();\n  body += \"\\n\\n\";\n}\n\n// Separator\nbody += \"YOUR_AWS_SECRET_KEY_HERE===\\n\";\nbody += \"\ud83d\udfe5 TIMESHEET NOT SUBMITTED \ud83d\udfe5\\n\\n\";\n\n// Add second email\nif (notSubmitted) {\n  body += notSubmitted.json.emailBody.trim();\n}\n\n// Prepare final single email\nreturn [{\n  json: {\n    emailSubject: \"\ud83d\udcca Weekly Timesheet Report + Pending Submissions \u26a0\ufe0f \",\n    emailBody: body,\n    managerEmail: [\n      \"user@example.com\",\n      \"user@example.com\",\n      \"user@example.com\"\n    ].join(\",\") // \ud83d\udc48 convert array to string\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "06313814-b4fe-4fb7-954a-5256d92dedb0",
      "name": "Sticky Note18",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1040,
        224
      ],
      "parameters": {
        "color": 6,
        "width": 512,
        "height": 288,
        "content": "## Schedule & Timesheet Fetching\nThis section runs every Friday and fetches all employee timesheets for the previous week from Salesforce."
      },
      "typeVersion": 1
    },
    {
      "id": "45729a4c-d739-442a-9345-dab1b4c26193",
      "name": "If New Timesheet",
      "type": "n8n-nodes-base.if",
      "position": [
        -304,
        816
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e72bb73a-abdf-4292-bbb9-191d785bf162",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.dbt__Status__c }}",
              "rightValue": "New"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "0b1cae80-7d2e-46d7-b4b3-78e47cc38d3a",
      "name": "If Submitted Timesheet",
      "type": "n8n-nodes-base.if",
      "position": [
        -464,
        16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "3d49a19d-24a8-409c-9ba6-55f2d2c915d1",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.dbt__Status__c }}",
              "rightValue": "Submitted"
            }
          ]
        }
      },
      "typeVersion": 2.2
    }
  ],
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "e92fe7d5-abe0-4431-86c8-c21dea2166d9",
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Merge AI Summary",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "OpenAI1": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Timesheet": {
      "main": [
        [
          {
            "node": "Salesforce - Get Employee Details",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          },
          {
            "node": "If New Timesheet",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge Pending Info",
            "type": "main",
            "index": 0
          },
          {
            "node": "If Submitted Timesheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send a message": {
      "main": [
        []
      ]
    },
    "Loop Over Items": {
      "main": [
        [
          {
            "node": "Merge AI Summary",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get Timesheet Line Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If New Timesheet": {
      "main": [
        [
          {
            "node": "Salesforce - Get Employee Details1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge AI Summary": {
      "main": [
        [
          {
            "node": "Prepare Submitted Section",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Timesheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Final Email": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Pending Info": {
      "main": [
        [
          {
            "node": "Prepare Pending Section",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate HTML Table": {
      "main": [
        [
          {
            "node": "OpenAI1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Submitted Timesheet": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Pending Section": {
      "main": [
        [
          {
            "node": "Merge Submitted + Pending",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Get Timesheet Line Items": {
      "main": [
        [
          {
            "node": "Generate HTML Table",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Submitted + Pending": {
      "main": [
        [
          {
            "node": "Create Final Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Submitted Section": {
      "main": [
        [
          {
            "node": "Merge Submitted + Pending",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Salesforce - Get Employee Details": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Salesforce - Get Employee Details1": {
      "main": [
        [
          {
            "node": "Merge Pending Info",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  }
}