Mango.Nop.Plugins/Nop.Plugin.Misc.AIPlugin/docs/ORDERDRAFT/README.md

5.0 KiB

Order-Draft Workflow

Part of Nop.Plugin.Misc.FruitBankPlugin. See ../README.md for the docs index, ../../README.md for project overview. Closely coupled to the pre-order subsystem — see ../PREORDER/README.md: an approved draft creates PreOrders and runs the same conversion. Known issues / planned work: ORDERDRAFT_ISSUES.md.

An order draft is an AI-assisted intake channel. External systems (Viber bot, email processor, dictation app) post a raw free-text message; the AI extracts delivery dates and product demands; a draft is created for admin review. On approval the draft becomes one or more PreOrders, which then flow through the existing PRE-ORDER conversion — so OrderDraft is a new, admin-gated placement channel into that pipeline, not a parallel order path.

Entity Hierarchy

OrderDraft                       ← one inbound message for one delivery date
  └─ OrderDraftItem              ← one parsed product-demand line               [1:N]

ExternalUserPartnerMapping       ← external username → nopCommerce CustomerId (learned)
ProductTextMapping               ← raw text → admin-chosen ProductId (learned, for ranking)

All four tables live in FruitBank.Common/Entities/ and are registered on FruitBankDbContext. OrderDraftStatus: PendingApproved / Rejected / Expired.

Workflow

1. Intake

OrderDraftApiController.Create (Controllers/): auth via X-FruitBank-ApiKey header (matched against FruitBankSettings.OrderDraftApiKey), body { externalUsername, messageText }.

2. Draft build

OrderDraftService.CreateDraftsFromMessageAsync:

  • Partner resolution from ExternalUserPartnerMapping (may be null → admin assigns later).
  • AI parse (OrderDraftParserService.ParseMessageAsync): one draft per delivery date; isPreorder = deliveryDate > today + PreorderThresholdDays (4).
  • Per item (ProcessDraftItemAsync): historic ProductTextMapping weighting (last 5 admin decisions) + AI canonical name/keywords + SearchProductsAsync + AI ranking → SuggestedProductIdsJson, first suggestion pre-selected.

3. Admin review

OrderDraftAdminController (Areas/Admin/): DataTables list (urgent = Pending > 20h), Detail page with candidates, UpdateItem / AssignCustomer.

4. Approval (the entry point into PRE-ORDER)

OrderDraftService.ApproveAsync:

  • Validates: partner assigned, every item has SelectedProductId.
  • Saves ProductTextMapping learning records.
  • Splits items by IsPreorder; both branches create a PreOrder via InsertPreOrderAsync, then call PreOrderConversionService.ConvertPreOrdersForProductsAsync. The "immediate" branch relies on the 4-day conversion window to convert at once.

5. Expiry

OrderDraftService.ExpireOldDraftsAsync: Pending drafts older than 14 days → Expired (background job).

AI touchpoints

Three distinct AI calls, all in OrderDraftParserService via OpenAIApiService.GetSimpleResponseAsync (OpenAI Chat Completions). OrderDraft uses OpenAI exclusively — unlike QuickOrderController / OrderController parsing, which use CerebrasApiService.

# Call Input → Output Used in
1 ParseMessageAsync raw message → delivery-date groups of { rawProductText, requestedQuantity }; one draft per date CreateDraftsFromMessageAsync
2 ResolveProductNameAsync one raw product text → { canonicalName, searchKeywords[] } (keywords feed NopCommerce SearchProductsAsync) ProcessDraftItemAsync
3 RankCandidatesAsync raw text + canonical name + candidate (id, name)[] → ranked product-id list; only when >1 non-historic search hit ProcessDraftItemAsync

Resolution is hybrid: AI proposes candidates, but ordering is front-loaded by historic admin decisions (ProductTextMapping, last 5 for the same raw text) — a DB heuristic, not AI. Product search itself (SearchProductsAsync) is NopCommerce full-text, not AI.

Model: GetModelName() from FruitBankSettings.OpenAIModelName (default gpt-3.5-turbo; code paths for gpt-4o-mini/4.1-mini/4.1-nano/5-nano at temperature=0.2, and a gpt-5 path at reasoning_effort=minimal).

Not yet AI-wired: partner identification (plain ExternalUserPartnerMapping username lookup), quantity/unit normalisation (relies on the parse prompt only), confidence/uncertainty scoring (admin approves everything manually), and the preorder-vs-immediate split (fixed 4-day PreorderThresholdDays, not AI).

Key Source

Type Location
OrderDraftService / IOrderDraftService Services/
OrderDraftParserService (AI parse / resolve / rank) Services/
OrderDraftApiController Controllers/
OrderDraftAdminController (+ models) Areas/Admin/
OrderDraftDbTable / OrderDraftItemDbTable / ExternalUserPartnerMapDbTable / ProductTextMappingDbTable Domains/DataLayer/
OrderDraft / OrderDraftItem / ExternalUserPartnerMapping / ProductTextMapping entities FruitBank.Common/Entities/