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
Inventory Reorder Management
[YourCompany.com] · Operations Department · Prepared by FullSpec · [Today's Date]
This document gives the FullSpec build team everything needed to construct, connect, and ship the Inventory Reorder Management automation. It covers the current-state process map with bottlenecks identified, full agent specifications including IO contracts, the end-to-end data flow, and the recommended build stack. The FullSpec team owns all build and integration work. The operations team at [YourCompany.com] is responsible for confirming reorder thresholds, supplying clean supplier contact data, and granting API access before build begins.
01Process snapshot
02Agent specifications
Monitors Cin7 continuously by polling the inventory API at a configured interval. When a SKU's on-hand quantity meets or falls below its defined reorder point, the agent retrieves the SKU's average daily sales velocity and supplier lead time from Google Sheets, applies a safety stock buffer, and calculates the recommended order quantity. It then assembles a structured draft PO payload containing all fields required for Cin7 PO creation and passes this to the PO Dispatch and Tracking Agent. This agent replaces the four most time-intensive manual steps in the current process: the daily stock export, the spreadsheet threshold comparison, supplier lookup, and manual quantity calculation. Estimated build time: 10 hours. Complexity: Moderate.
// Input
Cin7 webhook/poll payload: {
sku_id: string,
sku_name: string,
on_hand_qty: number,
reorder_point: number,
unit_cost: number,
default_supplier_id: string
}
Google Sheets lookup row: {
sku_id: string,
avg_daily_sales: number,
supplier_lead_time_days: number,
safety_stock_days: number,
min_order_qty: number,
supplier_email: string,
supplier_name: string
}
// Output
Draft PO payload: {
sku_id: string,
sku_name: string,
supplier_id: string,
supplier_name: string,
supplier_email: string,
reorder_qty: number,
unit_cost: number,
total_value: number,
expected_delivery_date: ISO8601 date,
calculation_basis: {
avg_daily_sales: number,
lead_time_days: number,
safety_stock_days: number,
on_hand_at_trigger: number
},
cin7_draft_po_id: string
}- Cin7 API tier requirement: the Cin7 Core (formerly DEAR) plan must include API access. Confirm the active subscription tier before build begins. The polling endpoint is GET /v1/ref/product with quantity fields; PO creation uses POST /v1/purchase.
- Google Sheets reference tab must be named 'SKU_Reference' with columns in the exact order: sku_id, avg_daily_sales, supplier_lead_time_days, safety_stock_days, min_order_qty, supplier_email, supplier_name. Column headers are case-sensitive for the lookup mapping.
- Reorder quantity formula: reorder_qty = MAX(min_order_qty, CEIL((avg_daily_sales * (lead_time_days + safety_stock_days)) - on_hand_qty)). If the result is zero or negative after recalculation, the agent must skip the SKU and log a 'no_action' record rather than raising a zero-quantity PO.
- Dedupe behaviour: if a draft PO for the same sku_id already exists in Cin7 with status 'Draft' or 'Backordered', the agent must skip creation and log a 'duplicate_skipped' event. Check via GET /v1/purchase?productID={sku_id}&status=Draft before posting.
- Fallback: if the Google Sheets lookup returns no row for a sku_id, the agent must route to an error branch, post a Slack alert to the operations channel with the missing sku_id, and halt further processing for that SKU only.
- The Cin7 API key must be stored in the shared credential store and never hardcoded in workflow nodes. Credential name: 'Cin7_API_Prod'.
- Confirm with the operations manager that all SKUs in scope have the reorder_point field populated in Cin7 before go-live. Null or zero values will suppress triggers and cause missed reorders.
Receives the completed draft PO payload from the Reorder Calculation Agent and orchestrates the full dispatch, approval, notification, and tracking sequence. It evaluates the total PO value against a configurable approval threshold. Orders below the threshold proceed directly to supplier dispatch. Orders at or above the threshold trigger a Slack approval message to the operations manager with inline approve and reject actions. Once approved (or auto-approved), the agent sends the PO to the supplier via Gmail, creates a draft bill in Xero, appends a row to the Google Sheets PO tracker, and schedules a 24-hour chaser email if no supplier acknowledgement is detected. This agent replaces five manual steps and converts the manager approval from an untracked email chain into a structured, auditable Slack workflow. Estimated build time: 18 hours. Complexity: Complex.
// Input
Draft PO payload from Reorder Calculation Agent: {
sku_id, sku_name, supplier_id, supplier_name,
supplier_email, reorder_qty, unit_cost,
total_value, expected_delivery_date,
cin7_draft_po_id
}
// Decision branch
IF total_value >= approval_threshold:
-> POST Slack message to #ops-approvals channel
Slack payload: {
blocks: [section, actions],
actions: [{ action_id: 'approve_po', text: 'Approve' },
{ action_id: 'reject_po', text: 'Reject' }],
metadata: { cin7_draft_po_id, total_value, sku_name }
}
-> Wait for Slack interactive callback (timeout: 48 hrs)
-> On timeout: escalate to ops manager via direct Slack message
// On approval (or auto-approve for sub-threshold orders)
Cin7: PATCH /v1/purchase/{cin7_draft_po_id} -> status: 'Approved'
Gmail: Send PO email {
to: supplier_email,
subject: 'Purchase Order {cin7_draft_po_id} - {sku_name}',
body: template 'po_dispatch_template',
attachment: Cin7 PO PDF (GET /v1/purchase/{cin7_draft_po_id}/pdf)
}
Xero: POST /api.xro/2.0/PurchaseOrders -> Draft bill {
Contact: { supplier_name, supplier_id (Xero ContactID) },
LineItems: [{ Description: sku_name, Quantity: reorder_qty,
UnitAmount: unit_cost, AccountCode: '310' }],
Date: today, DueDate: expected_delivery_date,
Reference: cin7_draft_po_id
}
Google Sheets: APPEND row to 'PO_Tracker' tab {
po_id, sku_id, sku_name, supplier_name, reorder_qty,
total_value, expected_delivery_date, dispatch_timestamp,
approval_status, xero_bill_id, ack_received: false
}
// Chaser logic (24-hour timer node)
IF no supplier reply detected after 24 hrs:
Gmail: Send chaser {
to: supplier_email,
subject: 'Acknowledgement required: PO {cin7_draft_po_id}',
body: template 'po_chaser_template'
}
Slack: POST alert to #ops-approvals {
text: 'No acknowledgement received for PO {cin7_draft_po_id}
({sku_name}). Chaser sent to {supplier_email}.'
}
Google Sheets: UPDATE row -> chaser_sent: true, chaser_timestamp: now
// Output
Final tracker row state: {
po_id, sku_id, sku_name, supplier_name, reorder_qty,
total_value, expected_delivery_date, dispatch_timestamp,
approval_status: 'approved' | 'auto-approved' | 'rejected',
xero_bill_id: string,
ack_received: boolean,
chaser_sent: boolean
}- Approval threshold value must be agreed with the operations manager before build and stored as a configurable environment variable (ENV: PO_APPROVAL_THRESHOLD_USD). Do not hardcode this value in workflow nodes.
- Slack app must be installed to the workspace with scopes: chat:write, chat:write.public, im:write, and incoming-webhook. The interactive components endpoint must be configured in the Slack app manifest to point to the automation platform's webhook receiver URL.
- Gmail OAuth2 credentials must use the operations coordinator's Google Workspace account (or a dedicated service account with domain-wide delegation). Scopes required: https://www.googleapis.com/auth/gmail.send. Credential name in shared store: 'Gmail_OpsCoordinator_OAuth'.
- Xero connection requires OAuth2 with scopes: openid, profile, email, accounting.transactions, accounting.contacts.read. The Xero organisation must have the supplier already set up as a Contact before the bill creation step can map the ContactID. Build a lookup step: GET /api.xro/2.0/Contacts?where=Name='{supplier_name}' to resolve ContactID dynamically.
- Xero account code '310' is the default purchases account. Confirm this code is valid in the connected Xero organisation before go-live. Make it configurable via environment variable (ENV: XERO_PURCHASES_ACCOUNT_CODE).
- The 24-hour chaser timer must use the automation platform's native delay or schedule node, not a polling loop. If the platform supports it, store the scheduled chaser job ID in the Google Sheets tracker row so it can be cancelled if manual acknowledgement is logged.
- Rejection handling: if the operations manager rejects a PO in Slack, the workflow must update the Cin7 draft PO status to 'Voided', post a Slack notification to the operations channel with the rejection reason (captured from the Slack modal), and append a 'rejected' status row to the PO tracker. No email is sent to the supplier.
- Google Sheets tracker tab must be named exactly 'PO_Tracker'. The sheet ID must be stored as an environment variable (ENV: GSHEETS_PO_TRACKER_ID). Column order: po_id, sku_id, sku_name, supplier_name, reorder_qty, total_value, expected_delivery_date, dispatch_timestamp, approval_status, xero_bill_id, ack_received, chaser_sent, chaser_timestamp.
- Confirm that all active suppliers have a valid email address in the Google Sheets SKU_Reference tab before go-live. A missing supplier_email must trigger the error branch and Slack alert rather than a failed Gmail send.
03End-to-end data flow
// ─────────────────────────────────────────────────────────────────
// TRIGGER: Cin7 poll detects SKU at or below reorder_point
// ─────────────────────────────────────────────────────────────────
Cin7.poll() -> GET /v1/ref/product
Filter: on_hand_qty <= reorder_point
Emit per matching SKU: {
sku_id, sku_name, on_hand_qty,
reorder_point, unit_cost, default_supplier_id
}
// ─────────────────────────────────────────────────────────────────
// DEDUPE CHECK: skip if active draft PO already exists
// ─────────────────────────────────────────────────────────────────
Cin7.GET /v1/purchase?productID={sku_id}&status=Draft
IF existing draft found:
-> log 'duplicate_skipped' to error log table
-> HALT branch for this sku_id
ELSE:
-> continue to AGENT HANDOFF 1
// ═════════════════════════════════════════════════════════════════
// AGENT HANDOFF 1: Trigger -> Reorder Calculation Agent
// Fields passed: sku_id, sku_name, on_hand_qty, unit_cost,
// default_supplier_id
// ═════════════════════════════════════════════════════════════════
// ─────────────────────────────────────────────────────────────────
// REORDER CALCULATION AGENT: lookup + quantity calculation
// ─────────────────────────────────────────────────────────────────
GoogleSheets.lookup('SKU_Reference', sku_id)
Returns: {
avg_daily_sales, supplier_lead_time_days,
safety_stock_days, min_order_qty,
supplier_email, supplier_name
}
IF no row found for sku_id:
-> Slack.alert('#ops-approvals', 'Missing SKU reference: {sku_id}')
-> log error, HALT branch
reorder_qty = MAX(
min_order_qty,
CEIL((avg_daily_sales * (supplier_lead_time_days + safety_stock_days))
- on_hand_qty)
)
IF reorder_qty <= 0:
-> log 'no_action', HALT branch
expected_delivery_date = TODAY + supplier_lead_time_days (ISO8601)
total_value = reorder_qty * unit_cost
Cin7.POST /v1/purchase -> create draft PO {
supplier_id, sku_id, reorder_qty,
unit_cost, expected_delivery_date
}
Returns: cin7_draft_po_id
// ═════════════════════════════════════════════════════════════════
// AGENT HANDOFF 2: Reorder Calculation Agent -> PO Dispatch Agent
// Fields passed: sku_id, sku_name, supplier_id, supplier_name,
// supplier_email, reorder_qty, unit_cost, total_value,
// expected_delivery_date, cin7_draft_po_id
// ═════════════════════════════════════════════════════════════════
// ─────────────────────────────────────────────────────────────────
// PO DISPATCH AND TRACKING AGENT: approval gate
// ─────────────────────────────────────────────────────────────────
IF total_value >= ENV.PO_APPROVAL_THRESHOLD_USD:
Slack.POST('#ops-approvals') -> approval message {
sku_name, supplier_name, reorder_qty,
total_value, expected_delivery_date,
actions: [approve_po, reject_po],
metadata: { cin7_draft_po_id }
}
WAIT for Slack interactive callback (timeout: 48 hrs)
ON reject_po:
Cin7.PATCH /v1/purchase/{cin7_draft_po_id} -> status: 'Voided'
GoogleSheets.APPEND 'PO_Tracker' -> approval_status: 'rejected'
Slack.POST('#ops-approvals') -> rejection notification
-> HALT
ON timeout:
Slack.DM(ops_manager_user_id) -> escalation message
-> HALT until response received
ELSE:
approval_status = 'auto-approved'
-> continue
// ─────────────────────────────────────────────────────────────────
// PO DISPATCH: Cin7 approve, Gmail send, Xero bill, Sheets log
// ─────────────────────────────────────────────────────────────────
Cin7.PATCH /v1/purchase/{cin7_draft_po_id} -> status: 'Approved'
Cin7.GET /v1/purchase/{cin7_draft_po_id}/pdf -> po_pdf_bytes
Gmail.send({
to: supplier_email,
subject: 'Purchase Order {cin7_draft_po_id} - {sku_name}',
body: template('po_dispatch_template', { po fields }),
attachment: { filename: 'PO_{cin7_draft_po_id}.pdf', content: po_pdf_bytes }
})
Records: dispatch_timestamp = now()
Xero.GET /api.xro/2.0/Contacts?where=Name='{supplier_name}'
Returns: xero_contact_id
Xero.POST /api.xro/2.0/PurchaseOrders {
Contact: { ContactID: xero_contact_id },
LineItems: [{ Description: sku_name, Quantity: reorder_qty,
UnitAmount: unit_cost,
AccountCode: ENV.XERO_PURCHASES_ACCOUNT_CODE }],
Date: today, DueDate: expected_delivery_date,
Reference: cin7_draft_po_id,
Status: 'DRAFT'
}
Returns: xero_bill_id
GoogleSheets.APPEND('PO_Tracker', {
po_id: cin7_draft_po_id, sku_id, sku_name,
supplier_name, reorder_qty, total_value,
expected_delivery_date, dispatch_timestamp,
approval_status, xero_bill_id,
ack_received: false, chaser_sent: false
})
// ─────────────────────────────────────────────────────────────────
// CHASER TIMER: 24-hour delayed node
// ─────────────────────────────────────────────────────────────────
Schedule.delay(24hrs) ->
IF ack_received == false (read from PO_Tracker row):
Gmail.send({
to: supplier_email,
subject: 'Acknowledgement required: PO {cin7_draft_po_id}',
body: template('po_chaser_template', { po fields })
})
Slack.POST('#ops-approvals') {
text: 'No acknowledgement for PO {cin7_draft_po_id}
({sku_name}). Chaser sent to {supplier_email}.'
}
GoogleSheets.UPDATE('PO_Tracker', po_id) {
chaser_sent: true, chaser_timestamp: now()
}
// ─────────────────────────────────────────────────────────────────
// FINAL STATE: PO_Tracker row (terminal)
// ─────────────────────────────────────────────────────────────────
PO_Tracker row: {
po_id, sku_id, sku_name, supplier_name, reorder_qty,
total_value, expected_delivery_date, dispatch_timestamp,
approval_status: 'approved' | 'auto-approved' | 'rejected',
xero_bill_id, ack_received, chaser_sent, chaser_timestamp
}04Recommended build stack
More documents for this process
Every document generated for Inventory Reorder Management.