Skip to content

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.

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):

  1. 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.tsx inside CockpitShell.astro), with supervisor queue monitoring on /team (SupervisorInboxRoot.tsx).
  2. 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-pane review-v2 workbench (src/components/orders/review-v2/ReviewResponsiveBody.tsx inside CockpitShell.astro).
  3. Manual Order Entry (src/pages/orders/new.astro): Users can manually input order details and submit directly to the ERP.

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.astro renders InboxCockpit.tsx inside CockpitShell.astro (<ShellFrame scroll="hidden">) as a 3-pane workspace (56px AppRail.astro + CockpitRow.tsx queue list + CockpitDetail.tsx / CockpitItemPreview.tsx inline 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 on src/pages/team.astro (SupervisorInboxRoot.tsx).
  • Functionality:
    • Displays unified inbox work items (work_items table) linked to pdfDocuments, extractedOrders, orders, and emailEvents.
    • Renders an inline document/email preview and AuroraRoutingCard in the right pane (CockpitDetail.tsx), with one-click navigation or Enter shortcut 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/upload or via inbox upload modal) or create a manual order (/orders/new).
  • 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.

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/orderEditStore for managing the state of the order being edited, saving changes locally via PUT /api/orders/[id], and submitting final changes to the ERP via POST /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 starts PDFExtractionWorkflow on Temporal).
    • Polls status endpoint /api/pdf-documents/[id] and redirects to /orders/pdf/[documentId]/review on completion.

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, associated extractedOrders, and signedViewUrl.
    • If mode=edit and orderId is present, fetches the corresponding order.
    • Renders the 3-pane review-v2 workbench (src/components/orders/review-v2/ReviewResponsiveBody.tsx) above ReadinessFooter.tsx (note: legacy v1 DocumentOrderReview.tsx, PDFOrderReview.tsx, and QuoteSplitter.tsx were deleted in #2640).

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, ReviewMobileTabs on < 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 (280px left) β€” 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, and ERPSearchModal.tsx.
    • Pane 3 (minmax(0, 1.5fr) right) β€” ReviewDocViewer.tsx: Synchronized source document viewer (PDFViewerWithHighlights with bounding-box highlights, SpreadsheetFullView, CSVPreview, ImageViewerWithHighlights, EmailPreview, TextPreview, AudioPreview).
    • Footer β€” ReadinessFooter.tsx & ReviewSubmitFlow.tsx: auto 1fr auto footer displaying validation readiness, order totals, and the ERP submit / quote / approval CTA (ReviewErrorDrawer.tsx for submission errors).
  • 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 in apps/ts-temporal-worker/src/workflows/erp-submission.ts returning 202 with a workflowId (see Β§12, Β§14).

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].
  • pdfDocuments (packages/db/src/schema/pdf-documents.ts):
    • Stores metadata about uploaded PDFs (id, userId, clerkOrganizationId, filename, status, r2Key, processingError, createdAt, updatedAt).
  • 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).
  • 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).
  • 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).
  • 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).

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 main orders table.
    • [id].ts: Likely handles GET (fetch one), PUT (update local DB), DELETE.
    • (Implied index.ts or 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 filters deleted_at IS NULL: a soft-deleted (tombstoned) order is treated as not found and returns 404 on [id] GET/PUT/DELETE, approve, sync, and submit’s by-order-id path, while generate-quote (and submit’s extracted-order path) never reuses a tombstoned QUOTE_PENDING row 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 WhereFour SO<n>-OM order ref and evaluates the erp_submission_idempotency_ref_guard_v1 connection 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 a QUOTE_PENDING orders row, then returns 202 with a workflowId the 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 β€” an orders row that already has an erpOrderNumber β€” via a durable Temporal workflow (POST). Redis-fingerprint-guarded against duplicate concurrent requests. Returns 202 + workflowId on start, or 409 with a machine-readable code when the same approval is already in flight or already succeeded (see Β§14).
    • [id].ts: GET fetches an order. PUT has two paths: quote updates (the order is QUOTE_PENDING/QUOTE_GENERATED/QUOTE_FAILED, or isQuote is set) start a durable Temporal workflow (ERPOrderUpdateWorkflow) and return 202 + workflowId (or 409 DUPLICATE_REQUEST_IN_PROGRESS when the same update is already in flight); non-quote sales-order updates still call the ERP connector synchronously and return 200 with the result, or a 4xx/5xx error 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-edit orderEditStore. 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. Returns 202 + workflowId; on WhereFour connections with sequential numbering the body also carries the pre-claimed erpOrderRef (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.)

  • Client-side token refresh is managed by @clerk/astro (clerk() in apps/webapp/astro.config.mjs), while server-side authentication is enforced by @repo/auth/middleware (createAuthMiddleware in src/middleware.ts) and apiAuthEnforcementMiddleware.
  • Page routes use authenticateRequestForSSR(request) (which returns Clerk’s 307 handshake redirect when __session is expired-but-refreshable) and populate Astro.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.
  • Client-Side:
    • React components and page scripts use try...catch blocks 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 dedicated error states to hold detailed error information, potentially including suggestions from the backend.
  • API/Backend:
    • API endpoints return non-2xx status codes on errors.
    • Error responses often include a JSON body with an error message and sometimes a more detailed details object (e.g., ErpErrorDetails containing type, message, erpMessage, problematicItemId, suggestedAction).
    • The ERP order-action endpoints (generate-quote.ts, [id]/approve.ts, [id].ts) also attach a machine-readable code to the error body via APIResponse.error(message, status, code). The client-side ErpApiError class (@/services/erpApi.ts) carries this code through 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 processingError field of the pdfDocuments table.
    • ERP submission errors are stored in the submissionError (JSONB) field of the orders table.
  • Database: Status fields (status in orders, pdfDocuments, extractedOrders) track the outcome of operations (e.g., failed, partial).
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:2px

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).