Add DeliveryTask, Payment, and Sale features
Implemented DeliveryTask, Payment, and Sale entities with full CRUD, business logic, and API endpoints. Updated Order/OrderItem for sale linkage and fulfillment tracking. Registered new DbSets and DI services. Revised documentation for delivery, sale, and payment workflows.
This commit is contained in:
@@ -1,80 +1,132 @@
|
||||
# 06 — Sale Ride Execution
|
||||
|
||||
**Status**: Not Started
|
||||
**Status**: Planning
|
||||
**Depends On**: 04 — Loading Event
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
Allow a sales agent on an active Sale ride to create sales in the field. Every sale creates an
|
||||
order. Immediate handover deducts vehicle stock and closes the order immediately. Non-immediate
|
||||
handover leaves the order pending for a future delivery ride.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
- `SaleService.CreateAsync` already exists and handles both `FulfillImmediately` paths.
|
||||
- What Step 06 adds: vehicle stock validation and deduction on immediate handover, and the
|
||||
Payment recording endpoint.
|
||||
- `Sale.PaymentStatus` and the `Payment` model were added in pre-planning.
|
||||
- Pricing is already resolved via `PriceListService` inside `SaleService`.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Field Invoice
|
||||
No new entities. Everything needed was added in pre-planning:
|
||||
|
||||
`RideId`, `StoreId`, `CreatedByUserId`, `TotalAmount`, `PaymentStatus` (`Paid`, `PartiallyPaid`,
|
||||
`OnCredit`).
|
||||
| What | Where | Status |
|
||||
|---|---|---|
|
||||
| `Payment` entity | `Models/Payment.cs` | ✅ Done |
|
||||
| `PaymentStatus` enum | `Models/Enums.cs` | ✅ Done |
|
||||
| `PaymentMethod` enum | `Models/Enums.cs` | ✅ Done |
|
||||
| `Sale.PaymentStatus` | `Models/Sale.cs` | ✅ Done |
|
||||
| `OrderItem.QuantityFulfilled` | `Models/OrderItem.cs` | ✅ Done |
|
||||
|
||||
### 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`.
|
||||
Migration needed for: `Payment` table, `Sale.PaymentStatus` column, `OrderItem.QuantityFulfilled` column.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
## Changes to Existing SaleService
|
||||
|
||||
| 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 |
|
||||
`SaleService.CreateAsync` needs two additions when `FulfillImmediately = true` and `RideId` is set:
|
||||
|
||||
1. **Stock check** — for each item, verify vehicle has enough stock (`StockBalance` at
|
||||
`LocationType.Vehicle` with `LocationId = VehicleId`). Reject the whole sale if any item is short.
|
||||
2. **Stock deduction** — debit vehicle balance per item, create one `StockMovement` per item:
|
||||
- `MovementType.Sale`
|
||||
- `FromLocationId = VehicleId`, `FromLocationType = Vehicle`
|
||||
- `ReferenceId = OrderId`
|
||||
|
||||
`VehicleId` is resolved from `Sale.FK_RideId → DeliveryRun.FK_VehicleId`.
|
||||
|
||||
**SaleService constructor additions required:**
|
||||
- `IStockRepository` — for stock balance check and debit
|
||||
- `IRideRepository` — to look up the ride and resolve `FK_VehicleId`
|
||||
|
||||
When `FulfillImmediately = false`, no stock movement — order stays Pending for a delivery ride.
|
||||
|
||||
---
|
||||
|
||||
## New Feature — Payment
|
||||
|
||||
**Feature folder:** `Features/Payment/`
|
||||
|
||||
### API Endpoints
|
||||
|
||||
| Method | Route | Who | Description |
|
||||
|---|---|---|---|
|
||||
| POST | `/api/sales/{id}/payments` | Driver/Agent | Record a payment against a sale |
|
||||
| GET | `/api/sales/{id}/payments` | Any | List all payments for a sale |
|
||||
|
||||
### Payment Creation Logic
|
||||
|
||||
On `POST /api/sales/{id}/payments`:
|
||||
1. Validate sale exists and is not `Cancelled`.
|
||||
2. Add `Payment` record (`FK_SaleId`, `FK_StoreId` from sale, `Amount`, `Method`, `CollectedAt`, `CollectedBy`).
|
||||
3. Sum all payments for this sale. Recalculate `Sale.PaymentStatus`:
|
||||
- `sum == 0` → `Pending`
|
||||
- `sum < Sale.TotalAmount` → `PartiallyPaid`
|
||||
- `sum >= Sale.TotalAmount` → `Paid`
|
||||
4. Update `Sale.PaymentStatus` in the same transaction.
|
||||
|
||||
### DTOs
|
||||
|
||||
```csharp
|
||||
public record CreatePaymentRequest(decimal Amount, PaymentMethod Method);
|
||||
public record PaymentResponse(Guid Id, Guid SaleId, Guid StoreId, decimal Amount,
|
||||
PaymentMethod Method, DateTime CollectedAt, Guid CollectedBy);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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).
|
||||
1. Driver cannot sell more than vehicle stock for immediate handover — entire sale rejected if any
|
||||
item is short (no partial sale creation).
|
||||
2. Every sale creates an order, even when all items are handed over immediately.
|
||||
3. `FulfillImmediately = true`:
|
||||
- Vehicle stock debited immediately per item.
|
||||
- Order → `Fulfilled`, Sale → `Completed` at creation time (already in SaleService).
|
||||
- `QuantityFulfilled = Quantity` on all order items (already in SaleService).
|
||||
4. `FulfillImmediately = false`:
|
||||
- No stock movement at sale time.
|
||||
- Order → `Pending`, Sale → `Pending` at creation time.
|
||||
5. Payment can be recorded at any time — sale does not need to be `Completed` first.
|
||||
6. Overpayment is allowed (`sum > TotalAmount`) — `PaymentStatus` stays `Paid`.
|
||||
7. A `Cancelled` sale cannot receive new payments.
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
## Implementation Build Order
|
||||
|
||||
- Cash collected must reconcile against invoiced totals at ride close — see
|
||||
[07_ride_reconciliation.md](./07_ride_reconciliation.md).
|
||||
1. Migration — `Payment` table, `Sale.PaymentStatus`, `OrderItem.QuantityFulfilled`
|
||||
2. Extend `SaleService.CreateAsync` — stock check + stock movement on `FulfillImmediately`
|
||||
3. DTOs — `PaymentDTOs.cs` in `Features/Payment/`
|
||||
4. Repository — `IPaymentRepository`, `PaymentRepository`
|
||||
5. Service — `PaymentService`
|
||||
6. Controller — `PaymentController`
|
||||
7. DI registration in `Program.cs`
|
||||
|
||||
**Feature folder:** `Features/Payment/` (new), `Features/Sale/` (extended)
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
## Open Questions — Resolved
|
||||
|
||||
- Are field invoices numbered/sequenced separately from warehouse-issued invoices, or share one
|
||||
sequence?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
- ✅ Payment timing — can be recorded at any time, not locked to sale completion
|
||||
- ✅ Overpayment — allowed, status stays Paid
|
||||
- ✅ Stock check scope — entire sale rejected if any item is short, no partial creation
|
||||
- ✅ Non-immediate handover — no stock movement at sale time, order picked up by delivery ride
|
||||
|
||||
Reference in New Issue
Block a user