ERP Unlocked: Order Handling Process
This document details the pages and components involved in creating and managing orders within the ERP Unlocked application, covering both manual order entry and PDF-based order extraction.
Overview
Section titled βOverviewβThe application provides three main pathways for receiving and creating orders destined for an ERP system (Prophet 21, WhereFour, Acumatica, Eclipse E4, NetSuite, Dynamics, Odoo):
- Unified Inbox & Email Intake (
src/pages/inbox.astro,src/pages/team.astro,src/pages/orders/email/[emailId].astro): Inbound emails and uploaded documents appear as work items in the 3-pane unified inbox (/inboxβInboxCockpit.tsxinsideCockpitShell.astro), with supervisor queue monitoring on/team(SupervisorInboxRoot.tsx). - Document Order Extraction & Review (
src/pages/orders/pdf/[documentId]/review.astro): Uploaded or emailed purchase orders (PDF, spreadsheet, CSV, image, email, text, audio) are processed by the Temporal extraction pipeline (apps/temporal-worker) to extract order data, which operators review, edit, and submit using the 3-panereview-v2workbench (src/components/orders/review-v2/ReviewResponsiveBody.tsxinsideCockpitShell.astro). - Manual Order Entry (
src/pages/orders/new.astro): Users can manually input order details and submit directly to the ERP.
Key Pages and Components
Section titled βKey Pages and ComponentsβHereβs a breakdown of the relevant files (under apps/webapp/) and their purposes:
1. Three-Pane Unified Inbox (src/pages/inbox.astro) & Supervisor Team View (src/pages/team.astro)
Section titled β1. Three-Pane Unified Inbox (src/pages/inbox.astro) & Supervisor Team View (src/pages/team.astro)β- Purpose:
src/pages/inbox.astrorendersInboxCockpit.tsxinsideCockpitShell.astro(<ShellFrame scroll="hidden">) as a 3-pane workspace (56pxAppRail.astro+CockpitRow.tsxqueue list +CockpitDetail.tsx/CockpitItemPreview.tsxinline preview). Operators triage, claim, review, and track incoming email and uploaded orders across status buckets (needs_review,needs_fix,processing,ready,failed). Supervisors monitor team queues and SLAs onsrc/pages/team.astro(SupervisorInboxRoot.tsx). - Functionality:
- Displays unified inbox work items (
work_itemstable) linked topdfDocuments,extractedOrders,orders, andemailEvents. - Renders an inline document/email preview and
AuroraRoutingCardin the right pane (CockpitDetail.tsx), with one-click navigation orEntershortcut to the full 3-pane review workbench (/orders/pdf/[documentId]/review) or email order view (/orders/email/[emailId]). - Provides quick actions to upload a document (
/orders/pdf/uploador via inbox upload modal) or create a manual order (/orders/new).
- Displays unified inbox work items (
2. Manual Order Creation (src/pages/orders/new.astro)
Section titled β2. Manual Order Creation (src/pages/orders/new.astro)β- Purpose: Allows users to create a new order manually step-by-step.
- Functionality:
- Multi-step form layout using React components (
<CustomerSearch>,<ShippingAddressSelector>,<ItemSearch>). - Uses Nanostores (
@/stores/orderStore) to manage state (selectedCustomer,selectedAddress,orderItems,isSubmitting). - Submits structured order data to
POST /api/erp/orders.
- Multi-step form layout using React components (
3. Order Detail & List Views (src/pages/orders/[id].astro)
Section titled β3. Order Detail & List Views (src/pages/orders/[id].astro)β- Purpose: Displays detailed status, line items, and ERP verification results for a specific order (
/orders/[id]).
4. Manual Order Edit (src/pages/orders/edit/[id].astro)
Section titled β4. Manual Order Edit (src/pages/orders/edit/[id].astro)β- Purpose: Allows modification of existing manually created orders (orders not originating from a PDF).
- Functionality:
- Fetches the specific order data and pre-populates form fields.
- Uses
@/stores/orderEditStorefor managing the state of the order being edited, saving changes locally viaPUT /api/orders/[id], and submitting final changes to the ERP viaPOST /api/erp/orders/[id]/submit.
5. PDF Order Upload (src/pages/orders/pdf/upload.astro)
Section titled β5. PDF Order Upload (src/pages/orders/pdf/upload.astro)β- Purpose: Allows users to upload PDF purchase orders for automated processing.
- Functionality:
- Uploads PDF via
POST /api/pdf-documents(stores in Cloudflare R2 and startsPDFExtractionWorkflowon Temporal). - Polls status endpoint
/api/pdf-documents/[id]and redirects to/orders/pdf/[documentId]/reviewon completion.
- Uploads PDF via
6. Email Order View (src/pages/orders/email/[emailId].astro)
Section titled β6. Email Order View (src/pages/orders/email/[emailId].astro)β- Purpose: Displays inbound email metadata, body, attachments, and linked extracted orders for orders received via email intake or OAuth pull.
7. Order Review Page (src/pages/orders/pdf/[documentId]/review.astro)
Section titled β7. Order Review Page (src/pages/orders/pdf/[documentId]/review.astro)β- Purpose: Allows users to review, edit, and approve/submit order data extracted from a document inside
CockpitShell.astro. Also handles editing of approved orders (mode=edit) and ERP revision resolution (RevisionPanel.tsx). - Functionality:
- Fetches
pdfDocument, associatedextractedOrders, andsignedViewUrl. - If
mode=editandorderIdis present, fetches the correspondingorder. - Renders the 3-pane
review-v2workbench (src/components/orders/review-v2/ReviewResponsiveBody.tsx) aboveReadinessFooter.tsx(note: legacy v1DocumentOrderReview.tsx,PDFOrderReview.tsx, andQuoteSplitter.tsxwere deleted in #2640).
- Fetches
8. Three-Pane Review V2 Workbench (src/components/orders/review-v2/)
Section titled β8. Three-Pane Review V2 Workbench (src/components/orders/review-v2/)β- Purpose: Provides the full-viewport 3-pane cockpit UI (
grid-template-columns: 280px minmax(320px, 1fr) minmax(0, 1.5fr)on desktop>= 768px,ReviewMobileTabson< 768px) for reviewing extracted fields, validating customers/addresses/items against ERP data, resolving revisions, and submitting orders to the ERP. - Key Sub-Components (
src/components/orders/review-v2/):- Pane 1 (
280pxleft) βReviewIssuesRail.tsx: Order header/metadata, customer & ship-to resolution cards,PoNumberInput,PoHistoryStrip, and prioritized issue/revision list (Needs attention,Changes,Unchanged). - Pane 2 (
minmax(320px, 1fr)center) βReviewCenterPane.tsx&RevisionPanel.tsx: Line-item table,MismatchDecisionCard,LineOverrideEditor,DiscountMatchPanel,QuoteSplitPanel,TemplateRevisionPicker, andERPSearchModal.tsx. - Pane 3 (
minmax(0, 1.5fr)right) βReviewDocViewer.tsx: Synchronized source document viewer (PDFViewerWithHighlightswith bounding-box highlights,SpreadsheetFullView,CSVPreview,ImageViewerWithHighlights,EmailPreview,TextPreview,AudioPreview). - Footer β
ReadinessFooter.tsx&ReviewSubmitFlow.tsx:auto 1fr autofooter displaying validation readiness, order totals, and the ERP submit / quote / approval CTA (ReviewErrorDrawer.tsxfor submission errors).
- Pane 1 (
- Functionality:
- Uses modular Nanostores in
src/stores/pdfOrder/(order.ts,actions.ts,validation.ts,header-suggest.ts,price-review.ts,quote-splits.ts) for review/approval state. - Supports direct ERP submission (
POST /api/erp/orders/[id]/submit), quote generation (POST /api/erp/orders/generate-quote), quote updates (PUT /api/erp/orders/[id]), and quote approval (POST /api/erp/orders/[id]/approve) β backed by durable Temporal workflows inapps/ts-temporal-worker/src/workflows/erp-submission.tsreturning202with aworkflowId(see Β§12, Β§14).
- Uses modular Nanostores in
9. Order Edit Router (src/pages/orders/pdf-order/[id].astro)
Section titled β9. Order Edit Router (src/pages/orders/pdf-order/[id].astro)β- Purpose: Acts as a stable URL entry point for editing any order, redirecting to the appropriate UI based on the orderβs origin.
- Functionality:
- Checks if the order has an
extractedOrderId(meaning it came from a PDF). - If yes, redirects to
/orders/pdf/[documentId]/review?mode=edit&orderId=[id]. - If no, redirects to
/orders/edit/[id].
- Checks if the order has an
10. Database Schema (packages/db/src/schema/)
Section titled β10. Database Schema (packages/db/src/schema/)βpdfDocuments(packages/db/src/schema/pdf-documents.ts):- Stores metadata about uploaded PDFs (
id,userId,clerkOrganizationId,filename,status,r2Key,processingError,createdAt,updatedAt).
- Stores metadata about uploaded PDFs (
extractedOrders(packages/db/src/schema/pdf-documents.ts):- Stores order data extracted from a PDF document (
id,pdfDocumentId,status,customerName,customerEmail,customerShippingAddress,orderDate,erpCustomerId,approvedOrderId,isEdited,isCustomerValidated,isShippingValidated).
- Stores order data extracted from a PDF document (
extractedOrderItems(packages/db/src/schema/pdf-documents.ts):- Stores line item data extracted from a PDF document (
id,extractedOrderId,itemIds,itemName,quantity,unitPrice,erpItemId,boundingBox,isEdited,isValidated).
- Stores line item data extracted from a PDF document (
orders(packages/db/src/schema/orders.ts):- Stores the final order/quote records submitted to the ERP (
id,userId,clerkOrganizationId,erpConnectionId,extractedOrderId,orderNumber,erpOrderNumber,erpOrderRef,customerName,orderDate,status,erpVerificationStatus,erpUnverifiedItemIds,totalAmount,items,orderData,errorMessage,submissionError,deletedAt).
- Stores the final order/quote records submitted to the ERP (
11. State Management (apps/webapp/src/stores/)
Section titled β11. State Management (apps/webapp/src/stores/)βsrc/stores/orderStore.ts: Manages state for the manual order creation flow (new.astro).src/stores/pdfOrder/(index.ts,order.ts,actions.ts,validation.ts,header-suggest.ts,price-review.ts,quote-splits.ts): Modular Nanostore suite managing state for PDF order review, validation, splitting, and ERP submission (review.astro,src/components/orders/review-v2/).src/stores/orderEditStore.ts: Manages state for editing existing manual orders (edit/[id].astro).
12. Backend API Routes (Structure)
Section titled β12. Backend API Routes (Structure)βThe API routes follow Astroβs file-based routing under src/pages/api/. Key directories related to orders include:
/api/orders/: Handles CRUD operations for the mainorderstable.[id].ts: Likely handles GET (fetch one), PUT (update local DB), DELETE.- (Implied
index.tsor similar for POST - create manual order initially, not directly to ERP).
/api/pdf-documents/: Manages the PDF document lifecycle.index.ts: Lists documents (GET) and uploads new documents (POST).[id].ts: Gets status/details of a specific PDF document (GET).
/api/extracted-orders/: Likely handles actions on orders before approval (e.g., saving edits during review, marking as rejected, potentially the approval action itself)./api/erp/orders/: Handles direct interactions with the ERP system. Every order lookup on these routes filtersdeleted_at IS NULL: a soft-deleted (tombstoned) order is treated as not found and returns404on[id]GET/PUT/DELETE,approve,sync, andsubmitβs by-order-id path, whilegenerate-quote(andsubmitβs extracted-order path) never reuses a tombstonedQUOTE_PENDINGrow and creates a fresh one instead. The orders list (GET /api/erp/orders) excludes tombstoned rows. This prevents actions on a tombstoned order from resurrecting it in the ERP.index.ts: Creates a new manual order directly in the ERP (POST). Claims the WhereFourSO<n>-OMorder ref and evaluates theerp_submission_idempotency_ref_guard_v1connection flag before starting the creation workflow β same numbering/idempotency behavior as[id]/submit.ts.generate-quote.ts: Starts a quote for an extracted order (or one split of it) as a durable Temporal workflow (POST). Pre-commits aQUOTE_PENDINGordersrow, then returns202with aworkflowIdthe client awaits over SSE (/api/temporal/workflow/[workflowId]). Duplicate or already-quoted requests are rejected synchronously (4xx) before any workflow starts.[id]/approve.ts: Approves an existing quote β anordersrow that already has anerpOrderNumberβ via a durable Temporal workflow (POST). Redis-fingerprint-guarded against duplicate concurrent requests. Returns202+workflowIdon start, or409with a machine-readablecodewhen the same approval is already in flight or already succeeded (see Β§14).[id].ts:GETfetches an order.PUThas two paths: quote updates (the order isQUOTE_PENDING/QUOTE_GENERATED/QUOTE_FAILED, orisQuoteis set) start a durable Temporal workflow (ERPOrderUpdateWorkflow) and return202+workflowId(or409 DUPLICATE_REQUEST_IN_PROGRESSwhen the same update is already in flight); non-quote sales-order updates still call the ERP connector synchronously and return200with the result, or a4xx/5xxerror on failure β Redis-fingerprint-guarded, held for the full ERP call (up to several minutes for slower connectors).[id]/submit.ts: A separate, heavier direct-submission path used by the review-v2 UI (ReviewSubmitFlow.tsx) and the legacy manual-order-editorderEditStore. Runs extensive server-side gates (price review, serialization, discounts, address deliverability, revision conflicts, work-item ownership) before starting its own Temporal workflow (ERPOrderSubmissionWorkflow). Distinct from the generate-quote/approve pair above β this endpoint submits an order directly rather than going through a quote step. Returns202+workflowId; on WhereFour connections with sequential numbering the body also carries the pre-claimederpOrderRef(SO<n>-OM), so the UI can show a copyable order number even if the async workflow result is lost.
/api/erp/customers/,/api/erp/shipping/,/api/erp/parts/: Provide search/lookup functionality used by the React search components.
(Note: Exact HTTP methods and functionality within each API route file are inferred and would require reading the specific .ts files for confirmation.)
13. Authentication & Authorization
Section titled β13. Authentication & Authorizationβ- Client-side token refresh is managed by
@clerk/astro(clerk()inapps/webapp/astro.config.mjs), while server-side authentication is enforced by@repo/auth/middleware(createAuthMiddlewareinsrc/middleware.ts) andapiAuthEnforcementMiddleware. - Page routes use
authenticateRequestForSSR(request)(which returns Clerkβs307handshake redirect when__sessionis expired-but-refreshable) and populateAstro.locals.{auth, user, organization, authStatus}. - Unauthenticated page requests (
locals.authStatus === 'unauthenticated') are redirected to/sign-in. - Tenant isolation is enforced on every query and mutation using
clerkOrganizationId(locals.auth.orgId/locals.organization.id). See Authentication Architecture.
14. Error Handling
Section titled β14. Error Handlingβ- Client-Side:
- React components and page scripts use
try...catchblocks for API calls. - Loading and submission states (
isSaving,isSubmitting) are managed in stores to provide UI feedback (e.g., disabling buttons, showing spinners). - User-friendly error messages are displayed in designated status divs or alert components based on API responses or client-side validation failures.
- Stores (
orderEditStore,pdfOrderStore) often have dedicatederrorstates to hold detailed error information, potentially including suggestions from the backend.
- React components and page scripts use
- API/Backend:
- API endpoints return non-2xx status codes on errors.
- Error responses often include a JSON body with an
errormessage and sometimes a more detaileddetailsobject (e.g.,ErpErrorDetailscontainingtype,message,erpMessage,problematicItemId,suggestedAction). - The ERP order-action endpoints (
generate-quote.ts,[id]/approve.ts,[id].ts) also attach a machine-readablecodeto the error body viaAPIResponse.error(message, status, code). The client-sideErpApiErrorclass (@/services/erpApi.ts) carries thiscodethrough so callers can tell βthis exact action already succeeded / another request for it is already in flightβ apart from a genuine ERP failure, instead of treating every non-2xx response the same. Current known values:DUPLICATE_REQUEST_IN_PROGRESSβ a Redis fingerprint shows an identical request already in flight ([id]/approve.ts,[id].ts).ALREADY_APPROVEDβ the quoteβs approval workflow already completed successfully ([id]/approve.ts).APPROVAL_IN_PROGRESSβ a Temporal workflow for this approval is already running ([id]/approve.ts).ALREADY_HAS_ERP_ORDER_NUMBERβ the order already has an ERP order/quote number; use the update endpoint (PUT [id].ts) instead of generating a new quote (generate-quote.ts).
- PDF processing errors are stored in the
processingErrorfield of thepdfDocumentstable. - ERP submission errors are stored in the
submissionError(JSONB) field of theorderstable.
- Database: Status fields (
statusinorders,pdfDocuments,extractedOrders) track the outcome of operations (e.g.,failed,partial).
Mermaid Diagram: Order Flow (Updated)
Section titled βMermaid Diagram: Order Flow (Updated)βgraph TD subgraph User Interface (Astro Pages & React Components) INBOX["Unified Inbox<br>(/inbox)"] --> F("Review PDF Order<br>/orders/pdf/:documentId/review"); INBOX --> EMAIL_VIEW("Email Order View<br>/orders/email/:emailId"); INBOX --> D1("View Order Details<br>/orders/:id"); A["Dashboard<br>(/dashboard)"] --> B("Create Manual Order<br>/orders/new"); A --> C("Upload PDF Order<br>/orders/pdf/upload"); A --> INBOX;
B -- Uses --> B1[CustomerSearch React]; B -- Uses --> B2[ShippingAddressSelector React]; B -- Uses --> B3[ItemSearch React]; B -- Uses --> S1[orderStore]; B -- Submits --> API_ERP_ORDERS_POST(POST /api/erp/orders);
C -- Uploads file --> API_PDF_UPLOAD(POST /api/pdf-documents); API_PDF_UPLOAD --> R2[Cloudflare R2]; API_PDF_UPLOAD --> DB2(Database: pdfDocuments); API_PDF_UPLOAD --> Q(Temporal: PDFExtractionWorkflow); C -- Polls --> API_PDF_GET_ID(GET /api/pdf-documents/:id); API_PDF_GET_ID -- Status=completed --> F;
D1 -- Links to Edit --> RP("Edit Order Router<br>/orders/pdf-order/:id");
RP -- Is PDF? --> F_EDIT{"Review Page in Edit Mode<br>/orders/pdf/:documentId/review?mode=edit&orderId=:id"}; RP -- Is Manual? --> D2("Edit Manual Order<br>/orders/edit/:id");
F -- Renders --> F1["3-Pane ReviewResponsiveBody<br>(orders/review-v2 inside CockpitShell)"]; F_EDIT -- Renders --> F1;
F1 -- Pane 3 --> F2["ReviewDocViewer<br>(PDFViewerWithHighlights / Spreadsheet / Email)"]; F1 -- Pane 1 & 2 --> F3["ReviewIssuesRail (280px) + ReviewCenterPane / RevisionPanel"]; F1 -- Uses --> F4["ERPSearchModal + ReadinessFooter / ReviewSubmitFlow"]; F1 -- Uses --> S2["stores/pdfOrder/* (Review/Validate/Submit)"]; F1 -- Uses (Edit Mode) --> S3["orderEditStore (Save/Submit)"]; F1 -- Direct Submit --> API_ERP_SUBMIT("POST /api/erp/orders/:id/submit<br>202, Temporal workflow"); F1 -- Generates Quote --> API_GEN_QUOTE("POST /api/erp/orders/generate-quote<br>202, Temporal workflow"); F1 -- Updates Existing Quote --> API_UPDATE_QUOTE("PUT /api/erp/orders/:id<br>quote: 202 + workflowId<br>non-quote: sync 200/4xx"); F1 -- Approves Quote --> API_APPROVE("POST /api/erp/orders/:id/approve<br>202, Temporal workflow"); F1 -- Saves (Edit Mode) --> API_ORDERS_PUT(PUT /api/orders/:id);
D2 -- Uses --> S3; D2 -- Saves --> API_ORDERS_PUT; D2 -- Submits --> API_ERP_SUBMIT; end
subgraph Backend & Services API_ERP_ORDERS_POST --> DB1(Database: orders); API_ERP_ORDERS_POST --> ERP(ERP System);
Q -- Processes PDF --> AI(Temporal Worker: Mistral OCR + Gemini); Q -- Updates Status --> DB2; Q -- Saves Extracted --> DB3(Database: extractedOrders/Items);
API_PDF_GET_ID -- Reads --> DB2;
API_GEN_QUOTE -- Pre-commits QUOTE_PENDING row, starts workflow --> DB1; API_GEN_QUOTE --> ERP; API_UPDATE_QUOTE -- Quote: Temporal workflow; non-quote: sync ERP --> DB1; API_UPDATE_QUOTE --> ERP; API_APPROVE -- Reads/updates via workflow --> DB1; API_APPROVE --> ERP;
API_ORDERS_PUT -- Updates --> DB1;
API_ERP_SUBMIT -- Reads/updates via workflow --> DB1; API_ERP_SUBMIT --> ERP;
B1 -- Searches --> API_ERP_CUST(API: /erp/customers); B2 -- Searches --> API_ERP_SHIP(API: /erp/shipping); B3 -- Searches --> API_ERP_PARTS(API: /erp/parts); F4 -- Searches --> API_ERP_CUST; F4 -- Searches --> API_ERP_SHIP; F4 -- Searches --> API_ERP_PARTS; end
subgraph State Management (Nanostores) S1["orderStore<br>(New Order)"]; S2["stores/pdfOrder/*<br>(PDF Review/Approve)"]; S3["orderEditStore<br>(Edit Approved Order)"]; end
style R2 fill:#f9f,stroke:#333,stroke-width:2px style ERP fill:#ccf,stroke:#333,stroke-width:2px style AI fill:#fcc,stroke:#333,stroke-width:2px style Q fill:#fec,stroke:#333,stroke-width:2px style DB1 fill:#cfc,stroke:#333,stroke-width:2px style DB2 fill:#cfc,stroke:#333,stroke-width:2px style DB3 fill:#cfc,stroke:#333,stroke-width:2pxSummary
Section titled βSummaryβThe order handling process combines the 3-pane unified inbox (/inbox β InboxCockpit.tsx inside CockpitShell.astro), the 3-pane review workbench (/orders/pdf/[documentId]/review β src/components/orders/review-v2/ReviewResponsiveBody.tsx and src/stores/pdfOrder/), email intake views (/orders/email/[emailId]), and manual order entry/editing (/orders/new, /orders/edit/[id]). Approved and submitted orders reside in the orders table (packages/db/src/schema/orders.ts), while document extraction and inbox workflow state reside in workItems (packages/db/src/schema/work-items.ts), pdfDocuments, extractedOrders, and extractedOrderItems (packages/db/src/schema/pdf-documents.ts).