Add planning docs for steps 10–23 and update roadmap
Added detailed implementation planning documents for steps 10–23, covering all major feature areas. Updated mainPlan.md to reference these steps, their dependencies, and planning files. No code changes—these documents clarify requirements and guide future development.
This commit is contained in:
@@ -7,7 +7,11 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Close every hole identified in the currently-implemented modules (Region, Route, Vehicle, Store,
|
||||
Warehouse, Order, User/Role/Permission) before any feature from [mainPlan.md](./mainPlan.md)
|
||||
is built on top of them. This file is the running list of "Partially implemented" and foundational
|
||||
"Not implemented" items pulled from that reference doc that block later steps, not just cosmetic
|
||||
cleanup.
|
||||
|
||||
---
|
||||
|
||||
@@ -15,45 +19,94 @@
|
||||
|
||||
### 1. Route DTO — Missing RegionId
|
||||
|
||||
|
||||
`RouteDTOs.cs` does not expose `RegionId` on create/update, so a Route cannot be linked to a
|
||||
Region via the API even though `Route.FK_RegionId` exists on the model. Blocks §2/§3 of the
|
||||
function reference (`assignAreaToRegion`, `getRegionHierarchy`) and any region-scoped authorization.
|
||||
|
||||
### 2. Store DTO — Missing RouteId
|
||||
|
||||
|
||||
`StoreDTOs.cs` does not expose `RouteId` on create/update, so a Store cannot be linked to a Route
|
||||
via the API even though `Store.FK_RouteId` exists on the model. Blocks `assignOutletsToRoute` (§3)
|
||||
and route/region-scoped authorization for Stores and Orders.
|
||||
|
||||
### 3. Role Controller — Missing Authorization
|
||||
|
||||
|
||||
`RoleController`'s GET endpoints carry only `[Authorize]`, with no `[Authorize(Policy = "...")]`
|
||||
per action like every other controller. Any authenticated user — including a SalesRep — can read
|
||||
every role and its full permission list today.
|
||||
|
||||
### 4. Secrets & Configuration Management
|
||||
|
||||
|
||||
JWT signing key and DB connection string currently live in `appsettings.json`. Move both to
|
||||
environment variables or user secrets before any non-local environment is stood up.
|
||||
|
||||
### 5. Input Validation
|
||||
|
||||
|
||||
No DTO in any feature carries validation attributes (`[Required]`, `[MaxLength]`, range checks,
|
||||
etc.). Bad data (empty names, negative levels, malformed emails) can enter the system silently
|
||||
through any existing POST/PUT endpoint.
|
||||
|
||||
### 6. Item Model — Missing Properties
|
||||
|
||||
`Item : Template` has no properties (no SKU, name, unit of measure, category). `OrderItem`
|
||||
references `Item`, but there's nothing there to reference. Fully scoped as its own step in
|
||||
[01_item_catalog.md](./01_item_catalog.md) — tracked here only as the blocking dependency.
|
||||
|
||||
### 7. Order Has No Status / State Machine
|
||||
|
||||
`Order` has no status field, so `updateOrderStatus`, `approveOrder`/`rejectOrder`, and
|
||||
`convertOrderToInvoice` (§1 of the function reference) have nowhere to write to. Every order-status
|
||||
consumer downstream (delivery ride execution, sale ride execution, reconciliation, agent controls)
|
||||
depends on this existing first.
|
||||
|
||||
### 8. No Credit Limit / Segment Fields on Store
|
||||
|
||||
`validateCreditLimit`, `updateStoreCreditLimit`, `getStoreOutstandingBalance`, and `classifyStore`
|
||||
(§1, §4) all need fields that don't exist on `Store` today (credit limit, outstanding balance,
|
||||
segment). Not scoped to a single feature file yet — flagged here so it isn't silently assumed by
|
||||
a later step.
|
||||
|
||||
### 9. No Area Entity Between Region and Route
|
||||
|
||||
`assignAreaToRegion`, `createRoute(name, areaId)` (§2, §3) assume an `Area` level between Region
|
||||
and Route that doesn't exist in the schema — only `Region → Route → Store` exists. Confirm with
|
||||
the user whether `Area` is a real requirement or whether Region/Route is the intended two-level
|
||||
hierarchy before building anything that assumes a third level.
|
||||
|
||||
### 10. No Driver Entity
|
||||
|
||||
`DeliveryRun.FK_DriverId`/`Driver` navigation is commented out in the model. `assignDriverToVehicle`,
|
||||
`checkDriverCompliance` (§5) and any ride that needs a named driver depend on this existing.
|
||||
Blocks [03_ride_lifecycle.md](./03_ride_lifecycle.md).
|
||||
|
||||
---
|
||||
|
||||
## Scope (What This Step Does NOT Include)
|
||||
|
||||
|
||||
- No new feature modules (Procurement, Finance, Promotions, etc.) — those are separate steps,
|
||||
see [mainPlan.md](./mainPlan.md) for the full list and their own
|
||||
numbered files below.
|
||||
- No UI/client changes.
|
||||
- No changes to authentication (JWT) — only authorization policy coverage on `RoleController`.
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- A gap fix must not change the shape of an already-shipped response DTO in a breaking way without
|
||||
flagging it — additive fields only unless explicitly agreed.
|
||||
- Soft-delete (`IsActive`) pattern must be preserved on every entity touched.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Is `Area` a real business concept (a level between Region and Route), or should
|
||||
`createRoute`/`assignAreaToRegion` in the function reference be reinterpreted as Region-only?
|
||||
- Should `Store.CreditLimit` and `Store.OutstandingBalance` be added now as part of gap-fixing, or
|
||||
deferred to a dedicated Finance step (§13)?
|
||||
- Does `Order.Status` need a fixed enum now (`pending/approved/picked/dispatched/delivered/invoiced`
|
||||
per §1), or a smaller interim set until the fuller order lifecycle is built?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,13 +7,20 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Turn the currently-unused `DeliveryRun` model into a working ride: a session that turns a vehicle
|
||||
into a temporary warehouse. Covers `assignVehicleToRoute`, `assignDriverToVehicle`,
|
||||
`checkDriverCompliance` from [mainPlan.md §5](./mainPlan.md#5-vehicle--fleet-management)
|
||||
and `planDeliveryRoute` from §3.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
`Models/DeliveryRun.cs` exists (`FK_VehicleId`, `FK_RouteId`, `Meterstart`, `Meterend`) but has no
|
||||
repository, service, or controller — it is completely unwired today, per [mainPlan.md](./mainPlan.md)'s
|
||||
"Known Gaps" table. `FK_DriverId`/`Driver` navigation is commented out because no `Driver` entity
|
||||
exists yet (gap #10 in [00_existing_gaps.md](./00_existing_gaps.md)) — this step is blocked on that
|
||||
being resolved first if driver assignment is required at ride-open time.
|
||||
|
||||
---
|
||||
|
||||
@@ -21,39 +28,52 @@
|
||||
|
||||
### Ride (extending DeliveryRun)
|
||||
|
||||
|
||||
Add a `Status` field to `DeliveryRun` for the state machine below, and a `RideType` field
|
||||
(`Delivery` | `Sale`).
|
||||
|
||||
### Ride State Machine
|
||||
|
||||
|
||||
`Draft → Open → Active → Closed`. A vehicle can only be on one `Open`/`Active` ride at a time —
|
||||
enforced in the service layer, not the database.
|
||||
|
||||
### Ride Type (Delivery vs Sale)
|
||||
|
||||
|
||||
`Delivery` rides fulfill pre-confirmed orders (see [05_delivery_ride.md](./05_delivery_ride.md));
|
||||
`Sale` rides create invoices on the spot with no pre-orders (see [06_sale_ride.md](./06_sale_ride.md)).
|
||||
Both share the same ride lifecycle and loading event.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `assignVehicleToRoute(vehicleId, routeId)` (§5) | Implicit in `POST /api/rides` — a ride binds a vehicle to a route for its duration |
|
||||
| `assignDriverToVehicle(vehicleId, driverId)` (§5) | `PUT /api/rides/{id}/driver` — blocked on Driver entity, see gap #10 |
|
||||
| `checkDriverCompliance(driverId)` (§5) | Called internally before a ride can move `Draft → Open` |
|
||||
| `planDeliveryRoute(warehouseId, orderIds[])` (§3) | `POST /api/rides` (for `RideType = Delivery`) |
|
||||
| — | `POST /api/rides/{id}/open`, `POST /api/rides/{id}/close` — state transitions |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- A ride cannot open without a compliant driver (once Driver entity exists) and a vehicle not
|
||||
already on an open ride.
|
||||
- Opening a ride creates the vehicle's stock ledger location (see [02_stock_ledger.md](./02_stock_ledger.md)).
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
- `checkDriverCompliance` failures must block ride open, not just warn.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Is a `Driver` a new entity distinct from `User`, or is "driver" just a `User` with a specific
|
||||
`Role`? Affects whether gap #10 needs a new table or just a role addition.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,13 +7,18 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Move stock from a source warehouse onto an opened ride's vehicle, with two-party confirmation.
|
||||
Covers the dispatch half of [mainPlan.md §6](./mainPlan.md#6-warehouse-management):
|
||||
`generatePickList`, `confirmPicking`, `packOrder`, `buildDispatchLoad`, and `calculateLoadPlan`
|
||||
from §5.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
This is the first place the [02_stock_ledger.md](./02_stock_ledger.md) movement model gets
|
||||
exercised end-to-end: a `Load` movement debits the warehouse and credits the vehicle location
|
||||
created when the ride opened ([03_ride_lifecycle.md](./03_ride_lifecycle.md)).
|
||||
|
||||
---
|
||||
|
||||
@@ -21,39 +26,55 @@
|
||||
|
||||
### Loading Sheet
|
||||
|
||||
|
||||
Header record: `RideId`, `SourceWarehouseId`, `Status` (`Pending`, `DispatchConfirmed`,
|
||||
`ReceiptConfirmed`, `DiscrepancyFlagged`, `Resolved`).
|
||||
|
||||
### Loading Line Item
|
||||
|
||||
|
||||
`LoadingSheetId`, `ItemId`, `QuantityDispatched` (set by warehouse manager), `QuantityReceived`
|
||||
(set by driver) — a mismatch between the two is a discrepancy.
|
||||
|
||||
### Two-Party Confirmation
|
||||
|
||||
|
||||
Warehouse manager confirms what was dispatched (`generatePickList` → `confirmPicking` →
|
||||
`packOrder`); driver separately confirms what was received onto the vehicle. Both confirmations are
|
||||
required before the ride can move `Open → Active`.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `generatePickList(orderIds[])` (§6) | `POST /api/loading-sheets` |
|
||||
| `confirmPicking(pickListId, pickedItems[])` (§6) | `POST /api/loading-sheets/{id}/confirm-dispatch` |
|
||||
| `packOrder(orderId, packageDetails)` (§6) | `POST /api/loading-sheets/{id}/pack` |
|
||||
| `buildDispatchLoad(vehicleId, orderIds[])` (§6) | Groups multiple orders into one loading sheet |
|
||||
| `calculateLoadPlan(vehicleId, orderIds[])` (§5) | Validates total volume/weight against vehicle capacity before dispatch confirmation |
|
||||
| — | `POST /api/loading-sheets/{id}/confirm-receipt` — driver-side confirmation |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- `QuantityDispatched != QuantityReceived` on any line flags the sheet `DiscrepancyFlagged` and
|
||||
blocks ride activation until resolved.
|
||||
- `calculateLoadPlan` must reject a load exceeding vehicle capacity before dispatch is confirmed,
|
||||
not after.
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
- Discrepancies must be immediately visible, not silently averaged into stock adjustments — see
|
||||
[09_fraud_controls.md](./09_fraud_controls.md).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Does `Vehicle` need a `Capacity` field added now (volume/weight), or is `calculateLoadPlan`
|
||||
deferred until that's available?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,13 +7,18 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Execute pre-confirmed orders against a loaded, active delivery ride. Covers the order-status half
|
||||
of [mainPlan.md §1](./mainPlan.md#1-order-management):
|
||||
`updateOrderStatus`, `convertOrderToInvoice`, `createBackorder`. Requires `Order.Status` to exist
|
||||
first — see gap #7 in [00_existing_gaps.md](./00_existing_gaps.md).
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
Today `Order` has no status and no link to a ride. This step is where the order lifecycle
|
||||
(`pending → approved → picked → dispatched → delivered → invoiced`) actually gets driven, once the
|
||||
order has been picked/loaded ([04_loading_event.md](./04_loading_event.md)).
|
||||
|
||||
---
|
||||
|
||||
@@ -21,39 +26,51 @@
|
||||
|
||||
### Delivery Task (per order on ride)
|
||||
|
||||
|
||||
`RideId`, `OrderId`, `Sequence` (visit order), `Status` (`Pending`, `Delivered`, `PartiallyDelivered`,
|
||||
`Failed`).
|
||||
|
||||
### Delivery Outcome
|
||||
|
||||
|
||||
Per delivery task: quantities actually delivered per line, vs. ordered — feeds `createBackorder`
|
||||
for the shortfall.
|
||||
|
||||
### Customer Confirmation
|
||||
|
||||
|
||||
Signature or OTP captured against the delivery task before it can be marked complete.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `updateOrderStatus(orderId, status)` (§1) | `PUT /api/rides/{rideId}/tasks/{orderId}/status` |
|
||||
| `convertOrderToInvoice(orderId)` (§1) | Triggered automatically on `Delivered`/`PartiallyDelivered` — see [19_finance_accounting.md](./19_finance_accounting.md) |
|
||||
| `createBackorder(orderId, unfulfilledItems[])` (§1) | Triggered automatically on `PartiallyDelivered` |
|
||||
| — | `POST /api/rides/{rideId}/tasks/{orderId}/confirm` — captures customer signature/OTP |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- A delivery task cannot be marked `Delivered` without a customer confirmation.
|
||||
- `PartiallyDelivered` always creates a `createBackorder` entry and a credit note for the
|
||||
undelivered quantity (see [18_returns_claims.md](./18_returns_claims.md) for credit note shape).
|
||||
- Each confirmed delivery task creates a stock movement (`Sale`/consumption) against the vehicle
|
||||
location per [02_stock_ledger.md](./02_stock_ledger.md).
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
- OTP/signature capture must be tamper-evident (timestamped, tied to the delivery task, not
|
||||
editable after submission).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Signature capture (image) or OTP (SMS code) — one, or both, in v1?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,13 +7,18 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Support on-the-spot van sales with no pre-order: driver picks items/quantities, an invoice is
|
||||
generated immediately, and payment is captured at point of sale. Covers `createInvoice`,
|
||||
`recordPayment` from [mainPlan.md §13](./mainPlan.md#13-finance--accounting),
|
||||
and reuses `applyPricingAndDiscounts` (§1) plus `evaluateSchemeEligibility`/`applySchemeDiscount`
|
||||
(§11) if promotions are live by the time this ships.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
Distinct from [05_delivery_ride.md](./05_delivery_ride.md): there is no pre-existing `Order` row —
|
||||
the field invoice *is* the order, created and settled in one step.
|
||||
|
||||
---
|
||||
|
||||
@@ -21,39 +26,52 @@
|
||||
|
||||
### Field Invoice
|
||||
|
||||
|
||||
`RideId`, `StoreId`, `CreatedByUserId`, `TotalAmount`, `PaymentStatus` (`Paid`, `PartiallyPaid`,
|
||||
`OnCredit`).
|
||||
|
||||
### Field Invoice Line Item
|
||||
|
||||
|
||||
`FieldInvoiceId`, `ItemId`, `Quantity`, `UnitPrice` (subject to the discount-limit rules in
|
||||
[08_sales_agent_controls.md](./08_sales_agent_controls.md)).
|
||||
|
||||
### Payment Record
|
||||
|
||||
|
||||
`FieldInvoiceId`, `Amount`, `Method` (`Cash`, `Credit`), `CollectedAt`.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `createInvoice(orderId)` (§13) | `POST /api/rides/{rideId}/field-invoices` — orderId is the field invoice itself here |
|
||||
| `recordPayment(invoiceId, amount, method)` (§13) | `POST /api/field-invoices/{id}/payments` |
|
||||
| `applyPricingAndDiscounts(orderId)` (§1) | Applied at line-item entry time, within agent discount limits |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- Driver cannot sell more than what is loaded on the vehicle — every line item is checked against
|
||||
the vehicle's current stock balance ([02_stock_ledger.md](./02_stock_ledger.md)) before the
|
||||
invoice is finalized.
|
||||
- Each confirmed sale line creates a `Sale` stock movement against the vehicle location,
|
||||
immediately (not batched at ride close).
|
||||
- Discount applied per line must not exceed the agent's role-level limit (§8).
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
- Cash collected must reconcile against invoiced totals at ride close — see
|
||||
[07_ride_reconciliation.md](./07_ride_reconciliation.md).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Are field invoices numbered/sequenced separately from warehouse-issued invoices, or share one
|
||||
sequence?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,13 +7,18 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Physically count what's left on the vehicle at ride close and reconcile it against the stock
|
||||
ledger and cash collected. Covers `runCycleCount` and `reconcileStock` from
|
||||
[mainPlan.md §6](./mainPlan.md#6-warehouse-management), applied to a
|
||||
vehicle location instead of a warehouse zone.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
This is the control point that catches loading discrepancies ([04_loading_event.md](./04_loading_event.md))
|
||||
that went unresolved, and any shortfall between what was sold/delivered and what's physically on
|
||||
the vehicle.
|
||||
|
||||
---
|
||||
|
||||
@@ -21,45 +26,61 @@
|
||||
|
||||
### Ride Close Summary
|
||||
|
||||
|
||||
`RideId`, `ClosedAt`, `ClosedByUserId`, computed totals for the reconciliation equation below.
|
||||
|
||||
### Variance Record
|
||||
|
||||
|
||||
`RideId`, `ItemId` (or `Cash` for the monetary variance), `ExpectedQuantity`/`ExpectedAmount`,
|
||||
`ActualQuantity`/`ActualAmount`, `Variance`, `ReasonCode`, `Status` (`Pending`, `Approved`,
|
||||
`Rejected`).
|
||||
|
||||
### Supervisor Approval
|
||||
|
||||
|
||||
Every non-zero variance requires a supervisor-level approval (`Role.Level >= 20`) before the ride
|
||||
can transition to `Closed`.
|
||||
|
||||
---
|
||||
|
||||
## The Reconciliation Equation
|
||||
|
||||
|
||||
```
|
||||
Opening Stock − Sales − Damages + Returns = Closing Stock (physical count) [runCycleCount / reconcileStock]
|
||||
Cash Collected + Credit Extended = Total Sales − Returns
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `runCycleCount(warehouseId, zoneId)` (§6) | `POST /api/rides/{id}/cycle-count` — `zoneId` maps to the ride's vehicle location |
|
||||
| `reconcileStock(warehouseId, countResults)` (§6) | `POST /api/rides/{id}/reconcile` |
|
||||
| — | `POST /api/rides/{id}/variances/{id}/approve` — supervisor sign-off |
|
||||
| — | `POST /api/rides/{id}/close` — only succeeds once every variance is `Approved` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- A ride cannot close with an unexplained variance — every `Variance` row must be `Approved` or
|
||||
`Rejected` (with the discrepancy converted to a stock `Adjustment` movement) before close.
|
||||
- `reconcileStock` writes an `Adjustment` movement per variance line, never edits the running
|
||||
balance directly (see [02_stock_ledger.md](./02_stock_ledger.md)).
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
- Unresolved/high-frequency discrepancies are logged permanently against the agent's record — feeds
|
||||
the anomaly detection in [09_fraud_controls.md](./09_fraud_controls.md).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Is there a variance threshold below which supervisor approval is auto-granted (e.g. rounding-
|
||||
level cash differences), or does every non-zero variance require explicit sign-off?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,13 +7,21 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Wire role levels to concrete business limits and approval chains. Covers the approval-routing half
|
||||
of [mainPlan.md §1](./mainPlan.md#1-order-management)
|
||||
(`submitOrderForApproval`, `approveOrder`, `rejectOrder`) and the performance-tracking functions
|
||||
from [§10 SFA/CRM](./mainPlan.md#10-sales-force-automation-sfa--crm)
|
||||
(`trackTargetVsAchievement`, `calculateIncentive`). Lead/interaction functions from §10
|
||||
(`logCustomerInteraction`, `createLead`, `convertLeadToStore`) are out of scope here — see
|
||||
[16_sfa_crm.md](./16_sfa_crm.md).
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
`Role.Level` already exists and drives the permission seed
|
||||
(`Data/Seed/RoleAndPermissionSeed.cs`), but nothing today turns a level into a discount limit,
|
||||
credit limit, or approval threshold — it only gates which API endpoints a role can call.
|
||||
|
||||
---
|
||||
|
||||
@@ -21,39 +29,56 @@
|
||||
|
||||
### Agent Limit Configuration (per role level)
|
||||
|
||||
|
||||
`RoleId`, `MaxDiscountPercent`, `MaxSingleTransactionValue` — read by both
|
||||
[06_sale_ride.md](./06_sale_ride.md) (line-item discount check) and order approval routing here.
|
||||
|
||||
### Override Log
|
||||
|
||||
|
||||
`OverrideById` (the supervisor), `TargetType`/`TargetId` (e.g. `Order`, `FieldInvoice`), `OldValue`,
|
||||
`NewValue`, `Reason`, `CreatedAt` — a one-time exception, logged against the supervisor's account.
|
||||
|
||||
### Customer Credit Profile
|
||||
|
||||
|
||||
Reuses `Store.CreditLimit`/`Store.OutstandingBalance` once added (gap #8 in
|
||||
[00_existing_gaps.md](./00_existing_gaps.md)) rather than a separate entity.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `submitOrderForApproval(orderId)` (§1) | Triggered automatically when an order exceeds its agent's limit |
|
||||
| `approveOrder(orderId, approverId)` / `rejectOrder(orderId, reason)` (§1) | `POST /api/orders/{id}/approve`, `POST /api/orders/{id}/reject` |
|
||||
| `trackTargetVsAchievement(userId, period)` (§10) | `GET /api/agents/{userId}/performance?period=...` |
|
||||
| `calculateIncentive(userId, period)` (§10) | `GET /api/agents/{userId}/incentive?period=...` |
|
||||
| — | `POST /api/orders/{id}/override` — supervisor one-time exception, writes an Override Log entry |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- An order/invoice whose value or discount exceeds the agent's `Agent Limit Configuration` cannot
|
||||
proceed without either supervisor approval or a logged override — no silent pass-through.
|
||||
- `calculateIncentive` reads only settled/closed rides (post-[07_ride_reconciliation.md](./07_ride_reconciliation.md)),
|
||||
never in-flight ones.
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
- Every `Override Log` entry is permanent and immutable once written — feeds
|
||||
[09_fraud_controls.md](./09_fraud_controls.md)'s anomaly detection (agents with unusually
|
||||
frequent overrides).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Are discount/transaction limits configured per `Role`, or can an individual `User` have a
|
||||
tighter/looser override of their role's default?
|
||||
- What sales target source feeds `trackTargetVsAchievement` — is there a target-setting endpoint
|
||||
needed here, or does it come from §2's `setRegionalTarget` rolled down to the individual?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,13 +7,18 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
A dedicated, immutable audit log plus automated anomaly detection over everything built in steps
|
||||
01–08. Covers [mainPlan.md §18](./mainPlan.md#18-compliance--audit)
|
||||
(`logAuditEvent`, `checkLicenseCompliance`, `generateComplianceReport`) and the fraud-relevant piece
|
||||
of §15 (`sendRouteDeviationAlert`) and §12 (return-without-prior-sale detection).
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
Every prior step in this plan references "logged" or "flagged" behavior (override logs, loading
|
||||
discrepancies, reconciliation variances) — this step is where that logging actually becomes a
|
||||
queryable, immutable audit trail instead of scattered per-feature notes.
|
||||
|
||||
---
|
||||
|
||||
@@ -21,45 +26,69 @@
|
||||
|
||||
### Audit Log Entry
|
||||
|
||||
|
||||
`UserId`, `Action`, `EntityType`, `EntityId`, `OldValue`/`NewValue` (JSON snapshot), `CreatedAt`,
|
||||
`SourceIp`/`RequestId` if available. Append-only — no update or delete path exposed anywhere.
|
||||
|
||||
### Anomaly Flag
|
||||
|
||||
|
||||
`Type` (`ReturnWithoutSale`, `RouteDeviation`, `HighVarianceRate`, `CashShortfall`), `EntityType`/
|
||||
`EntityId`, `RaisedAt`, `Status` (`Open`, `Reviewed`, `Dismissed`).
|
||||
|
||||
### Return Approval Record
|
||||
|
||||
|
||||
Reuses the `approveReturn` flow from [18_returns_claims.md](./18_returns_claims.md) — this step
|
||||
adds the automated pre-check (`ReturnWithoutSale`) that runs before a human approver sees it.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `logAuditEvent(userId, action, entityType, entityId)` (§18) | Internal — called by every mutating service, not directly exposed |
|
||||
| `checkLicenseCompliance(entityId, licenseType)` (§18) | `GET /api/compliance/{entityType}/{entityId}/license-check` |
|
||||
| `generateComplianceReport(period)` (§18) | `GET /api/compliance/report?period=...` |
|
||||
| `sendRouteDeviationAlert(routeId, salesRepId)` (§15) | Triggered from route check-in/out data (§3 of the function reference; not yet in any numbered step — see [11_route_sales_management.md](./11_route_sales_management.md)) |
|
||||
| — | `GET /api/audit-log?entityType=...&entityId=...` |
|
||||
| — | `GET /api/anomaly-flags?status=Open` |
|
||||
|
||||
---
|
||||
|
||||
## Automated Checks & Flags
|
||||
|
||||
|
||||
- Returns without a matching prior sale (`ReturnWithoutSale`).
|
||||
- Sales/deliveries outside the geofenced route (`RouteDeviation`) — depends on GPS check-in/out
|
||||
data from [11_route_sales_management.md](./11_route_sales_management.md).
|
||||
- Agents with consistently high variance rates across closed rides (`HighVarianceRate`) — reads
|
||||
`Variance Record` history from [07_ride_reconciliation.md](./07_ride_reconciliation.md).
|
||||
- Cash collected below invoiced amount (`CashShortfall`) — reads `Payment Record` from
|
||||
[06_sale_ride.md](./06_sale_ride.md).
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- Every state-changing action across every feature must call `logAuditEvent` — this is the one
|
||||
cross-cutting requirement every other numbered step inherits once this lands.
|
||||
- Return approval requires supervisor sign-off before stock is credited back
|
||||
([18_returns_claims.md](./18_returns_claims.md)), regardless of whether an anomaly flag fired.
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
- Anomaly reports are surfaced to area managers on a scheduled basis — batch job or on-demand
|
||||
endpoint, not real-time push, in v1.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Should `logAuditEvent` be wired in as a cross-cutting concern (e.g. an EF `SaveChanges`
|
||||
interceptor) rather than an explicit call in every service, to guarantee nothing is missed?
|
||||
- What licenses does `checkLicenseCompliance` actually need to validate for this business — is
|
||||
regulated goods handling in scope at all, or is this function speculative until a real
|
||||
requirement shows up?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# 10 — Region Management (Beyond CRUD)
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 00 — Existing Gap Fixes
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Region CRUD already exists (`Features/Region`). This step covers everything in
|
||||
[mainPlan.md §2](./mainPlan.md#2-region-management) that isn't plain
|
||||
CRUD: targets, budgets, agency mapping, tax rules, hierarchy, and dashboarding.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
`Region` today has only a `Name`. Nothing points to it except `Route.FK_RegionId`. There is no
|
||||
`Area` level (see Open Question in [00_existing_gaps.md](./00_existing_gaps.md#9-no-area-entity-between-region-and-route)).
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Regional Target / Budget
|
||||
|
||||
`RegionId`, `Period` (e.g. `2026-08`), `TargetValue`, `BudgetAmount`.
|
||||
|
||||
### Agency-Region Map
|
||||
|
||||
`RegionId`, `AgencyId` — depends on whether "Regional Agency" becomes a distinct entity or stays a
|
||||
`Role` (see Open Questions).
|
||||
|
||||
### Regional Tax Rule
|
||||
|
||||
`RegionId`, `RuleConfig` (JSON) — deferred to whenever tax/pricing rule overrides are prioritized.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `assignAreaToRegion(areaId, regionId)` | Blocked — no `Area` entity exists (see [00_existing_gaps.md](./00_existing_gaps.md#9-no-area-entity-between-region-and-route)) |
|
||||
| `setRegionalTarget(regionId, period, targetValue)` | `PUT /api/regions/{id}/targets/{period}` |
|
||||
| `setRegionalBudget(regionId, period, budgetAmount)` | `PUT /api/regions/{id}/budgets/{period}` |
|
||||
| `mapAgencyToRegion(agencyId, regionId)` | `POST /api/regions/{id}/agencies` |
|
||||
| `setRegionalTaxRule(regionId, ruleConfig)` | `PUT /api/regions/{id}/tax-rule` |
|
||||
| `getRegionHierarchy(regionId)` | `GET /api/regions/{id}/hierarchy` |
|
||||
| `getRegionDashboard(regionId, period)` | `GET /api/regions/{id}/dashboard?period=...` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- Only `RegionalManager` and above (per role level in `Data/Seed/RoleAndPermissionSeed.cs`) can
|
||||
set targets/budgets for their own region; only `RegionalAgency` can set them across regions.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Is "Regional Agency" a business entity distinct from the `RegionalAgency` role (e.g. a contracted
|
||||
third party with its own record), or is the role sufficient?
|
||||
- Does `getRegionDashboard` need real-time aggregation, or a scheduled/cached rollup?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,71 @@
|
||||
# 11 — Route & Sales Visit Management
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 00 — Existing Gap Fixes
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Route CRUD already exists (`Features/Route`). This step covers the sales-visit half of
|
||||
[mainPlan.md §3](./mainPlan.md#3-route-management) — the part that is
|
||||
about a SalesRep's daily visit schedule, not the ride/dispatch route from
|
||||
[03_ride_lifecycle.md](./03_ride_lifecycle.md)'s `planDeliveryRoute`.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
Today a `Store` has a single `FK_RouteId` (bulk-assign not yet supported via API — see gap #2 in
|
||||
[00_existing_gaps.md](./00_existing_gaps.md)), and route access is only granted via `UserRouteScope`
|
||||
(a many-to-many grant), not single ownership. There is no visit check-in/out data anywhere.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Route Ownership
|
||||
|
||||
`RouteId`, `OwnerUserId` — single-owner assignment, distinct from the broader `UserRouteScope`
|
||||
access grant which may cover multiple users (e.g. a Supervisor overseeing several reps' routes).
|
||||
|
||||
### Route Visit Log
|
||||
|
||||
`RouteId`, `StoreId`, `UserId`, `CheckInAt`, `CheckInGps`, `CheckOutAt` — one row per planned visit.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `assignOutletsToRoute(routeId, outletIds[])` | `POST /api/routes/{id}/stores` — bulk version of the existing single `Store.RouteId` field |
|
||||
| `assignRouteToSalesRep(routeId, salesRepId)` | `PUT /api/routes/{id}/owner` |
|
||||
| `optimizeRouteSequence(routeId)` | `POST /api/routes/{id}/optimize` |
|
||||
| `recordRouteCheckIn(routeId, outletId, gpsCoords, timestamp)` | `POST /api/routes/{id}/visits/check-in` |
|
||||
| `recordRouteCheckOut(routeId, outletId, timestamp)` | `POST /api/routes/{id}/visits/check-out` |
|
||||
| `getRouteAdherence(routeId, date)` | `GET /api/routes/{id}/adherence?date=...` |
|
||||
| `reassignRoute(routeId, newSalesRepId)` | `PUT /api/routes/{id}/owner` (same as assign, different actor) |
|
||||
| `getRoutePerformance(routeId, period)` | `GET /api/routes/{id}/performance?period=...` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- Check-in `gpsCoords` feeds `sendRouteDeviationAlert` in
|
||||
[09_fraud_controls.md](./09_fraud_controls.md) — a check-in materially outside the store's
|
||||
registered location should flag, not silently accept.
|
||||
- `getStoreVisitHistory` (§4) reads directly from `Route Visit Log` — no separate storage.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Is route sequence optimization (`optimizeRouteSequence`) a manual reorder endpoint, or does it
|
||||
need actual distance/priority-based computation (external mapping API)?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,74 @@
|
||||
# 12 — Store Management (Beyond CRUD)
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 00 — Existing Gap Fixes
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Store CRUD already exists (`Features/Store`). This step covers everything in
|
||||
[mainPlan.md §4](./mainPlan.md#4-store-outlet-management) that isn't
|
||||
plain CRUD: KYC, credit, segmentation, and dashboarding.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
`Store` currently has no credit limit, outstanding balance, segment, or KYC fields — flagged as
|
||||
gap #8 in [00_existing_gaps.md](./00_existing_gaps.md).
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Store — New Fields
|
||||
|
||||
`CreditLimit` (decimal), `OutstandingBalance` (decimal, derived/cached from Finance ledger once
|
||||
[19_finance_accounting.md](./19_finance_accounting.md) exists), `Segment` (string), `KycStatus`
|
||||
(`Pending`, `Verified`, `Rejected`).
|
||||
|
||||
### KYC Document
|
||||
|
||||
`StoreId`, `DocumentType`, `FileReference`, `UploadedAt` — file storage mechanism TBD (out of scope
|
||||
for this step's data model, just the record).
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `createStore(storeData, kycDocs)` | Extends existing `POST /api/stores` with doc upload |
|
||||
| `verifyStoreKyc(storeId)` | `PUT /api/stores/{id}/kyc/verify` |
|
||||
| `updateStoreCreditLimit(storeId, newLimit, approverId)` | `PUT /api/stores/{id}/credit-limit` |
|
||||
| `getStoreOutstandingBalance(storeId)` | `GET /api/stores/{id}/outstanding-balance` |
|
||||
| `classifyStore(storeId, segment)` | `PUT /api/stores/{id}/segment` |
|
||||
| `deactivateStore(storeId, reason)` | Extends existing `DELETE /api/stores/{id}` with a reason field |
|
||||
| `getStoreVisitHistory(storeId)` | `GET /api/stores/{id}/visit-history` — reads from [11_route_sales_management.md](./11_route_sales_management.md) |
|
||||
| `checkStoreSchemeEligibility(storeId, schemeId)` | `GET /api/stores/{id}/schemes/{schemeId}/eligibility` — depends on [17_promotions_schemes.md](./17_promotions_schemes.md) |
|
||||
| `getStoreSalesDashboard(storeId, period)` | `GET /api/stores/{id}/dashboard?period=...` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- `updateStoreCreditLimit` requires an approver at Supervisor level or above.
|
||||
- A store with `KycStatus != Verified` should not be able to place orders above a configurable
|
||||
threshold (or at all) — confirm exact rule with the user before implementing.
|
||||
- `deactivateStore` blocks new orders against it, matching the existing `IsActive` soft-delete
|
||||
filter already used in `OrderService`.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Is `OutstandingBalance` a cached/denormalized field updated on invoice/payment events, or always
|
||||
computed live from Finance records?
|
||||
- What document types are required for KYC, and is file storage local, blob, or third-party?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,78 @@
|
||||
# 13 — Fleet Management (Beyond CRUD)
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 00 — Existing Gap Fixes
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Vehicle CRUD already exists (`Features/Vehicle`). This step covers everything in
|
||||
[mainPlan.md §5](./mainPlan.md#5-vehicle--fleet-management) that isn't
|
||||
plain CRUD or already scoped into [03_ride_lifecycle.md](./03_ride_lifecycle.md)/
|
||||
[04_loading_event.md](./04_loading_event.md): maintenance, fuel, telemetry, and utilization.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
`Vehicle` today only has `RegNumber`/`NumberPlate`. No capacity, ownership, maintenance, or fuel
|
||||
data exists. There is no `Driver` entity — see gap #10 in
|
||||
[00_existing_gaps.md](./00_existing_gaps.md).
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Vehicle — New Fields
|
||||
|
||||
`CapacityVolume`, `CapacityWeight` (needed by `calculateLoadPlan` in
|
||||
[04_loading_event.md](./04_loading_event.md)), `OwnershipType` (`Owned`, `Leased`, `Contracted`).
|
||||
|
||||
### Maintenance Task
|
||||
|
||||
`VehicleId`, `MaintenanceType`, `DueAt` (date or mileage-based), `CompletedAt`.
|
||||
|
||||
### Fuel Log
|
||||
|
||||
`VehicleId`, `Amount`, `Liters`, `Date`.
|
||||
|
||||
### Telemetry Record
|
||||
|
||||
`VehicleId`, `GpsCoords`, `Speed`, `Timestamp` — high-volume; likely a separate store/retention
|
||||
policy from the rest of the schema (see Open Questions).
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `scheduleMaintenance(vehicleId, maintenanceType, dueAt)` | `POST /api/vehicles/{id}/maintenance` |
|
||||
| `logFuelExpense(vehicleId, amount, liters, date)` | `POST /api/vehicles/{id}/fuel-logs` |
|
||||
| `recordVehicleTelemetry(vehicleId, gpsCoords, speed, timestamp)` | `POST /api/vehicles/{id}/telemetry` — likely ingested via [22_integration_api.md](./22_integration_api.md) rather than direct client calls |
|
||||
| `getVehicleUtilization(vehicleId, period)` | `GET /api/vehicles/{id}/utilization?period=...` |
|
||||
| `retireVehicle(vehicleId, reason)` | Extends existing `DELETE /api/vehicles/{id}` with a reason field |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- `retireVehicle` must be blocked while the vehicle is on an open/active ride
|
||||
([03_ride_lifecycle.md](./03_ride_lifecycle.md)).
|
||||
- Overdue `Maintenance Task`s should block a vehicle from being assigned to a new ride — exact
|
||||
enforcement level (hard block vs. warning) to confirm.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Is real-time telemetry (`recordVehicleTelemetry`) actually in scope, or is odometer
|
||||
start/end (`DeliveryRun.Meterstart`/`Meterend`, already modeled) sufficient for this business?
|
||||
- What third-party telematics provider, if any, is being integrated (affects [22_integration_api.md](./22_integration_api.md))?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,70 @@
|
||||
# 14 — Pricing & Supplier Master Data
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 01 — Item Catalog
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers the remainder of [mainPlan.md §7](./mainPlan.md#7-master-data-management)
|
||||
not already handled by [01_item_catalog.md](./01_item_catalog.md): price lists and supplier master
|
||||
records. `createProduct`/`deactivateSku` live in 01; this step is everything else in §7.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
`Item.BasePrice` (from [01_item_catalog.md](./01_item_catalog.md)) is a single flat price. There is
|
||||
no concept of a region- or date-scoped price list, and no `Supplier` entity anywhere in the schema.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Price List
|
||||
|
||||
`Name`, `RegionId` (nullable — null means global), `EffectiveFrom`, `EffectiveTo`.
|
||||
|
||||
### Price List Item
|
||||
|
||||
`PriceListId`, `ItemId`, `Price` — overrides `Item.BasePrice` when an applicable, currently-
|
||||
effective price list exists for the order's region.
|
||||
|
||||
### Supplier
|
||||
|
||||
`Name`, `ContactInfo`, `PerformanceScore` (nullable, populated by
|
||||
[15_procurement.md](./15_procurement.md)'s `evaluateSupplier`).
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `createPriceList(name, region, effectiveDates)` | `POST /api/price-lists` |
|
||||
| `updateProductPricing(sku, priceListId, price)` | `PUT /api/price-lists/{id}/items/{sku}` |
|
||||
| `createSupplierMaster(supplierData)` | `POST /api/suppliers` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- `applyPricingAndDiscounts` (§1, used in [01_item_catalog.md](./01_item_catalog.md) and
|
||||
[06_sale_ride.md](./06_sale_ride.md)) resolves price in this order: region-scoped active price
|
||||
list → global active price list → `Item.BasePrice`.
|
||||
- Two price lists cannot have overlapping `EffectiveFrom`/`EffectiveTo` ranges for the same
|
||||
`(ItemId, RegionId)` pair.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Are price lists customer/segment-specific as well as region-specific (ties into `Store.Segment`
|
||||
from [12_store_management.md](./12_store_management.md)), or region-only in v1?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,74 @@
|
||||
# 15 — Procurement / Purchasing
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 01 — Item Catalog, 14 — Pricing & Supplier Master Data
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §9](./mainPlan.md#9-procurement--purchasing) in full
|
||||
— an entirely new subsystem, nothing in this area exists in the current schema.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
Inbound stock (`createGoodsReceipt` in [04_loading_event.md](./04_loading_event.md)/
|
||||
[mainPlan.md §6](./mainPlan.md#6-warehouse-management)) currently has no
|
||||
purchase order to receive against — this step is what creates that PO in the first place.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Purchase Requisition
|
||||
|
||||
`RequestedByUserId`, `Status` (`Draft`, `Submitted`, `Approved`, `Rejected`), line items
|
||||
(`ItemId`, `Quantity`).
|
||||
|
||||
### Purchase Order
|
||||
|
||||
`SupplierId`, `RequisitionId` (nullable — a PO can be raised without a prior requisition), `Status`
|
||||
(`Issued`, `PartiallyReceived`, `Received`, `Closed`), line items (`ItemId`, `Quantity`, `UnitCost`).
|
||||
|
||||
### Purchase Return
|
||||
|
||||
`PurchaseOrderId`, reason, line items — goods sent back to the supplier post-receipt.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `createPurchaseRequisition(items[], requestedBy)` | `POST /api/purchase-requisitions` |
|
||||
| `createPurchaseOrder(supplierId, items[])` | `POST /api/purchase-orders` |
|
||||
| `matchInvoiceToPO(invoiceId, poId, grnId)` | `POST /api/purchase-orders/{id}/match-invoice` |
|
||||
| `evaluateSupplier(supplierId, criteria)` | `POST /api/suppliers/{id}/evaluate` |
|
||||
| `createPurchaseReturn(poId, items[], reason)` | `POST /api/purchase-orders/{id}/returns` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- `createGoodsReceipt` (§6, [04_loading_event.md](./04_loading_event.md)'s warehouse-inbound
|
||||
counterpart) must reference an `Issued`/`PartiallyReceived` Purchase Order — no receipts against
|
||||
a closed or nonexistent PO.
|
||||
- `matchInvoiceToPO` (3-way match) must block payment posting in
|
||||
[19_finance_accounting.md](./19_finance_accounting.md) until PO, GRN, and invoice quantities/
|
||||
amounts reconcile within a configurable tolerance.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Is procurement in scope for this project at all right now, or is inbound stock assumed to be
|
||||
seeded/manual for the foreseeable future? Confirm before investing in this step — it's the
|
||||
largest net-new subsystem in the reference doc.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,68 @@
|
||||
# 16 — Sales Force Automation (SFA) / CRM
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 12 — Store Management
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers the lead/interaction half of
|
||||
[mainPlan.md §10](./mainPlan.md#10-sales-force-automation-sfa--crm):
|
||||
`logCustomerInteraction`, `createLead`, `convertLeadToStore`. Performance/incentive functions
|
||||
(`trackTargetVsAchievement`, `calculateIncentive`) are already scoped in
|
||||
[08_sales_agent_controls.md](./08_sales_agent_controls.md) — not repeated here.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
There is no `Lead` entity or interaction log anywhere in the schema today — new outlet onboarding
|
||||
currently goes straight to `POST /api/stores` with no pre-qualification step.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Lead
|
||||
|
||||
`ProspectName`, `ContactInfo`, `Notes`, `Status` (`New`, `Qualified`, `Converted`, `Rejected`),
|
||||
`AssignedToUserId`.
|
||||
|
||||
### Customer Interaction
|
||||
|
||||
`StoreId` (nullable — can be logged against a `Lead` instead), `LeadId` (nullable), `UserId`,
|
||||
`Notes`, `CreatedAt`.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `logCustomerInteraction(storeId, notes)` | `POST /api/stores/{id}/interactions` |
|
||||
| `createLead(prospectData)` | `POST /api/leads` |
|
||||
| `convertLeadToStore(leadId)` | `POST /api/leads/{id}/convert` — creates a `Store` via the existing `POST /api/stores` path internally |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- `convertLeadToStore` reuses `createStore` (§4, [12_store_management.md](./12_store_management.md))
|
||||
rather than duplicating store-creation logic — a `Lead` conversion is not a separate creation
|
||||
path.
|
||||
- Exactly one of `StoreId`/`LeadId` must be set on a `Customer Interaction` row, never both, never
|
||||
neither.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Does a `Lead` need its own scope/authorization model (region/route), or does it inherit the
|
||||
creating user's scope with no further restriction until converted?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,70 @@
|
||||
# 17 — Promotions & Trade Scheme Management
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 01 — Item Catalog, 14 — Pricing & Supplier Master Data
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §11](./mainPlan.md#11-promotions--trade-scheme-management)
|
||||
in full — an entirely new subsystem. Once built, `applyPricingAndDiscounts` (§1) and
|
||||
`checkStoreSchemeEligibility` (§4) both read from it.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
No scheme/promotion entity exists today. Every reference to "eligible discount" elsewhere in this
|
||||
doc set (order pricing, field invoices, agent discount limits) currently has no scheme data source
|
||||
— it only enforces the flat per-role discount ceiling from
|
||||
[08_sales_agent_controls.md](./08_sales_agent_controls.md).
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Scheme
|
||||
|
||||
`Name`, `RuleType` (`BuyXGetY`, `SlabDiscount`, `FlatPercent`), `RuleConfig` (JSON), `Eligibility`
|
||||
(store segment / region scope), `DateRangeFrom`, `DateRangeTo`.
|
||||
|
||||
### Scheme Redemption
|
||||
|
||||
`SchemeId`, `OrderId` (or `FieldInvoiceId`), `DiscountAmount`, `RedeemedAt` — feeds
|
||||
`trackSchemeSpend` and `getSchemeROI` without re-scanning every order.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `createScheme(schemeRules, eligibility, dateRange)` | `POST /api/schemes` |
|
||||
| `evaluateSchemeEligibility(storeId, orderId)` | `GET /api/schemes/eligible?storeId=...&orderId=...` |
|
||||
| `applySchemeDiscount(orderId, schemeId)` | `POST /api/orders/{id}/apply-scheme` |
|
||||
| `trackSchemeSpend(schemeId)` | `GET /api/schemes/{id}/spend` |
|
||||
| `getSchemeROI(schemeId)` | `GET /api/schemes/{id}/roi` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- A scheme discount stacks with, but does not bypass, the per-role discount ceiling in
|
||||
[08_sales_agent_controls.md](./08_sales_agent_controls.md) unless explicitly flagged as an
|
||||
agent-limit-exempt scheme.
|
||||
- `getSchemeROI` requires a baseline (pre-scheme expected sales) to compare against — confirm data
|
||||
source before implementing (see Open Questions).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- What counts as "incremental sales" for `getSchemeROI` — a manual baseline entered when the scheme
|
||||
is created, or a trailing-period average computed automatically?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,74 @@
|
||||
# 18 — Returns & Claims Management
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 05 — Delivery Ride Execution, 06 — Sale Ride Execution
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §12](./mainPlan.md#12-returns--claims-management) in
|
||||
full. [05_delivery_ride.md](./05_delivery_ride.md) and [09_fraud_controls.md](./09_fraud_controls.md)
|
||||
both reference this step's credit note and approval shapes — this is where those are actually
|
||||
defined.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
No return, credit note, or claim entity exists today. `createBackorder` (§1,
|
||||
[05_delivery_ride.md](./05_delivery_ride.md)) needs a credit note target once this step lands.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Return Request
|
||||
|
||||
`OrderId`, `Items[]` (`ItemId`, `Quantity`, `ReasonCode`), `Status` (`Pending`, `Approved`,
|
||||
`Rejected`), `RequestedByUserId`.
|
||||
|
||||
### Credit Note
|
||||
|
||||
`ReturnRequestId`, `InvoiceId`, `Amount`, `IssuedAt`.
|
||||
|
||||
### Claim
|
||||
|
||||
`AgencyId`, `Details`, `Status` (`Open`, `Settled`), `SettlementAmount` (nullable until settled).
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `createReturnRequest(orderId, items[], reasonCode)` | `POST /api/returns` |
|
||||
| `approveReturn(returnId, approverId)` | `POST /api/returns/{id}/approve` |
|
||||
| `generateCreditNote(returnId)` | `POST /api/returns/{id}/credit-note` — auto-triggered on approval |
|
||||
| `createClaim(agencyId, claimDetails)` | `POST /api/claims` |
|
||||
| `settleClaim(claimId, settlementAmount)` | `POST /api/claims/{id}/settle` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- `approveReturn` requires supervisor sign-off before stock is credited back — matches the rule
|
||||
already stated in [09_fraud_controls.md](./09_fraud_controls.md).
|
||||
- A `Return Request` must reference a real prior sale/delivery line — the absence of one is exactly
|
||||
the `ReturnWithoutSale` anomaly flagged automatically in
|
||||
[09_fraud_controls.md](./09_fraud_controls.md).
|
||||
- Approving a return creates a `Return` stock movement crediting the relevant location, per
|
||||
[02_stock_ledger.md](./02_stock_ledger.md).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Are `Claim`s only against Regional Agencies (as the reference doc frames them), or can a claim
|
||||
also be raised against an internal warehouse/supplier discrepancy?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,75 @@
|
||||
# 19 — Finance & Accounting
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 05 — Delivery Ride Execution, 06 — Sale Ride Execution, 18 — Returns & Claims Management
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §13](./mainPlan.md#13-finance--accounting) beyond
|
||||
what [06_sale_ride.md](./06_sale_ride.md) already scoped for field invoices
|
||||
(`createInvoice`/`recordPayment` there are the van-sale-specific case; this step is the general
|
||||
warehouse-fulfilled-order invoicing path plus GL posting and reporting).
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
`convertOrderToInvoice` is referenced from [05_delivery_ride.md](./05_delivery_ride.md) but has no
|
||||
home until this step. There is no invoice, payment, or GL entity for warehouse-fulfilled
|
||||
(non-field-sale) orders today.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Invoice
|
||||
|
||||
`OrderId` (nullable if it originates from a field invoice instead), `StoreId`, `TotalAmount`,
|
||||
`TaxAmount`, `Status` (`Issued`, `PartiallyPaid`, `Paid`), `IssuedAt`.
|
||||
|
||||
### Payment
|
||||
|
||||
`InvoiceId`, `Amount`, `Method`, `CollectedAt` — same shape as the `Payment Record` in
|
||||
[06_sale_ride.md](./06_sale_ride.md); consider whether to unify into one table keyed by a generic
|
||||
`InvoiceId` covering both field invoices and warehouse invoices.
|
||||
|
||||
### GL Posting
|
||||
|
||||
`TransactionId`, `Account`, `DebitAmount`, `CreditAmount`, `PostedAt`.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `createInvoice(orderId)` | `POST /api/orders/{id}/invoice` — the warehouse-fulfilled counterpart to [06_sale_ride.md](./06_sale_ride.md)'s field invoice creation |
|
||||
| `recordPayment(invoiceId, amount, method)` | `POST /api/invoices/{id}/payments` |
|
||||
| `postToGeneralLedger(transactionId)` | Internal — triggered on invoice/payment/credit-note events |
|
||||
| `generateTaxInvoice(invoiceId, jurisdiction)` | `GET /api/invoices/{id}/tax-invoice?jurisdiction=...` |
|
||||
| `getFinancialReport(entityId, period, reportType)` | `GET /api/finance/reports?entityId=...&period=...&reportType=...` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- `Store.OutstandingBalance` (from [12_store_management.md](./12_store_management.md)) is
|
||||
maintained by this step's invoice/payment events, not edited directly.
|
||||
- `postToGeneralLedger` must be idempotent per `TransactionId` — replays must not double-post.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should field invoices ([06_sale_ride.md](./06_sale_ride.md)) and warehouse invoices share one
|
||||
`Invoice` table, or stay as separate entities that both feed the same GL posting logic?
|
||||
- Is e-invoicing (`generateTaxInvoice` for a specific jurisdiction like GST/VAT) an actual near-term
|
||||
requirement, or speculative until a specific market is targeted?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,65 @@
|
||||
# 20 — Reporting, Analytics & BI
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: Every prior numbered step whose data it reports on (this step is a read-only
|
||||
aggregation layer, not a new source of truth)
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §14](./mainPlan.md#14-reporting-analytics--bi) in
|
||||
full. This step deliberately reads data other steps already own — it does not introduce new
|
||||
mutable entities.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
Individual dashboards are already referenced piecemeal elsewhere (`getRegionDashboard` in
|
||||
[10_region_management.md](./10_region_management.md), `getStoreSalesDashboard` in
|
||||
[12_store_management.md](./12_store_management.md), `getWarehouseKpis` in
|
||||
[04_loading_event.md](./04_loading_event.md)/[07_ride_reconciliation.md](./07_ride_reconciliation.md)
|
||||
context). This step is the role-aware, cross-cutting dashboard/report layer that sits above all of
|
||||
them.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
None expected — this step reads existing tables. If a specific report proves too expensive to
|
||||
compute live, a cached/materialized summary table may be added per-report, decided when that report
|
||||
is actually built.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `getDashboardData(role, scope)` | `GET /api/dashboard?scope=...` — role taken from the authenticated user, not a request param |
|
||||
| `generateKpiReport(kpiType, filters)` | `GET /api/reports/{kpiType}?filters=...` |
|
||||
| `getPredictiveAlert(type, scope)` | `GET /api/alerts/predictive?type=...&scope=...` — explicitly flagged below as the highest-risk item in this step |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- `getDashboardData` must respect the same scope rules (`IScopeAccessService`) as every other
|
||||
endpoint — a dashboard is not an exception to region/route/store scoping.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- `getPredictiveAlert` implies a forecasting/ML component that doesn't exist anywhere in this
|
||||
codebase today — is this in scope at all for the near term, or should it be dropped from this
|
||||
step until a concrete predictive use case and data volume exist to justify it?
|
||||
- Which specific KPIs matter first (fill rate, OOS%, credit risk are mentioned in the reference —
|
||||
confirm the actual priority list with the user before building `generateKpiReport`)?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,67 @@
|
||||
# 21 — Notifications & Alerts
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 02 — Stock Ledger, 12 — Store Management, 08 — Sales Agent Controls
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §15](./mainPlan.md#15-notifications--alerts) in full
|
||||
— a cross-cutting delivery mechanism (email/SMS/push) triggered by events other steps already
|
||||
produce.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
No notification channel exists in this codebase today. This step is purely the delivery mechanism
|
||||
and trigger wiring; the underlying conditions it watches for are already defined elsewhere
|
||||
(reorder point in [02_stock_ledger.md](./02_stock_ledger.md), credit limit in
|
||||
[12_store_management.md](./12_store_management.md), route deviation logic in
|
||||
[09_fraud_controls.md](./09_fraud_controls.md)).
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Notification
|
||||
|
||||
`RecipientUserId`, `Type`, `Payload` (JSON), `Channel` (`Email`, `SMS`, `Push`, `InApp`), `SentAt`,
|
||||
`ReadAt` (nullable).
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `sendLowStockAlert(warehouseId, sku)` | Internal — triggered when a stock movement crosses the reorder point set in [02_stock_ledger.md](./02_stock_ledger.md) |
|
||||
| `sendCreditLimitBreachAlert(storeId)` | Internal — triggered on order/invoice creation when `Store.OutstandingBalance` exceeds `Store.CreditLimit` |
|
||||
| `sendApprovalPendingNotification(approverId, requestId)` | Internal — triggered by `submitOrderForApproval` ([08_sales_agent_controls.md](./08_sales_agent_controls.md)) and return approval ([18_returns_claims.md](./18_returns_claims.md)) |
|
||||
| `sendRouteDeviationAlert(routeId, salesRepId)` | Internal — triggered from [09_fraud_controls.md](./09_fraud_controls.md)'s anomaly flag |
|
||||
| — | `GET /api/notifications` — recipient's own notification inbox |
|
||||
| — | `PUT /api/notifications/{id}/read` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- Every `send*` function in §15 is a trigger, not a user-facing endpoint — the only client-facing
|
||||
endpoints are inbox read/list.
|
||||
- Notification delivery failures (e.g. SMS provider down) must not roll back the business
|
||||
transaction that triggered them — fire-and-forget, logged separately.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Which channels are actually needed at launch — in-app only, or SMS/email/push from day one? This
|
||||
significantly changes the infrastructure needed (third-party provider integration, see
|
||||
[22_integration_api.md](./22_integration_api.md)).
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,64 @@
|
||||
# 22 — Integration & API Layer
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 19 — Finance & Accounting, 13 — Fleet Management
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §16](./mainPlan.md#16-integration--api-layer) in full
|
||||
— outbound/inbound integration with third-party systems.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
No external system integration exists today. Each function here depends on a specific named
|
||||
third-party provider (accounting platform, telematics provider, B2B portal) that hasn't been
|
||||
chosen yet — this step cannot be scoped precisely until that choice is made per Open Questions.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Integration Sync Log
|
||||
|
||||
`TargetSystem`, `EntityType`, `EntityId`, `Status` (`Success`, `Failed`, `Retrying`), `AttemptedAt`,
|
||||
`ErrorDetail` (nullable).
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `syncToAccountingSystem(transactionId, targetSystem)` | Internal/background job — triggered on GL posting ([19_finance_accounting.md](./19_finance_accounting.md)) |
|
||||
| `syncTelematicsData(vehicleId, provider)` | Internal/background job — feeds `recordVehicleTelemetry` ([13_fleet_management.md](./13_fleet_management.md)) |
|
||||
| `pushOrderToDistributorPortal(orderId)` | Internal/background job — triggered on order status change ([05_delivery_ride.md](./05_delivery_ride.md)) |
|
||||
| — | `GET /api/integrations/sync-log?targetSystem=...` — for support/debugging |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- Every outbound sync must be retryable and idempotent — a failed sync logs to `Integration Sync
|
||||
Log` and retries rather than silently dropping.
|
||||
- No integration should be able to block the originating business transaction (order creation, GL
|
||||
posting) — sync happens after commit, asynchronously.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Which accounting platform (QuickBooks/SAP/Xero — reference doc names all three as examples), and
|
||||
which telematics provider — is either actually contracted yet, or is this step purely
|
||||
speculative until a vendor is chosen?
|
||||
- Does a "distributor-facing B2B portal" already exist to push orders to, or is this describing a
|
||||
future/external system not yet built?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,66 @@
|
||||
# 23 — Mobile & Offline Capability
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 06 — Sale Ride Execution, 05 — Delivery Ride Execution
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §17](./mainPlan.md#17-mobile--offline-capability) in
|
||||
full — offline-first support for field agents (drivers/sales reps) whose connectivity can't be
|
||||
assumed during a ride.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
There is no mobile client in this repository — this step is the server-side contract a future
|
||||
mobile client would sync against (queueing, conflict resolution, barcode resolution), not a client
|
||||
implementation itself.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Offline Action Queue Entry
|
||||
|
||||
`DeviceId`, `UserId`, `ActionType` (e.g. `CreateFieldInvoice`, `RecordDelivery`), `Payload` (JSON),
|
||||
`ClientCreatedAt`, `SyncedAt` (nullable), `ConflictStatus` (`None`, `Resolved`, `Unresolved`).
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `queueOfflineOrder(orderData)` | Client-side only — server sees it as a normal write once synced |
|
||||
| `syncOfflineQueue(deviceId)` | `POST /api/sync?deviceId=...` — batch endpoint accepting queued actions, applying them in order, returning per-action success/conflict |
|
||||
| `scanBarcode(barcodeValue)` | `GET /api/items/by-barcode/{barcodeValue}` — resolves to an `Item` (needs a `Barcode` field added to `Item`, see [01_item_catalog.md](./01_item_catalog.md)) |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- Every action type accepted by `syncOfflineQueue` must be safely replayable (idempotent by a
|
||||
client-generated id) — a duplicate sync of the same queued action must not double-apply (e.g.
|
||||
double-decrement stock).
|
||||
- A sync conflict (e.g. stock sold twice from two devices while offline) must not silently pick a
|
||||
winner — flagged `Unresolved` and surfaced to a supervisor, consistent with the reconciliation
|
||||
approach in [07_ride_reconciliation.md](./07_ride_reconciliation.md).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Is a mobile client actually planned/being built by this team, or is offline support scoped for a
|
||||
hypothetical future client? This changes whether the sync contract needs to be finalized now or
|
||||
can wait.
|
||||
- Does `Item` need a `Barcode` field added now (extends [01_item_catalog.md](./01_item_catalog.md)),
|
||||
or is barcode scanning deferred with the rest of this step?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -44,6 +44,239 @@ They must be resolved before new feature steps begin because later steps depend
|
||||
| 07 | Ride Close & Reconciliation | Not Started | 05, 06 | [07_ride_reconciliation.md](./07_ride_reconciliation.md) |
|
||||
| 08 | Sales Agent Controls | Not Started | 07 | [08_sales_agent_controls.md](./08_sales_agent_controls.md) |
|
||||
| 09 | Fraud Controls & Audit Trail | Not Started | 08 | [09_fraud_controls.md](./09_fraud_controls.md) |
|
||||
| 10 | Region Management (Beyond CRUD) | Not Started | 00 | [10_region_management.md](./10_region_management.md) |
|
||||
| 11 | Route & Sales Visit Management | Not Started | 00 | [11_route_sales_management.md](./11_route_sales_management.md) |
|
||||
| 12 | Store Management (Beyond CRUD) | Not Started | 00 | [12_store_management.md](./12_store_management.md) |
|
||||
| 13 | Fleet Management (Beyond CRUD) | Not Started | 00 | [13_fleet_management.md](./13_fleet_management.md) |
|
||||
| 14 | Pricing & Supplier Master Data | Not Started | 01 | [14_pricing_and_suppliers.md](./14_pricing_and_suppliers.md) |
|
||||
| 15 | Procurement / Purchasing | Not Started | 01, 14 | [15_procurement.md](./15_procurement.md) |
|
||||
| 16 | Sales Force Automation / CRM | Not Started | 12 | [16_sfa_crm.md](./16_sfa_crm.md) |
|
||||
| 17 | Promotions & Trade Scheme Management | Not Started | 01, 14 | [17_promotions_schemes.md](./17_promotions_schemes.md) |
|
||||
| 18 | Returns & Claims Management | Not Started | 05, 06 | [18_returns_claims.md](./18_returns_claims.md) |
|
||||
| 19 | Finance & Accounting | Not Started | 05, 06, 18 | [19_finance_accounting.md](./19_finance_accounting.md) |
|
||||
| 20 | Reporting, Analytics & BI | Not Started | all prior | [20_reporting_analytics.md](./20_reporting_analytics.md) |
|
||||
| 21 | Notifications & Alerts | Not Started | 02, 12, 08 | [21_notifications_alerts.md](./21_notifications_alerts.md) |
|
||||
| 22 | Integration & API Layer | Not Started | 19, 13 | [22_integration_api.md](./22_integration_api.md) |
|
||||
| 23 | Mobile & Offline Capability | Not Started | 05, 06 | [23_mobile_offline.md](./23_mobile_offline.md) |
|
||||
|
||||
Steps 00–09 are the original ride/stock-ledger roadmap and keep their strict dependency order.
|
||||
Steps 10–23 fill in every remaining area from the [Function Reference by Feature](#function-reference-by-feature)
|
||||
section above that wasn't already covered by 00–09; they depend only on the specific earlier steps
|
||||
noted above, not on the full 00–09 chain, and can be resequenced relative to each other as
|
||||
priorities become clearer.
|
||||
|
||||
---
|
||||
|
||||
## Function Reference by Feature
|
||||
|
||||
Reference only, not a build plan — background material to consult while planning/implementing the
|
||||
steps above and below. Service-layer function names and what each should do when implemented.
|
||||
Names are illustrative (camelCase) — adapt to your language/framework conventions. None of the
|
||||
functions below are implemented yet unless noted. User Management/RBAC functions aren't repeated
|
||||
here — see `SRS_User_Management_Module.pdf`.
|
||||
|
||||
### 1. Order Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createOrder(storeId, items[], salesRepId)` | Validates stock/credit, creates a new order in `pending` state. | Partially implemented — `OrderService.CreateAsync` exists but has no credit/stock validation or state machine yet. |
|
||||
| `validateCreditLimit(storeId, orderAmount)` | Checks store's outstanding balance + new order value against its credit limit. | Not implemented — no credit limit field on `Store` yet. |
|
||||
| `checkStockAvailability(items[])` | Confirms requested SKUs/quantities are available at the fulfilling warehouse. | Not implemented — depends on [02_stock_ledger.md](./02_stock_ledger.md). |
|
||||
| `applyPricingAndDiscounts(orderId)` | Runs the active price list and eligible scheme rules against order lines. | Not implemented — depends on §7 Master Data and §11 Promotions below. |
|
||||
| `submitOrderForApproval(orderId)` | Routes the order to the correct approver based on its value/discount threshold. | Not implemented — depends on [08_sales_agent_controls.md](./08_sales_agent_controls.md). |
|
||||
| `approveOrder(orderId, approverId)` / `rejectOrder(orderId, reason)` | Moves order to `approved` or `rejected`, logging the actor. | Not implemented. |
|
||||
| `updateOrderStatus(orderId, status)` | Transitions order through `pending → approved → picked → dispatched → delivered → invoiced`. | Not implemented — `Order` has no status field or state machine today. |
|
||||
| `convertOrderToInvoice(orderId)` | Generates the invoice once the order is dispatched/delivered. | Not implemented — depends on §13 Finance & Accounting. |
|
||||
| `createBackorder(orderId, unfulfilledItems[])` | Splits unfulfillable quantity into a linked backorder. | Not implemented. |
|
||||
| `cancelOrder(orderId, reason)` | Cancels an order pre-dispatch and releases any reserved stock. | Not implemented — `OrderController` supports soft delete only, no reason code or stock release. |
|
||||
| `getOrdersByStore(storeId)` / `getOrdersBySalesRep(salesRepId)` | Scoped order listings. | `getOrdersByStore` implemented (`GET /api/orders/store/{storeId}`). `getOrdersBySalesRep` not implemented. |
|
||||
| `getOrderAnalytics(filters)` | Returns fill rate, average order value, rejection rate over a period. | Not implemented. |
|
||||
|
||||
### 2. Region Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createRegion(name, boundaries)` | Creates a new region record. | Partially implemented — `RegionController` supports create with `Name` only, no `boundaries` field. |
|
||||
| `assignAreaToRegion(areaId, regionId)` | Links an existing area under a region. | Not implemented — no `Area` entity exists in the schema. |
|
||||
| `setRegionalTarget(regionId, period, targetValue)` | Sets the sales target for a region for a given period. | Not implemented. |
|
||||
| `setRegionalBudget(regionId, period, budgetAmount)` | Allocates budget at the regional level. | Not implemented. |
|
||||
| `mapAgencyToRegion(agencyId, regionId)` | Assigns a Regional Agency's contract scope to a region. | Not implemented — `RegionalAgency` is currently a `Role`, not a distinct agency entity; scope is via `UserRegionScope`. |
|
||||
| `setRegionalTaxRule(regionId, ruleConfig)` | Overrides tax/pricing rules for a specific region. | Not implemented. |
|
||||
| `getRegionHierarchy(regionId)` | Returns all areas/territories nested under a region. | Not implemented — see [user_management_PLAN.md](../user_management_PLAN.md) territory hierarchy prerequisite. |
|
||||
| `getRegionDashboard(regionId, period)` | Aggregated KPIs: sales, fill rate, agency performance for the region. | Not implemented. |
|
||||
|
||||
### 3. Route Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createRoute(name, areaId)` | Creates a new sales visit route within an area. | Partially implemented — `RouteController` supports create, but by `RegionId` not `areaId` (no `Area` entity exists). |
|
||||
| `assignOutletsToRoute(routeId, outletIds[])` | Maps a set of stores to a route. | Partially implemented — a `Store` has a single `FK_RouteId`, set via `StoreController` create/update; no bulk-assign endpoint. |
|
||||
| `assignRouteToSalesRep(routeId, salesRepId)` | Assigns ownership of a route to an SR. | Not implemented — only `UserRouteScope` exists (scope grant), not a single-owner assignment. |
|
||||
| `optimizeRouteSequence(routeId)` | Reorders the visit sequence for efficiency (distance/priority-based). | Not implemented. |
|
||||
| `recordRouteCheckIn(routeId, outletId, gpsCoords, timestamp)` | Logs an SR's arrival at an outlet. | Not implemented. |
|
||||
| `recordRouteCheckOut(routeId, outletId, timestamp)` | Logs departure and closes out the visit. | Not implemented. |
|
||||
| `getRouteAdherence(routeId, date)` | Compares planned vs. actual visits for a route/day. | Not implemented — depends on check-in/check-out above. |
|
||||
| `reassignRoute(routeId, newSalesRepId)` | Moves a route to a different SR. | Not implemented. |
|
||||
| `getRoutePerformance(routeId, period)` | Sales per route, strike rate, missed-visit count. | Not implemented. |
|
||||
| `planDeliveryRoute(warehouseId, orderIds[])` | Builds the warehouse-to-outlet dispatch route (separate from the sales visit route). | Not implemented — this is the `DeliveryRun` concept; see [03_ride_lifecycle.md](./03_ride_lifecycle.md). |
|
||||
|
||||
### 4. Store (Outlet) Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createStore(storeData, kycDocs)` | Onboards a new outlet with required KYC. | Partially implemented — `StoreController` create exists, no KYC document handling. |
|
||||
| `verifyStoreKyc(storeId)` | Marks KYC as verified/rejected by an approver. | Not implemented. |
|
||||
| `updateStoreCreditLimit(storeId, newLimit, approverId)` | Changes a store's credit limit, subject to approval authority. | Not implemented — no credit limit field on `Store`. |
|
||||
| `getStoreOutstandingBalance(storeId)` | Returns current receivables against the store. | Not implemented — depends on §13 Finance & Accounting. |
|
||||
| `classifyStore(storeId, segment)` | Sets/updates the store's segment (key account, general trade, etc.). | Not implemented — no segment field on `Store`. |
|
||||
| `deactivateStore(storeId, reason)` | Soft-deletes a store, blocking new orders against it. | Partially implemented — `DELETE /api/stores/{id}` soft-deletes via `IsActive`, no reason code captured. |
|
||||
| `getStoreVisitHistory(storeId)` | Returns visit compliance history from Route Management. | Not implemented — depends on route check-in/check-out (§3). |
|
||||
| `checkStoreSchemeEligibility(storeId, schemeId)` | Evaluates whether a store qualifies for a given promotion. | Not implemented — depends on §11 Promotions. |
|
||||
| `getStoreSalesDashboard(storeId, period)` | Store-level sales and outstanding summary. | Not implemented. |
|
||||
|
||||
### 5. Vehicle / Fleet Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `registerVehicle(vehicleData)` | Creates the vehicle master record (registration, capacity, ownership). | Partially implemented — `VehicleController` create exists with `RegNumber`/`NumberPlate` only, no capacity/ownership fields. |
|
||||
| `assignVehicleToRoute(vehicleId, routeId)` | Assigns a vehicle to a delivery/sales route. | Not implemented — `Vehicle` has no route link; only `DeliveryRun.FK_RouteId`/`FK_VehicleId` model this, and `DeliveryRun` is unwired (see [03_ride_lifecycle.md](./03_ride_lifecycle.md)). |
|
||||
| `assignDriverToVehicle(vehicleId, driverId)` | Links a driver to a vehicle, checking license validity. | Not implemented — no `Driver` entity exists yet (`DeliveryRun.FK_DriverId` is commented out in the model). |
|
||||
| `scheduleMaintenance(vehicleId, maintenanceType, dueAt)` | Sets up a preventive maintenance task (mileage/calendar-based). | Not implemented. |
|
||||
| `logFuelExpense(vehicleId, amount, liters, date)` | Records a fuel transaction against the vehicle. | Not implemented. |
|
||||
| `recordVehicleTelemetry(vehicleId, gpsCoords, speed, timestamp)` | Ingests telematics data (location, driver behavior). | Not implemented — see §16 Integration. |
|
||||
| `checkDriverCompliance(driverId)` | Validates license/permit status before dispatch. | Not implemented — depends on a `Driver` entity. |
|
||||
| `calculateLoadPlan(vehicleId, orderIds[])` | Matches order volume/weight to available vehicle capacity. | Not implemented — depends on [04_loading_event.md](./04_loading_event.md). |
|
||||
| `getVehicleUtilization(vehicleId, period)` | Utilization and cost-per-delivery metrics. | Not implemented. |
|
||||
| `retireVehicle(vehicleId, reason)` | Removes a vehicle from active fleet, retaining history. | Partially implemented — `DELETE /api/vehicles/{id}` soft-deletes via `IsActive`, no reason code captured. |
|
||||
|
||||
### 6. Warehouse Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createGoodsReceipt(poId, receivedItems[])` | Records inbound stock against a purchase order (GRN). | Not implemented — depends on §9 Procurement. |
|
||||
| `inspectGoodsReceipt(grnId, inspectionResult)` | Logs quality-check outcome before putaway. | Not implemented. |
|
||||
| `putawayStock(grnId, locationAssignments[])` | Assigns received stock to warehouse locations (zone/ABC-based). | Not implemented — depends on [02_stock_ledger.md](./02_stock_ledger.md). |
|
||||
| `getStockByLocation(warehouseId, sku)` | Returns real-time quantity and location for a SKU. | Not implemented — depends on [02_stock_ledger.md](./02_stock_ledger.md). |
|
||||
| `generatePickList(orderIds[])` | Creates a pick list/task for one or more orders. | Not implemented — overlaps with [04_loading_event.md](./04_loading_event.md). |
|
||||
| `confirmPicking(pickListId, pickedItems[])` | Marks items as picked, decrementing available stock. | Not implemented. |
|
||||
| `packOrder(orderId, packageDetails)` | Records packing completion and package details. | Not implemented. |
|
||||
| `createCrossDockTransfer(inboundId, outboundOrderId)` | Routes inbound stock directly to an outbound order, skipping storage. | Not implemented. |
|
||||
| `runCycleCount(warehouseId, zoneId)` | Initiates a scheduled partial stock count. | Not implemented — overlaps with [07_ride_reconciliation.md](./07_ride_reconciliation.md) physical count concept. |
|
||||
| `reconcileStock(warehouseId, countResults)` | Adjusts system stock to match physical count results. | Not implemented — see [07_ride_reconciliation.md](./07_ride_reconciliation.md). |
|
||||
| `buildDispatchLoad(vehicleId, orderIds[])` | Groups orders into a dispatch load for a specific vehicle/route. | Not implemented — see [04_loading_event.md](./04_loading_event.md). |
|
||||
| `receiveReturn(returnId, items[], condition)` | Processes returned goods back into the warehouse. | Not implemented — see §12 Returns & Claims. |
|
||||
| `getWarehouseKpis(warehouseId, period)` | Dock-to-stock time, pick accuracy, turnover rate. | Not implemented. |
|
||||
|
||||
### 7. Master Data Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createProduct(skuData)` | Creates a SKU with category, UOM, barcode, pack hierarchy. | Not implemented — `Item` model exists but has no properties; see [01_item_catalog.md](./01_item_catalog.md). |
|
||||
| `deactivateSku(sku, reason)` | Retires a SKU from active ordering. | Not implemented. |
|
||||
| `createPriceList(name, region, effectiveDates)` | Defines a new price list scoped to a region/date range. | Not implemented — no price list entity exists. |
|
||||
| `updateProductPricing(sku, priceListId, price)` | Sets/updates the price of a SKU within a price list. | Not implemented. |
|
||||
| `createSupplierMaster(supplierData)` | Registers a new supplier/vendor. | Not implemented — no `Supplier` entity exists. |
|
||||
|
||||
### 8. Enterprise-wide Inventory & Stock
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `getMultiLocationStock(sku)` | Returns stock levels for a SKU across all warehouses/agencies. | Not implemented — see [02_stock_ledger.md](./02_stock_ledger.md). |
|
||||
| `setReorderPoint(sku, warehouseId, threshold)` | Configures the auto-replenishment trigger level. | Not implemented. |
|
||||
| `createStockTransfer(fromWarehouseId, toWarehouseId, items[])` | Initiates an inter-warehouse stock movement. | Not implemented. |
|
||||
| `trackBatchExpiry(sku, batchNo, expiryDate)` | Registers batch/expiry data for FEFO enforcement. | Not implemented. |
|
||||
| `calculateStockValuation(warehouseId, method)` | Computes stock value using FIFO/weighted-average. | Not implemented. |
|
||||
|
||||
### 9. Procurement / Purchasing
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createPurchaseRequisition(items[], requestedBy)` | Raises an internal request to purchase stock. | Not implemented — no procurement entities exist. |
|
||||
| `createPurchaseOrder(supplierId, items[])` | Issues a PO to a supplier. | Not implemented. |
|
||||
| `matchInvoiceToPO(invoiceId, poId, grnId)` | Performs the 3-way match (PO–GRN–Invoice) before payment. | Not implemented. |
|
||||
| `evaluateSupplier(supplierId, criteria)` | Scores a supplier on delivery/quality/price performance. | Not implemented. |
|
||||
| `createPurchaseReturn(poId, items[], reason)` | Initiates a return of received goods to the supplier. | Not implemented. |
|
||||
|
||||
### 10. Sales Force Automation (SFA) / CRM
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `logCustomerInteraction(storeId, notes)` | Records a visit note or customer interaction. | Not implemented. |
|
||||
| `trackTargetVsAchievement(userId, period)` | Compares a user's sales target to actuals. | Not implemented. |
|
||||
| `calculateIncentive(userId, period)` | Computes commission/incentive payout based on performance. | Not implemented. |
|
||||
| `createLead(prospectData)` | Captures a prospective new outlet. | Not implemented — no `Lead` entity exists. |
|
||||
| `convertLeadToStore(leadId)` | Converts a qualified lead into an active store record. | Not implemented. |
|
||||
|
||||
### 11. Promotions & Trade Scheme Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createScheme(schemeRules, eligibility, dateRange)` | Defines a promotion (buy-X-get-Y, slab discount, etc.). | Not implemented — no scheme entity exists. |
|
||||
| `evaluateSchemeEligibility(storeId, orderId)` | Checks whether an order qualifies for a scheme. | Not implemented. |
|
||||
| `applySchemeDiscount(orderId, schemeId)` | Applies the scheme's discount to order pricing. | Not implemented. |
|
||||
| `trackSchemeSpend(schemeId)` | Aggregates total discount value given under a scheme. | Not implemented. |
|
||||
| `getSchemeROI(schemeId)` | Compares scheme spend against incremental sales generated. | Not implemented. |
|
||||
|
||||
### 12. Returns & Claims Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createReturnRequest(orderId, items[], reasonCode)` | Initiates a return against a delivered order. | Not implemented — see [09_fraud_controls.md](./09_fraud_controls.md) for the fraud-control angle on returns. |
|
||||
| `approveReturn(returnId, approverId)` | Approves/rejects the return request. | Not implemented. |
|
||||
| `generateCreditNote(returnId)` | Issues a credit note against the original invoice. | Not implemented. |
|
||||
| `createClaim(agencyId, claimDetails)` | Raises a claim against a Regional Agency (e.g., damaged stock). | Not implemented. |
|
||||
| `settleClaim(claimId, settlementAmount)` | Closes out a claim with a final settlement amount. | Not implemented. |
|
||||
|
||||
### 13. Finance & Accounting
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createInvoice(orderId)` | Generates a formal invoice from a fulfilled order. | Not implemented — no invoice entity exists. |
|
||||
| `recordPayment(invoiceId, amount, method)` | Logs a payment/collection against an invoice. | Not implemented. |
|
||||
| `postToGeneralLedger(transactionId)` | Posts a financial transaction to the GL. | Not implemented. |
|
||||
| `generateTaxInvoice(invoiceId, jurisdiction)` | Produces a tax-compliant invoice (GST/VAT e-invoicing). | Not implemented. |
|
||||
| `getFinancialReport(entityId, period, reportType)` | Returns P&L/cash-flow reports scoped to a region/area. | Not implemented. |
|
||||
|
||||
### 14. Reporting, Analytics & BI
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `getDashboardData(role, scope)` | Returns the role-appropriate dashboard payload. | Not implemented. |
|
||||
| `generateKpiReport(kpiType, filters)` | Produces a specific KPI report (fill rate, OOS%, credit risk, etc.). | Not implemented. |
|
||||
| `getPredictiveAlert(type, scope)` | Returns AI-flagged issues, e.g. a region's declining fill rate. | Not implemented. |
|
||||
|
||||
### 15. Notifications & Alerts
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `sendLowStockAlert(warehouseId, sku)` | Notifies relevant roles when stock drops below the reorder point. | Not implemented. |
|
||||
| `sendCreditLimitBreachAlert(storeId)` | Notifies approvers when a store exceeds its credit limit. | Not implemented. |
|
||||
| `sendApprovalPendingNotification(approverId, requestId)` | Notifies an approver of a pending action. | Not implemented. |
|
||||
| `sendRouteDeviationAlert(routeId, salesRepId)` | Flags when an SR deviates materially from the planned route. | Not implemented — see [09_fraud_controls.md](./09_fraud_controls.md) geofencing check. |
|
||||
|
||||
### 16. Integration & API Layer
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `syncToAccountingSystem(transactionId, targetSystem)` | Pushes a transaction to an external accounting platform (QuickBooks/SAP/Xero). | Not implemented. |
|
||||
| `syncTelematicsData(vehicleId, provider)` | Pulls GPS/telematics data from a third-party provider. | Not implemented. |
|
||||
| `pushOrderToDistributorPortal(orderId)` | Syncs order status to a distributor-facing B2B portal. | Not implemented. |
|
||||
|
||||
### 17. Mobile & Offline Capability
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `queueOfflineOrder(orderData)` | Stores an order locally on the device when offline. | Not implemented — no mobile client in this repository. |
|
||||
| `syncOfflineQueue(deviceId)` | Pushes queued offline actions to the server once connectivity returns. | Not implemented. |
|
||||
| `scanBarcode(barcodeValue)` | Resolves a scanned barcode to a SKU or warehouse location. | Not implemented. |
|
||||
|
||||
### 18. Compliance & Audit
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `logAuditEvent(userId, action, entityType, entityId)` | Writes an immutable audit trail entry for a state-changing action. | Not implemented — see [09_fraud_controls.md](./09_fraud_controls.md), which scopes an audit log as part of Fraud Controls. |
|
||||
| `checkLicenseCompliance(entityId, licenseType)` | Validates a required license/permit is current (e.g., regulated goods). | Not implemented. |
|
||||
| `generateComplianceReport(period)` | Produces a report of compliance status across the org for a period. | Not implemented. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user