diff --git a/Distribution/docs/00_existing_gaps.md b/Distribution/docs/00_existing_gaps.md index 97779fd..73cf60e 100644 --- a/Distribution/docs/00_existing_gaps.md +++ b/Distribution/docs/00_existing_gaps.md @@ -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? --- diff --git a/Distribution/docs/03_ride_lifecycle.md b/Distribution/docs/03_ride_lifecycle.md index c661bc2..03250f0 100644 --- a/Distribution/docs/03_ride_lifecycle.md +++ b/Distribution/docs/03_ride_lifecycle.md @@ -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. --- diff --git a/Distribution/docs/04_loading_event.md b/Distribution/docs/04_loading_event.md index 5a8f747..8c1465f 100644 --- a/Distribution/docs/04_loading_event.md +++ b/Distribution/docs/04_loading_event.md @@ -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? --- diff --git a/Distribution/docs/05_delivery_ride.md b/Distribution/docs/05_delivery_ride.md index f18bbc0..656dffd 100644 --- a/Distribution/docs/05_delivery_ride.md +++ b/Distribution/docs/05_delivery_ride.md @@ -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? --- diff --git a/Distribution/docs/06_sale_ride.md b/Distribution/docs/06_sale_ride.md index a497294..e162525 100644 --- a/Distribution/docs/06_sale_ride.md +++ b/Distribution/docs/06_sale_ride.md @@ -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? --- diff --git a/Distribution/docs/07_ride_reconciliation.md b/Distribution/docs/07_ride_reconciliation.md index 0e53706..9186340 100644 --- a/Distribution/docs/07_ride_reconciliation.md +++ b/Distribution/docs/07_ride_reconciliation.md @@ -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? --- diff --git a/Distribution/docs/08_sales_agent_controls.md b/Distribution/docs/08_sales_agent_controls.md index 1bafeac..ae8adc9 100644 --- a/Distribution/docs/08_sales_agent_controls.md +++ b/Distribution/docs/08_sales_agent_controls.md @@ -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? --- diff --git a/Distribution/docs/09_fraud_controls.md b/Distribution/docs/09_fraud_controls.md index 7e8534d..a1ed569 100644 --- a/Distribution/docs/09_fraud_controls.md +++ b/Distribution/docs/09_fraud_controls.md @@ -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? --- diff --git a/Distribution/docs/10_region_management.md b/Distribution/docs/10_region_management.md new file mode 100644 index 0000000..5ed01e5 --- /dev/null +++ b/Distribution/docs/10_region_management.md @@ -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. diff --git a/Distribution/docs/11_route_sales_management.md b/Distribution/docs/11_route_sales_management.md new file mode 100644 index 0000000..54b369c --- /dev/null +++ b/Distribution/docs/11_route_sales_management.md @@ -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. diff --git a/Distribution/docs/12_store_management.md b/Distribution/docs/12_store_management.md new file mode 100644 index 0000000..42753ec --- /dev/null +++ b/Distribution/docs/12_store_management.md @@ -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. diff --git a/Distribution/docs/13_fleet_management.md b/Distribution/docs/13_fleet_management.md new file mode 100644 index 0000000..795673b --- /dev/null +++ b/Distribution/docs/13_fleet_management.md @@ -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. diff --git a/Distribution/docs/14_pricing_and_suppliers.md b/Distribution/docs/14_pricing_and_suppliers.md new file mode 100644 index 0000000..ea0ca15 --- /dev/null +++ b/Distribution/docs/14_pricing_and_suppliers.md @@ -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. diff --git a/Distribution/docs/15_procurement.md b/Distribution/docs/15_procurement.md new file mode 100644 index 0000000..a4d36c5 --- /dev/null +++ b/Distribution/docs/15_procurement.md @@ -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. diff --git a/Distribution/docs/16_sfa_crm.md b/Distribution/docs/16_sfa_crm.md new file mode 100644 index 0000000..e658dbf --- /dev/null +++ b/Distribution/docs/16_sfa_crm.md @@ -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. diff --git a/Distribution/docs/17_promotions_schemes.md b/Distribution/docs/17_promotions_schemes.md new file mode 100644 index 0000000..f0a67e4 --- /dev/null +++ b/Distribution/docs/17_promotions_schemes.md @@ -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. diff --git a/Distribution/docs/18_returns_claims.md b/Distribution/docs/18_returns_claims.md new file mode 100644 index 0000000..653d46e --- /dev/null +++ b/Distribution/docs/18_returns_claims.md @@ -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. diff --git a/Distribution/docs/19_finance_accounting.md b/Distribution/docs/19_finance_accounting.md new file mode 100644 index 0000000..906d3b5 --- /dev/null +++ b/Distribution/docs/19_finance_accounting.md @@ -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. diff --git a/Distribution/docs/20_reporting_analytics.md b/Distribution/docs/20_reporting_analytics.md new file mode 100644 index 0000000..9ee9530 --- /dev/null +++ b/Distribution/docs/20_reporting_analytics.md @@ -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. diff --git a/Distribution/docs/21_notifications_alerts.md b/Distribution/docs/21_notifications_alerts.md new file mode 100644 index 0000000..6c7ea07 --- /dev/null +++ b/Distribution/docs/21_notifications_alerts.md @@ -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. diff --git a/Distribution/docs/22_integration_api.md b/Distribution/docs/22_integration_api.md new file mode 100644 index 0000000..4ca4258 --- /dev/null +++ b/Distribution/docs/22_integration_api.md @@ -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. diff --git a/Distribution/docs/23_mobile_offline.md b/Distribution/docs/23_mobile_offline.md new file mode 100644 index 0000000..769af23 --- /dev/null +++ b/Distribution/docs/23_mobile_offline.md @@ -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. diff --git a/Distribution/docs/mainPlan.md b/Distribution/docs/mainPlan.md index 3b7cc94..f6bb2fb 100644 --- a/Distribution/docs/mainPlan.md +++ b/Distribution/docs/mainPlan.md @@ -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. | ---