Mango.Nop.Plugins/Nop.Plugin.Misc.AIPlugin/docs/MEASUREMENT.md

163 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Measurement Workflows
> Part of `Nop.Plugin.Misc.FruitBankPlugin`. See `README.md` for project overview.
> For entity model see `docs/DOMAIN_MODEL.md`.
> For SignalR endpoints see `docs/SIGNALR/README.md`.
> For core measurement system rules and common domain traps, see: `../../../../../FruitBankHybridApp/FruitBank.Common/docs/GLOSSARY.md`
Three parallel measurement workflows share the same weight formula and `MeasuringStatus` lifecycle.
## Shared concepts (canonical in DOMAIN_MODEL.md)
These are defined once in [`DOMAIN_MODEL.md`](DOMAIN_MODEL.md) — referenced here, not duplicated:
- **Weight formula** — `NetWeight = GrossWeight PalletWeight (TrayQuantity × TareWeight)`. See [DOMAIN_MODEL.md § Weight Formula](DOMAIN_MODEL.md#weight-formula) for field meanings and edge cases.
- **MeasuringStatus lifecycle** — `NotStarted (0) → Started (10) → Finnished (20) → Audited (30)`. See [DOMAIN_MODEL.md § MeasuringStatus Lifecycle](DOMAIN_MODEL.md#measuringstatus-lifecycle) for state transitions. Note: `Finnished` is an intentional double-n typo — do not "fix" it.
- **IsMeasurable Flag** — `ProductDto.IsMeasurable` master toggle for weight validation. See [DOMAIN_MODEL.md § IsMeasurable Flag](DOMAIN_MODEL.md#ismeasurable-flag) for `false`-branch semantics.
## Workflow 1: Inbound Shipping
Supplier truck arrives at the warehouse. Goods are weighed and compared to the delivery document.
```
Shipping (truck)
→ ShippingDocument (supplier delivery note)
→ ShippingItem (product line)
→ ShippingItemPallet (individual measurement)
```
### Flow
1. **Create Shipping** — truck registration (LicencePlate, ShippingDate, CargoCompany)
2. **Add ShippingDocument** — link to Partner (supplier), upload PDF delivery note
3. **Add ShippingItem** — product line from document. `IsMeasurable` copied from `ProductDto.IsMeasurable`
4. **Weigh pallets**`FruitBankDataController.AddOrUpdateMeasuredShippingItemPallet()`
- Each pallet: GrossWeight, PalletWeight, TareWeight, TrayQuantity → NetWeight (formula in Shared concepts above)
5. **Cascade update** — after each pallet measurement:
- `ShippingItem`: MeasuredGrossWeight, MeasuredNetWeight, MeasuredQuantity recalculated from pallets
- `ShippingDocument.IsAllMeasured` = all items measured
- `Shipping.IsAllMeasured` = all documents measured
- Product `AverageWeight` updated from measured data
### Event Cascade (FruitBankEventConsumer)
```
ShippingItemPallet deleted → ShippingItem measuring values refreshed
ShippingItem inserted/updated → ShippingDocument.IsAllMeasured rechecked
ShippingDocument inserted/updated → Shipping.IsAllMeasured rechecked
Shipping deleted → cascade delete ShippingDocuments
ShippingDocument deleted → cascade delete ShippingItems
ShippingItem deleted → cascade delete ShippingItemPallets
```
### ShippingItem: Declared vs Measured
| Declared (from document) | Measured (warehouse) | Discrepancy tracked |
|---|---|---|
| `QuantityOnDocument` | `MeasuredQuantity` | Yes |
| `GrossWeightOnDocument` | `MeasuredGrossWeight` | Yes |
| `NetWeightOnDocument` | `MeasuredNetWeight` | Yes |
## Workflow 2: Outbound Order
Customer order is prepared. Items are weighed before dispatch.
```
OrderDto (order)
→ OrderItemDto (line item)
→ OrderItemPallet (measurement record)
```
### Flow
1. **Start Measuring**`CustomOrderSignalREndpoint.StartMeasuring(orderId, userId)`
- Sets `MeasurementOwnerId` via GenericAttribute
- Order status set to Processing, Payment to Pending
- Broadcasts `SendOrderChanged`
2. **Weigh pallets**`CustomOrderSignalREndpoint.AddOrUpdateMeasuredOrderItemPallet()`
- Same weight formula as shipping
- Triggers price recalculation after each pallet
- Broadcasts `SendOrderItemPalletChanged`
3. **Complete order**`CustomOrderSignalREndpoint.SetOrderStatusToComplete(orderId, revisorId)`
- Validates: all order items must have pallets
- Recalculates final prices via `CustomPriceCalculationService`
- Updates product stock quantities and weights
- Sets `RevisorId` on each OrderItemPallet → MeasuringStatus becomes Audited
- Order status → Complete
- Publishes `OrderPaidEvent` for nopCommerce integration
### Average Weight Validation
After measuring, `OrderItemDto.AverageWeightIsValid` checks:
```
deviation = |ProductDto.AverageWeight - OrderItemDto.AverageWeight|
percentage = (deviation / ProductDto.AverageWeight) * 100
valid = percentage < ProductDto.AverageWeightTreshold
```
`OrderDto.IsAllOrderItemAvgWeightValid` = all items pass this check.
### PendingMeasurementCheckoutFilter
ASP.NET Core action filter on the Checkout `ConfirmOrder` action.
| Step | Action |
|---|---|
| Intercept | `ConfirmOrder` POST in CheckoutController |
| Check | `OrderMeasurementService.IsPendingMeasurementAsync()` |
| If pending | Set OrderStatus=Processing, PaymentStatus=Pending, redirect to PendingMeasurementWarning |
| If not pending | Allow normal checkout flow |
## Workflow 3: StockTaking
Inventory session compares logical stock with physical count.
```
StockTaking (session)
→ StockTakingItem (product line)
→ StockTakingItemPallet (measurement record)
```
### Flow
1. **Create session**`StockSignalREndpointServer.AddStockTaking()`
- Auto-populates `StockTakingItem` for each product
- Captures `OriginalStockQuantity` (current stock) and `InProcessOrdersQuantity` (reserved in pending orders)
- `TotalOriginalQuantity = OriginalStockQuantity + InProcessOrdersQuantity`
2. **Weigh items**`StockSignalREndpointServer.AddOrUpdateMeasuredStockTakingItemPallet()`
- Same weight formula
- Refreshes `MeasuredStockQuantity` and `MeasuredNetWeight` from pallets
3. **Close session**`StockSignalREndpointServer.CloseStockTaking()`
- Validates: all items must be measured
- For each item: `QuantityDiff = MeasuredStockQuantity - TotalOriginalQuantity`
- Updates `Product.StockQuantity` with the diff
- Updates `Product.NetWeight` with measured weight
- Creates stock quantity history entries
- Marks session as closed (`IsClosed = true`)
## CustomPriceCalculationService
Extends nopCommerce's `PriceCalculationService`. Overrides order item pricing based on measurement data.
| Scenario | Price calculation |
|---|---|
| Measurable product | `FinalPrice = NetWeight * UnitPriceExclTax` (weight-based) |
| Non-measurable product | `FinalPrice = Quantity * UnitPriceExclTax` (standard quantity-based) |
**Methods:**
| Method | Purpose |
|---|---|
| `CalculateOrderItemFinalPrice(OrderItemDto)` | Single item price calculation |
| `CheckAndUpdateOrderItemFinalPricesAsync(int orderId)` | Recalculates all items in an order |
| `CheckAndUpdateOrderTotalPrice(int orderId)` | Updates order total from item sum |
## Stock Update Flow
When an order is completed or a stocktaking session closes, `FruitBankDbContext.UpdateStockQuantityAndWeightAsync()`:
1. Updates `Product.StockQuantity` (add/subtract delta)
2. Updates `Product.NetWeight` via GenericAttribute
3. Creates `StockQuantityHistory` entry with extension record for audit trail