Back to Purchase Approval Workflow

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

Purchase Approval Workflow

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

This document provides the full technical specification for the Purchase Approval Workflow automation. It is written for the FullSpec build team and covers the current-state process map, agent-by-agent specifications with IO schemas, the end-to-end data flow, and the recommended build stack. All build decisions, credential management, and testing are handled by FullSpec. The process owner at [YourCompany.com] is responsible for supplying the confirmed spend-threshold rules table and granting tool access before build begins.

01Process snapshot

Step
Name
Description
1
Staff Member Submits Request
Requester emails finance or sends a Slack message with item, amount, and supplier. No standard format; details are frequently missing. (10 min)
2
Finance Logs Request Manually
Finance copies the request into Google Sheets, assigns a request ID, and follows up if cost or supplier is absent. (8 min)
3
Determine Correct Approver
Finance consults an often-outdated spend policy document to identify the correct approver by amount and department. (6 min)
4
Send Request to Approver
Finance forwards the request by email or Slack to the identified approver with any attached quotes. No deadline is set. (5 min)
5
Wait for Approver Response
Approver reviews in their own time and replies by email, Slack, or verbally. Requests wait two to five business days with no automatic follow-up. (1,440 min avg)
6
Chase Approver if No Response
Finance sends a manual follow-up if no reply arrives after two or three days. This step is forgotten as often as it is completed. (5 min)
7
Record Approval or Rejection
Finance updates the Google Sheet with the decision, approver name, date, and any conditions. (5 min)
8
Notify Requester of Outcome
Finance sends an email or Slack message confirming approval or explaining rejection, sometimes including a PO number. (5 min)
9
Raise Purchase Order in Xero
Finance manually creates a purchase order in Xero with supplier, line items, and approved amount. Done in a batch at day end. (10 min)
10
File Documentation
Finance saves the email chain or Slack screenshot into a shared folder by month and department. Retrieval at audit is slow. (4 min)
Time cost summary: Each purchase request cycle carries approximately 1,498 minutes (roughly 25 hours) of elapsed time, dominated by the uncontrolled approver wait at step 5. Active manual time per request is 63 minutes across steps 2, 3, 4, 6, 7, 8, 9, and 10. At 60 requests per month this equates to approximately 5 hours of active finance staff time per week and a 2-5 business day end-to-end cycle. The automation replaces steps 2, 3, 4, 5, 6, 7, 8, 9, and 10 in full, leaving step 5 replaced by a timed Slack interaction and steps 8-9 handled automatically on decision. Step 1 is replaced by a structured Jotform submission. The one retained human step is the finance manager reviewing and confirming the draft PO in Xero before supplier dispatch.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Request Intake Agent

Receives the Jotform webhook payload the moment a purchase request is submitted, validates that all mandatory fields (requester name, department, supplier name, amount, and business justification) are present, assigns a sequential request ID in the format REQ-YYYYMM-NNN, writes a timestamped row to the master purchase register in Google Sheets, and sends the requester an automated Gmail prompt if any required field is absent. Complexity is Moderate because it requires webhook receipt, conditional field validation, ID generation logic, a Sheets write, and a conditional Gmail branch.

Trigger
Jotform webhook POST received on new form submission
Tools
Jotform, Google Sheets, Gmail
Replaces steps
Steps 1 and 2 (submission intake and manual logging)
Estimated build
8 hours, Moderate
// Input
jotform_payload: {
  submission_id: string,
  requester_name: string,
  requester_email: string,
  department: string,
  supplier_name: string,
  amount_usd: number,
  justification: string,
  quote_url: string | null,
  submitted_at: ISO8601
}

// Output (on valid submission)
sheets_row: {
  request_id: 'REQ-YYYYMM-NNN',
  submission_id: string,
  requester_name: string,
  requester_email: string,
  department: string,
  supplier_name: string,
  amount_usd: number,
  justification: string,
  quote_url: string | null,
  status: 'Pending',
  logged_at: ISO8601
}

// Output (on missing fields)
gmail_resubmit_email: {
  to: requester_email,
  subject: 'Action required: incomplete purchase request',
  body: template_missing_fields_list
}
  • Jotform plan must support webhook output (Starter tier does not include webhooks; confirm the account is on the Bronze plan or above before build).
  • The Google Sheets purchase register must have a named tab 'Purchase Register' with columns matching the output schema exactly; the FullSpec team will create this schema during build.
  • Request ID generation must query the current maximum ID from the Sheets register before writing to avoid duplicates under concurrent submissions; use a Sheets LOCK or sequential append approach.
  • If the Jotform payload contains a quote_url, the field is written as-is; the agent does not download or validate the linked file.
  • The Gmail resubmit prompt lists only the specific missing fields and links back to the Jotform form URL; confirm the form URL is stable (not a draft link) before configuring the template.
  • Confirm with the process owner that the Jotform form is the single intake channel before go-live; email and Slack ad-hoc requests must be retired or redirected at launch.
Approval Routing Agent

Monitors the purchase register in Google Sheets for rows where status equals 'Pending'. On detecting a new Pending row, it looks up the correct approver from a spend-threshold rules table (a separate named Sheets tab), constructs a Slack Block Kit message containing the request details and inline Approve and Reject buttons, and sends it to the identified approver. A 48-hour timer is set at send time. If no response is recorded before the timer fires, a Gmail escalation email is sent to the approver and their line manager. When the approver responds via Slack interactive callback, the decision, approver Slack user ID, and response timestamp are written back to the purchase register and the row status is updated to 'Approved' or 'Rejected'. Complexity is Complex because it requires polling or change-detection on Sheets, a rules-table lookup with tier matching, Slack Block Kit interactive messaging, a timed escalation branch, and a Sheets write-back from a Slack callback.

Trigger
New row with status 'Pending' written to the Google Sheets purchase register
Tools
Google Sheets, Slack, Gmail
Replaces steps
Steps 3, 4, 5, 6, and 7 (approver lookup, routing, wait, chase, and decision recording)
Estimated build
14 hours, Complex
// Input
sheets_row: {
  request_id: string,
  requester_name: string,
  department: string,
  supplier_name: string,
  amount_usd: number,
  justification: string,
  status: 'Pending',
  logged_at: ISO8601
}

rules_table_lookup: {
  department: string,
  amount_usd: number
  -> approver_slack_user_id: string,
     approver_email: string,
     line_manager_email: string,
     tier_label: 'Tier1' | 'Tier2' | 'Tier3'
}

// Output (on Slack response)
sheets_update: {
  request_id: string,
  status: 'Approved' | 'Rejected',
  approver_slack_user_id: string,
  decision_timestamp: ISO8601,
  rejection_reason: string | null
}

// Output (on 48-hour timeout)
gmail_escalation_email: {
  to: approver_email,
  cc: line_manager_email,
  subject: 'Pending approval: [request_id] awaiting your decision',
  body: template_escalation_with_request_detail
}
  • The spend-threshold rules table must be fully populated and signed off by the process owner before this agent is built; a rules table with gaps or informal tiers cannot drive automated routing.
  • Slack Block Kit interactive messages require the automation platform to expose a public HTTPS callback URL; confirm the platform's webhook/callback infrastructure supports this before configuring interactive components.
  • The Slack app must be installed to the workspace with scopes: chat:write, users:read, and incoming-webhook at minimum; the Slack app creation and OAuth installation must be completed in the FullSpec sandbox workspace first.
  • If an approver's Slack user ID cannot be resolved from the rules table (for example, a new joiner not yet in Slack), the agent must fall back to sending the approval request via Gmail to the approver_email field and log a warning row.
  • The 48-hour escalation timer is measured from the Slack message send timestamp, not from the original form submission time; confirm this logic is acceptable with the process owner.
  • Rejection responses must capture a free-text reason field in the Slack modal; the Block Kit message must include an input block for this when the Reject button is clicked.
  • Dedupe: if the same request_id has already been routed (status no longer 'Pending'), the agent must skip that row to prevent duplicate Slack messages on repeated polling cycles.
PO Creation and Notification Agent

Monitors the purchase register for rows where status has just changed to 'Approved' or 'Rejected'. On an Approved status, it calls the Xero Purchases API to create a draft purchase order using the supplier name, approved amount, department, and line item description from the original submission, then sends a Gmail confirmation email to the requester including the Xero PO number. On a Rejected status, it sends a Gmail rejection email to the requester including the recorded rejection reason. The Xero PO is created at Draft status; a finance manager must review and confirm it in Xero before dispatch to the supplier. Complexity is Moderate because it requires a conditional branch on decision status, a Xero API write with field mapping, and two Gmail notification templates.

Trigger
A row status in the Google Sheets purchase register changes to 'Approved' or 'Rejected'
Tools
Xero, Gmail
Replaces steps
Steps 8 and 9 (requester notification and Xero PO creation)
Estimated build
6 hours, Moderate
// Input
sheets_row: {
  request_id: string,
  requester_name: string,
  requester_email: string,
  department: string,
  supplier_name: string,
  amount_usd: number,
  justification: string,
  status: 'Approved' | 'Rejected',
  approver_slack_user_id: string,
  decision_timestamp: ISO8601,
  rejection_reason: string | null
}

// Output (on Approved)
xero_purchase_order: {
  Type: 'PURCHASEORDER',
  Status: 'DRAFT',
  Contact: { Name: supplier_name },
  Date: decision_timestamp (date portion),
  LineItems: [
    {
      Description: justification,
      Quantity: 1,
      UnitAmount: amount_usd,
      AccountCode: department_mapped_account_code
    }
  ],
  Reference: request_id
}

gmail_approval_email: {
  to: requester_email,
  subject: 'Your purchase request [request_id] has been approved',
  body: template_approval_with_po_number
}

// Output (on Rejected)
gmail_rejection_email: {
  to: requester_email,
  subject: 'Your purchase request [request_id] was not approved',
  body: template_rejection_with_reason
}

// On approval (retained human step)
xero_manual_review: {
  action: 'Finance Manager reviews and confirms draft PO in Xero before supplier dispatch',
  po_status_before_review: 'DRAFT',
  po_status_after_review: 'SUBMITTED' | 'AUTHORISED'
}
  • Xero API access requires OAuth 2.0 with scopes: accounting.transactions and accounting.contacts.read; the Xero app must be created in the Xero Developer Portal and connected to the correct Xero organisation before this agent is built.
  • The department-to-account-code mapping table must be agreed with the process owner and finance manager before build; the agent cannot create a valid Xero line item without a known account code.
  • The Xero Contact (supplier) is matched by name; if the supplier does not exist in Xero, the API will create a new Contact record automatically. Confirm with finance whether new supplier auto-creation is acceptable or whether an existing-contact-only policy applies.
  • The Gmail approval email must include the Xero PO number returned in the Xero API response body (PurchaseOrders[0].PurchaseOrderNumber); if the Xero call fails, the agent must log an error row and send an alert rather than silently dropping the notification.
  • Rejection emails must include the rejection_reason field verbatim as recorded in the Sheets register; if the field is null, the template must fall back to a default explanation ('No reason was recorded by the approver').
  • This agent must not re-process rows it has already handled; implement a 'PO Raised' or 'Notified' status column flag to prevent duplicate PO creation on repeated polling.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Purchase Approval Workflow: trigger-to-output data flow
// ─── TRIGGER ───────────────────────────────────────────────────────────────
// Staff member submits Jotform purchase request form
JOTFORM_WEBHOOK_POST -> automation_platform
  payload fields: submission_id, requester_name, requester_email,
                   department, supplier_name, amount_usd,
                   justification, quote_url, submitted_at

// ─── AGENT 1: Request Intake Agent ─────────────────────────────────────────
// Field validation check
IF any(requester_name, requester_email, department,
        supplier_name, amount_usd, justification) IS NULL:
  -> GMAIL.send(to: requester_email,
                subject: 'Action required: incomplete purchase request',
                body: list_of_missing_fields + jotform_form_url)
  -> END (await resubmission)

// ID generation and register write
request_id = 'REQ-' + YYYYMM + '-' + SHEETS.max_id_in_register + 1
GOOGLE_SHEETS['Purchase Register'].appendRow({
  request_id, submission_id, requester_name, requester_email,
  department, supplier_name, amount_usd, justification,
  quote_url, status: 'Pending', logged_at: NOW()
})
// -> Handoff to Agent 2 triggered by new Pending row

// ─── AGENT 2: Approval Routing Agent ───────────────────────────────────────
// Rules table lookup
approver = GOOGLE_SHEETS['Rules Table'].lookup(
  department == row.department AND
  amount_threshold_min <= row.amount_usd <= amount_threshold_max
) -> { approver_slack_user_id, approver_email,
        line_manager_email, tier_label }

// Slack interactive message dispatch
SLACK.postMessage(
  channel: approver_slack_user_id,
  blocks: Block_Kit_card {
    request_id, requester_name, department,
    supplier_name, amount_usd, justification, quote_url,
    actions: [ button('Approve', value:'approved'),
               button('Reject', value:'rejected', opens_modal: true) ]
  }
)
timer_start = NOW()   // 48-hour escalation window begins

// Escalation branch (48 hours elapsed, no response recorded)
IF NOW() - timer_start > 48h AND sheets_row.status == 'Pending':
  -> GMAIL.send(
       to: approver_email,
       cc: line_manager_email,
       subject: 'Pending approval: [request_id] awaiting your decision',
       body: template_escalation_with_request_detail
     )

// Slack callback (approver responds)
SLACK_INTERACTIVE_CALLBACK -> automation_platform
  payload fields: action_value ('approved'|'rejected'),
                   approver_slack_user_id, response_timestamp,
                   rejection_reason (string | null)

GOOGLE_SHEETS['Purchase Register'].updateRow(request_id, {
  status: action_value == 'approved' ? 'Approved' : 'Rejected',
  approver_slack_user_id,
  decision_timestamp: response_timestamp,
  rejection_reason: rejection_reason | null
})
// -> Handoff to Agent 3 triggered by status change to Approved or Rejected

// ─── AGENT 3: PO Creation and Notification Agent ───────────────────────────
// Branch: Approved path
IF status == 'Approved':
  xero_response = XERO.PurchaseOrders.create({
    Type: 'PURCHASEORDER',
    Status: 'DRAFT',
    Contact: { Name: supplier_name },
    Date: decision_timestamp.date,
    LineItems: [{
      Description: justification,
      Quantity: 1,
      UnitAmount: amount_usd,
      AccountCode: rules_table.department_account_code
    }],
    Reference: request_id
  })
  po_number = xero_response.PurchaseOrders[0].PurchaseOrderNumber
  GMAIL.send(
    to: requester_email,
    subject: 'Your purchase request [request_id] has been approved',
    body: template_approval { po_number, supplier_name, amount_usd }
  )

// Branch: Rejected path
IF status == 'Rejected':
  GMAIL.send(
    to: requester_email,
    subject: 'Your purchase request [request_id] was not approved',
    body: template_rejection { rejection_reason | 'No reason recorded' }
  )

// ─── RETAINED HUMAN STEP ───────────────────────────────────────────────────
// Finance Manager reviews DRAFT PO in Xero and confirms before supplier dispatch
// Status transition: DRAFT -> SUBMITTED | AUTHORISED (manual, in Xero UI)

// ─── END OF AUTOMATED FLOW ─────────────────────────────────────────────────
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation platform running one workflow per agent (three workflows total): Workflow 1 handles Jotform webhook receipt, validation, and Sheets write; Workflow 2 handles Sheets polling for Pending rows, rules-table lookup, Slack dispatch, escalation timer, and Slack callback write-back; Workflow 3 handles Sheets polling for Approved/Rejected rows, Xero PO creation, and Gmail notifications. All three workflows share a single credential store to avoid duplicated token management.
Webhook configuration
Jotform is configured to POST to the automation platform's inbound webhook URL for Workflow 1 on every form submission. The Slack interactive callback URL (for Approve/Reject button responses) points to Workflow 2's webhook endpoint. Both URLs must use HTTPS. The Jotform webhook should include all form fields in the POST body as JSON; confirm the Jotform account's webhook format matches the expected JSON schema during sandbox testing.
Templating approach
All outbound email bodies (resubmit prompt, escalation, approval confirmation, rejection notification) are maintained as named templates within the automation platform, with variable substitution for request_id, requester_name, supplier_name, amount_usd, po_number, and rejection_reason. The Slack Block Kit message is constructed as a JSON block template with the same variable tokens. Templates are version-controlled in the platform and editable by the FullSpec team without touching workflow logic.
Error logging
All agent errors (Jotform payload parse failure, Sheets write failure, Slack API error, Xero API error, Gmail send failure) are written to a dedicated 'Error Log' tab in the Google Sheets purchase register workbook, capturing: workflow_name, request_id (if available), error_type, error_message, timestamp, and retry_count. A Gmail alert is sent to the designated support address at support@gofullspec.com and to the process owner's email on any Xero API failure or escalation timer failure, as these carry the highest business impact. Non-critical errors (for example, a missing quote_url) are logged but do not trigger an alert.
Testing approach
All three agents are built and validated in a sandbox environment first: a Jotform test form, a separate Google Sheets test workbook, a Slack test workspace, a Xero demo company, and Gmail aliases for requester and approver roles. The sandbox uses cloned credential sets. After sandbox sign-off, the workflows are promoted to the live credential store. End-to-end integration testing covers 15 scripted scenarios including happy-path approval, multi-tier routing, 48-hour escalation, missing-field resubmit, and rejection with reason. See the FullSpec Test and QA Plan (FS-DOC-06) for the full test case register.
Estimated total build time
Request Intake Agent: 8 hours. Approval Routing Agent: 14 hours. PO Creation and Notification Agent: 6 hours. End-to-end integration testing and credential handover: 6 hours (included in the 28-hour build effort total confirmed in the project snapshot). Total: 34 hours including testing, consistent with the 4-week delivery schedule.
Pre-build blockers: three items must be resolved before build can start. First, the spend-threshold rules table must be fully documented and signed off by the process owner and finance manager. Second, Xero API OAuth credentials and the department-to-account-code mapping must be confirmed. Third, all approvers must be verified as active Slack users or explicitly flagged for the Gmail fallback path. The FullSpec team will run a discovery checkpoint to confirm all three before Workflow 2 build begins.
Support: for any questions during or after build, contact the FullSpec team at support@gofullspec.com. Reference the process name 'Purchase Approval Workflow' and your [YourCompany.com] account in all correspondence.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Purchase Approval Workflow.

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