From c9b0bf83345d17aa7bd8158c687b7f294bc52bc9 Mon Sep 17 00:00:00 2001 From: Loretta Date: Sat, 30 May 2026 17:07:29 +0200 Subject: [PATCH] Enhance ToonDescription docs for PreOrder entities Expanded and clarified ToonDescription attributes for PreOrder and PreOrderItem classes. Added business rules, constraints, and improved property-level documentation to better describe allocation, conversion, and status logic. No functional changes. --- FruitBank.Common/Entities/Preorder.cs | 12 +++++++++--- FruitBank.Common/Entities/PreorderItem.cs | 14 ++++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/FruitBank.Common/Entities/Preorder.cs b/FruitBank.Common/Entities/Preorder.cs index a16e9fa7..cece1030 100644 --- a/FruitBank.Common/Entities/Preorder.cs +++ b/FruitBank.Common/Entities/Preorder.cs @@ -9,22 +9,28 @@ namespace FruitBank.Common.Entities; [AcBinarySerializable(false, true, false, true, false, false)] [Table(Name = FruitBankConstClient.PreOrderDbTableName)] [System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PreOrderDbTableName)] -[ToonDescription("Customer advance order placed before the goods physically arrive", Purpose = "Header of a customer pre-order against an upcoming inbound delivery. Pending items are allocated from incoming stock first-come-first-served by PreOrderId when a shipping document confirms arrival, then converted into a real NopCommerce Order once any quantity is fulfilled.")] +[ToonDescription("Customer advance order placed before the goods physically arrive", Purpose = "Header of a customer pre-order against an upcoming inbound delivery. When a supplier shipping document confirms incoming stock, pending items are allocated first-come-first-served by PreOrderId (only within the conversion window — PreOrderConversionWindowDays, 4 days, of DateOfReceipt) and the preorder is converted into a real NopCommerce Order once any quantity is fulfilled. Preorders past DateOfReceipt are swept closed, dropping any still-Pending items.")] public sealed class PreOrder : MgEntityBase { + [ToonDescription(Purpose = "FK to the nopCommerce Customer who placed the preorder.")] public int CustomerId { get; set; } + + [ToonDescription(Purpose = "nopCommerce multi-store scope.")] public int StoreId { get; set; } - [ToonDescription(Purpose = "Requested delivery date. Drives the conversion window (only preorders within PreOrderConversionWindowDays of this date are eligible for allocation) and the expiry sweep — once this date is past, any still-Pending items are Dropped.")] + [ToonDescription(Purpose = "Requested delivery date. Drives the conversion window (only preorders within PreOrderConversionWindowDays — 4 days — of this date are eligible for allocation) and the expiry sweep — once this date is past, any still-Pending items are Dropped.")] public DateTime DateOfReceipt { get; set; } - [ToonDescription(Purpose = "Header lifecycle: Pending -> Confirmed (all items Fulfilled) / PartiallyFulfilled (some Dropped or partial, none left Pending) / Cancelled.")] + [ToonDescription(Purpose = "Header lifecycle: Pending / Confirmed (all items Fulfilled) / PartiallyFulfilled (some Dropped or partial, none left Pending) / Cancelled.", + BusinessRule = "set by RefreshPreOrderStatusAsync from item states: items.All(Fulfilled) => Confirmed; (any Dropped or PartiallyFulfilled) && none Pending => PartiallyFulfilled; else Pending. Cancelled is set explicitly.")] public PreOrderStatus Status { get; set; } [ToonDescription(Purpose = "Optional free-text note entered by the customer when placing the preorder.")] public string? CustomerNote { get; set; } public DateTime CreatedOnUtc { get; set; } + + [ToonDescription(Purpose = "Bumped to UtcNow on every status / allocation change.")] public DateTime UpdatedOnUtc { get; set; } [ToonDescription(Purpose = "FK to the real NopCommerce Order created from this preorder. Null until the first item is fulfilled; set once on first conversion and reused so subsequent shipping documents append items to the same order.")] diff --git a/FruitBank.Common/Entities/PreorderItem.cs b/FruitBank.Common/Entities/PreorderItem.cs index 9f8f8756..ad8259e4 100644 --- a/FruitBank.Common/Entities/PreorderItem.cs +++ b/FruitBank.Common/Entities/PreorderItem.cs @@ -12,18 +12,24 @@ namespace FruitBank.Common.Entities; [ToonDescription("Single product line of a customer preorder with fulfilment tracking", Purpose = "A requested product line within a PreOrder. Tracks requested versus cumulatively fulfilled quantity as incoming stock is allocated across one or more shipping-document conversion runs.")] public sealed class PreOrderItem : MgEntityBase { + [ToonDescription(Purpose = "FK to the parent PreOrder.")] public int PreOrderId { get; set; } + + [ToonDescription(Purpose = "FK to the nopCommerce Product being preordered.")] public int ProductId { get; set; } - [ToonDescription(Purpose = "Quantity of the product the customer requested.")] + [ToonDescription(Purpose = "Quantity of the product the customer requested.", Constraints = "positive")] public int RequestedQuantity { get; set; } - [ToonDescription(Purpose = "Quantity allocated from incoming stock so far; accumulates across conversion runs until it reaches RequestedQuantity.")] + [ToonDescription(Purpose = "Quantity allocated from incoming stock so far; accumulates across conversion runs until it reaches RequestedQuantity.", + BusinessRule = "this >= 0 && this <= RequestedQuantity")] public int FulfilledQuantity { get; set; } - [ToonDescription(Purpose = "Gross unit price locked at preorder time. Used as the order-item price on conversion for non-measurable products; measurable products are priced 0 at conversion and weighed afterwards.")] + [ToonDescription(Purpose = "Gross unit price locked at preorder time. Used as the order-item price on conversion for non-measurable products; measurable products are priced 0 at conversion and weighed afterwards.", + Constraints = "non-negative")] public decimal UnitPriceInclTax { get; set; } - [ToonDescription(Purpose = "Item lifecycle: Pending -> Fulfilled (fully allocated) / PartiallyFulfilled (partly allocated) / Dropped (expired or no incoming stock).")] + [ToonDescription(Purpose = "Item lifecycle: Pending / Fulfilled (fully allocated) / PartiallyFulfilled (partly allocated) / Dropped (expired or no incoming stock).", + BusinessRule = "set during conversion: FulfilledQuantity >= RequestedQuantity ? Fulfilled : FulfilledQuantity > 0 ? PartiallyFulfilled : Dropped; stays Pending until first allocation")] public PreOrderItemStatus Status { get; set; } }