Back to Purchase Order Management

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 Order Management

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

This document is the primary technical reference for the FullSpec build team delivering the Purchase Order Management automation. It covers the current-state step map with bottleneck flags, the full specification for each agent including IO contracts, the end-to-end data flow trace, and the recommended build stack with time estimates. The FullSpec team owns everything in this document from build through to go-live. The process owner and operations team supply access credentials, confirm approval threshold rules, and validate test outputs during UAT.

01Process snapshot

Step
Name
Description
1
Receive Purchase Request
A staff member submits a request by email, Slack, or verbally. The ops manager records details manually in a shared spreadsheet. (10 min)
2
Validate Request Details
The ops manager checks for supplier name, item description, quantity, estimated cost, and budget code. Missing fields require a follow-up message to the requester. (12 min)
3
Route Request for Approval
The ops manager identifies the correct approver by spend threshold and department, then emails a summary and waits for a reply. Bottleneck: email-based routing has no structured channel or SLA. (8 min)
4
Follow Up with Approver
If no reply arrives within 24 to 48 hours, the ops manager sends a manual chase. This step often repeats two or three times per order. Bottleneck: repeated manual chasing with no automated reminder. (15 min)
5
Create Purchase Order Document
Once approved, the ops manager copies details into a PO template, assigns a PO number, and saves as a PDF. (20 min)
6
Send PO to Supplier
The ops manager emails the PDF to the supplier and requests written confirmation of receipt and lead time. (8 min)
7
Log PO in Accounting System
The ops manager manually enters PO details into Xero so finance can match against the incoming invoice. (10 min)
8
Track Supplier Confirmation
The ops manager checks incoming email for supplier confirmation and updates the spreadsheet. If no confirmation arrives within two days, a chase email is sent manually. Bottleneck: no automated monitoring or follow-up trigger. (12 min)
9
Notify Requester of PO Status
Once the supplier confirms, the ops manager messages the original requester with the expected delivery date. (5 min)
10
File PO Documentation
The PDF PO and email correspondence are saved to a shared drive folder organised by month and supplier. (5 min)
Time cost summary: Total manual time per PO cycle is 105 minutes. At approximately 60 purchase orders per month, this equates to roughly 7 hours of manual work per week and 28 hours per 30-day period. Steps 1 and 2 (intake and validation) are replaced by the Intake and Validation Agent. Steps 3 and 4 (approval routing and chasing) are replaced by the Approval Routing Agent. Steps 5 through 10 (PO creation, supplier email, Xero logging, confirmation tracking, requester notification, and filing) are replaced by the PO Creation and Notification Agent. One manual exception path is retained: the ops manager reviews all rejected or escalated requests.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Intake and Validation Agent

Receives the structured Google Form submission as its trigger event. Checks that all required fields are present and internally consistent: supplier name, item description, quantity, estimated cost, and budget code. If the submission is complete, the agent serialises a validated request record and passes it downstream to the Approval Routing Agent. If any required field is absent or inconsistent (for example, a cost value of zero or a budget code not matching the department list), the agent sends a Slack direct message to the requester listing each missing or invalid field and halts the record until a corrected resubmission is received. No duplicate records are created on resubmission; the agent deduplicates by matching on the form submission ID stored in the platform's execution context.

Trigger
Google Form purchase request submitted (webhook or polling, depends on platform connector)
Tools
Google Forms, Slack
Replaces steps
Steps 1 and 2 (Receive Purchase Request, Validate Request Details)
Estimated build
10 hours, Moderate complexity
// Input
form_submission_id: string       // Google Forms response ID
requester_slack_id: string       // Pre-mapped from form email to Slack member ID
supplier_name: string
item_description: string
quantity: integer
estimated_cost_usd: float
budget_code: string              // Must match approved department code list
department: string
//
// Validation rules
required_fields: [supplier_name, item_description, quantity, estimated_cost_usd, budget_code, department]
estimated_cost_usd > 0
budget_code IN approved_budget_codes[]
//
// Output (on pass)
validated_request: {
  request_id: string             // UUID generated at this step
  form_submission_id: string
  requester_slack_id: string
  supplier_name: string
  item_description: string
  quantity: integer
  estimated_cost_usd: float
  budget_code: string
  department: string
  submitted_at: ISO8601 timestamp
  validation_status: 'passed'
}
//
// Output (on fail)
slack_dm -> requester_slack_id: {
  message: 'Your purchase request is incomplete. Please resubmit with the following: [missing_fields]'
  missing_fields: string[]
}
execution_halted: true
  • Google Forms must be configured with a webhook or the platform connector must poll on a schedule no longer than every 5 minutes. Confirm the platform's Google Forms connector type before build.
  • The approved budget code list must be supplied by the ops team as a static reference array before build starts. This list must be updated in the platform configuration whenever codes change.
  • Slack requester lookup requires mapping from the form submitter's email address to a Slack member ID. This lookup must use the Slack users.lookupByEmail API endpoint. Confirm the Slack plan tier supports this endpoint (Standard or above required).
  • Deduplication: the agent stores each processed form_submission_id in a platform-side key-value store or a Google Sheets dedup log. If an ID is seen twice (for example, due to a webhook retry), the second execution is discarded with a logged warning.
  • On a validation failure, the agent does NOT create a request_id. A new request_id is only assigned on first successful pass.
  • Confirm before build: approved budget code list, Slack workspace plan tier, and whether the ops team wants a Slack channel notification (in addition to DM) for all failed validation attempts.
Approval Routing Agent

Receives the validated request record from the Intake and Validation Agent. Applies the spend-threshold and department routing rules to identify the correct approver's Slack member ID. Posts a structured Slack Block Kit message to that approver containing the spend summary, supplier name, budget code, and two interactive action buttons: Approve and Reject. The agent records the timestamp of the approval request against the request_id. If no button response is received within 24 hours, the agent sends a single automated reminder to the same approver. If a second 24-hour window elapses with no response, the agent escalates by notifying the ops manager Slack ID and setting the request status to 'escalated'. All decisions and timestamps are written to the Google Sheets procurement register as they occur.

Trigger
Validated request record received from Intake and Validation Agent
Tools
Slack
Replaces steps
Steps 3 and 4 (Route Request for Approval, Follow Up with Approver)
Estimated build
14 hours, Moderate complexity
// Input
validated_request: {            // Full record from Intake and Validation Agent
  request_id: string
  requester_slack_id: string
  supplier_name: string
  item_description: string
  quantity: integer
  estimated_cost_usd: float
  budget_code: string
  department: string
  submitted_at: ISO8601 timestamp
}
//
// Routing logic (configured pre-build)
IF estimated_cost_usd < threshold_low -> approver_slack_id = department_manager_id
IF estimated_cost_usd >= threshold_low AND < threshold_high -> approver_slack_id = ops_manager_id
IF estimated_cost_usd >= threshold_high -> approver_slack_id = finance_lead_id
// Thresholds to be confirmed with process owner before build
//
// Output (on approve)
approval_decision: {
  request_id: string
  decision: 'approved'
  approver_slack_id: string
  approver_name: string
  decided_at: ISO8601 timestamp
}
//
// Output (on reject)
approval_decision: {
  request_id: string
  decision: 'rejected'
  rejection_reason: string      // Free-text from Slack modal on reject button
  approver_slack_id: string
  decided_at: ISO8601 timestamp
}
// Rejected requests route to ops manager exception path (manual)
//
// On reminder (24 hrs no response)
slack_dm -> approver_slack_id: {
  message: 'Reminder: purchase request [request_id] is awaiting your approval.'
  action_buttons: ['Approve', 'Reject']
}
//
// On escalation (48 hrs no response)
slack_dm -> ops_manager_slack_id: {
  message: 'Request [request_id] has not received a response after two reminders. Manual follow-up required.'
}
request_status: 'escalated'
  • Slack interactive messages require the platform's Slack app to have the chat:write and commands OAuth scopes enabled, and an interactive components endpoint configured in the Slack App manifest.
  • Spend threshold values (threshold_low and threshold_high) and the Slack member IDs of each approver tier must be provided by the process owner before build and stored as environment variables in the platform credential store.
  • The Reject button must trigger a Slack modal (view_open) prompting the approver for a free-text rejection reason. This reason is stored against the request record and included in the ops manager exception notification.
  • The 24-hour reminder timer must use the platform's built-in wait or delay node, not an external cron job, to keep execution state within the workflow context.
  • Fallback: if the approver_slack_id lookup returns no match (for example, the staff member has left the workspace), the agent must notify the ops manager Slack ID immediately and set the request status to 'routing_error'.
  • Confirm before build: the three spend threshold values, the Slack member IDs for all approver tiers and the ops manager, and whether rejected requests should also trigger a Slack DM to the original requester explaining the outcome.
PO Creation and Notification Agent

Triggered by a confirmed approval decision from the Approval Routing Agent. Creates a draft purchase order in Xero using the validated request data, assigning the next sequential PO number and attaching the budget code to the line item. Generates the PO as a PDF and emails it to the supplier contact via Gmail with a request for written confirmation of receipt and estimated delivery date. Writes a complete row to the Google Sheets procurement register including PO number, supplier, value, approval timestamp, and expected delivery date. Sends a Slack direct message to the original requester confirming the PO number, supplier, and expected delivery date. Monitors for supplier email reply: if no reply is received within 48 hours, a single automated follow-up email is sent via Gmail. All step outcomes and timestamps are logged to the Google Sheets register.

Trigger
Approval decision confirmed as 'approved' by Approval Routing Agent
Tools
Xero, Gmail, Google Sheets, Slack
Replaces steps
Steps 5, 6, 7, 8, 9, and 10 (PO creation, supplier email, Xero logging, supplier confirmation tracking, requester notification, and filing)
Estimated build
20 hours, Complex complexity
// Input
approval_decision: {
  request_id: string
  decision: 'approved'
  approver_name: string
  decided_at: ISO8601 timestamp
}
validated_request: {            // Passed through from Intake Agent record
  requester_slack_id: string
  supplier_name: string
  supplier_email: string        // From form field or supplier reference sheet
  item_description: string
  quantity: integer
  estimated_cost_usd: float
  budget_code: string
  department: string
}
//
// Xero PO creation
POST /api.xro/2.0/PurchaseOrders
{
  Type: 'PURCHASEORDER',
  Contact: { Name: supplier_name },
  LineItems: [{
    Description: item_description,
    Quantity: quantity,
    UnitAmount: estimated_cost_usd / quantity,
    AccountCode: budget_code
  }],
  Status: 'DRAFT'
}
// Response includes PurchaseOrderID and PurchaseOrderNumber
//
// Gmail supplier send
to: supplier_email
subject: 'Purchase Order [po_number] from [YourCompany.com]'
body: PO summary + PDF attachment
reply_monitoring: true          // Watch for reply thread within 48 hrs
//
// Google Sheets register row
sheet: 'Procurement Register'
columns: [request_id, po_number, supplier_name, item_description,
          quantity, estimated_cost_usd, budget_code, department,
          approver_name, decided_at, supplier_email_sent_at,
          supplier_confirmed_at, confirmation_status, requester_notified_at]
//
// Slack requester notification
slack_dm -> requester_slack_id: {
  message: 'Your purchase request has been approved. PO [po_number] has been sent to [supplier_name]. Expected delivery: [delivery_date].'
}
//
// Output
po_number: string               // Assigned by Xero
xero_purchase_order_id: string
supplier_email_sent_at: ISO8601 timestamp
sheets_row_id: integer          // Row number in Procurement Register
requester_notified_at: ISO8601 timestamp
confirmation_status: 'pending' | 'confirmed' | 'follow_up_sent' | 'no_response'
//
// On approval (supplier confirmation follow-up after 48 hrs)
IF no reply to Gmail thread within 48 hrs:
  send follow-up email to supplier_email
  update sheets: confirmation_status = 'follow_up_sent'
IF still no reply after further 48 hrs:
  update sheets: confirmation_status = 'no_response'
  slack_dm -> ops_manager_slack_id: 'No supplier confirmation received for PO [po_number]. Manual follow-up required.'
  • Xero API access requires a connected app with OAuth 2.0 credentials and the accounting.transactions scope. Confirm the Xero subscription plan supports API access (Starter plan does not include full API; Growing or Established plan required).
  • The Xero PO contact must match an existing Xero contact record by supplier name. If no matching contact exists, the agent must create a new contact before raising the PO. A fallback flag must be logged when a new contact is auto-created.
  • Supplier email address must be stored in the Google Form as a required field or sourced from a validated supplier reference Google Sheet. The reference sheet column name must be agreed before build (recommended: 'supplier_email').
  • Gmail send must use a service account or OAuth token scoped to gmail.send. Confirm whether the sending address should be a shared ops mailbox or a personal account. A shared mailbox is strongly recommended for audit consistency.
  • Supplier reply monitoring: the platform must poll the Gmail thread or label for a reply. If the platform's Gmail connector does not support native thread monitoring, implement a scheduled poll every 6 hours checking for a reply to the sent message ID.
  • Google Sheets column order must be locked before build. The FullSpec team will provide a column schema; the ops manager must not add or reorder columns without notifying the build team, as downstream row-append logic references fixed column indices.
  • Confirm before build: Xero plan tier and connected app credentials, Gmail sending address and OAuth method, supplier reference data source and column name, and Google Sheets file ID and sheet tab name for the Procurement Register.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow. Field names are exact. Environment variables (ENV.*) are set in the platform credential store before build.
// ============================================================
// PURCHASE ORDER MANAGEMENT: END-TO-END DATA FLOW
// FullSpec automation platform | Operations Department
// ============================================================

// TRIGGER
// Staff member submits Google Form purchase request
GOOGLE_FORMS.onSubmit -> {
  form_submission_id: string      // Google Forms response ID (e.g. 'ACYDBNi...')
  timestamp: ISO8601
  requester_email: string         // Used to resolve Slack member ID
  supplier_name: string
  item_description: string
  quantity: integer
  estimated_cost_usd: float
  budget_code: string
  department: string
  supplier_email: string
}

// ---- AGENT HANDOFF 1: Form submission -> Intake and Validation Agent ----

// INTAKE AND VALIDATION AGENT
// Resolves requester_email -> requester_slack_id via Slack users.lookupByEmail
SLACK.users.lookupByEmail(requester_email) -> requester_slack_id: string

// Dedup check: form_submission_id looked up in dedup_log
IF form_submission_id IN dedup_log -> DISCARD, log warning, HALT
ELSE -> WRITE form_submission_id to dedup_log

// Field validation
required_fields: [supplier_name, item_description, quantity,
                  estimated_cost_usd, budget_code, department, supplier_email]
estimated_cost_usd > 0
budget_code IN approved_budget_codes[]

// On validation fail
IF missing_fields.length > 0:
  SLACK.chat.postMessage(
    channel: requester_slack_id,
    text: 'Purchase request incomplete. Missing: [missing_fields]'
  )
  HALT

// On validation pass
request_id = UUID.generate()     // Assigned here, carried through all agents
validated_request = {
  request_id, form_submission_id, requester_slack_id,
  supplier_name, item_description, quantity,
  estimated_cost_usd, budget_code, department,
  supplier_email, submitted_at, validation_status: 'passed'
}

// ---- AGENT HANDOFF 2: Validated record -> Approval Routing Agent ----

// APPROVAL ROUTING AGENT
// Spend threshold routing (values set in env vars pre-build)
IF estimated_cost_usd < THRESHOLD_LOW:
  approver_slack_id = ENV.DEPT_MANAGER_SLACK_ID
ELSE IF estimated_cost_usd < THRESHOLD_HIGH:
  approver_slack_id = ENV.OPS_MANAGER_SLACK_ID
ELSE:
  approver_slack_id = ENV.FINANCE_LEAD_SLACK_ID

// Fallback: if approver_slack_id unresolved
IF approver_slack_id == NULL:
  SLACK.chat.postMessage(channel: ENV.OPS_MANAGER_SLACK_ID,
    text: 'Routing error for request [request_id]. Approver not found.')
  request_status = 'routing_error'
  HALT

// Post Slack Block Kit approval message
approval_sent_at = NOW()
SLACK.chat.postMessage(
  channel: approver_slack_id,
  blocks: [
    section: '[item_description] from [supplier_name] | $[estimated_cost_usd] | [budget_code]',
    actions: [{ type: 'button', text: 'Approve', action_id: 'po_approve' },
              { type: 'button', text: 'Reject',  action_id: 'po_reject'  }]
  ]
)

// Wait for response (24-hour window)
WAIT 24h
IF no_response:
  SLACK.chat.postMessage(channel: approver_slack_id,
    text: 'Reminder: request [request_id] awaits your approval.')
  WAIT 24h
  IF no_response:
    SLACK.chat.postMessage(channel: ENV.OPS_MANAGER_SLACK_ID,
      text: 'Escalation: request [request_id] unresponded after 48h.')
    request_status = 'escalated'
    HALT

// On reject button
IF action_id == 'po_reject':
  SLACK.views.open(trigger_id, modal: { input: 'rejection_reason' })
  approval_decision = {
    request_id, decision: 'rejected',
    rejection_reason: string,
    approver_slack_id, decided_at: NOW()
  }
  // Route to ops manager exception path (manual) - no further automation
  SLACK.chat.postMessage(channel: ENV.OPS_MANAGER_SLACK_ID,
    text: 'Request [request_id] rejected. Reason: [rejection_reason]')
  HALT

// On approve button
IF action_id == 'po_approve':
  approval_decision = {
    request_id, decision: 'approved',
    approver_slack_id, approver_name: string, decided_at: NOW()
  }

// ---- AGENT HANDOFF 3: Approval decision -> PO Creation and Notification Agent ----

// PO CREATION AND NOTIFICATION AGENT

// Step A: Create PO in Xero
// Check for existing Xero contact
GET /api.xro/2.0/Contacts?where=Name==[supplier_name]
IF contact_not_found:
  POST /api.xro/2.0/Contacts { Name: supplier_name, EmailAddress: supplier_email }
  LOG 'Auto-created Xero contact for [supplier_name]'

POST /api.xro/2.0/PurchaseOrders {
  Type: 'PURCHASEORDER',
  Contact: { ContactID: xero_contact_id },
  LineItems: [{
    Description: item_description,
    Quantity: quantity,
    UnitAmount: estimated_cost_usd / quantity,
    AccountCode: budget_code
  }],
  Status: 'DRAFT'
}
-> xero_purchase_order_id: string
-> po_number: string              // Xero-assigned sequential number

// Step B: Send PO to supplier via Gmail
supplier_email_sent_at = NOW()
GMAIL.send(
  to: supplier_email,
  from: ENV.OPS_GMAIL_ADDRESS,
  subject: 'Purchase Order [po_number] from [YourCompany.com]',
  body: PO summary text,
  attachment: Xero PO PDF (GET /api.xro/2.0/PurchaseOrders/[xero_purchase_order_id]/pdf)
)
sent_message_id: string          // Gmail thread ID stored for reply monitoring

// Step C: Log to Google Sheets Procurement Register
GOOGLE_SHEETS.appendRow(
  spreadsheet_id: ENV.PROCUREMENT_SHEET_ID,
  sheet: 'Procurement Register',
  values: [request_id, po_number, supplier_name, item_description,
           quantity, estimated_cost_usd, budget_code, department,
           approver_name, decided_at, supplier_email_sent_at,
           '', 'pending', '']
)                                // confirmation cols updated on reply

// Step D: Notify requester in Slack
requester_notified_at = NOW()
SLACK.chat.postMessage(
  channel: requester_slack_id,
  text: 'PO [po_number] approved and sent to [supplier_name]. Expected delivery to be confirmed.'
)

// Step E: Supplier confirmation monitoring
WAIT 48h
POLL GMAIL thread(sent_message_id) for reply
IF reply_received:
  supplier_confirmed_at = NOW()
  GOOGLE_SHEETS.updateRow(confirmation_status: 'confirmed', supplier_confirmed_at)
  SLACK.chat.postMessage(channel: requester_slack_id,
    text: 'Supplier [supplier_name] has confirmed PO [po_number].')
ELSE:
  GMAIL.send(to: supplier_email, subject: 'Follow-up: PO [po_number] confirmation requested')
  GOOGLE_SHEETS.updateRow(confirmation_status: 'follow_up_sent')
  WAIT 48h
  IF still_no_reply:
    GOOGLE_SHEETS.updateRow(confirmation_status: 'no_response')
    SLACK.chat.postMessage(channel: ENV.OPS_MANAGER_SLACK_ID,
      text: 'No supplier confirmation for PO [po_number]. Manual follow-up required.')

// ============================================================
// END OF AUTOMATED FLOW
// Rejected and escalated requests exit to ops manager (manual exception path)
// ============================================================
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
One workflow per agent, three workflows total: Intake and Validation, Approval Routing, PO Creation and Notification. All three share a single credential store within the automation platform so Xero, Gmail, Slack, and Google Sheets tokens are configured once and referenced by each workflow. Inter-workflow communication uses the platform's native call/webhook mechanism or a shared execution context variable to pass the validated_request and approval_decision objects between agents.
Webhook configuration
The Intake and Validation Agent registers a webhook endpoint with the automation platform to receive Google Forms submissions. The Google Form's response webhook is configured in the Form's Apps Script trigger or via the platform's native Forms connector. The Approval Routing Agent exposes an interactive components endpoint for Slack action payloads (button clicks and modal submissions). This endpoint URL is registered in the Slack App manifest under 'Interactivity and Shortcuts'. Both endpoints must be HTTPS and must return a 200 response within 3 seconds to prevent Slack from retrying. The platform's built-in webhook handler satisfies this requirement; the Slack payload processing is handled asynchronously after the immediate 200 acknowledgement.
Templating approach
Slack Block Kit JSON templates for the approval message and DM notifications are stored as reusable JSON files within the platform workflow. Variable substitution (supplier_name, estimated_cost_usd, request_id, po_number) is applied at runtime using the platform's expression engine. The Gmail supplier email body uses a plain-text template stored as an environment variable, with the Xero-generated PDF attached via a direct API download from the Xero PurchaseOrders PDF endpoint. No external templating engine is required.
Error logging
All agent-level errors (validation failures, Xero API errors, Gmail send failures, Slack API errors, Google Sheets write failures) are written to a Supabase table named po_automation_errors with columns: error_id (UUID), request_id, agent_name, error_code, error_message, occurred_at (ISO8601), resolved (boolean). A Slack alert is sent to the ops manager Slack ID whenever a new row is inserted into this table, using a Supabase database webhook firing the platform's Slack send step. This provides real-time visibility of build-side failures without requiring the ops team to monitor platform logs directly.
Testing approach
All three agents are built and tested in sandbox environments before connecting to production credentials. Xero sandbox (demo company) is used for PO creation tests. A Slack test workspace is used for approval routing tests. A test Google Sheet and test Gmail account are used for Sheets and email tests. Each agent is tested independently before end-to-end flow testing. End-to-end testing uses real sample request data supplied by the ops team during UAT. Production credentials are only connected after UAT sign-off from the process owner.
Estimated total build time
Intake and Validation Agent: 10 hours. Approval Routing Agent: 14 hours. PO Creation and Notification Agent: 20 hours. End-to-end integration testing and UAT support: 4 hours. Total: 48 hours across a 4-week delivery schedule.
Before build starts, the FullSpec team requires the following from the process owner: (1) Xero connected app client ID and secret with the accounting.transactions OAuth scope confirmed; (2) Slack app credentials with chat:write, users:read, users:read.email, and views:open scopes approved in the workspace; (3) Gmail OAuth credentials for the ops sending address or confirmation that a Google service account will be used; (4) Google Sheets file ID and tab name for the Procurement Register; (5) spend threshold values for approval routing; (6) Slack member IDs for all approver tiers and the ops manager; (7) approved budget code list; and (8) supplier reference data source. Any of these items outstanding at build start will cause a proportional delay to the delivery timeline. Direct any access or credential queries to support@gofullspec.com.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Purchase Order Management.

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