From dd3c1c58c01a4e0e3af4036e67e41e1885ca0028 Mon Sep 17 00:00:00 2001 From: Loretta Date: Thu, 15 Jan 2026 11:33:34 +0100 Subject: [PATCH] Add ToonDescription metadata to entities and DTOs Expanded use of the ToonDescription attribute across core entities and DTOs to provide rich metadata, including business rules, purposes, and status flags. Added class-level and property-level annotations to improve self-documentation and support automated tooling. Introduced new computed properties with ToonDescription in ProductDto, StockQuantityHistoryDto, and StockTakingItem. Updated ToonTypeRelation with an Entity constant. Enhanced test coverage for Toon metadata. Cleaned up imports and removed obsolete test_debug.ps1 script. Updated settings.local.json to support additional Bash commands for tooling. These changes improve introspectability and support for serialization, UI, and API documentation. --- .claude/settings.local.json | 6 +++++- FruitBank.Common/Dtos/OrderDto.cs | 6 +++--- FruitBank.Common/Dtos/OrderItemDto.cs | 18 ++++++++++++------ FruitBank.Common/Dtos/ProductDto.cs | 12 ++++++++++-- .../Dtos/StockQuantityHistoryDto.cs | 14 +++++++++----- FruitBank.Common/Entities/Files.cs | 4 +++- .../Entities/MeasuringItemPalletBase.cs | 6 ++++++ FruitBank.Common/Entities/OrderItemPallet.cs | 7 +++++++ FruitBank.Common/Entities/Pallet.cs | 4 +++- FruitBank.Common/Entities/Partner.cs | 4 +++- FruitBank.Common/Entities/Shipping.cs | 4 +++- FruitBank.Common/Entities/ShippingDocument.cs | 2 ++ .../Entities/ShippingDocumentToFiles.cs | 3 +++ FruitBank.Common/Entities/ShippingItem.cs | 7 ++++--- .../Entities/ShippingItemPallet.cs | 4 +++- .../Entities/StockQuantityHistoryExt.cs | 2 ++ FruitBank.Common/Entities/StockTaking.cs | 4 +++- FruitBank.Common/Entities/StockTakingItem.cs | 11 ++++++++++- .../Entities/StockTakingItemPallet.cs | 4 +++- FruitBankHybrid.Shared.Tests/ToonTests.cs | 10 ++++++---- FruitBankHybrid.Shared.Tests/test_debug.ps1 | 17 ----------------- 21 files changed, 100 insertions(+), 49 deletions(-) delete mode 100644 FruitBankHybrid.Shared.Tests/test_debug.ps1 diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 76a90611..062a7b19 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -6,7 +6,11 @@ "Bash(find:*)", "Bash(grep:*)", "Bash(dotnet test:*)", - "Bash(dotnet build:*)" + "Bash(dotnet build:*)", + "Bash(ls:*)", + "Bash(while read:*)", + "Bash(do sed -i '1a using AyCode.Core.Serializers.Toons;\\\\n' \"$f\")", + "Bash(done)" ] } } diff --git a/FruitBank.Common/Dtos/OrderDto.cs b/FruitBank.Common/Dtos/OrderDto.cs index 7b523ea6..701afba5 100644 --- a/FruitBank.Common/Dtos/OrderDto.cs +++ b/FruitBank.Common/Dtos/OrderDto.cs @@ -71,15 +71,15 @@ public class OrderDto : MgOrderDto, IOrderDto public DateTime DateOfReceiptOrCreated => DateOfReceipt ?? CreatedOnUtc; [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] - [ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrNull(nameof(IOrderDto.DateOfReceipt))")] + [ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrNull('DateOfReceipt')")] public DateTime? DateOfReceipt => GenericAttributes.GetValueOrNull(nameof(IOrderDto.DateOfReceipt)); [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] - [ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault(nameof(IOrderDto.RevisorId), 0)")] + [ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault('RevisorId', 0)")] public int RevisorId => GenericAttributes.GetValueOrDefault(nameof(IOrderDto.RevisorId), 0); [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] - [ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault(nameof(IOrderDto.MeasurementOwnerId), 0)")] + [ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault('MeasurementOwnerId', 0)")] public int MeasurementOwnerId => GenericAttributes.GetValueOrDefault(nameof(IOrderDto.MeasurementOwnerId), 0); [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] diff --git a/FruitBank.Common/Dtos/OrderItemDto.cs b/FruitBank.Common/Dtos/OrderItemDto.cs index c26e3f58..d4a43904 100644 --- a/FruitBank.Common/Dtos/OrderItemDto.cs +++ b/FruitBank.Common/Dtos/OrderItemDto.cs @@ -17,7 +17,7 @@ namespace FruitBank.Common.Dtos; [LinqToDB.Mapping.Table(Name = nameof(OrderItem))] [System.ComponentModel.DataAnnotations.Schema.Table(nameof(OrderItem))] -[ToonDescription($"Data transfer object for {nameof(OrderItem)}", TypeRelation = ToonTypeRelation.DtoOf, RelatedTypes = [typeof(OrderItem)])] +[ToonDescription("Order item with measurements, pallets, and validation", TypeRelation = ToonTypeRelation.DtoOf, RelatedTypes = [typeof(OrderItem)])] public class OrderItemDto : MgOrderItemDto, IOrderItemDto { [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] @@ -35,6 +35,7 @@ public class OrderItemDto : MgOrderItemDto, IOrderItemDto public OrderDto OrderDto { get; set; } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(Purpose = "Status flag", BusinessRule = "get => IsMeasuredAndValid()")] public bool IsMeasured { get => IsMeasuredAndValid(); @@ -42,7 +43,7 @@ public class OrderItemDto : MgOrderItemDto, IOrderItemDto } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] - [ToonDescription(BusinessRule = "get => ProductDto!.IsMeasurable", Constraints = "[#SmartTypeConstraints], readonly")] + [ToonDescription(Purpose = "Status flag", BusinessRule = "get => ProductDto!.IsMeasurable")] public bool IsMeasurable { get => ProductDto!.IsMeasurable; @@ -50,7 +51,7 @@ public class OrderItemDto : MgOrderItemDto, IOrderItemDto } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] - [ToonDescription(BusinessRule = "get => OrderItemPallets.Sum(x => x.TrayQuantity)", Constraints = "[#SmartTypeConstraints], readonly")] + [ToonDescription(BusinessRule = "get => OrderItemPallets.Sum(x => x.TrayQuantity)")] public int TrayQuantity { get => OrderItemPallets.Sum(x => x.TrayQuantity); @@ -58,7 +59,7 @@ public class OrderItemDto : MgOrderItemDto, IOrderItemDto } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] - [ToonDescription(BusinessRule = "get => double.Round(OrderItemPallets.Sum(x => x.NetWeight), 1)", Constraints = "[#SmartTypeConstraints], readonly")] + [ToonDescription(BusinessRule = "get => double.Round(OrderItemPallets.Sum(x => x.NetWeight), 1)")] public double NetWeight { get @@ -76,7 +77,7 @@ public class OrderItemDto : MgOrderItemDto, IOrderItemDto } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] - [ToonDescription(BusinessRule = "get => double.Round(OrderItemPallets.Sum(x => x.NetWeight), 1)", Constraints = "[#SmartTypeConstraints], readonly")] + [ToonDescription(BusinessRule = "get => double.Round(OrderItemPallets.Sum(x => x.NetWeight), 1)")] public double GrossWeight { get @@ -94,19 +95,24 @@ public class OrderItemDto : MgOrderItemDto, IOrderItemDto } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => IsMeasurable && OrderItemPallets.Count > 0 ? double.Round(OrderItemPallets.Sum(oip => oip.AverageWeight) / OrderItemPallets.Count, 1) : 0d")] public double AverageWeight => IsMeasurable && OrderItemPallets.Count > 0 ? double.Round(OrderItemPallets.Sum(oip => oip.AverageWeight) / OrderItemPallets.Count, 1) : 0d; [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => IsMeasurable ? double.Round(ProductDto!.AverageWeight - AverageWeight, 1) : 0")] public double AverageWeightDifference => IsMeasurable ? double.Round(ProductDto!.AverageWeight - AverageWeight, 1) : 0; [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] - public bool AverageWeightIsValid => !IsMeasurable || + [ToonDescription(BusinessRule = "get => !IsMeasurable || (ProductDto!.AverageWeight > 0 && ((AverageWeightDifference / ProductDto!.AverageWeight) * 100) < ProductDto!.AverageWeightTreshold)")] + public bool AverageWeightIsValid => !IsMeasurable || (ProductDto!.AverageWeight > 0 && ((AverageWeightDifference / ProductDto!.AverageWeight) * 100) < ProductDto!.AverageWeightTreshold); [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(Purpose = "Status flag", BusinessRule = "get => OrderItemPallets.Count > 0 && OrderItemPallets.All(oip => oip.IsAudited)")] public bool IsAudited => OrderItemPallets.Count > 0 && OrderItemPallets.All(oip => oip.IsAudited); [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => complex conditional logic based on IsAudited, IsMeasured, and OrderItemPallets status")] public MeasuringStatus MeasuringStatus { get diff --git a/FruitBank.Common/Dtos/ProductDto.cs b/FruitBank.Common/Dtos/ProductDto.cs index 75465b9f..b95a661f 100644 --- a/FruitBank.Common/Dtos/ProductDto.cs +++ b/FruitBank.Common/Dtos/ProductDto.cs @@ -7,6 +7,7 @@ using Mango.Nop.Core.Interfaces.ForeignKeys; using Newtonsoft.Json; //using Nop.Core.Domain.Catalog; using Nop.Core.Domain.Common; +using Nop.Core.Domain.Orders; using System.ComponentModel.DataAnnotations.Schema; using System.Linq.Expressions; @@ -14,7 +15,7 @@ namespace FruitBank.Common.Dtos; [LinqToDB.Mapping.Table(Name = "Product")] [System.ComponentModel.DataAnnotations.Schema.Table("Product")] -[ToonDescription($"Data transfer object for Product")] +[ToonDescription("Product data with measurements and generic attributes", TypeRelation = ToonTypeRelation.DtoOf)] public class ProductDto : MgProductDto, IProductDto { [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] @@ -33,6 +34,7 @@ public class ProductDto : MgProductDto, IProductDto //{ } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(Purpose = "Status flag", BusinessRule = "get => GenericAttributes.GetValueOrDefault('IsMeasurable')")] public bool IsMeasurable { get => GenericAttributes.GetValueOrDefault(nameof(IMeasurable.IsMeasurable)); @@ -47,6 +49,7 @@ public class ProductDto : MgProductDto, IProductDto } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault('Tare')")] public double Tare { get => GenericAttributes.GetValueOrDefault(nameof(ITare.Tare)); @@ -55,6 +58,7 @@ public class ProductDto : MgProductDto, IProductDto } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault('NetWeight')")] public double NetWeight { get => GenericAttributes.GetValueOrDefault(nameof(IMeasuringNetWeight.NetWeight)); @@ -62,13 +66,14 @@ public class ProductDto : MgProductDto, IProductDto } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault('IncomingQuantity')")] public int IncomingQuantity { get => GenericAttributes.GetValueOrDefault(nameof(IIncomingQuantity.IncomingQuantity)); set => throw new Exception($"ProductDto.IncomingQuantity not set"); //set //{ - // var ga = GenericAttributes.FirstOrDefault(ga => ga.Key == nameof(IIncomingQuantity.IncomingQuantity)) ?? + // var ga = GenericAttributes.FirstOrDefault(ga => ga.Key == nameof(IIncomingQuantity.IncomingQuantity)) ?? // GenericAttributes.AddNewGenericAttribute("Product", nameof(IIncomingQuantity.IncomingQuantity), value.ToString(), Id); // ga.Value = value.ToString(); @@ -76,12 +81,15 @@ public class ProductDto : MgProductDto, IProductDto } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => StockQuantity + IncomingQuantity")] public int AvailableQuantity => StockQuantity + IncomingQuantity; [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault('AverageWeight')")] public double AverageWeight => GenericAttributes.GetValueOrDefault(nameof(IProductDto.AverageWeight)); [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault('AverageWeightTreshold')")] public double AverageWeightTreshold => GenericAttributes.GetValueOrDefault(nameof(IProductDto.AverageWeightTreshold)); public bool HasMeasuringValues() => Id > 0 && NetWeight != 0 && IsMeasurable; diff --git a/FruitBank.Common/Dtos/StockQuantityHistoryDto.cs b/FruitBank.Common/Dtos/StockQuantityHistoryDto.cs index 1718cae1..64b8d0bf 100644 --- a/FruitBank.Common/Dtos/StockQuantityHistoryDto.cs +++ b/FruitBank.Common/Dtos/StockQuantityHistoryDto.cs @@ -1,4 +1,4 @@ -using AyCode.Core.Serializers.Toons; +using AyCode.Core.Serializers.Toons; using FruitBank.Common.Interfaces; using LinqToDB.Mapping; using Mango.Nop.Core.Dtos; @@ -18,10 +18,11 @@ namespace FruitBank.Common.Dtos { [LinqToDB.Mapping.Table(Name = nameof(StockQuantityHistory))] [System.ComponentModel.DataAnnotations.Schema.Table(nameof(StockQuantityHistory))] - [ToonDescription($"Data transfer object for {nameof(StockQuantityHistory)}", TypeRelation = ToonTypeRelation.DtoOf, RelatedTypes = [typeof(StockQuantityHistory)])] + [ToonDescription("Stock quantity history with net weight adjustments", TypeRelation = ToonTypeRelation.DtoOf, RelatedTypes = [typeof(StockQuantityHistory)])] public class StockQuantityHistoryDto : MgStockQuantityHistoryDto, IStockQuantityHistoryDto { [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => StockQuantityHistoryExt?.StockQuantityHistoryId")] public int? StockQuantityHistoryId { get => StockQuantityHistoryExt?.StockQuantityHistoryId; @@ -29,6 +30,7 @@ namespace FruitBank.Common.Dtos } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => StockQuantityHistoryExt?.NetWeightAdjustment")] public double? NetWeightAdjustment { get => StockQuantityHistoryExt?.NetWeightAdjustment; @@ -36,20 +38,22 @@ namespace FruitBank.Common.Dtos } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => StockQuantityHistoryExt?.NetWeight")] public double? NetWeight { get => StockQuantityHistoryExt?.NetWeight; set => StockQuantityHistoryExt!.NetWeight = value; - } + } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(Purpose = "Status flag", BusinessRule = "get => StockQuantityHistoryExt?.IsInconsistent ?? false")] public bool IsInconsistent { get => StockQuantityHistoryExt?.IsInconsistent ?? false; set => StockQuantityHistoryExt!.IsInconsistent = value; - } + } [Association(ThisKey = nameof(Id), OtherKey = nameof(StockQuantityHistoryExt.StockQuantityHistoryId), CanBeNull = true)] - public StockQuantityHistoryExt? StockQuantityHistoryExt { get; set; } + public StockQuantityHistoryExt? StockQuantityHistoryExt { get; set; } } } diff --git a/FruitBank.Common/Entities/Files.cs b/FruitBank.Common/Entities/Files.cs index 34d626a2..158e54e2 100644 --- a/FruitBank.Common/Entities/Files.cs +++ b/FruitBank.Common/Entities/Files.cs @@ -1,9 +1,11 @@ -using FruitBank.Common.Interfaces; +using AyCode.Core.Serializers.Toons; +using FruitBank.Common.Interfaces; using LinqToDB.Mapping; using Mango.Nop.Core.Entities; namespace FruitBank.Common.Entities; +[ToonDescription("Uploaded file with extracted text content", Purpose = "A centralized repository for all uploaded binary content and metadata, featuring a 'RawText' field that stores OCR-extracted information for full-text search and automated data validation across the system")] [Table(Name = FruitBankConstClient.FilesDbTableName)] [System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.FilesDbTableName)] public class Files : MgEntityBase, IFiles diff --git a/FruitBank.Common/Entities/MeasuringItemPalletBase.cs b/FruitBank.Common/Entities/MeasuringItemPalletBase.cs index 97336d0f..87236498 100644 --- a/FruitBank.Common/Entities/MeasuringItemPalletBase.cs +++ b/FruitBank.Common/Entities/MeasuringItemPalletBase.cs @@ -1,3 +1,4 @@ +using AyCode.Core.Serializers.Toons; using FruitBank.Common.Dtos; using FruitBank.Common.Enums; using FruitBank.Common.Interfaces; @@ -8,6 +9,7 @@ using Newtonsoft.Json; namespace FruitBank.Common.Entities; +[ToonDescription("Base class for pallet measurements with net weight calculation")] public abstract class MeasuringItemPalletBase : MgEntityBase, IMeasuringItemPalletBase { private double _palletWeight; @@ -16,7 +18,9 @@ public abstract class MeasuringItemPalletBase : MgEntityBase, IMeasuringItemPall [NotColumn] protected int ForeignItemId; + [NotColumn] + [ToonDescription(BusinessRule = "get => ForeignItemId")] public int ForeignKey => ForeignItemId; public int TrayQuantity { get; set; } @@ -36,6 +40,7 @@ public abstract class MeasuringItemPalletBase : MgEntityBase, IMeasuringItemPall } [NotColumn, System.ComponentModel.DataAnnotations.Schema.NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => CalculateNetWeight()", Constraints = "[#SmartTypeConstraints], readonly")] public double NetWeight { get => CalculateNetWeight(); @@ -60,6 +65,7 @@ public abstract class MeasuringItemPalletBase : MgEntityBase, IMeasuringItemPall public DateTime Modified { get; set; } [NotColumn, System.ComponentModel.DataAnnotations.Schema.NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => IsMeasured ? MeasuringStatus.Finnished : Id > 0 ? MeasuringStatus.Started : MeasuringStatus.NotStarted")] public virtual MeasuringStatus MeasuringStatus => IsMeasured ? MeasuringStatus.Finnished : Id > 0 ? MeasuringStatus.Started : MeasuringStatus.NotStarted; diff --git a/FruitBank.Common/Entities/OrderItemPallet.cs b/FruitBank.Common/Entities/OrderItemPallet.cs index 1014977b..c87dd9d2 100644 --- a/FruitBank.Common/Entities/OrderItemPallet.cs +++ b/FruitBank.Common/Entities/OrderItemPallet.cs @@ -1,3 +1,4 @@ +using AyCode.Core.Serializers.Toons; using FruitBank.Common.Dtos; using FruitBank.Common.Enums; using FruitBank.Common.Interfaces; @@ -10,6 +11,7 @@ using Table = LinqToDB.Mapping.TableAttribute; namespace FruitBank.Common.Entities; +[ToonDescription("Pallet measurements for order items with audit tracking", Purpose = "A measurement record for outgoing goods, used to verify that the net weight being sent to the customer is accurate and audited")] [Table(Name = FruitBankConstClient.OrderItemPalletDbTableName)] [System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.OrderItemPalletDbTableName)] public class OrderItemPallet : MeasuringItemPalletBase, IOrderItemPallet @@ -21,6 +23,9 @@ public class OrderItemPallet : MeasuringItemPalletBase, IOrderItemPallet } public int RevisorId { get; set; } + + [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(Purpose = "Status flag", BusinessRule = "get => RevisorId > 0")] public bool IsAudited => RevisorId > 0; //[JsonIgnore, System.Text.Json.Serialization.JsonIgnore] @@ -28,6 +33,7 @@ public class OrderItemPallet : MeasuringItemPalletBase, IOrderItemPallet public OrderItemDto? OrderItemDto { get; set; } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => IsAudited ? MeasuringStatus.Audited : base.MeasuringStatus")] public override MeasuringStatus MeasuringStatus => IsAudited ? MeasuringStatus.Audited : base.MeasuringStatus; public override double CalculateNetWeight() => base.CalculateNetWeight(); @@ -37,6 +43,7 @@ public class OrderItemPallet : MeasuringItemPalletBase, IOrderItemPallet } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => double.Round(NetWeight / TrayQuantity, 1)")] public double AverageWeight => double.Round(NetWeight / TrayQuantity, 1); /// diff --git a/FruitBank.Common/Entities/Pallet.cs b/FruitBank.Common/Entities/Pallet.cs index 8d2afc56..8bdc79e0 100644 --- a/FruitBank.Common/Entities/Pallet.cs +++ b/FruitBank.Common/Entities/Pallet.cs @@ -1,10 +1,12 @@ -using FruitBank.Common.Interfaces; +using AyCode.Core.Serializers.Toons; +using FruitBank.Common.Interfaces; using LinqToDB; using LinqToDB.Mapping; using Mango.Nop.Core.Entities; namespace FruitBank.Common.Entities; +[ToonDescription("Pallet type definition with size and weight")] [Table(Name = FruitBankConstClient.PalletDbTableName)] [System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PalletDbTableName)] public class Pallet : MgEntityBase, IPallet diff --git a/FruitBank.Common/Entities/Partner.cs b/FruitBank.Common/Entities/Partner.cs index be56228f..1b568bc0 100644 --- a/FruitBank.Common/Entities/Partner.cs +++ b/FruitBank.Common/Entities/Partner.cs @@ -1,9 +1,11 @@ -using FruitBank.Common.Interfaces; +using AyCode.Core.Serializers.Toons; +using FruitBank.Common.Interfaces; using LinqToDB.Mapping; using Mango.Nop.Core.Entities; namespace FruitBank.Common.Entities; +[ToonDescription("Business partner with address and tax information", Purpose = "Represents an external legal entity, specifically a Supplier who provides goods or a business partner involved in the procurement chain")] [Table(Name = FruitBankConstClient.PartnerDbTableName)] [System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PartnerDbTableName)] public class Partner : MgEntityBase, IPartner diff --git a/FruitBank.Common/Entities/Shipping.cs b/FruitBank.Common/Entities/Shipping.cs index c15aa779..a87e0abd 100644 --- a/FruitBank.Common/Entities/Shipping.cs +++ b/FruitBank.Common/Entities/Shipping.cs @@ -1,9 +1,11 @@ -using FruitBank.Common.Interfaces; +using AyCode.Core.Serializers.Toons; +using FruitBank.Common.Interfaces; using LinqToDB.Mapping; using Mango.Nop.Core.Entities; namespace FruitBank.Common.Entities; +[ToonDescription("Shipping record with documents and measurement tracking", Purpose = "Represents a physical inbound delivery event (truck arrival) at the warehouse, tracking the vehicle and the overall measurement status of the shipment")] [Table(Name = FruitBankConstClient.ShippingDbTableName)] [System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingDbTableName)] public class Shipping : MgEntityBase, IShipping diff --git a/FruitBank.Common/Entities/ShippingDocument.cs b/FruitBank.Common/Entities/ShippingDocument.cs index 4ca89b89..d3ce8e35 100644 --- a/FruitBank.Common/Entities/ShippingDocument.cs +++ b/FruitBank.Common/Entities/ShippingDocument.cs @@ -1,10 +1,12 @@ using System.Collections.ObjectModel; +using AyCode.Core.Serializers.Toons; using FruitBank.Common.Interfaces; using LinqToDB.Mapping; using Mango.Nop.Core.Entities; namespace FruitBank.Common.Entities; +[ToonDescription("Shipping document with partner, items and files", Purpose = "A digital representation of a supplier's delivery note or invoice associated with the shipment, used for reconciling paper-based data with measured reality")] [Table(Name = FruitBankConstClient.ShippingDocumentDbTableName)] [System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingDocumentDbTableName)] public class ShippingDocument : MgEntityBase, IShippingDocument diff --git a/FruitBank.Common/Entities/ShippingDocumentToFiles.cs b/FruitBank.Common/Entities/ShippingDocumentToFiles.cs index 1e5f2f90..c85c6c2a 100644 --- a/FruitBank.Common/Entities/ShippingDocumentToFiles.cs +++ b/FruitBank.Common/Entities/ShippingDocumentToFiles.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations.Schema; +using AyCode.Core.Serializers.Toons; using FruitBank.Common.Interfaces; using LinqToDB.Mapping; using Mango.Nop.Core.Entities; @@ -6,6 +7,7 @@ using Newtonsoft.Json; namespace FruitBank.Common.Entities; +[ToonDescription("Links shipping documents to files with document type", Purpose = "A many-to-many link table that associates general uploaded files with specific shipping documents, assigning a functional context (DocumentType) to each file, such as identifying which PDF is the supplier's invoice versus the packing list")] [LinqToDB.Mapping.Table(Name = FruitBankConstClient.ShippingDocumentToFilesDbTableName)] [System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingDocumentToFilesDbTableName)] public class ShippingDocumentToFiles : MgEntityBase, IShippingDocumentToFiles @@ -16,6 +18,7 @@ public class ShippingDocumentToFiles : MgEntityBase, IShippingDocumentToFiles public int DocumentTypeId { get; set; } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => DocumentTypeId")] public DocumentType DocumentType { get => (DocumentType)DocumentTypeId; diff --git a/FruitBank.Common/Entities/ShippingItem.cs b/FruitBank.Common/Entities/ShippingItem.cs index 83841e6e..652acf61 100644 --- a/FruitBank.Common/Entities/ShippingItem.cs +++ b/FruitBank.Common/Entities/ShippingItem.cs @@ -1,4 +1,5 @@ using AyCode.Core.Interfaces; +using AyCode.Core.Serializers.Toons; using FruitBank.Common.Dtos; using FruitBank.Common.Enums; using FruitBank.Common.Interfaces; @@ -17,6 +18,7 @@ using Table = LinqToDB.Mapping.TableAttribute; namespace FruitBank.Common.Entities; +[ToonDescription("Shipping document item with measurements and pallets", Purpose = "Represents a specific product line item within a shipping document, storing the discrepancy between the supplier's declared weight/quantity and the warehouse's measured values")] [Table(Name = FruitBankConstClient.ShippingItemDbTableName)] [System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingItemDbTableName)] public class ShippingItem : MgEntityBase, IShippingItem @@ -29,10 +31,8 @@ public class ShippingItem : MgEntityBase, IShippingItem public string NameOnDocument { get; set; } public string HungarianName { get; set; } - /// - /// get => ProductDto?.Name ?? Name - /// [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => ProductDto?.Name ?? Name")] public string ProductName => ProductDto?.Name ?? Name; public int PalletsOnDocument { get; set; } @@ -84,6 +84,7 @@ public class ShippingItem : MgEntityBase, IShippingItem public DateTime Modified { get; set; } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => complex conditional logic based on IsMeasured and ShippingItemPallets status")] public MeasuringStatus MeasuringStatus { get diff --git a/FruitBank.Common/Entities/ShippingItemPallet.cs b/FruitBank.Common/Entities/ShippingItemPallet.cs index 05c5e047..ba4e4b72 100644 --- a/FruitBank.Common/Entities/ShippingItemPallet.cs +++ b/FruitBank.Common/Entities/ShippingItemPallet.cs @@ -1,4 +1,5 @@ -using FruitBank.Common.Interfaces; +using AyCode.Core.Serializers.Toons; +using FruitBank.Common.Interfaces; using LinqToDB.Mapping; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -6,6 +7,7 @@ using System.Security.Cryptography.X509Certificates; namespace FruitBank.Common.Entities; +[ToonDescription("Pallet measurements for shipping items", Purpose = "The smallest unit of measurement tracking, representing a single physical pallet of a shipping item, used for precise gross-to-net weight calculation and quality audit")] [LinqToDB.Mapping.Table(Name = FruitBankConstClient.ShippingItemPalletDbTableName)] [System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingItemPalletDbTableName)] public class ShippingItemPallet : MeasuringItemPalletBase, IShippingItemPallet diff --git a/FruitBank.Common/Entities/StockQuantityHistoryExt.cs b/FruitBank.Common/Entities/StockQuantityHistoryExt.cs index 20ab9287..4c4a871d 100644 --- a/FruitBank.Common/Entities/StockQuantityHistoryExt.cs +++ b/FruitBank.Common/Entities/StockQuantityHistoryExt.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using AyCode.Core.Serializers.Toons; namespace Mango.Nop.Core.Entities { @@ -23,6 +24,7 @@ namespace Mango.Nop.Core.Entities [Table(Name = FruitBankConstClient.StockQuantityHistoryExtDbTableName)] [System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockQuantityHistoryExtDbTableName)] + [ToonDescription("Extended weight-metadata for StockQuantityHistory", Purpose = "Validates quantity deltas against measured weight to detect inconsistencies")] public class StockQuantityHistoryExt : MgEntityBase, IStockQuantityHistoryExt { public int StockQuantityHistoryId { get; set; } diff --git a/FruitBank.Common/Entities/StockTaking.cs b/FruitBank.Common/Entities/StockTaking.cs index ac397b5f..616042fb 100644 --- a/FruitBank.Common/Entities/StockTaking.cs +++ b/FruitBank.Common/Entities/StockTaking.cs @@ -1,8 +1,10 @@ -using LinqToDB.Mapping; +using AyCode.Core.Serializers.Toons; +using LinqToDB.Mapping; using Mango.Nop.Core.Entities; namespace FruitBank.Common.Entities; +[ToonDescription("Inventory session record", Purpose = "Orchestrates inventory sessions by freezing logical stock states")] [Table(Name = FruitBankConstClient.StockTakingDbTableName)] [System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockTakingDbTableName)] public class StockTaking : MgStockTaking diff --git a/FruitBank.Common/Entities/StockTakingItem.cs b/FruitBank.Common/Entities/StockTakingItem.cs index a51fb5ee..cc4d7189 100644 --- a/FruitBank.Common/Entities/StockTakingItem.cs +++ b/FruitBank.Common/Entities/StockTakingItem.cs @@ -1,4 +1,5 @@ -using FruitBank.Common.Dtos; +using AyCode.Core.Serializers.Toons; +using FruitBank.Common.Dtos; using LinqToDB; using LinqToDB.Mapping; using Mango.Nop.Core.Entities; @@ -9,6 +10,7 @@ using Table = LinqToDB.Mapping.TableAttribute; namespace FruitBank.Common.Entities; +[ToonDescription("Line item for product reconciliation", Purpose = "Reconciles snapshot quantity with physical count to calculate final stock delta")] [Table(Name = FruitBankConstClient.StockTakingItemDbTableName)] [System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockTakingItemDbTableName)] public class StockTakingItem : MgStockTakingItem @@ -21,27 +23,34 @@ public class StockTakingItem : MgStockTakingItem [Column(DataType = DataType.DecFloat, CanBeNull = false)] public double MeasuredNetWeight { get; set; } + [ToonDescription(Purpose = "Reserved stock buffer to prevent double-deduction during closing")] public int InProcessOrdersQuantity { get; set; } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => OriginalStockQuantity + InProcessOrdersQuantity")] public int TotalOriginalQuantity => OriginalStockQuantity + InProcessOrdersQuantity; [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(Purpose = "Final adjustment value for Product.StockQuantity", BusinessRule = "get => IsMeasured ? MeasuredStockQuantity - TotalOriginalQuantity : 0")] public int QuantityDiff => IsMeasured ? MeasuredStockQuantity - TotalOriginalQuantity : 0; [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => IsMeasurable && IsMeasured ? double.Round(MeasuredNetWeight - OriginalNetWeight, 1) : 0d")] public double NetWeightDiff => IsMeasurable && IsMeasured ? double.Round(MeasuredNetWeight - OriginalNetWeight, 1) : 0d; [Association(ThisKey = nameof(Id), OtherKey = nameof(StockTakingItemPallet.StockTakingItemId), CanBeNull = true)] public List? StockTakingItemPallets { get; set; } [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(Purpose = "Status flag", BusinessRule = "get => !IsInvalid && (TotalOriginalQuantity != 0 || OriginalNetWeight != 0)")] public bool IsRequiredForMeasuring => !IsInvalid && (TotalOriginalQuantity != 0 || OriginalNetWeight != 0); [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(Purpose = "Status flag", BusinessRule = "get => TotalOriginalQuantity < 0")] public bool IsInvalid => TotalOriginalQuantity < 0; [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => conditional string based on IsInvalid, IsMeasured, IsRequiredForMeasuring")] public string DisplayText { get diff --git a/FruitBank.Common/Entities/StockTakingItemPallet.cs b/FruitBank.Common/Entities/StockTakingItemPallet.cs index 6aba0ff2..eb881783 100644 --- a/FruitBank.Common/Entities/StockTakingItemPallet.cs +++ b/FruitBank.Common/Entities/StockTakingItemPallet.cs @@ -1,4 +1,5 @@ -using FruitBank.Common.Dtos; +using AyCode.Core.Serializers.Toons; +using FruitBank.Common.Dtos; using FruitBank.Common.Interfaces; using LinqToDB.Mapping; using Mango.Nop.Core.Entities; @@ -12,6 +13,7 @@ public interface IStockTakingItemPallet : IMeasuringItemPalletBase public StockTakingItem? StockTakingItem{ get; set; } } +[ToonDescription("Weight record for inventory item", Purpose = "Granular weight-based evidence for a stock taking line item")] [LinqToDB.Mapping.Table(Name = FruitBankConstClient.StockTakingItemPalletDbTableName)] [System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockTakingItemPalletDbTableName)] public class StockTakingItemPallet : MeasuringItemPalletBase, IStockTakingItemPallet diff --git a/FruitBankHybrid.Shared.Tests/ToonTests.cs b/FruitBankHybrid.Shared.Tests/ToonTests.cs index 37a4b7c0..4f30b123 100644 --- a/FruitBankHybrid.Shared.Tests/ToonTests.cs +++ b/FruitBankHybrid.Shared.Tests/ToonTests.cs @@ -1,18 +1,20 @@ using AyCode.Core.Enums; using AyCode.Core.Extensions; using AyCode.Core.Loggers; +using AyCode.Core.Serializers.Toons; using FruitBank.Common; using FruitBank.Common.Dtos; using FruitBank.Common.Entities; using FruitBank.Common.Loggers; using FruitBankHybrid.Shared.Services.SignalRs; +using Mango.Nop.Core.Entities; using Newtonsoft.Json; +using Nop.Core.Domain.Catalog; using Nop.Core.Domain.Common; using Nop.Core.Domain.Orders; using Nop.Core.Domain.Payments; using System.Linq.Expressions; using System.Runtime.Serialization; -using AyCode.Core.Serializers.Toons; namespace FruitBankHybrid.Shared.Tests; @@ -37,9 +39,9 @@ public sealed class ToonTests [TestMethod] - public void OrderDtoToToon() + public async Task OrderDtoToToon() { - var orderDtos = _signalRClient.GetAllOrderDtos(); + var orderDtos = await _signalRClient.GetAllOrderDtos(); var toon = AcToonSerializer.Serialize(orderDtos, AcToonSerializerOptions.Default); Console.WriteLine(toon); @@ -106,7 +108,7 @@ public sealed class ToonTests [TestMethod] public void ToonTypes_NavigationMetadata_ShouldBeComplete() { - var toon = AcToonSerializer.SerializeMetadata([typeof(Shipping), typeof(OrderDto)]); + var toon = AcToonSerializer.SerializeMetadata([typeof(Shipping), typeof(OrderDto), typeof(StockTaking), typeof(StockQuantityHistory), typeof(StockQuantityHistoryExt)]); Console.WriteLine(toon); Console.WriteLine("\n=== NAVIGATION METADATA ELLENŐRZÉS ===\n"); diff --git a/FruitBankHybrid.Shared.Tests/test_debug.ps1 b/FruitBankHybrid.Shared.Tests/test_debug.ps1 deleted file mode 100644 index f466dd9c..00000000 --- a/FruitBankHybrid.Shared.Tests/test_debug.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -# Test debugger script for JsonExtensionTests -$projectPath = "H:\Applications\Mango\Source\FruitBankHybridApp" -Set-Location $projectPath - -Write-Host "Building test project..." -dotnet build FruitBankHybrid.Shared.Tests/FruitBankHybrid.Shared.Tests.csproj -c Debug - -Write-Host "`nRunning JsonExtensionTests..." -# Use --no-build to avoid the MSBuild conflict -dotnet test FruitBankHybrid.Shared.Tests/FruitBankHybrid.Shared.Tests.csproj ` - --no-build ` - -c Debug ` - --filter "ClassName=FruitBankHybrid.Shared.Tests.JsonExtensionTests" ` - 2>&1 | Tee-Object -FilePath "test_results.txt" - -Write-Host "`n=== Test Results ===" -Get-Content "test_results.txt" | Select-String -Pattern "FAILED|PASSED|Error|Assert" | tail -50