Back to Timesheet Collection & Approval

Developer Handover Pack

Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.

4 pagesPDF · Technical
FS-DOC-04Technical

Developer Handover Pack

Timesheet Collection and Approval

[YourCompany.com] · HR Department · Prepared by FullSpec · [Today's Date]

This document gives the FullSpec build team everything needed to implement the three-agent Timesheet Collection and Approval automation end to end. It covers the current-state step map with bottleneck identification, full agent specifications with IO contracts, the complete data flow from trigger to confirmation, and the recommended build stack. The process owner and HR team are responsible for confirming employee records, validation thresholds, and approval window timings before build begins. FullSpec handles all agent construction, integration wiring, testing, and deployment.

01Process snapshot

Step
Name
Description
1
Check Submission Status
HR opens the master timesheet tracker in Google Sheets and manually marks who has and has not submitted for the current pay period. Time cost: 20 minutes per cycle.
2
Send Reminder to Late Submitters
HR drafts and sends individual reminder emails or Slack messages to each employee who has not yet submitted. Time cost: 30 minutes per cycle.
3
Receive and Collect Submissions
Employees reply by email or ad-hoc form. HR downloads and stores each submission manually. Time cost: 25 minutes per cycle.
4
Consolidate Hours into Master Sheet
HR copies submitted hours from each employee submission into the master Google Sheet, checking for obvious errors. BOTTLENECK: inconsistent formats and volume create the highest manual load in the cycle. Time cost: 40 minutes per cycle.
5
Flag Anomalies or Incomplete Entries
HR reviews each row for missing fields, unusual hour totals, or entries exceeding shift patterns, then follows up with the relevant employee. Time cost: 20 minutes per cycle.
6
Route Timesheets to Line Managers
HR emails each manager a summary of their team's submitted hours and requests written approval before the payroll deadline. Time cost: 20 minutes per cycle.
7
Chase Manager Approvals
If a manager has not replied by the internal deadline, HR follows up individually by email or Slack until approval is received. BOTTLENECK: unresponsive managers frequently push the payroll close past deadline. Time cost: 25 minutes per cycle.
8
Record Approvals in Master Sheet
HR updates the master spreadsheet to log which managers have approved, with timestamps for the audit record. Time cost: 15 minutes per cycle.
9
Enter Approved Hours into Payroll System
HR manually keys approved hours from the master sheet into Xero, employee by employee. BOTTLENECK: error-prone, high-volume data re-entry that blocks payroll processing. Time cost: 35 minutes per cycle.
10
Confirm Payroll Data Accepted
HR checks the payroll system for import errors, resolves any rejected entries, and notifies the payroll owner that data is ready. Time cost: 10 minutes per cycle.
Time cost summary: Total manual time per cycle is 240 minutes (4 hours). At a weekly pay frequency this equals 6 hours per week of HR administrator time, or approximately 300 hours per year at a $30/hour rate. Steps 1 through 5 (collection and consolidation), steps 6 through 8 (approval routing and logging), and steps 9 through 10 (payroll data entry and confirmation) are all replaced by the three agents in the automated flow. One human review step for flagged anomalies is retained between the Collection Agent and Approval Agent.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Collection Agent

Monitors the pay period schedule and, on the close date, reads the master Google Sheet to identify employees who have not submitted. Sends personalised reminders via Gmail and Slack with a direct link to the Google Form submission page. Pulls in form responses, validates that all required fields are present and that hour totals fall within agreed thresholds, and writes clean data into the master sheet. Flags anomalies in a dedicated review column for human inspection before handing off to the Approval Agent.

Trigger
Scheduled trigger fires when the pay period close date is reached. Frequency is configurable for weekly or fortnightly cycles.
Tools
Google Sheets, Google Forms, Gmail, Slack
Replaces steps
Steps 1, 2, 3, 4, and 5
Estimated build
10 hours. Complexity: Moderate.
// Input
pay_period_id: string          // e.g. '2025-W06'
pay_period_close_date: date    // ISO 8601
employee_list: array           // from master sheet: [{employee_id, name, email, slack_handle, manager_id}]
submission_status: array       // from master sheet: [{employee_id, submitted: bool}]
form_responses: array          // Google Forms API: [{employee_id, hours_regular, hours_overtime, project_codes[], submitted_at}]
validation_thresholds: object  // {max_hours_per_day: 12, max_hours_per_week: 60}

// Output
master_sheet_updated: bool     // true when all valid rows written
rows_written: array            // [{employee_id, hours_regular, hours_overtime, project_codes[], status: 'validated'|'flagged'}]
flagged_entries: array         // [{employee_id, flag_reason: string}] — written to Anomalies tab
reminders_sent: array          // [{employee_id, channel: 'gmail'|'slack', sent_at: timestamp}]
collection_complete: bool      // true when all non-flagged rows validated; triggers Approval Agent
  • Google Workspace tier must include Workspace Business Starter or above for Gmail API sending limits; confirm the daily sending quota (500 messages/day for standard accounts) is sufficient for the employee count per cycle.
  • Google Forms must be the single authorised submission channel before go-live. Employees currently submitting via email attachment or paper must be migrated to the Form before the Collection Agent is activated.
  • The master Google Sheet must have a stable, named sheet tab ('Submissions_W{n}') with fixed column positions for employee_id, hours_regular, hours_overtime, project_codes, status, and anomaly_flag. Column positions must be confirmed with the HR team before build.
  • Validation thresholds (max daily hours, max weekly hours, required project code fields) must be agreed and documented by the process owner before the validation logic is coded. These will be stored as a config object in the automation platform credential store.
  • Reminder messages use personalised templates referencing the employee's first name, the pay period dates, and a unique Google Form prefill URL. Template content must be reviewed and approved by the HR team before build.
  • Deduplication: if a Form response arrives more than once for the same employee_id in the same pay_period_id, only the most recent submission is retained. Earlier entries are logged to an Audit tab, not overwritten silently.
  • Anomaly flagging does not block the pay period. Flagged rows are written to the master sheet with status 'flagged' and highlighted in the Anomalies tab. The HR administrator resolves flagged entries manually before the Approval Agent is triggered. The Collection Agent will not set collection_complete: true while any unresolved flagged entries remain.
Approval Agent

Triggered when the Collection Agent sets collection_complete to true in the master sheet. Reads the master sheet to group validated employee rows by line manager, then sends each manager a pre-formatted approval email listing their team's hours for the pay period. Each email contains a unique token-based approve link and a query link. The agent polls for link activations, logs approval timestamps against each employee row in the master sheet, and escalates automatically if a manager has not responded within the configured window. Escalation sends a follow-up via both Gmail and Slack.

Trigger
collection_complete flag set to true in master Google Sheet by Collection Agent.
Tools
Gmail, Google Sheets, Slack
Replaces steps
Steps 6, 7, and 8
Estimated build
10 hours. Complexity: Moderate.
// Input
pay_period_id: string          // passed from Collection Agent output
validated_rows: array          // [{employee_id, manager_id, hours_regular, hours_overtime, project_codes[]}]
manager_list: array            // [{manager_id, name, email, slack_handle}]
approval_window_hours: int     // config; e.g. 24 — time before first escalation
escalation_window_hours: int   // config; e.g. 4 — time before second escalation

// On approval (webhook callback from token link)
approval_token: string         // UUID bound to manager_id + pay_period_id
action: enum                   // 'approve' | 'query'
responded_at: timestamp

// Output
approval_log: array            // [{manager_id, employee_ids[], approved_at: timestamp, action: 'approve'|'query'}]
master_sheet_updated: bool     // approval columns populated for each employee row
escalations_sent: array        // [{manager_id, channel: 'gmail'|'slack', sent_at: timestamp}]
all_approvals_complete: bool   // true when every manager_id has actioned 'approve'; triggers Payroll Sync Agent
  • Approval tokens are UUIDs generated per manager per pay period. Tokens expire after 72 hours. If a token expires without action, the agent treats it as a non-response and escalates. Token generation and validation must be implemented within the automation platform; no external auth service is required at Standard tier.
  • Token-based links are sufficient for Standard build. If the organisation requires formal digital signatures or has strict SSO/access controls, an additional authentication layer must be scoped and agreed before build starts. This is out of scope for the current build specification.
  • Each manager receives one approval email per pay period covering all of their direct reports. The email must not be sent until collection_complete is confirmed, preventing partial approval requests.
  • If a manager clicks 'query' rather than 'approve', the Approval Agent logs the query action and sends an alert to the HR administrator (via Slack) to resolve the discrepancy manually. The pay period remains open for that manager until an 'approve' action is received.
  • The escalation timer is configurable in the credential/config store. Default values are 24 hours for first escalation and 4 hours for second escalation. A third non-response triggers a Slack alert to the HR administrator and payroll owner.
  • Master sheet approval columns to be written: approved_at (timestamp), approved_by (manager display name), approval_token_used (UUID), and pay_period_id. Column positions must be confirmed before build.
  • Deduplication: if a token is activated more than once (e.g. double-click), only the first activation is logged. Subsequent activations for the same token are discarded and logged to the Audit tab.
Payroll Sync Agent

Triggered when the Approval Agent sets all_approvals_complete to true. Reads the final approved rows from the master Google Sheet, maps each employee's hours to the corresponding Xero pay item using the employee_xero_id lookup, and pushes the data to Xero via the Payroll API. Verifies the Xero response for each employee record and retries on transient errors. Once all records are confirmed accepted by Xero, sends a Slack confirmation message to the payroll owner summarising the total employee count and total hours processed.

Trigger
all_approvals_complete flag set to true in master Google Sheet by Approval Agent.
Tools
Google Sheets, Xero, Slack
Replaces steps
Steps 9 and 10
Estimated build
8 hours. Complexity: Moderate.
// Input
pay_period_id: string          // passed from Approval Agent output
approved_rows: array           // [{employee_id, employee_xero_id, hours_regular, hours_overtime, project_codes[], approved_at}]
xero_pay_period_id: string     // Xero PayrollCalendar period reference; must match pay_period_close_date
xero_pay_item_map: object      // {hours_regular: 'EarningsRateID_001', hours_overtime: 'EarningsRateID_002'}

// Output
xero_sync_results: array       // [{employee_xero_id, status: 'accepted'|'rejected', xero_response_code: int}]
sync_complete: bool            // true when all records accepted by Xero
rejected_records: array        // [{employee_xero_id, reason: string}] — triggers HR alert if non-empty
slack_confirmation_sent: bool
confirmation_message: object   // {channel: '#payroll', text: 'Pay period {pay_period_id} synced. {n} employees, {total_hours} hours loaded into Xero.'}
  • Xero Payroll API access requires an OAuth 2.0 connection with the payroll.read and payroll.write scopes. The Xero app must be registered in the Xero developer portal before build begins. Credentials are stored in the automation platform credential store, not hardcoded.
  • Xero employee records must match the master Google Sheet employee list exactly on employee_xero_id. Any discrepancies (missing Xero ID, mismatched name, deactivated employee record) will cause a rejected_record entry and must be reconciled by the HR team before the Payroll Sync Agent is enabled. This reconciliation is a pre-build dependency.
  • EarningsRateIDs for regular hours and overtime must be retrieved from the Xero organisation's payroll settings and stored in the pay_item_map config object. These IDs are organisation-specific and cannot be assumed.
  • Xero Payroll API rate limit is 60 requests per minute. For up to 80 employees the sync will remain within limits. Batching is not required at this volume but the retry logic must handle 429 responses with exponential backoff (initial delay 2 seconds, maximum 3 retries).
  • If any records are rejected by Xero, sync_complete remains false, rejected_records are written to the master sheet Errors tab, and a Slack alert is sent to the HR administrator and payroll owner with the reason string from the Xero response. The sync does not silently fail.
  • The Slack confirmation message is sent to the '#payroll' channel (or the channel name confirmed by the process owner) and tags the payroll owner by Slack user ID. Channel name and user ID must be confirmed before build.
  • Gusto is referenced in the current-state process but the Standard build target for the API sync is Xero. If the organisation uses Gusto, a separate adapter must be scoped. This specification covers Xero only.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-confirmation data flow: Timesheet Collection and Approval
// ─────────────────────────────────────────────────────────────────
// TRIGGER
// ─────────────────────────────────────────────────────────────────
SCHEDULED_TRIGGER fires on pay_period_close_date
  -> emits: { pay_period_id, pay_period_close_date }

// ─────────────────────────────────────────────────────────────────
// COLLECTION AGENT — steps 1 through 5 replaced
// ─────────────────────────────────────────────────────────────────

// Step 1 replacement: read master sheet for submission status
Google_Sheets.read('Master_Timesheets', range='A:H')
  -> employee_list[{employee_id, name, email, slack_handle, manager_id}]
  -> submission_status[{employee_id, submitted: bool}]

// Step 2 replacement: send personalised reminders to non-submitters
FOR each employee WHERE submitted == false:
  Gmail.send({ to: employee.email, template: 'reminder_v1', prefill_url: Forms.prefill(employee_id, pay_period_id) })
  Slack.post({ channel: employee.slack_handle, text: reminder_text })
  -> reminders_sent[{employee_id, channel, sent_at}]

// Step 3 replacement: collect Google Forms responses
Google_Forms.listResponses(form_id, filter: pay_period_id)
  -> form_responses[{employee_id, hours_regular, hours_overtime, project_codes[], submitted_at}]

// Step 4 replacement: validate and write to master sheet
FOR each form_response:
  IF duplicate(employee_id, pay_period_id): retain latest, log earlier to Audit tab
  IF hours_regular + hours_overtime > validation_thresholds.max_hours_per_week:
    status = 'flagged'; write to Anomalies tab
  ELSE:
    status = 'validated'
  Google_Sheets.write('Master_Timesheets', row: { employee_id, hours_regular, hours_overtime, project_codes[], status })

// Step 5 replacement: anomaly review gate
IF flagged_entries.length > 0:
  WAIT for HR_Administrator to resolve all flagged rows (manual step)
  HR_Administrator updates status = 'validated' in master sheet
WHEN flagged_entries.length == 0:
  Google_Sheets.write('Master_Timesheets', cell: collection_complete = true)
  -> collection_complete: true

// ─────────────────────────────────────────────────────────────────
// HANDOFF: Collection Agent -> Approval Agent
// Condition: collection_complete == true
// Payload: pay_period_id, validated_rows[], manager_list[]
// ─────────────────────────────────────────────────────────────────

// APPROVAL AGENT — steps 6 through 8 replaced
// ─────────────────────────────────────────────────────────────────

// Step 6 replacement: route approval requests
FOR each manager_id in validated_rows (distinct):
  approval_token = UUID.generate(manager_id, pay_period_id)
  Gmail.send({ to: manager.email, template: 'approval_request_v1',
    team_rows: validated_rows.filter(manager_id),
    approve_url: token_endpoint + '?token=' + approval_token + '&action=approve',
    query_url:   token_endpoint + '?token=' + approval_token + '&action=query' })

// Step 7 replacement: escalation timer
START timer(approval_window_hours) per manager_id
  IF no callback received within approval_window_hours:
    Gmail.send(escalation_email_v1 -> manager.email)
    Slack.post(escalation_message -> manager.slack_handle)
  IF no callback within escalation_window_hours after first escalation:
    Slack.post(alert -> '#payroll' + HR_Administrator.slack_handle)

// Token webhook callback
ON POST /webhook/approval?token={approval_token}&action={approve|query}:
  IF duplicate activation: discard, log to Audit tab
  approval_log.push({ manager_id, employee_ids[], approved_at, action })

// Step 8 replacement: write approvals to master sheet
Google_Sheets.write('Master_Timesheets', columns:
  { approved_at, approved_by, approval_token_used, pay_period_id })
  -> per employee row, matched on employee_id
WHEN all manager_ids actioned 'approve':
  Google_Sheets.write('Master_Timesheets', cell: all_approvals_complete = true)
  -> all_approvals_complete: true

// ─────────────────────────────────────────────────────────────────
// HANDOFF: Approval Agent -> Payroll Sync Agent
// Condition: all_approvals_complete == true
// Payload: pay_period_id, approved_rows[], xero_pay_period_id
// ─────────────────────────────────────────────────────────────────

// PAYROLL SYNC AGENT — steps 9 and 10 replaced
// ─────────────────────────────────────────────────────────────────

// Step 9 replacement: push approved hours to Xero
FOR each approved_row:
  xero_payload = {
    EmployeeID: approved_row.employee_xero_id,
    PayPeriod:  xero_pay_period_id,
    EarningsLines: [
      { EarningsRateID: pay_item_map.hours_regular,   NumberOfUnits: approved_row.hours_regular },
      { EarningsRateID: pay_item_map.hours_overtime,  NumberOfUnits: approved_row.hours_overtime }
    ]
  }
  Xero.PayrollAPI.PUT('/payslip/{EmployeeID}', xero_payload)
  ON 429: backoff(2s, max_retries=3)
  ON 4xx/5xx: rejected_records.push({ employee_xero_id, reason: response.message })

// Step 10 replacement: verify and confirm
IF rejected_records.length > 0:
  Google_Sheets.write('Errors_Tab', rejected_records)
  Slack.post({ channel: '#payroll', text: 'Sync partial — {n} rejected records. See Errors tab.' })
ELSE:
  sync_complete = true
  Slack.post({ channel: '#payroll',
    text: 'Pay period {pay_period_id} synced. {approved_rows.length} employees,
           {sum(hours_regular + hours_overtime)} total hours loaded into Xero.' })

// ─────────────────────────────────────────────────────────────────
// END: sync_complete == true, payroll owner notified via Slack
// ─────────────────────────────────────────────────────────────────
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation platform handles all agent logic. Three separate workflows are created: one per agent (Collection, Approval, Payroll Sync). All workflows share a single credential store for API keys, OAuth tokens, and config objects (validation thresholds, pay item map, Slack channel IDs). No credentials are hardcoded in workflow nodes.
Webhook configuration
The Approval Agent exposes a webhook endpoint to receive token callbacks (approve/query) from manager email links. The endpoint validates the approval_token UUID against the active pay period before logging any action. Token expiry is enforced at 72 hours. The Collection Agent uses a scheduled (cron) trigger aligned to the pay period close date; this is configured per frequency (weekly: every Monday 07:00 local; fortnightly: every alternate Monday 07:00 local). Google Forms response collection uses the Google Forms API poll rather than a push webhook to avoid Forms webhook reliability issues.
Templating approach
Email and Slack message templates are stored as named template strings in the credential/config store. Variables injected at runtime include: employee.first_name, pay_period_id, pay_period_close_date, form_prefill_url, approve_url, query_url, manager.first_name, team_hours_table (HTML for Gmail, plain text for Slack). Templates are versioned (reminder_v1, approval_request_v1, escalation_email_v1) so updates do not break in-flight pay cycles.
Error logging
All agent errors (API failures, validation rejections, token mismatches, Xero sync rejections) are written to a dedicated Errors tab in the master Google Sheet and simultaneously to a Supabase errors table (columns: timestamp, agent_name, pay_period_id, error_type, error_message, resolved: bool). A Slack alert to '#payroll-alerts' fires on any unhandled error. The FullSpec team monitors this table during the parallel-run period and for 30 days post go-live.
Testing approach
All agents are built and tested against a sandbox environment first. Google Sheets: a dedicated 'Test_Master_Timesheets' sheet with synthetic employee data. Gmail: a test Gmail account used for all reminder and approval sends during QA. Xero: the Xero Demo Company tenant is used for all API sync tests before production credentials are connected. Slack: a '#payroll-test' channel receives all test notifications. No production Xero payroll records are touched until the end-to-end QA sign-off is complete.
Estimated total build time
Collection Agent: 10 hours. Approval Agent: 10 hours. Payroll Sync Agent: 8 hours. End-to-end integration testing and QA: 6 hours. Total: 34 hours across the 4-week delivery schedule. (Note: the process template states 28 hours of build effort; the additional 6 hours are allocated to end-to-end testing and cross-agent QA not captured in per-agent estimates.)
Pre-build blockers: three items must be confirmed before any agent build starts. (1) Google Forms is confirmed as the sole submission channel and all employees have been notified. (2) Xero employee records and EarningsRateIDs have been audited and reconciled against the master Google Sheet employee list. (3) Validation thresholds (max daily and weekly hours, required fields) and approval window timings have been signed off by the process owner. FullSpec will not begin agent construction until all three are cleared. Contact support@gofullspec.com to confirm readiness.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Timesheet Collection & Approval.

Launch Plan
Operations · Owner
View
ROI and Business Case
Finance · Owner
View
Process Runbook / SOP
Operations · Owner
View
Integration and API Spec
Technical · Developer
View
Test and QA Plan
Quality · Developer
View