7.0 KiB
Measurement Workflows
Part of
Nop.Plugin.Misc.FruitBankPlugin. SeeREADME.mdfor project overview. For entity model seedocs/DOMAIN_MODEL.md. For SignalR endpoints seedocs/SIGNALR_ENDPOINTS.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.
MeasuringStatus Lifecycle
NotStarted (0) → Started (10) → Finnished (20) → Audited (30)
[no record] [record created] [IsMeasured=true] [RevisorId > 0]
- NotStarted: No pallet record exists yet
- Started: Pallet record created (Id > 0) but
IsMeasured = false - Finnished:
IsMeasured = true— weights recorded - Audited:
RevisorId > 0— quality auditor confirmed (OrderItemPallet only)
IsMeasurable Flag
ProductDto.IsMeasurable is the master toggle (stored as GenericAttribute). When false:
- Weight validation is bypassed
- One pallet record is still created with
GrossWeight = 0.0 - Only
TrayQuantityis recorded (for tare-weight calculations) - Product is not included in average weight checks
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
- Create Shipping — truck registration (LicencePlate, ShippingDate, CargoCompany)
- Add ShippingDocument — link to Partner (supplier), upload PDF delivery note
- Add ShippingItem — product line from document.
IsMeasurablecopied fromProductDto.IsMeasurable - Weigh pallets —
FruitBankDataController.AddOrUpdateMeasuredShippingItemPallet()- Each pallet: GrossWeight, PalletWeight, TareWeight, TrayQuantity
- NetWeight computed:
GrossWeight - PalletWeight - (TrayQuantity * TareWeight)
- Cascade update — after each pallet measurement:
ShippingItem: MeasuredGrossWeight, MeasuredNetWeight, MeasuredQuantity recalculated from palletsShippingDocument.IsAllMeasured= all items measuredShipping.IsAllMeasured= all documents measured- Product
AverageWeightupdated 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
- Start Measuring —
CustomOrderSignalREndpoint.StartMeasuring(orderId, userId)- Sets
MeasurementOwnerIdvia GenericAttribute - Order status set to Processing, Payment to Pending
- Broadcasts
SendOrderChanged
- Sets
- Weigh pallets —
CustomOrderSignalREndpoint.AddOrUpdateMeasuredOrderItemPallet()- Same weight formula as shipping
- Triggers price recalculation after each pallet
- Broadcasts
SendOrderItemPalletChanged
- 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
RevisorIdon each OrderItemPallet → MeasuringStatus becomes Audited - Order status → Complete
- Publishes
OrderPaidEventfor 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
- Create session —
StockSignalREndpointServer.AddStockTaking()- Auto-populates
StockTakingItemfor each product - Captures
OriginalStockQuantity(current stock) andInProcessOrdersQuantity(reserved in pending orders) TotalOriginalQuantity = OriginalStockQuantity + InProcessOrdersQuantity
- Auto-populates
- Weigh items —
StockSignalREndpointServer.AddOrUpdateMeasuredStockTakingItemPallet()- Same weight formula
- Refreshes
MeasuredStockQuantityandMeasuredNetWeightfrom pallets
- Close session —
StockSignalREndpointServer.CloseStockTaking()- Validates: all items must be measured
- For each item:
QuantityDiff = MeasuredStockQuantity - TotalOriginalQuantity - Updates
Product.StockQuantitywith the diff - Updates
Product.NetWeightwith 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():
- Updates
Product.StockQuantity(add/subtract delta) - Updates
Product.NetWeightvia GenericAttribute - Creates
StockQuantityHistoryentry with extension record for audit trail