# 06 — Sale Ride Execution **Status**: Done **Depends On**: 04 — Loading Event --- ## Goal 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 - `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 No new entities. Everything needed was added in pre-planning: | 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 | Migration needed for: `Payment` table, `Sale.PaymentStatus` column, `OrderItem.QuantityFulfilled` column. --- ## Changes to Existing SaleService `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/` ### Sales API Endpoints | Method | Route | Who | Description | |---|---|---|---| | GET | `/api/sales` | Any | List sales — supports `?rideId=`, `?page=`, `?pageSize=` query params | | POST | `/api/sales/{saleId:guid}/payments` | Driver/Agent | Record a payment against a sale | | GET | `/api/sales/{saleId:guid}/payments` | Any | List all payments for a sale | **`GET /api/sales` filter params:** | Param | Type | Description | |---|---|---| | `rideId` | `Guid?` | Filter to sales created on a specific ride | | `page` | `int` | Page number (default 1) | | `pageSize` | `int` | Items per page (default 20) | Returns `PagedResponse`: `{ page, pageSize, total, items }`. Scope rules still apply: non-global users only see sales for stores in their scope. ### Payment Creation Logic On `POST /api/sales/{saleId:guid}/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 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. --- ## Implementation Build Order 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 — Resolved - ✅ 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