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

33 KiB

Domain Model Schema (Toon Format)

Part of Nop.Plugin.Misc.FruitBankPlugin. See README.md for project overview. Full domain model in Toon (Token-Oriented Object Notation) format — see AyCode.Core/Serializers/Toons/README.md (in AyCode.Core solution). This is the authoritative schema for entities, DTOs, and enums in the FruitBank domain. For behavioral documentation (workflows, lifecycles, event cascades) see docs/DOMAIN_MODEL.md.

@meta { version = "1.0" format = "toon" source-code-language = "C#" context = "This is a nopCommerce plugin developed for FruitBank, a fruit and vegetable wholesaler. The plugin manages supplier inbound delivery (receiving), warehouse weighing (net/gross/pallet/tare weights), and inventory stocktaking. The business logic is centered around FruitBank's requirement for precise physical measurement and quantity tracking." types = ["OrderStatus", "ShippingStatus", "PaymentStatus", "GenericAttributeDto", "MeasuringStatus", "VatNumberStatus", "TaxDisplayType", "OrderNote", "PreOrderItemStatus", "PreOrderStatus", "DocumentType", "Files", "Pallet", "ProductDto", "Customer", "PreOrderItem", "PreOrder", "CargoPartner", "CargoTruck", "FullProcessModel", "OrderDto", "OrderItemDto", "OrderItemPallet", "Partner", "Shipping", "ShippingDocument", "ShippingDocumentToFiles", "ShippingItem", "ShippingItemPallet", "StockTaking", "StockTakingItem", "StockTakingItemPallet"] }

@types { OrderStatus: enum underlying-type: "int" default-value: 10 values: Pending = 10 Processing = 20 Complete = 30 Cancelled = 40

ShippingStatus: enum underlying-type: "int" default-value: 10 values: ShippingNotRequired = 10 NotYetShipped = 20 PartiallyShipped = 25 Shipped = 30 Delivered = 40

PaymentStatus: enum underlying-type: "int" default-value: 10 values: Pending = 10 Authorized = 20 Paid = 30 PartiallyRefunded = 35 Refunded = 40 Voided = 50

GenericAttributeDto: "Data transfer object for GenericAttribute" table-name: "GenericAttribute" related-type: "dto-of GenericAttribute" CreatedOrUpdatedDateUTC: DateTime? EntityId: int constraints: "polymorphic-fk(KeyGroup)" Key: string KeyGroup: string StoreId: int Value: string purpose: "Raw string representation of the Key's value" Id: int purpose: "Primary key / unique identification" primary-key: true

MeasuringStatus: enum underlying-type: "int" default-value: 0 values: NotStarted = 0 Started = 10 Finnished = 20 Audited = 30

VatNumberStatus: enum underlying-type: "int" default-value: 0 values: Unknown = 0 Empty = 10 Valid = 20 Invalid = 30

TaxDisplayType: enum underlying-type: "int" default-value: 0 values: IncludingTax = 0 ExcludingTax = 10

OrderNote: "NopCommerce order note entity" table-name: "OrderNote" CreatedOnUtc: DateTime DisplayToCustomer: bool DownloadId: int Note: string OrderId: int description: "Foreign key to parent Order" Id: int purpose: "Primary key / unique identification" primary-key: true

PreOrderItemStatus: enum underlying-type: "int" default-value: 0 values: Pending = 0 Fulfilled = 10 PartiallyFulfilled = 20 Dropped = 30

PreOrderStatus: enum underlying-type: "int" default-value: 0 values: Pending = 0 Confirmed = 10 PartiallyFulfilled = 20 Cancelled = 30

DocumentType: enum underlying-type: "int" default-value: 0 values: NotSet = 0 Unknown = 5 ShippingDocument = 10 OrderConfirmation = 15 Invoice = 20

Files: "Uploaded file with extracted text content" table-name: "fbFiles" 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" Created: DateTime FileExtension: string FileHash: string FileName: string FileSubPath: string IsCompressed: bool purpose: "Status flag" Modified: DateTime RawText: string Id: int purpose: "Primary key / unique identification" primary-key: true

Pallet: "Pallet type definition with size and weight" table-name: "fbPallet" Created: DateTime Modified: DateTime Name: string Size: string Weight: double? Id: int purpose: "Primary key / unique identification" primary-key: true

ProductDto: "Product data with measurements and generic attributes" table-name: "Product" related-type: "dto-of Product" AvailableQuantity: int business-logic: "get => StockQuantity + IncomingQuantity" constraints: "readonly, not-mapped" AverageWeight: double business-logic: "get => GenericAttributes.GetValueOrDefault('AverageWeight')" constraints: "readonly, not-mapped" AverageWeightTreshold: double business-logic: "get => GenericAttributes.GetValueOrDefault('AverageWeightTreshold')" constraints: "readonly, not-mapped" GenericAttributes: List navigation: "one-to-many" IncomingQuantity: int business-logic: "get => GenericAttributes.GetValueOrDefault('IncomingQuantity')" constraints: "not-mapped" IsMeasurable: bool purpose: "Master flag: if false, the system bypasses weight validation but still creates one Measurement Record (PalletItem) with TrayQuantity." business-logic: "get => GenericAttributes.GetValueOrDefault('IsMeasurable')" constraints: "not-mapped" NetWeight: double business-logic: "get => GenericAttributes.GetValueOrDefault('NetWeight')" constraints: "not-mapped" Tare: double business-logic: "get => GenericAttributes.GetValueOrDefault('Tare')" constraints: "not-mapped" Deleted: bool FullDescription: string Height: decimal Length: decimal LimitedToStores: bool Name: string ParentGroupedProductId: int Price: decimal ProductCost: decimal ProductTypeId: int ShortDescription: string StockQuantity: int SubjectToAcl: bool WarehouseId: int Weight: decimal Width: decimal Id: int purpose: "Primary key / unique identification" primary-key: true

Customer: "NopCommerce customer entity" table-name: "Customer" Active: bool AdminComment: string AffiliateId: int BillingAddressId: int? CannotLoginUntilDateUtc: DateTime? City: string Company: string CountryId: int County: string CreatedOnUtc: DateTime CurrencyId: int? CustomCustomerAttributesXML: string CustomerGuid: Guid DateOfBirth: DateTime? Deleted: bool Email: string constraints: "email-format" EmailToRevalidate: string constraints: "email-format" FailedLoginAttempts: int Fax: string FirstName: string Gender: string HasShoppingCartItems: bool IsSystemAccount: bool purpose: "Status flag" IsTaxExempt: bool purpose: "Status flag" LanguageId: int? constraints: "range: 0-150" LastActivityDateUtc: DateTime LastIpAddress: string LastLoginDateUtc: DateTime? LastName: string MustChangePassword: bool Phone: string RegisteredInStoreId: int RequireReLogin: bool ShippingAddressId: int? StateProvinceId: int StreetAddress: string StreetAddress2: string SystemName: string TaxDisplayType: TaxDisplayType? TaxDisplayTypeId: int? TimeZoneId: string Username: string VatNumber: string VatNumberStatus: VatNumberStatus VatNumberStatusId: int VendorId: int ZipPostalCode: string Id: int purpose: "Primary key / unique identification" primary-key: true

PreOrderItem: "Single product line of a customer preorder with fulfilment tracking" table-name: "fbPreOrderItem" 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." FulfilledQuantity: int purpose: "Quantity allocated from incoming stock so far; accumulates across conversion runs until it reaches RequestedQuantity." business-logic: "this >= 0 && this <= RequestedQuantity" PreOrderId: int purpose: "FK to the parent PreOrder." ProductId: int purpose: "FK to the nopCommerce Product being preordered." RequestedQuantity: int purpose: "Quantity of the product the customer requested." constraints: "positive" Status: PreOrderItemStatus purpose: "Item lifecycle: Pending / Fulfilled (fully allocated) / PartiallyFulfilled (partly allocated) / Dropped (expired or no incoming stock)." business-logic: "set during conversion: FulfilledQuantity >= RequestedQuantity ? Fulfilled : FulfilledQuantity > 0 ? PartiallyFulfilled : Dropped; stays Pending until first allocation" UnitPriceInclTax: decimal 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" Id: int purpose: "Primary key / unique identification" primary-key: true

PreOrder: "Customer advance order placed before the goods physically arrive" table-name: "fbPreOrder" 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." CreatedOnUtc: DateTime CustomerId: int purpose: "FK to the nopCommerce Customer who placed the preorder." CustomerNote: string purpose: "Optional free-text note entered by the customer when placing the preorder." DateOfReceipt: DateTime 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." OrderId: int? 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." PreOrderItems: List other-key: "PreOrderId" navigation: "one-to-many" Status: PreOrderStatus purpose: "Header lifecycle: Pending / Confirmed (all items Fulfilled) / PartiallyFulfilled (some Dropped or partial, none left Pending) / Cancelled." business-logic: "set by RefreshPreOrderStatusAsync from item states: items.All(Fulfilled) => Confirmed; (any Dropped or PartiallyFulfilled) && none Pending => PartiallyFulfilled; else Pending. Cancelled is set explicitly." StoreId: int purpose: "nopCommerce multi-store scope." UpdatedOnUtc: DateTime purpose: "Bumped to UtcNow on every status / allocation change." Id: int purpose: "Primary key / unique identification" primary-key: true

CargoPartner: "Transport / haulage company (carrier) with its vehicle fleet" table-name: "fbCargoPartner" purpose: "A carrier that delivers goods to the warehouse and owns the CargoTrucks (both trucks and trailers). Distinct from Partner, which is the goods supplier. Name, address and tax fields are inherited from PartnerBase." CargoTrucks: List other-key: "CargoPartnerId" navigation: "one-to-many" inverse-property: "CargoPartner" CertificationNumber: string City: string Country: string CountryCode: string County: string Created: DateTime Modified: DateTime Name: string PostalCode: string State: string Street: string TaxId: string Id: int purpose: "Primary key / unique identification" primary-key: true

CargoTruck: "Cargo vehicle — truck or trailer — owned by a transport partner" table-name: "fbCargoTruck" purpose: "A single vehicle in a transport company's fleet. One table holds both tractor units and trailers, distinguished by IsTrailer. Truck and trailer are tracked as separate vehicles because Hungarian EKÁER road-freight reporting (NAV) declares the towing vehicle and the trailer as distinct entries (vehicle / vehicle2), each with its own licence plate and country code — i.e. these shipments carry an EKÁER declaration obligation." CargoPartner: CargoPartner foreign-key: "CargoPartnerId" navigation: "many-to-one" inverse-property: "CargoTrucks" CargoPartnerId: int purpose: "FK to the owning transport company (CargoPartner) — the carrier, not the goods supplier." CargoPartnerName: string constraints: "readonly, not-mapped" CountryCode: string Created: DateTime IsTrailer: bool purpose: "Discriminates the shared table: false = tractor/truck unit, true = trailer." LicencePlate: string Modified: DateTime Id: int purpose: "Primary key / unique identification" primary-key: true

FullProcessModel: "Object of type FullProcessModel" table-name: "FullProcessModel" purpose: "Container model for Shipping, Order" Orders: List navigation: "one-to-many" PreOrders: List navigation: "one-to-many" Shippings: List navigation: "one-to-many" StockTakings: List navigation: "one-to-many"

OrderDto: "Data transfer object for Order" table-name: "Order" related-type: "dto-of Order" DateOfReceipt: DateTime? business-logic: "get => GenericAttributes.GetValueOrNull('DateOfReceipt')" constraints: "readonly, not-mapped" DateOfReceiptOrCreated: DateTime constraints: "readonly, not-mapped" GenericAttributes: List navigation: "one-to-many" IsAllOrderItemAudited: bool purpose: "Status flag" business-logic: "get => OrderItemDtos.Count > 0 && OrderItemDtos.All(oi => oi.IsAudited)" constraints: "readonly, not-mapped" IsAllOrderItemAvgWeightValid: bool purpose: "Status flag" business-logic: "get => OrderItemDtos.All(oi => oi.AverageWeightIsValid)" constraints: "readonly, not-mapped" IsComplete: bool purpose: "Status flag" business-logic: "get => OrderStatus == OrderStatus.Complete" constraints: "readonly, not-mapped" IsMeasurable: bool purpose: "Status flag" business-logic: "get => OrderItemDtos.Any(oi => oi.IsMeasurable)" constraints: "readonly, not-mapped" IsMeasured: bool purpose: "Status flag" business-logic: "get => Id > 0 && OrderItemDtos.Count > 0 && OrderItemDtos.All(x => x.IsMeasured)" constraints: "readonly, not-mapped" MeasurementOwnerId: int business-logic: "get => GenericAttributes.GetValueOrDefault('MeasurementOwnerId', 0)" constraints: "readonly, not-mapped" MeasuringStatus: MeasuringStatus constraints: "readonly, not-mapped" RevisorId: int business-logic: "get => GenericAttributes.GetValueOrDefault('RevisorId', 0)" constraints: "readonly, not-mapped" TimeOfReceiptText: string constraints: "readonly, not-mapped" CreatedOnUtc: DateTime CustomOrderNumber: string CustomValuesXml: string Customer: Customer foreign-key: "CustomerId" navigation: "many-to-one" CustomerId: int Deleted: bool OrderDiscount: decimal OrderGuid: Guid OrderItemDtos: List other-key: "OrderId" navigation: "one-to-many" inverse-property: "OrderDto" OrderNotes: List other-key: "OrderId" navigation: "one-to-many" OrderStatus: OrderStatus purpose: "Enum wrapper" business-logic: "get, set => OrderStatusId" OrderStatusId: int OrderTotal: decimal PaidDateUtc: DateTime? PaymentStatus: PaymentStatus purpose: "Enum wrapper" business-logic: "get, set => PaymentStatusId" PaymentStatusId: int ShippingMethod: string ShippingStatus: ShippingStatus purpose: "Enum wrapper" business-logic: "get, set => ShippingStatusId" ShippingStatusId: int StoreId: int Id: int purpose: "Primary key / unique identification" primary-key: true

OrderItemDto: "Order item with measurements, pallets, and validation" table-name: "OrderItem" related-type: "dto-of OrderItem" AverageWeight: double business-logic: "get => IsMeasurable && OrderItemPallets.Count > 0 ? double.Round(OrderItemPallets.Sum(oip => oip.AverageWeight) / OrderItemPallets.Count, 1) : 0d" constraints: "readonly, not-mapped" AverageWeightDifference: double business-logic: "get => IsMeasurable ? double.Round(ProductDto!.AverageWeight - AverageWeight, 1) : 0" constraints: "readonly, not-mapped" AverageWeightIsValid: bool business-logic: "get => !IsMeasurable || (ProductDto!.AverageWeight > 0 && ((AverageWeightDifference / ProductDto!.AverageWeight) * 100) < ProductDto!.AverageWeightTreshold)" constraints: "readonly, not-mapped" GenericAttributes: List navigation: "one-to-many" GrossWeight: double business-logic: "get => double.Round(OrderItemPallets.Sum(x => x.NetWeight), 1)" constraints: "not-mapped" IsAudited: bool purpose: "Status flag" business-logic: "get => OrderItemPallets.Count > 0 && OrderItemPallets.All(oip => oip.IsAudited)" constraints: "readonly, not-mapped" IsMeasurable: bool purpose: "Status flag" business-logic: "get => ProductDto!.IsMeasurable" constraints: "not-mapped" IsMeasured: bool purpose: "Status flag" business-logic: "get => IsMeasuredAndValid()" constraints: "not-mapped" MeasuringStatus: MeasuringStatus business-logic: "get => complex conditional logic based on IsAudited, IsMeasured, and OrderItemPallets status" constraints: "readonly, not-mapped" NetWeight: double business-logic: "get => double.Round(OrderItemPallets.Sum(x => x.NetWeight), 1)" constraints: "not-mapped" OrderDto: OrderDto foreign-key: "OrderId" navigation: "many-to-one" inverse-property: "OrderItemDtos" OrderItemPallets: List other-key: "OrderItemId" navigation: "one-to-many" inverse-property: "OrderItemDto" TrayQuantity: int business-logic: "get => OrderItemPallets.Sum(x => x.TrayQuantity)" constraints: "not-mapped" AttributesXml: string ItemWeight: decimal? OrderId: int OrderItemGuid: Guid PriceExclTax: decimal PriceInclTax: decimal ProductDto: ProductDto foreign-key: "ProductId" navigation: "many-to-one" ProductId: int ProductName: string business-logic: "get => ProductDto?.Name ?? 'ProductDto is null!!!'" constraints: "readonly" Quantity: int UnitPriceExclTax: decimal UnitPriceInclTax: decimal Id: int purpose: "Primary key / unique identification" primary-key: true

OrderItemPallet: "Pallet measurements for order items with audit tracking" table-name: "fbOrderItemPallet" purpose: "A measurement record for outgoing goods, used to verify that the net weight being sent to the customer is accurate and audited. NOTE: Despite the 'Pallet' name, this is a general measurement record that is ALWAYS created for every item. If the product is not measurable (IsMeasurable=false), weights are recorded as 0.0 and only TrayQuantity is stored." AverageWeight: double business-logic: "get => double.Round(NetWeight / TrayQuantity, 1)" constraints: "readonly, not-mapped" IsAudited: bool purpose: "Status flag" business-logic: "get => RevisorId > 0" constraints: "readonly, not-mapped" MeasuringStatus: MeasuringStatus business-logic: "get => IsAudited ? MeasuringStatus.Audited : base.MeasuringStatus" constraints: "readonly, not-mapped" OrderItemDto: OrderItemDto foreign-key: "OrderItemId" navigation: "many-to-one" inverse-property: "OrderItemPallets" OrderItemId: int RevisorId: int purpose: "User/Customer ID of the quality auditor" Created: DateTime CreatorId: int? ForeignKey: int business-logic: "get => ForeignItemId" constraints: "readonly, not-mapped" GrossWeight: double purpose: "Measured gross weight; 0.0 if product is not measurable" IsMeasured: bool purpose: "Status flag" Modified: DateTime ModifierId: int? NetWeight: double business-logic: "get => GrossWeight - PalletWeight - (TrayQuantity * TareWeight)" constraints: "readonly, not-mapped" PalletWeight: double purpose: "Weight of the physical pallet if used; 0.0 if goods arrive without a pallet" TareWeight: double TrayQuantity: int purpose: "Always recorded, regardless of measurability" Id: int purpose: "Primary key / unique identification" primary-key: true

Partner: "Business partner with address and tax information" table-name: "fbPartner" purpose: "Represents an external legal entity, specifically a Supplier who provides goods or a business partner involved in the procurement chain" ShippingDocuments: List other-key: "PartnerId" navigation: "one-to-many" inverse-property: "Partner" CertificationNumber: string City: string Country: string CountryCode: string County: string Created: DateTime Modified: DateTime Name: string PostalCode: string State: string Street: string TaxId: string Id: int purpose: "Primary key / unique identification" primary-key: true

Shipping: "Shipping record with documents and measurement tracking" table-name: "fbShipping" purpose: "Inbound delivery event (truck arrival) at the warehouse. Created early and filled progressively — carrier, truck and trailer are assigned later." CargoCompany: string CargoPartner: CargoPartner purpose: "Carrier (transport company); assigned later, null until known. Supplier is separate — see ShippingDocument.Partner." foreign-key: "CargoPartnerId" navigation: "many-to-one" CargoPartnerId: int? CargoTrailer: CargoTruck purpose: "Trailer (CargoTruck table, IsTrailer=true); optional, assigned later — null if none or not yet known." foreign-key: "CargoTrailerId" navigation: "many-to-one" CargoTrailerId: int? CargoTruck: CargoTruck purpose: "Tractor unit (CargoTruck, IsTrailer=false) from the carrier's fleet; assigned later, null until known." foreign-key: "CargoTruckId" navigation: "many-to-one" CargoTruckId: int? Comment: string Created: DateTime IsAllMeasured: bool purpose: "Status flag" LicencePlate: string MeasuredDate: DateTime? Modified: DateTime ShippingDate: DateTime ShippingDocuments: List other-key: "ShippingId" navigation: "one-to-many" inverse-property: "Shipping" Id: int purpose: "Primary key / unique identification" primary-key: true

ShippingDocument: "Shipping document with partner, items and files" table-name: "fbShippingDocument" purpose: "Supplier's delivery note or invoice for the shipment; reconciles paper data with measured reality. Populated progressively — much entered at order time, the rest as it becomes known." Comment: string Country: string Created: DateTime DocumentIdNumber: string IsAllMeasured: bool purpose: "Status flag" Modified: DateTime Partner: Partner foreign-key: "PartnerId" navigation: "many-to-one" inverse-property: "ShippingDocuments" PartnerId: int PdfFileName: string Shipping: Shipping foreign-key: "ShippingId" navigation: "many-to-one" inverse-property: "ShippingDocuments" ShippingDate: DateTime ShippingDocumentToFiles: List other-key: "ShippingDocumentId" navigation: "one-to-many" inverse-property: "ShippingDocument" ShippingId: int? ShippingItems: List other-key: "ShippingDocumentId" navigation: "one-to-many" inverse-property: "ShippingDocument" TotalPallets: int Id: int purpose: "Primary key / unique identification" primary-key: true

ShippingDocumentToFiles: "Links shipping documents to files with document type" table-name: "fbShippingDocumentToFiles" 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" Created: DateTime DocumentType: DocumentType purpose: "Enum wrapper" business-logic: "get, set => DocumentTypeId" constraints: "not-mapped" DocumentTypeId: int constraints: "enum-reference: DocumentType, enum-type: DocumentType" FilesId: int Modified: DateTime ShippingDocument: ShippingDocument foreign-key: "ShippingDocumentId" navigation: "many-to-one" inverse-property: "ShippingDocumentToFiles" ShippingDocumentFile: Files foreign-key: "FilesId" navigation: "many-to-one" ShippingDocumentId: int Id: int purpose: "Primary key / unique identification" primary-key: true

ShippingItem: "Shipping document item with measurements and pallets" table-name: "fbShippingItem" 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" Created: DateTime GrossWeightOnDocument: double HungarianName: string IsMeasurable: bool purpose: "Status flag" IsMeasured: bool purpose: "Status flag" MeasuredGrossWeight: double constraints: "range: 1-100000" MeasuredNetWeight: double constraints: "range: 1-100000" MeasuredQuantity: int constraints: "range: 1-100000" MeasuringCount: int constraints: "range: 1-100000, non-negative" MeasuringStatus: MeasuringStatus business-logic: "get => complex conditional logic based on IsMeasured and ShippingItemPallets status" constraints: "readonly, not-mapped" Modified: DateTime Name: string NameOnDocument: string NetWeightOnDocument: double Pallet: Pallet foreign-key: "PalletId" navigation: "many-to-one" PalletId: int? PalletsOnDocument: int ProductDto: ProductDto foreign-key: "ProductId" navigation: "many-to-one" ProductId: int? ProductName: string business-logic: "get => ProductDto?.Name ?? Name" constraints: "readonly, not-mapped" QuantityOnDocument: int ShippingDocument: ShippingDocument foreign-key: "ShippingDocumentId" navigation: "many-to-one" inverse-property: "ShippingItems" ShippingDocumentId: int ShippingItemPallets: List other-key: "ShippingItemId" navigation: "one-to-many" inverse-property: "ShippingItem" UnitPriceOnDocument: double Id: int purpose: "Primary key / unique identification" primary-key: true

ShippingItemPallet: "Pallet measurements for shipping items" table-name: "fbShippingItemPallet" purpose: "The smallest unit of measurement tracking, representing a single physical measurement event. NOTE: Technically named 'Pallet' for legacy reasons, but it is ALWAYS created even if goods arrive without a physical pallet. For non-measurable products, weights are 0.0 and only TrayQuantity is tracked for tare-weight calculations." ShippingItem: ShippingItem foreign-key: "ShippingItemId" navigation: "many-to-one" inverse-property: "ShippingItemPallets" ShippingItemId: int Created: DateTime CreatorId: int? ForeignKey: int business-logic: "get => ForeignItemId" constraints: "readonly, not-mapped" GrossWeight: double purpose: "Measured gross weight; 0.0 if product is not measurable" IsMeasured: bool purpose: "Status flag" MeasuringStatus: MeasuringStatus business-logic: "get => IsMeasured ? MeasuringStatus.Finnished : Id > 0 ? MeasuringStatus.Started : MeasuringStatus.NotStarted" constraints: "readonly, not-mapped" Modified: DateTime ModifierId: int? NetWeight: double business-logic: "get => GrossWeight - PalletWeight - (TrayQuantity * TareWeight)" constraints: "readonly, not-mapped" PalletWeight: double purpose: "Weight of the physical pallet if used; 0.0 if goods arrive without a pallet" TareWeight: double TrayQuantity: int purpose: "Always recorded, regardless of measurability" Id: int purpose: "Primary key / unique identification" primary-key: true

StockTaking: "Inventory session record" table-name: "fbStockTaking" purpose: "Orchestrates inventory sessions by freezing logical stock states" Created: DateTime Creator: int IsClosed: bool purpose: "Status flag" Modified: DateTime StartDateTime: DateTime StockTakingItems: List other-key: "StockTakingId" navigation: "one-to-many" inverse-property: "StockTaking" Id: int purpose: "Primary key / unique identification" primary-key: true

StockTakingItem: "Line item for product reconciliation" table-name: "fbStockTakingItem" purpose: "Reconciles snapshot quantity with physical count to calculate final stock delta" DisplayText: string business-logic: "get => conditional string based on IsInvalid, IsMeasured, IsRequiredForMeasuring" constraints: "readonly, not-mapped" InProcessOrdersQuantity: int purpose: "Reserved stock buffer (not yet shipped) to prevent double-deduction during closing" IsInvalid: bool purpose: "Status flag" business-logic: "get => TotalOriginalQuantity < 0" constraints: "readonly, not-mapped" IsMeasurable: bool purpose: "Status flag" IsRequiredForMeasuring: bool purpose: "Status flag" business-logic: "get => !IsInvalid && (TotalOriginalQuantity != 0 || OriginalNetWeight != 0)" constraints: "readonly, not-mapped" MeasuredNetWeight: double NetWeightDiff: double business-logic: "get => IsMeasurable && IsMeasured ? double.Round(MeasuredNetWeight - OriginalNetWeight, 1) : 0d" constraints: "readonly, not-mapped" OriginalNetWeight: double QuantityDiff: int purpose: "Final adjustment value for Product.StockQuantity" business-logic: "get => IsMeasured ? MeasuredStockQuantity - TotalOriginalQuantity : 0" constraints: "readonly, not-mapped" StockTakingItemPallets: List other-key: "StockTakingItemId" navigation: "one-to-many" inverse-property: "StockTakingItem" TotalOriginalQuantity: int purpose: "Snapshot of total logical stock at session start" business-logic: "get => OriginalStockQuantity + InProcessOrdersQuantity" constraints: "readonly, not-mapped" Created: DateTime IsMeasured: bool purpose: "Status flag" MeasuredStockQuantity: int Modified: DateTime OriginalStockQuantity: int Product: ProductDto foreign-key: "ProductId" navigation: "many-to-one" ProductId: int StockTaking: StockTaking foreign-key: "StockTakingId" navigation: "many-to-one" inverse-property: "StockTakingItems" StockTakingId: int Id: int purpose: "Primary key / unique identification" primary-key: true

StockTakingItemPallet: "Weight record for inventory item" table-name: "fbStockTakingItemPallet" purpose: "Granular weight-based evidence for a stock taking line item. NOTE: This record is mandatory for every inventory item. If weighing is skipped (non-measurable), it serves as a container for TrayQuantity with zeroed weight fields. The term 'Pallet' is a legacy naming convention." StockTakingItem: StockTakingItem foreign-key: "StockTakingItemId" navigation: "many-to-one" inverse-property: "StockTakingItemPallets" StockTakingItemId: int Created: DateTime CreatorId: int? ForeignKey: int business-logic: "get => ForeignItemId" constraints: "readonly, not-mapped" GrossWeight: double purpose: "Measured gross weight; 0.0 if product is not measurable" IsMeasured: bool purpose: "Status flag" MeasuringStatus: MeasuringStatus business-logic: "get => IsMeasured ? MeasuringStatus.Finnished : Id > 0 ? MeasuringStatus.Started : MeasuringStatus.NotStarted" constraints: "readonly, not-mapped" Modified: DateTime ModifierId: int? NetWeight: double business-logic: "get => GrossWeight - PalletWeight - (TrayQuantity * TareWeight)" constraints: "readonly, not-mapped" PalletWeight: double purpose: "Weight of the physical pallet if used; 0.0 if goods arrive without a pallet" TareWeight: double TrayQuantity: int purpose: "Always recorded, regardless of measurability" Id: int purpose: "Primary key / unique identification" primary-key: true }