Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d2390e7b12 |
@@ -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,18 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Give `Item` real properties and a CRUD feature slice, so it can be created, updated, deactivated,
|
||||
and referenced meaningfully from `OrderItem`. Covers `createProduct` and `deactivateSku` from
|
||||
[mainPlan.md §7](./mainPlan.md#7-master-data-management). Price lists and
|
||||
supplier master data are intentionally out of scope — see [14_pricing_and_suppliers.md](./14_pricing_and_suppliers.md).
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
`Models/Item.cs` currently only inherits `Template` (`Id`, `CreatedAt`, `IsActive`) with no
|
||||
properties of its own. `OrderItem` already has `FK_ItemId` wired, but there's no data behind it and
|
||||
no `Features/Item/` slice exists. This is the same gap flagged as #6 in [00_existing_gaps.md](./00_existing_gaps.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -21,29 +26,51 @@
|
||||
|
||||
### Item
|
||||
|
||||
Add to `Models/Item.cs` (still inherits `Template`):
|
||||
|
||||
- `Sku` (string, unique, required) — the stock-keeping unit code.
|
||||
- `Name` (string, required)
|
||||
- `Unit` (string, required) — unit of measure (e.g. `EA`, `CASE`, `KG`).
|
||||
- `Category` (string, optional) — for grouping/reporting; not a separate entity in this step.
|
||||
- `BasePrice` (decimal) — default price used until §15 (price lists) is built.
|
||||
|
||||
### Related Changes to Existing Models
|
||||
|
||||
|
||||
- No changes to `OrderItem` — `FK_ItemId` already points at the right place.
|
||||
- `deactivateSku` maps directly onto the existing soft-delete (`IsActive = false`) pattern; no new
|
||||
field needed.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Item — `/api/items`
|
||||
|
||||
| Method | Route | Description | Function reference |
|
||||
|---|---|---|---|
|
||||
| GET | `/` | Get all active items | — |
|
||||
| GET | `/{id}` | Get item by id | — |
|
||||
| POST | `/` | Create item | `createProduct(skuData)` |
|
||||
| PUT | `/{id}` | Update item | — |
|
||||
| DELETE | `/{id}` | Soft delete (deactivate) | `deactivateSku(sku, reason)` — reason is logged, not stored, in v1 |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- `Sku` must be unique among active items.
|
||||
- An `Item` cannot be hard-deleted once referenced by any `OrderItem` — deactivate only.
|
||||
- Follow the existing feature-slice pattern exactly: `ItemDTOs.cs`, `IItemRepository.cs`,
|
||||
`ItemRepository.cs`, `ItemService.cs`, `ItemController.cs` (see `Features/Region/` for the
|
||||
reference shape).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Does `deactivateSku` need a stored `reason` field, or is an audit-log entry (§18, once built)
|
||||
sufficient instead of a new column on `Item`?
|
||||
- Should `Category` be a free-text field in v1, or a proper lookup entity from day one?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,13 +7,18 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Introduce stock-per-location as a first-class, transaction-driven concept. Covers
|
||||
[mainPlan.md §8](./mainPlan.md#8-enterprise-wide-inventory--stock) in full,
|
||||
plus the location-query half of §6 (`getStockByLocation`, `putawayStock`). Everything downstream —
|
||||
loading events, delivery/sale ride execution, reconciliation — reads or writes through this ledger.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
Nothing in the current schema tracks quantity of an `Item` anywhere. `checkStockAvailability` (§1)
|
||||
and every warehouse function in §6 have no data source to query today. This step must land before
|
||||
[04_loading_event.md](./04_loading_event.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -21,39 +26,60 @@
|
||||
|
||||
### Stock Balance (per location)
|
||||
|
||||
|
||||
A denormalized running balance per `(ItemId, LocationId)` pair, kept in sync by the movement
|
||||
transaction table below — never edited directly by any service.
|
||||
|
||||
### Stock Movement Transaction
|
||||
|
||||
|
||||
One row per stock change: `ItemId`, `LocationId`, `Quantity` (signed: +load/return, -sale/damage),
|
||||
`MovementType` (`Load`, `Sale`, `Return`, `Damage`, `Adjustment`), `ReferenceId` (the order/ride/GRN
|
||||
that caused it), `CreatedAt`. This is the source of truth; the balance is its running sum.
|
||||
|
||||
### Location Abstraction (Warehouse vs Vehicle)
|
||||
|
||||
|
||||
A `Location` concept that can resolve to either a `Warehouse` or a `Vehicle` (once a ride is open —
|
||||
see [03_ride_lifecycle.md](./03_ride_lifecycle.md)), so the same movement/balance model serves both
|
||||
`getMultiLocationStock` (§8) and the vehicle-as-temporary-warehouse pattern.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `getMultiLocationStock(sku)` (§8) | `GET /api/stock/items/{sku}` — balance across every location |
|
||||
| `getStockByLocation(warehouseId, sku)` (§6) | `GET /api/stock/locations/{locationId}/items/{sku}` |
|
||||
| `setReorderPoint(sku, warehouseId, threshold)` (§8) | `PUT /api/stock/locations/{locationId}/items/{sku}/reorder-point` |
|
||||
| `createStockTransfer(fromWarehouseId, toWarehouseId, items[])` (§8) | `POST /api/stock/transfers` |
|
||||
| `trackBatchExpiry(sku, batchNo, expiryDate)` (§8) | Deferred — see Open Questions |
|
||||
| `calculateStockValuation(warehouseId, method)` (§8) | `GET /api/stock/locations/{locationId}/valuation?method=fifo\|weighted-average` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- Stock is never edited directly — every change is a `Stock Movement Transaction`; the balance is
|
||||
always the running sum, recalculated or cached but never hand-edited.
|
||||
- A movement cannot take a balance negative unless explicitly flagged as an `Adjustment` (physical
|
||||
count correction).
|
||||
- Every movement must carry a `ReferenceId` back to the business event that caused it — no
|
||||
freestanding movements outside of documented `Adjustment`s.
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
- `Adjustment` movements should be restricted to Supervisor level and above (see role-permission
|
||||
mapping in `Data/Seed/RoleAndPermissionSeed.cs`), and should require a reason code once
|
||||
[09_fraud_controls.md](./09_fraud_controls.md) lands.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Is batch/expiry tracking (`trackBatchExpiry`) needed in v1, or can it be deferred until a
|
||||
regulated/perishable product line requires FEFO enforcement?
|
||||
- FIFO vs weighted-average valuation — does the business need both, or a single fixed method?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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