Skip to content

Order Processing Workflow

Complete workflow from document receipt to ERP submission, showing all intake paths, the shared validation pipeline, and every feature-flag decision point.

flowchart TD
%% ── Styles ──────────────────────────────────────────────────────────────
classDef active fill:#d4edda,stroke:#28a745,color:#000
classDef inactive fill:#fff,stroke:#dc3545,color:#999,stroke-dasharray:5 5
classDef flag fill:#fff3cd,stroke:#ffc107,color:#000
classDef process fill:#e2e3e5,stroke:#6c757d,color:#000
classDef convergence fill:#cce5ff,stroke:#007bff,color:#000
%% ── Email Intake Paths ──────────────────────────────────────────────────
subgraph EMAIL["Email Intake"]
E2["Cloudflare Email Worker\n(POST /api/webhook/inbound-email)"]:::active
E3["Gmail Poll\n(ScheduledEmailPullDispatcher)"]:::active
end
%% Cloudflare path
E2 -->|"HMAC verify → insert emailEvent\n→ link pdfDocuments"| CF_EXTRACT
CF_EXTRACT["Start PDFExtractionWorkflow\nper attachment"]:::active
%% Per-connection poll path
E3 -->|"poll_gmail_for_connection β†’\nfan-out per message"| EPW
%% ── Email Processing Workflow ───────────────────────────────────────────
EPW["EmailProcessingWorkflow\n(Temporal)"]:::active
EPW -->|"1. fetch_gmail_message\n2. lookup_alias\n3. triage_email"| TRIAGE
TRIAGE{"Triage\nDecision"}:::process
TRIAGE -->|"skip / rfq"| DONE_EMAIL["Email Complete\n(skipped or saved as RFQ)"]:::process
TRIAGE -->|"order"| EMAIL_EXTRACT["4. extract_from_email\n5. match_customer\n6. batch_match_items\n7. save_email_order"]:::active
EMAIL_EXTRACT --> AV_CHILD
%% ── PDF Upload Path ─────────────────────────────────────────────────────
subgraph UPLOAD["PDF / Document Upload"]
U1["Manual Upload\n(POST /api/pdf-documents)"]:::active
end
U1 -->|"Upload to R2 β†’ insert pdfDocument\n+ workItem β†’ startDocumentProcessing"| PDFWF
CF_EXTRACT --> PDFWF
%% ── PDF Extraction Workflow ─────────────────────────────────────────────
PDFWF["PDFExtractionWorkflow\n(Temporal)"]:::active
PDFWF -->|"0. pre_extract_document\n1. extract_text_with_gemini_multiformat\n2. match_customer\n3. batch_match_items\n3.5. enrich_order_with_inventory_routing\n3.6. resolve_org_discounts\n4. save_extracted_order"| AV_CHILD
%% ── Convergence: Agentic Validation ─────────────────────────────────────
AV_CHILD["Start child:\nAgenticValidationWorkflow"]:::convergence
AV_CHILD --> FLAG_AV
FLAG_AV{{"agentic_validation_enabled\n(default: false)"}}:::flag
FLAG_AV -->|"false"| AV_DISABLED["Persist 'disabled' status\n→ advance work_item\nto manual review"]:::active
FLAG_AV -->|"true"| AV_RUN
AV_RUN["1. Fetch ERP capabilities\n2. LLM Validation (Gemini)\n3. persistAutoPricedMatches\n4. ERP item validity check\n5. Merge results & compute confidence"]:::active
AV_RUN --> FLAG_THRESHOLD
FLAG_THRESHOLD{{"agentic_confidence_threshold\n(default: 0.95)"}}:::flag
FLAG_THRESHOLD -->|"confidence β‰₯ threshold\n& all_valid"| AUTO_SUBMIT_DECISION
FLAG_THRESHOLD -->|"confidence < threshold\nor issues found"| ESCALATED["Escalated\n→ Manual Review Queue"]:::active
AUTO_SUBMIT_DECISION{{"agentic_auto_submit_enabled\n(default: false)\nAND\nerp_write_enabled\n(default: false)"}}:::flag
AUTO_SUBMIT_DECISION -.->|"both true\n(INACTIVE)"| AUTO_SUBMIT["agent_submit\n→ submitting"]:::inactive
AUTO_SUBMIT_DECISION -->|"either false\n(current default)"| DRY_RUN["Dry Run\n→ log decision only"]:::active
%% ── Manual Submit Path (Active) ─────────────────────────────────────────
ESCALATED --> MANUAL_REVIEW
AV_DISABLED --> MANUAL_REVIEW
DRY_RUN --> MANUAL_REVIEW
MANUAL_REVIEW["Operator Reviews Order\nin Webapp UI"]:::active
MANUAL_REVIEW -->|"Generate Quote\n(POST /api/erp/orders/generate-quote)"| TEMPORAL_QUOTE
MANUAL_REVIEW -->|"Submit Order\n(POST /api/erp/orders/[id]/submit)"| SUBMIT_ORDER
TEMPORAL_QUOTE["ERPQuoteGenerationWorkflow\n(Temporal, async 202)"]:::active
SUBMIT_ORDER["Manual Submit\n(POST /api/erp/orders/[id]/submit)"]:::active
SUBMIT_ORDER --> TEMPORAL_SUBMIT["ERPOrderSubmissionWorkflow\n(Temporal, async 202)"]:::active
TEMPORAL_QUOTE --> ERP_DONE
TEMPORAL_SUBMIT --> ERP_DONE
ERP_DONE["Order in ERP βœ“"]:::active
Style Meaning
Green solid border Active path β€” currently in use with default flag values
Red dashed border Inactive path β€” requires feature flags to be enabled
Yellow diamond/hexagon Feature flag decision point
Blue box Convergence point where multiple paths merge
Grey box Intermediate processing step or terminal state
Path Trigger Entry Point Temporal Workflow
Cloudflare Email Worker Cloudflare Worker parses inbound email, uploads attachments to R2, posts webhook POST /api/webhook/inbound-email PDFExtractionWorkflow (per attachment)
Gmail Poll Temporal schedule (per-connection mailbox poll every N minutes) ScheduledEmailPullDispatcher EmailProcessingWorkflow (per message)
PDF Upload User uploads PDF/Excel via webapp UI POST /api/pdf-documents PDFExtractionWorkflow

All paths converge on AgenticValidationWorkflow, which runs as a child workflow of either EmailProcessingWorkflow or PDFExtractionWorkflow on Temporal Server (GCE VM).

An operator can also re-run validation from the review rail (β€œRe-run validation”, POST /api/pdf-documents/orders/[id]/validate). A manual run never submits to the ERP: it returns 202 with workflow ID agentic-validation-manual-{id}-v{orderContentVersion}, and returns 409 with { error, code } when agentic_validation_enabled is off (agentic_validation_disabled) or the order was already sent to the ERP (already_submitted). Every content edit bumps extracted_orders.order_content_version, and a verdict is saved only if the order is still at the version that was validated.

Flag Type Default Effect
agentic_validation_enabled boolean false Gates the entire AgenticValidationWorkflow. When off, orders skip validation and go directly to the manual review queue with a disabled status.
agentic_auto_submit_enabled boolean false Allows automatic ERP submission after successful agentic validation. Requires agentic_validation_enabled. When off, passing validation results in a dry_run (decision logged but not submitted).
agentic_confidence_threshold multivariate 0.95 Minimum confidence score for auto-submit. Orders below this threshold are escalated to manual review regardless of other flags.
erp_write_enabled boolean false Global safety switch allowing the agentic workflow to write to ERP systems. Must be true alongside agentic_auto_submit_enabled for actual submission. When off, auto-submit decisions become dry runs.
agentic_shadow_submit_v1 boolean false Records every intake agent submit, live or shadow, and the gate that held it, in agent_shadow_submissions. Does not force a dry run; live submit needs agentic_auto_submit_enabled and erp_write_enabled.
email_enabled boolean false Master switch for email functionality (inbox, aliases, email-to-order).

Retired (2026-06): use_temporal_for_erp_writes, use_temporal_for_quote_writes, and use_temporal_for_order_writes are no longer used. All manual ERP write paths (quote generation, order creation, order sync) now route through Temporal unconditionally; the synchronous P21 fallback and these flags have been removed.

  1. Email/PDF intake paths are always active (email processing requires email_enabled per-org).
  2. Agentic validation is disabled β€” orders go straight to the manual review queue.
  3. Manual submit uses a Temporal workflow (ERPOrderSubmissionWorkflow) for the POST /api/erp/orders/[id]/submit path.
  4. Quote generation uses a Temporal workflow (ERPQuoteGenerationWorkflow) for the POST /api/erp/orders/generate-quote path β€” unconditional, same as order creation and sync.
  5. Auto-submit is completely inactive β€” even if validation were enabled and passed, agentic_auto_submit_enabled and erp_write_enabled both default to off.