Skip to content

Simplified PO Spec V1

We’re adding a fast “Generate PO from Order Queue” workflow that lets a user take all card-triggered replenishment needs, grouped by Vendor + Order Mechanism, review/edit them in a PO side panel, and generate a vendor-ready PO PDF via the Documint service.

The goal is to reduce PO friction


  • Order Queue view groups items by Vendor and Order Mechanism
  • “Order All” per group opens a side panel populated from queue data
  • Aggregation of like items into single PO lines (combining quantities, preserving contributing card traceability)
  • Full editability in the side panel (PO header + PO lines + vendor fields)
  • Vendor name typeahead lookup with free-text fallback
  • Generate PO action: send PO payload → Documint service → receive PO PDF (or link)
  • On clicking “generate”, copy standard vendor email body to clipboard + generate PDF
  • Explicit empty/error states for missing vendor/mechanism, doc failures, clipboard failures
  • Persisting generated POs as a first-class “PO object” in Arda (unless already exists)
  • Full Vendor master list / vendor management UI (we only do typeahead + free entry)
  • Sending email directly from Arda (we only copy email body; user attaches PDF manually)
  • Advanced pricing, tax, shipping cost calculations (unless already in Arda core)
  • Receiving workflow changes

3) Primary User Flow (Order Queue → Side Panel → Generate PO)

Section titled “3) Primary User Flow (Order Queue → Side Panel → Generate PO)”
  1. User navigates to Order Queue
  2. Order Queue renders grouped sections by:
    • Vendor
    • Order Mechanism
  3. User selects a section and clicks Order All or Order Item
  4. Side panel opens with:
    • Vendor fields
      1. Vendor Name taken from Order Queue aggregation
      2. Address, free field (until we have a vendor database)
      3. Contact info - Free field (until we have a vendor database)
    • PO header fields (free entry)
    • Aggregated PO lines derived from all cards in that group
    • Editable email template “Hi there, Please see attached PO”
  5. User optionally edits header/lines (including quantity and units)
  6. User clicks Generate PO
  7. System sends PO data to Documint
  8. Documint returns PDF in new tab
  9. UI shows a progress wheel while PO is generated in the side panel as well as in the Order Queue.
  10. System copies edited email body to clipboard and confirms “Copied”

4) Interaction Spec (screen-by-screen + component behaviors)

Section titled “4) Interaction Spec (screen-by-screen + component behaviors)”

Wireframe outline (text)

Order Queue

  • Search / filter (optional) ………………… Refresh

Group: Vendor = McMaster-Carr | Mechanism = Email

  • Button: Order All

  • Summary: N cards, M unique items

  • List of queue entries (can remain per-card/per-trigger in the queue view)

Group: Vendor = Grainger | Mechanism = Portal

  • Button: Order All

  • Summary: N cards, M unique items

  • List entries

Group: Vendor = Unassigned | Mechanism = Unknown

  • Order All disabled (default)

  • Guidance: “Assign a vendor to order these items.”

  1. Enter Order Queue
  • System must fetch current queue entries (card-triggered items).
  • UI must group them into sections by Vendor + Order Mechanism.
  1. Group header
  • Must display: vendor name and mechanism.
  • Must show an Order All button
  • Order Queue item definition
  • Each entry is a card-triggered replenishment request.
  • Group CTA: Order All
  1. Click Order All
  • Must open PO side panel anchored to the right.
  • Must load only items belonging to the clicked vendor + mechanism group.
  1. Selectable lines
  • Select box next to each line in order queue.
  • If some are selected, Order All changes to Order Selected

Wireframe outline (text)

Side Panel Title: Create Purchase Order

Section: Vendor

  • Vendor Name (Populated from Vendor Queue)
  • Contact Email (free text)
  • Phone (free text)
  • Address (free text)
  • Order Mechanism (read-only, inherited from group)

Section: PO Header

  • PO Number (auto or editable?)
  • Date (defaults to today, editable)
  • Good Through
  • Ship To (free text until we have vendors)
  • Bill To (free text until we have vendors)
  • Payment Terms (free text until we have vendors)
  • Shipping Terms [Selectable overnight, ground]+ free text override)
  • Notes / Instructions (free text until we have vendors)

Section: Lines (Aggregated) (AG Grid element?)

  • Table/list: Item / Description | Qty | Unit | Vendor SKU | Taxable status | Notes (all editable)
  • Shipping (free text)
  • Taxes Rate (auto calculates based on line items with unit price)
  • Total

Footer

  • Primary CTA: Generate PO (Generate PO + Copy Email body)
  • Secondary: Cancel

After generation

  • Show: PDF generated
  • Show: “Email body copied to clipboard” (if successful)
  1. Vendor Name (filled in from order queue)
  • Allow to edit Vendor name on PO
  • Minimum required vendor field: vendor name (required to generate).
  • Optional vendor fields (free entry): email, phone, address.
  1. PO header fields
  • Must be editable free fields.
  • Defaults:
    • Date defaults to today (is editable)
    • PO Number either auto-generated placeholder or blank (decision).
  1. Aggregated lines section
  • Populated automatically from the chosen Order Queue group or selected lines
  • Each line represents a unique item (same item ID/SKU) aggregated across contributing cards when generated
  • Each line must have editable fields:
    • Quantity (editable; overrides computed quantity)
    • Description (editable)
    • Vendor SKU
    • Unit (editable if present)
    • Notes (editable)
    • Unit price only if we include it in the UI (optional for this iteration)
  • Contributing Cards
    • The cards are aggregated into each line. Contributing card references are not persisted after PO generation
    • The cards that aggregate to each line are marked as Order in progress when Generate PO is clicked
  1. Cancel / close
  • Cancel closes the panel without generating.
    • Contributing cards are not marked as Order in Progress
  • If edits exist, show confirmation:
    • Title: “Discard changes?”
    • Buttons: “Discard” / “Keep editing”
  1. Generate PO
  • Button label: Generate PO

  • On click:

    • Validate required fields (vendor name; at least one line).
    • Show loading state: “Generating…”
      Call Document service.
  • On success:

    • Show confirmation: “PO generated.”
    • Opens new tab with PO PDF (unless there’s other behavior we can do with Documint)
    • Copy vendor email body to clipboard and confirm “Email body copied to clipboard”
  • On failure:

    • Show: “Couldn’t generate PO. Try again.”
    • Do not show clipboard success unless copy actually occurred.
  1. Clipboard copy behavior
  • Recommended: copy occurs after successful PDF generation.

  • If clipboard copy fails, show fallback:

    • Message: “Clipboard permission blocked.”

    • Button: Copy email text


5) Data Behavior Rules (grouping, aggregation, quantities, editability)

Section titled “5) Data Behavior Rules (grouping, aggregation, quantities, editability)”

5.1 Grouping rules (Order Queue → group lists)

Section titled “5.1 Grouping rules (Order Queue → group lists)”
  1. Group key = Vendor + Order Mechanism

  2. If vendor missing:

  • Place in “Unassigned vendor” group

  • Order All disabled by default (recommended)

  1. If mechanism missing:
  • Place in “Unknown mechanism” group

  • Order All disabled by default (recommended)

5.2 Aggregation rules (group → PO lines)

Section titled “5.2 Aggregation rules (group → PO lines)”
  1. “Like items” definition: same item ID (canonical), fallback to SKU if item ID missing

  2. For each unique item:

  • Create one PO line

  • Attach all contributing cards for that item from the selected group

  1. Quantity computation:
  • Computed quantity = sum of reorder quantities across contributing cards

  • Line quantity defaults to computed quantity

  1. Apply units from first card
  1. All line fields are editable prior to generation

  2. Quantity override:

  • User-entered quantity becomes the final quantity for the generated PO
  1. Cards traceability:
  • None once the PO is generated
  1. Free text entry
    1. Button to add a line to the PO
    2. All columns free text
    3. Quantity

6) Backend / Integration Touchpoints (requests, responses, error handling)

Section titled “6) Backend / Integration Touchpoints (requests, responses, error handling)”

Order Queue entries must provide:

  • Item identity (ID + display label)

  • Card identity (ID + optional friendly label)

  • Reorder quantity (+ unit if applicable)

  • Vendor association (name or ID)

  • Order mechanism (for example: Email, Portal, Unknown)

When the user clicks Generate PO:

  1. Frontend assembles a PO payload containing:
  • Header fields (vendor + PO header fields)

  • Lines (aggregated by item)

  • Per-line: computed quantity, final quantity, and list of contributing cards with their quantities

  • Context metadata (source: order queue; vendor/mechanism group key; user)

  1. Document service returns:
  • Either a PDF URL/link + filename, or a PDF blob (preferred: URL + metadata)

Link to Documint Template Details (https://docs.google.com/document/d/10kmDva7aiIT1N-x3BAm6fmDOfjZAj1IfWJT1tCtBIeo/edit?tab=t.0\#heading=h.nel4km8rdz8q)

  • Document service failure:

    • UI: “Couldn’t generate PO. Try again.”

    • Enable retry

    • Do not claim clipboard copied if generation failed

  • Clipboard failure:

    • UI: “Clipboard permission blocked. Click to copy.”

    • Provide manual copy button


  1. Unassigned vendor
  • Group appears as “Unassigned vendor”

  • Order All disabled (default)

  • Tooltip: “Assign a vendor to order these items.”

  1. Order Queue changes while panel open
  • Nothing happens.
  1. Validation
  • Missing vendor name: inline error “Vendor name is required.”

  • No lines: “Add at least one line item to generate a PO.”

  1. User cancels mid-generation
  • If in-flight request, confirm: “Cancel generation?” and either abort (if supported) or explain “Finishing request…” (decision).

8) Success Criteria (what “working” means)

Section titled “8) Success Criteria (what “working” means)”
  • Order Queue groups entries by vendor + mechanism with clear, correct grouping.

  • Order All opens side panel with aggregated lines:

    • Like items combined into one line

    • Quantities equal the sum of contributing card reorder quantities

    • Contributing cards visible via expand/collapse

  • All header and line fields are editable prior to generation.

  • Generate PO produces a valid PDF and presents it to the user.

  • On successful generation, email body is copied to clipboard and confirmed; clipboard failures have a usable fallback.

  • Failures never show false success states.


  1. PO Number: auto-generated by Arda vs user-entered vs generated by Document service? Resolved: auto-generated by Arda, editable by the user in the side panel before generation.

  2. Snapshot vs live sync: how to handle Order Queue changes while side panel is open?

  3. Can users exclude a line or exclude specific contributing cards from the PO draft (without changing the queue)?

  4. Do we have item “order unit”, pack size, or ordering increments today? If not, is “quantities as-is” acceptable?

  5. Typeahead suggestion source: past vendor strings, item supply records, or something else?

  6. Document service response: PDF URL vs PDF blob; do we need storage, access control, expiration?

  7. After generation: do we persist a PO record in Arda (even minimal) or treat this as export-only for now?

  8. Clipboard timing: copy email body only on successful generation (recommended) vs immediately on click?

  9. Email template ownership: fixed template vs org-configurable (signature, wording)?

PathTypeUsed in PO Template?
$object
eIdstring
headerobject
header.rIdstring
header.asOfobject
header.asOf.effectivenumber
header.asOf.recordednumber
header.payloadobject
header.payload.eIdstring
header.payload.statusstring
header.payload.orderNumberstringYes
header.payload.orderDateobject
header.payload.orderDate.timestampdateYes
header.payload.orderDate.tzstring
header.payload.allowPartialboolean
header.payload.expediteboolean
header.payload.deliverByobject
header.payload.deliverBy.timestampdateYes
header.payload.deliverBy.tzstring
header.payload.deliveryAddressobject
header.payload.deliveryAddress.addressLine1stringYes
header.payload.deliveryAddress.addressLine2stringYes
header.payload.deliveryAddress.citystringYes
header.payload.deliveryAddress.statestringYes
header.payload.deliveryAddress.postalCodestringYes
header.payload.deliveryAddress.countrystring
header.payload.deliveryAddress.geoLocationobject
header.payload.deliveryAddress.geoLocation.latitudenumber
header.payload.deliveryAddress.geoLocation.longitudenumber
header.payload.deliveryAddress.geoLocation.elevationnumber
header.payload.procurementobject
header.payload.procurement.salutationstring
header.payload.procurement.firstNamestring
header.payload.procurement.middleNamestring
header.payload.procurement.lastNamestring
header.payload.procurement.jobTitlestring
header.payload.procurement.emailstringYes
header.payload.procurement.phonestringYes
header.payload.procurement.postalAddressobject
header.payload.procurement.postalAddress.addressLine1stringYes
header.payload.procurement.postalAddress.addressLine2stringYes
header.payload.procurement.postalAddress.citystringYes
header.payload.procurement.postalAddress.statestringYes
header.payload.procurement.postalAddress.postalCodestringYes
header.payload.procurement.postalAddress.countrystringYes
header.payload.procurement.postalAddress.geoLocationobject
header.payload.procurement.postalAddress.geoLocation.latitudenumber
header.payload.procurement.postalAddress.geoLocation.longitudenumber
header.payload.procurement.postalAddress.geoLocation.elevationnumber
header.payload.procurement.emailsobject
header.payload.procurement.emails.vacationstring
header.payload.procurement.phonesobject
header.payload.procurement.addressesobject
header.payload.procurement.addresses.in lawsobject
header.payload.procurement.addresses.in laws.addressLine1string
header.payload.procurement.addresses.in laws.addressLine2string
header.payload.procurement.addresses.in laws.citystring
header.payload.procurement.addresses.in laws.statestring
header.payload.procurement.addresses.in laws.postalCodestring
header.payload.procurement.addresses.in laws.countrystring
header.payload.procurement.addresses.in laws.geoLocationobject
header.payload.procurement.addresses.in laws.geoLocation.latitudenumber
header.payload.procurement.addresses.in laws.geoLocation.longitudenumber
header.payload.procurement.addresses.in laws.geoLocation.elevationnumber
header.payload.procurement.sitesobject
header.payload.procurement.sites.linkedInstring
header.payload.supplierNamestringYes
header.payload.supplierAddressobject
header.payload.supplierAddress.addressLine1stringYes
header.payload.supplierAddress.addressLine2stringYes
header.payload.supplierAddress.citystringYes
header.payload.supplierAddress.statestringYes
header.payload.supplierAddress.postalCodestringYes
header.payload.supplierAddress.countrystring
header.payload.supplierAddress.geoLocationobject
header.payload.supplierAddress.geoLocation.latitudenumber
header.payload.supplierAddress.geoLocation.longitudenumber
header.payload.supplierAddress.geoLocation.elevationnumber
header.payload.orderMethodstring
header.payload.salesobject
header.payload.sales.salutationstring
header.payload.sales.firstNamestring
header.payload.sales.middleNamestring
header.payload.sales.lastNamestring
header.payload.sales.jobTitlestring
header.payload.sales.emailstringYes
header.payload.sales.phonestringYes
header.payload.sales.postalAddressobject
header.payload.sales.postalAddress.addressLine1string
header.payload.sales.postalAddress.addressLine2string
header.payload.sales.postalAddress.citystring
header.payload.sales.postalAddress.statestring
header.payload.sales.postalAddress.postalCodestring
header.payload.sales.postalAddress.countrystring
header.payload.sales.postalAddress.geoLocationobject
header.payload.sales.postalAddress.geoLocation.latitudenumber
header.payload.sales.postalAddress.geoLocation.longitudenumber
header.payload.sales.postalAddress.geoLocation.elevationnumber
header.payload.sales.emailsobject
header.payload.sales.emails.vacationstring
header.payload.sales.phonesobject
header.payload.sales.addressesobject
header.payload.sales.addresses.in lawsobject
header.payload.sales.addresses.in laws.addressLine1string
header.payload.sales.addresses.in laws.addressLine2string
header.payload.sales.addresses.in laws.citystring
header.payload.sales.addresses.in laws.statestring
header.payload.sales.addresses.in laws.postalCodestring
header.payload.sales.addresses.in laws.countrystring
header.payload.sales.addresses.in laws.geoLocationobject
header.payload.sales.addresses.in laws.geoLocation.latitudenumber
header.payload.sales.addresses.in laws.geoLocation.longitudenumber
header.payload.sales.addresses.in laws.geoLocation.elevationnumber
header.payload.sales.sitesobject
header.payload.sales.sites.linkedInstring
header.payload.goodsValueobject
header.payload.goodsValue.valuenumberYes
header.payload.goodsValue.currencystring
header.payload.taxesAndFeesobject
header.payload.taxesAndFees.Sales Taxobject
header.payload.taxesAndFees.Sales Tax.valuenumberYes
header.payload.taxesAndFees.Sales Tax.currencystring
header.payload.taxesAndFees.Shippingobject
header.payload.taxesAndFees.Shipping.valuenumberYes
header.payload.taxesAndFees.Shipping.currencystring
header.payload.taxesAndFees.Otherobject
header.payload.taxesAndFees.Other.valuenumberYes
header.payload.taxesAndFees.Other.currencystring
header.payload.totalAmountobject
header.payload.totalAmount.valuenumberYes
header.payload.totalAmount.currencystring
header.payload.accountingReferencestringYes
header.payload.termsAndConditionsstringYes
header.payload.notesstringYes
header.payload.privateNotesstring
header.payload.companyNamestringYes
header.metadataobject
header.metadata.tenantIdstring
header.metadata.currentLinesnumber
header.authorstring
header.createdBystring
header.createdAtobject
header.createdAt.effectivenumber
header.createdAt.recordednumber
header.retiredboolean
linesobject
lines.thisPagestring
lines.nextPagestring
lines.previousPagestring
lines.resultsarray
lines.results[]object
lines.results[].rIdstring
lines.results[].asOfobject
lines.results[].asOf.effectivenumber
lines.results[].asOf.recordednumber
lines.results[].payloadobject
lines.results[].payload.eIdstring
lines.results[].payload.titlestring
lines.results[].payload.descriptionstring
lines.results[].payload.statusstring
lines.results[].payload.itemobject
lines.results[].payload.item.eIdstring
lines.results[].payload.item.namestringYes
lines.results[].payload.supplierSkustringYes
lines.results[].payload.quantityobject
lines.results[].payload.quantity.amountnumberYes
lines.results[].payload.quantity.unitstringYes
lines.results[].payload.unitCostobject
lines.results[].payload.unitCost.valuenumberYes
lines.results[].payload.unitCost.currencystring
lines.results[].payload.receivedobject
lines.results[].payload.received.amountnumber
lines.results[].payload.received.unitstring
lines.results[].payload.notesstringYes
lines.results[].payload.privateNotesstring
lines.results[].payload.servicedCardsarray
lines.results[].payload.servicedCards[]object
lines.results[].payload.servicedCards[].cardEIdstring
lines.results[].payload.servicedCards[].serialNumberstring
lines.results[].payload.servicedCards[].quantityobject
lines.results[].payload.servicedCards[].quantity.amountnumber
lines.results[].payload.servicedCards[].quantity.unitstring
lines.results[].payload.servicedCards[].referenceobject
lines.results[].payload.servicedCards[].reference.cardEIdstring
lines.results[].payload.costobject
lines.results[].payload.cost.valuenumberYes
lines.results[].payload.cost.currencystring
lines.results[].metadataobject
lines.results[].metadata.parentEidstring
lines.results[].metadata.ranknumber
lines.results[].authorstring
lines.results[].createdBystring
lines.results[].createdAtobject
lines.results[].createdAt.effectivenumber
lines.results[].createdAt.recordednumber
lines.results[].retiredboolean
lines.totalCountnumber

\ No newline at end of file