Developer Handover Pack
Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.
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
02Agent specifications
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.
// 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.
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.
// 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.
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.
// 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.
03End-to-end data flow
// ============================================================
// 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)
// ============================================================04Recommended build stack
More documents for this process
Every document generated for Purchase Order Management.