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

70 lines
5.0 KiB
Markdown

# 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`](../PREORDER/README.md): an approved draft creates `PreOrder`s and runs the same conversion.
> Known issues / planned work: [`ORDERDRAFT_ISSUES.md`](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 `PreOrder`s, 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`: `Pending``Approved` / `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/` |