Compare commits
29 Commits
45195b9cdf
...
db9f7aa12f
| Author | SHA1 | Date |
|---|---|---|
|
|
db9f7aa12f | |
|
|
ea643d0855 | |
|
|
6ec77266a2 | |
|
|
431ed8c037 | |
|
|
352b3b2d21 | |
|
|
5b0b080b5a | |
|
|
b9b2767843 | |
|
|
40d6567f29 | |
|
|
9f8dbd29fc | |
|
|
ab51b948ae | |
|
|
25522f7c27 | |
|
|
aecd54ffdd | |
|
|
ac7b4d58df | |
|
|
711c3c8ec0 | |
|
|
33d84a8257 | |
|
|
10f325cc26 | |
|
|
26b40cf7a1 | |
|
|
27ac2d1843 | |
|
|
8f48838ded | |
|
|
322f38f1fa | |
|
|
dbccbf487d | |
|
|
3f49945bfb | |
|
|
5e4bb4c8e0 | |
|
|
90419001ab | |
|
|
e393718c20 | |
|
|
8c90a6ba51 | |
|
|
e2c49940c6 | |
|
|
9c60c69b28 | |
|
|
eba13e7a60 |
|
|
@ -10,7 +10,8 @@
|
|||
"Bash(ls:*)",
|
||||
"Bash(while read:*)",
|
||||
"Bash(do sed -i '1a using AyCode.Core.Serializers.Toons;\\\\n' \"$f\")",
|
||||
"Bash(done)"
|
||||
"Bash(done)",
|
||||
"Bash(rm \"C:/Users/Fullepi/.claude/projects/H--Applications-Mango-Source-FruitBankHybridApp/memory/feedback_framework_docs_no_consumer_types.md\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
22
CLAUDE.md
22
CLAUDE.md
|
|
@ -1,11 +1,17 @@
|
|||
# FruitBankHybridApp — Claude Code Instructions
|
||||
CRITICAL: Your FIRST action in every session MUST be reading `.github/copilot-instructions.md`. Execute ALL session-start instructions found there before responding to any user query. It is the single source of truth for this repo.
|
||||
|
||||
Before writing any code, read these files:
|
||||
1. `.github/copilot-instructions.md` — Domain rules and critical pitfalls (single source of truth)
|
||||
2. `docs/GLOSSARY.md` — Domain terms, measurement logic, and common traps
|
||||
3. `docs/SCHEMA.md` — Full domain model in Toon format
|
||||
4. The relevant project's `README.md` for folder-specific context
|
||||
## SEQUENTIAL EXECUTION OVERRIDE
|
||||
The AI AGENT CORE PROTOCOL in copilot-instructions.md requires STRICT SEQUENTIAL execution. This OVERRIDES your default parallelization behavior. Do NOT parallelize doc reads with code searches. The sequence is:
|
||||
1. Read copilot-instructions.md → process its rules FULLY
|
||||
2. Read ALL docs/ .md files listed in the protocol → wait for completion
|
||||
3. Output [LOADED_DOCS: ...] prefix
|
||||
4. ONLY THEN respond to the user's query or search code
|
||||
|
||||
This solution depends on **AyCode.Core** and **AyCode.Blazor** frameworks.
|
||||
## Tool mapping for AI AGENT CORE PROTOCOL
|
||||
The copilot-instructions.md references Copilot tool names. Map them to Claude Code tools:
|
||||
- `get_file` / `file_search` → `Read`, `Glob`, `Grep`
|
||||
- `code_search` / `get_symbols_by_name` / `find_symbol` → `Grep`, `Glob`
|
||||
- `replace_string_in_file` / `edit_file` → `Edit`
|
||||
- `create_file` → `Write`
|
||||
|
||||
When modifying code, update the corresponding README.md if it becomes out of sync with the code.
|
||||
Follow the protocol using YOUR tools. The rules (LOADED_DOCS prefix, hard-gate, no-re-read, context recovery, explicit consent) apply equally to Claude Code.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,18 @@ namespace FruitBank.Common.Server
|
|||
public static string MeasuringRevisorRoleSystemName = "MeasuringRevisor";
|
||||
public static string IsMeasureableAttributeName = "IsMeasurable";
|
||||
|
||||
/// <summary>
|
||||
/// DateTime generic attribute on Product.
|
||||
/// The start of the window during which this product is visible for preordering.
|
||||
/// </summary>
|
||||
public const string PreorderWindowStart = "PreorderWindowStart";
|
||||
|
||||
/// <summary>
|
||||
/// DateTime generic attribute on Product.
|
||||
/// The end of the window during which this product is visible for preordering.
|
||||
/// </summary>
|
||||
public const string PreorderWindowEnd = "PreorderWindowEnd";
|
||||
|
||||
static FruitBankConst()
|
||||
{
|
||||
ProjectId = Guid.Parse(ProjectIdString);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,3 @@ Server-side marker interfaces extending the shared Common interfaces. Used for D
|
|||
- **`IFruitBankDataControllerServer.cs`** — Extends IFruitBankDataControllerCommon. Empty server marker.
|
||||
- **`ICustomOrderSignalREndpointServer.cs`** — Extends ICustomOrderSignalREndpointCommon. Empty server marker.
|
||||
- **`IStockSignalREndpointServer.cs`** — Extends IStockSignalREndpointCommon. Empty server marker.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
# FruitBank.Common.Server
|
||||
|
||||
@project {
|
||||
type = "product"
|
||||
own-dep-projects = [
|
||||
"AyCode.Core, AyCode.Interfaces, AyCode.Models.Server, AyCode.Services, AyCode.Services.Server (in AyCode.Core repo)",
|
||||
"Mango.Nop.Core (in Mango.Nop Libraries repo)"
|
||||
]
|
||||
}
|
||||
|
||||
Server-side library: SignalR hubs, real-time broadcast service, logging infrastructure, and nopCommerce integration constants.
|
||||
|
||||
## Folder Structure
|
||||
|
|
@ -18,7 +26,3 @@ Server-side library: SignalR hubs, real-time broadcast service, logging infrastr
|
|||
- nopCommerce via Mango.Nop.Core
|
||||
- AyCode.Core, AyCode.Services.Server (DLL references)
|
||||
- Microsoft.AspNetCore.SignalR
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,3 @@ Server-side logging implementations.
|
|||
|
||||
- **`ConsoleLogWriter.cs`** — Lightweight console logger extending AcConsoleLogWriter. Configurable by AppType, LogLevel, category.
|
||||
- **`LoggerToLoggerApiController.cs`** — Aggregates multiple IAcLogWriterBase implementations into a single logger for API controllers.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,3 @@ Server-side SignalR hubs, real-time broadcast, and logging infrastructure.
|
|||
|---|---|
|
||||
| [`Loggers/`](Loggers/README.md) | Console and API controller log writers |
|
||||
| [`SignalRs/`](SignalRs/README.md) | SignalR hubs and broadcast service |
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,3 @@ SignalR hub implementations and real-time broadcast service.
|
|||
- **`AcWebSignalRHubWithSessionBase.cs`** — Generic base hub with session management (OnConnected/OnDisconnected hooks).
|
||||
- **`SignalRSendToClientService.cs`** — Broadcasts real-time notifications to all clients: SendOrderChanged, SendOrderItemChanged, SendShippingChanged, SendProductChanged, etc.
|
||||
- **`LoggerSignalRHub.cs`** — Minimal hub for logging/diagnostics.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,3 @@ Local in-memory database abstraction for offline/cached data using ConcurrentDic
|
|||
## Key Files
|
||||
|
||||
- **`DatabaseLocalBase.cs`** — Abstract base with generic table management for IEntityInt entities. Thread-safe AddTable, GetRows, GetRow, AddRow, AddRows, DeleteRow.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Nop.Core.Domain.Orders;
|
|||
|
||||
namespace FruitBank.Common.Dtos;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[LinqToDB.Mapping.Table(Name = nameof(GenericAttribute))]
|
||||
[System.ComponentModel.DataAnnotations.Schema.Table(nameof(GenericAttribute))]
|
||||
[ToonDescription($"Data transfer object for {nameof(GenericAttribute)}", TypeRelation = ToonTypeRelation.DtoOf, RelatedTypes = [typeof(GenericAttribute)])]
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ using System.Linq.Expressions;
|
|||
|
||||
namespace FruitBank.Common.Dtos;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[LinqToDB.Mapping.Table(Name = nameof(Order))]
|
||||
[System.ComponentModel.DataAnnotations.Schema.Table(nameof(Order))]
|
||||
[ToonDescription($"Data transfer object for {nameof(Order)}", TypeRelation = ToonTypeRelation.DtoOf, RelatedTypes = [typeof(Order)])]
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ using System.Linq.Expressions;
|
|||
|
||||
namespace FruitBank.Common.Dtos;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[LinqToDB.Mapping.Table(Name = nameof(OrderItem))]
|
||||
[System.ComponentModel.DataAnnotations.Schema.Table(nameof(OrderItem))]
|
||||
[ToonDescription("Order item with measurements, pallets, and validation", TypeRelation = ToonTypeRelation.DtoOf, RelatedTypes = [typeof(OrderItem)])]
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ using System.Linq.Expressions;
|
|||
|
||||
namespace FruitBank.Common.Dtos;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[LinqToDB.Mapping.Table(Name = nameof(Product))]
|
||||
[System.ComponentModel.DataAnnotations.Schema.Table(nameof(Product))]
|
||||
[ToonDescription("Product data with measurements and generic attributes", TypeRelation = ToonTypeRelation.DtoOf, RelatedTypes = [typeof(Product)])]
|
||||
|
|
|
|||
|
|
@ -13,7 +13,3 @@ Binary-serializable DTOs for efficient SignalR communication. All marked with `[
|
|||
## Why DTOs Exist
|
||||
|
||||
nopCommerce entities (Order, OrderItem, Product) are extended with measurement logic via these DTOs. The DTOs add computed properties and GenericAttribute access that the raw nopCommerce entities don't have.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ using FruitBank.Common.Entities;
|
|||
|
||||
namespace FruitBank.Common.Dtos
|
||||
{
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[LinqToDB.Mapping.Table(Name = nameof(StockQuantityHistory))]
|
||||
[System.ComponentModel.DataAnnotations.Schema.Table(nameof(StockQuantityHistory))]
|
||||
[ToonDescription("Stock quantity history with net weight adjustments", TypeRelation = ToonTypeRelation.DtoOf, RelatedTypes = [typeof(StockQuantityHistory)])]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
using AyCode.Interfaces.EntityComment;
|
||||
using FruitBank.Common.Interfaces;
|
||||
using Mango.Nop.Core.Entities;
|
||||
|
||||
namespace FruitBank.Common.Entities;
|
||||
|
||||
public class CustomerCredit: MgEntityBase, IEntityComment
|
||||
{
|
||||
public int CustomerId { get; set; }
|
||||
public decimal CreditLimit { get; set; }
|
||||
public string? Comment { get; set; }
|
||||
public DateTime CreatedOnUtc { get; set; }
|
||||
public DateTime UpdatedOnUtc { get; set; }
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ using Mango.Nop.Core.Entities;
|
|||
|
||||
namespace FruitBank.Common.Entities;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[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)]
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ using Table = LinqToDB.Mapping.TableAttribute;
|
|||
|
||||
namespace FruitBank.Common.Entities;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[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. 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.")]
|
||||
[Table(Name = FruitBankConstClient.OrderItemPalletDbTableName)]
|
||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.OrderItemPalletDbTableName)]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using Mango.Nop.Core.Entities;
|
|||
|
||||
namespace FruitBank.Common.Entities;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[ToonDescription("Pallet type definition with size and weight")]
|
||||
[Table(Name = FruitBankConstClient.PalletDbTableName)]
|
||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PalletDbTableName)]
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Mango.Nop.Core.Entities;
|
|||
|
||||
namespace FruitBank.Common.Entities;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[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)]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
using FruitBank.Common.Enums;
|
||||
using LinqToDB.Mapping;
|
||||
using Mango.Nop.Core.Entities;
|
||||
|
||||
namespace FruitBank.Common.Entities;
|
||||
|
||||
[Table(Name = FruitBankConstClient.PreOrderDbTableName)]
|
||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PreOrderDbTableName)]
|
||||
public class Preorder : MgEntityBase
|
||||
{
|
||||
public int CustomerId { get; set; }
|
||||
public int StoreId { get; set; }
|
||||
public DateTime DateOfReceipt { get; set; }
|
||||
public PreorderStatus Status { get; set; }
|
||||
public string? CustomerNote { get; set; }
|
||||
public DateTime CreatedOnUtc { get; set; }
|
||||
public DateTime UpdatedOnUtc { get; set; }
|
||||
public int? OrderId { get; set; }
|
||||
|
||||
public List<PreorderItem> PreorderItems { get; set; } = new();
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using FruitBank.Common.Enums;
|
||||
using LinqToDB.Mapping;
|
||||
using Mango.Nop.Core.Entities;
|
||||
|
||||
namespace FruitBank.Common.Entities;
|
||||
|
||||
[Table(Name = FruitBankConstClient.PreOrderItemDbTableName)]
|
||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PreOrderItemDbTableName)]
|
||||
public class PreorderItem : MgEntityBase
|
||||
{
|
||||
public int PreorderId { get; set; }
|
||||
public int ProductId { get; set; }
|
||||
public int RequestedQuantity { get; set; }
|
||||
public int FulfilledQuantity { get; set; }
|
||||
public decimal UnitPriceInclTax { get; set; }
|
||||
public PreorderItemStatus Status { get; set; }
|
||||
}
|
||||
|
|
@ -31,7 +31,3 @@ Domain entities for inbound/outbound goods tracking and inventory. All map to `f
|
|||
## Critical: "Pallet" Naming
|
||||
|
||||
Despite the name, `XxxItemPallet` entities are **measurement records**, NOT physical pallets. They are ALWAYS created for every item. For non-measurable products, weights = 0.0 and only TrayQuantity is tracked.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using Mango.Nop.Core.Entities;
|
|||
|
||||
namespace FruitBank.Common.Entities;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[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)]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using System.Collections.ObjectModel;
|
|||
|
||||
namespace FruitBank.Common.Entities;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[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)]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ using System.ComponentModel.DataAnnotations.Schema;
|
|||
|
||||
namespace FruitBank.Common.Entities;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[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)]
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ using Table = LinqToDB.Mapping.TableAttribute;
|
|||
|
||||
namespace FruitBank.Common.Entities;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[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)]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ using System.Security.Cryptography.X509Certificates;
|
|||
|
||||
namespace FruitBank.Common.Entities;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[ToonDescription("Pallet measurements for shipping items", 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.")]
|
||||
[LinqToDB.Mapping.Table(Name = FruitBankConstClient.ShippingItemPalletDbTableName)]
|
||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingItemPalletDbTableName)]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace FruitBank.Common.Entities
|
|||
public bool IsInconsistent { get; set; }
|
||||
}
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[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")]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using Mango.Nop.Core.Entities;
|
|||
|
||||
namespace FruitBank.Common.Entities;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[ToonDescription("Inventory session record", Purpose = "Orchestrates inventory sessions by freezing logical stock states")]
|
||||
[Table(Name = FruitBankConstClient.StockTakingDbTableName)]
|
||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockTakingDbTableName)]
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ using Table = LinqToDB.Mapping.TableAttribute;
|
|||
|
||||
namespace FruitBank.Common.Entities;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[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)]
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public interface IStockTakingItemPallet : IMeasuringItemPalletBase
|
|||
public StockTakingItem? StockTakingItem{ get; set; }
|
||||
}
|
||||
|
||||
[AcBinarySerializable(false, true, false, true)]
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[ToonDescription("Weight record for inventory item", 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.")]
|
||||
[LinqToDB.Mapping.Table(Name = FruitBankConstClient.StockTakingItemPalletDbTableName)]
|
||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockTakingItemPalletDbTableName)]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace FruitBank.Common.Enums;
|
||||
|
||||
public enum PreorderItemStatus
|
||||
{
|
||||
Pending = 0,
|
||||
Fulfilled = 10,
|
||||
PartiallyFulfilled = 20,
|
||||
Dropped = 30
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
namespace FruitBank.Common.Enums;
|
||||
|
||||
public enum PreorderStatus
|
||||
{
|
||||
Pending = 0,
|
||||
Confirmed = 10,
|
||||
PartiallyFulfilled = 20,
|
||||
Cancelled = 30
|
||||
}
|
||||
|
|
@ -5,7 +5,3 @@ Core enumeration types for measurement and document classification.
|
|||
## Key Files
|
||||
|
||||
- **`MeasuringStatus.cs`** — NotStarted(0) → Started(10) → **Finnished**(20) → Audited(30). Note: "Finnished" is an intentional legacy typo — do NOT fix.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<EmitCompilerGeneratedFiles>false</EmitCompilerGeneratedFiles>
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ public static class FruitBankConstClient
|
|||
|
||||
public static string BaseUrl = "https://localhost:59579"; //FrutiBank nop
|
||||
//public static string BaseUrl = "https://localhost:44372"; //FrutiBank nop
|
||||
//public static string BaseUrl = "https://fruitbank.mangoweb.hu/"; //FrutiBank nop
|
||||
//public static string BaseUrl = "https://shop.fruitbank.hu"; //FrutiBank nop
|
||||
//public static string BaseUrl = "https://fruitbank.mangoweb.hu"; //FrutiBank nop test
|
||||
#if RELEASE
|
||||
//public static string BaseUrl = "https://shop.fruitbank.hu"; //FrutiBank nop
|
||||
#endif
|
||||
|
|
@ -44,6 +45,10 @@ public static class FruitBankConstClient
|
|||
public const string StockTakingItemDbTableName = "fbStockTakingItem";
|
||||
public const string StockTakingItemPalletDbTableName = "fbStockTakingItemPallet";
|
||||
|
||||
public const string CustomerCreditDbTableName = "fbCustomerCredit";
|
||||
public const string PreOrderDbTableName = "fbPreorder";
|
||||
public const string PreOrderItemDbTableName = "fbPreorderItem";
|
||||
|
||||
public const string DomainDescription = "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.";
|
||||
|
||||
//public static Guid[] DevAdminIds = new Guid[2] { Guid.Parse("dcf451d2-cc4c-4ac2-8c1f-da00041be1fd"), Guid.Parse("4cbaed43-2465-4d99-84f1-c8bc6b7025f7") };
|
||||
|
|
|
|||
|
|
@ -7,7 +7,3 @@ Measurement aggregation utilities.
|
|||
- **`MeasuringValuesHelper.cs`** — Static helper for rolling up pallet-level measurements to shipping item level.
|
||||
- `SetShippingItemTotalMeasuringValues()` — Sums quantities and weights from all pallets.
|
||||
- `GetTotalNetAndGrossWeightFromPallets()` — Returns (Quantity, NetWeight, GrossWeight) tuple.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,3 @@ SignalR endpoint contracts, measurement composition traits, and entity interface
|
|||
|
||||
- **`IMeasurementServiceBase<TLogger>`** — Base service marker
|
||||
- **`ISecureCredentialService`** — Save/retrieve/clear credentials with 2-day expiration
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,3 @@ SignalR client-to-server log writer.
|
|||
## Key Files
|
||||
|
||||
- **`SignaRClientLogItemWriter.cs`** — Routes client logs to `{BaseUrl}/loggerHub` via SignalR. Configurable by AppType and LogLevel.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,3 @@ Application and view models for UI state management.
|
|||
## Subfolders
|
||||
|
||||
- **`SignalRs/SignalRMessageToClientWithText<T>.cs`** — Generic message wrapper with optional text and typed content.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
# FruitBank.Common
|
||||
|
||||
@project {
|
||||
type = "product"
|
||||
own-dep-projects = [
|
||||
"AyCode.Core, AyCode.Entities, AyCode.Interfaces, AyCode.Models, AyCode.Services, AyCode.Utils (in AyCode.Core repo)",
|
||||
"Mango.Nop.Core (in Mango.Nop Libraries repo)"
|
||||
]
|
||||
}
|
||||
|
||||
Shared domain library for the FruitBank nopCommerce plugin. Contains entities, DTOs, interfaces, measurement helpers, SignalR tags, and constants for fruit & vegetable wholesale operations.
|
||||
|
||||
## Folder Structure
|
||||
|
|
@ -27,8 +35,4 @@ Shared domain library for the FruitBank nopCommerce plugin. Contains entities, D
|
|||
- **Shipping = INBOUND** (supplier → warehouse), **Order = OUTBOUND** (warehouse → customer)
|
||||
- **"Pallet" = measurement record**, always created even for non-measurable products
|
||||
- **NetWeight = GrossWeight − PalletWeight − (TrayQuantity × TareWeight)**
|
||||
- See [`docs/GLOSSARY.md`](../docs/GLOSSARY.md) for full terminology
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
- See `docs/GLOSSARY.md` for full terminology
|
||||
|
|
|
|||
|
|
@ -8,7 +8,3 @@ Business logic services and credential management.
|
|||
- **`ISecureCredentialService.cs`** — Interface: SaveCredentialsAsync (2-day expiry), GetCredentialsAsync, ClearCredentialsAsync. StoredCredentials sealed record.
|
||||
|
||||
Platform implementations: MAUI → SecureStorage, Web → obfuscated localStorage, Server → no-op.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,3 @@ SignalR method identifiers as numeric constants for type-safe client-server comm
|
|||
- **195-200:** Authentication
|
||||
- **500+:** Server→client notifications (SendOrderChanged, SendShippingChanged, etc.)
|
||||
- **1000+:** Diagnostic/Logging
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
# FruitBank.Common Domain Rules & Glossary
|
||||
|
||||
> This file acts as the single source of truth for the core Measurement System and Common Traps shared across all FruitBank applications (Hybrid App, Blazor, and nopCommerce server plugin).
|
||||
|
||||
## Measurement System
|
||||
|
||||
| Term | Definition |
|
||||
|---|---|
|
||||
| **IsMeasurable** | Product-level flag. If `false`: weights = 0.0, only `TrayQuantity` matters. A Pallet record is still created. |
|
||||
| **NetWeight** | `GrossWeight − PalletWeight − (TrayQuantity × TareWeight)` — universal formula across all three hierarchies. |
|
||||
| **TrayQuantity** | Always recorded, regardless of measurability. Count of trays/crates. |
|
||||
| **GrossWeight** | Total weight including pallet and packaging. 0.0 if not measurable. |
|
||||
| **PalletWeight** | Weight of the physical pallet. 0.0 if goods arrive without one. |
|
||||
| **TareWeight** | Weight of a single tray/crate. Used in NetWeight calculation. |
|
||||
| **AverageWeight** | Per-pallet average: `NetWeight / TrayQuantity`. Validated against threshold. |
|
||||
| **MeasuringStatus** | NotStarted(0) → Started(10) → **Finnished**(20) → Audited(30). Note: "Finnished" is intentional. |
|
||||
| **RevisorId** | Quality auditor's Customer ID. OrderItemPallet becomes "Audited" when RevisorId > 0. |
|
||||
|
||||
## Three Measurement Hierarchies
|
||||
|
||||
All share `MeasuringItemPalletBase` with the same NetWeight formula:
|
||||
|
||||
| Flow | Parent | Pallet Record | Extra |
|
||||
|--------------|-----------------|-------------------------|---------------------------------------|
|
||||
| **Inbound** | ShippingItem | ShippingItemPallet | Declared vs measured discrepancy |
|
||||
| **Outbound** | OrderItemDto | OrderItemPallet | RevisorId for audit |
|
||||
| **Inventory**| StockTakingItem | StockTakingItemPallet | QuantityDiff for stock adjustment |
|
||||
|
||||
## Common Traps
|
||||
|
||||
| Trap | Correct Behavior |
|
||||
|---|---|
|
||||
| "Pallet" = physical pallet | ❌ It's a measurement record. Always created. |
|
||||
| Shipping = outgoing | ❌ Shipping = INBOUND. Order = OUTBOUND. |
|
||||
| Fix "Finnished" spelling | ❌ Intentional legacy typo. Do NOT fix. |
|
||||
| IsMeasurable=false means no Pallet | ❌ Pallet is always created, weights just = 0.0 |
|
||||
| NetWeight is stored/settable | ❌ It is calculated. The setter throws an Exception! It only exists to satisfy the `IMeasuringItemPalletBase` interface boundary. Set `GrossWeight`, `PalletWeight`, `TareWeight` instead. |
|
||||
| Setting MeasuringStatus | ❌ It's a calculated property (evaluates `IsMeasured`, `Id`, or child pallets). Do not try to set it. |
|
||||
| Setting ForeignKey | ❌ `ForeignKey` is read-only. Use `SetForeignKey(id)` method instead. |
|
||||
| GenericAttribute is simple | ❌ It's polymorphic: KeyGroup determines which entity type owns the record |
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# FruitBank.Common documentation
|
||||
|
||||
Topic documentation for the `FruitBank.Common` project (shared types across Hybrid client).
|
||||
|
||||
## Reference docs (flat)
|
||||
|
||||
- [`GLOSSARY.md`](GLOSSARY.md) — Common domain terms for the Hybrid client side
|
||||
|
||||
## Navigation
|
||||
|
||||
Per the AI Agent Core Protocol (folder navigation rule), start from this README when browsing `docs/`. Currently only single-file reference.
|
||||
|
||||
## See also
|
||||
|
||||
- **Repo-level glossary**: `../../docs/GLOSSARY.md`
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# FruitBankHybrid.Shared.Common
|
||||
|
||||
@project {
|
||||
type = "product"
|
||||
}
|
||||
|
||||
Shared common library. Currently a placeholder — no source files yet. .NET 10.0 with AOT enabled.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -31,11 +31,7 @@ namespace FruitBankHybrid.Shared.Tests
|
|||
{
|
||||
if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
||||
|
||||
_signalRClient = new FruitBankSignalRClient(new List<IAcLogWriterClientBase>
|
||||
{
|
||||
//new ConsoleLogWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests)),
|
||||
new SignaRClientLogItemWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests))
|
||||
});
|
||||
_signalRClient = TestSignalRClientFactory.Create(nameof(FruitBankClientTests));
|
||||
}
|
||||
|
||||
#region Partner
|
||||
|
|
|
|||
|
|
@ -190,10 +190,7 @@ public sealed class JsonExtensionTests
|
|||
{
|
||||
if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
||||
|
||||
_signalRClient = new FruitBankSignalRClient(new List<IAcLogWriterClientBase>
|
||||
{
|
||||
new SignaRClientLogItemWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests))
|
||||
});
|
||||
_signalRClient = TestSignalRClientFactory.Create(nameof(FruitBankClientTests));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
|
|||
|
|
@ -28,11 +28,7 @@ public sealed class OrderClientTests
|
|||
{
|
||||
if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
||||
|
||||
_signalRClient = new FruitBankSignalRClient(new List<IAcLogWriterClientBase>
|
||||
{
|
||||
//new ConsoleLogWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests)),
|
||||
new SignaRClientLogItemWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests))
|
||||
});
|
||||
_signalRClient = TestSignalRClientFactory.Create(nameof(OrderClientTests));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
# FruitBankHybrid.Shared.Tests
|
||||
|
||||
@project {
|
||||
type = "test"
|
||||
own-dep-projects = [
|
||||
"AyCode.Entities, AyCode.Services, AyCode.Utils (in AyCode.Core repo)",
|
||||
"Mango.Nop.Core, Mango.Nop.Services (in Mango.Nop Libraries repo)"
|
||||
]
|
||||
}
|
||||
|
||||
MSTest integration and serialization tests. Covers SignalR client operations, JSON reference handling, binary serialization, Toon format, and bunit component rendering.
|
||||
|
||||
## Folder Structure
|
||||
|
|
@ -19,7 +27,3 @@ MSTest integration and serialization tests. Covers SignalR client operations, JS
|
|||
- **`SandboxEndpointSimpleTests.cs`** — Endpoint connectivity and SignalR negotiate tests.
|
||||
- **`GridPartnerBaseTests.cs`** — Grid component tests (disabled).
|
||||
- **`GridPartnerRazorTests.cs`** — bunit Blazor rendering tests (disabled).
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ namespace FruitBankHybrid.Shared.Tests;
|
|||
|
||||
/// <summary>
|
||||
/// Teszt a TestSignalREndpoint-hoz.
|
||||
/// FONTOS: A SANDBOX-ot manuálisan kell elindítani a tesztek futtatása előtt!
|
||||
/// Indítás: dotnet run --project Mango.Sandbox.EndPoints --urls http://localhost:59579
|
||||
/// FONTOS: A SANDBOX-ot manu<EFBFBD>lisan kell elind<6E>tani a tesztek futtat<61>sa el<65>tt!
|
||||
/// Ind<EFBFBD>t<EFBFBD>s: dotnet run --project Mango.Sandbox.EndPoints --urls http://localhost:59579
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class SandboxEndpointSimpleTests
|
||||
|
|
@ -24,7 +24,7 @@ public class SandboxEndpointSimpleTests
|
|||
private static readonly string SandboxUrl = FruitBankConstClient.BaseUrl; //"http://localhost:59579";
|
||||
private static readonly string HubUrl = $"{SandboxUrl}/fbHub";
|
||||
|
||||
// Teszt SignalR Tags (TestSignalRTags-ből)
|
||||
// Teszt SignalR Tags (TestSignalRTags-b<EFBFBD>l)
|
||||
private const int PingTag = SignalRTags.PingTag;
|
||||
private const int EchoTag = SignalRTags.EchoTag;
|
||||
private const int GetTestItemsTag = 9003;
|
||||
|
|
@ -34,13 +34,9 @@ public class SandboxEndpointSimpleTests
|
|||
[TestInitialize]
|
||||
public void TestInit()
|
||||
{
|
||||
if (!SandboxUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
||||
if (!SandboxUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTEL<EFBFBD>NK!");
|
||||
|
||||
_signalRClient = new FruitBankSignalRClient(new List<IAcLogWriterClientBase>
|
||||
{
|
||||
//new ConsoleLogWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests)),
|
||||
new SignaRClientLogItemWriter(AppType.TestUnit, LogLevel.Detail, nameof(SandboxEndpointSimpleTests))
|
||||
});
|
||||
_signalRClient = TestSignalRClientFactory.Create(nameof(SandboxEndpointSimpleTests));
|
||||
}
|
||||
|
||||
#region HTTP Endpoint Tests
|
||||
|
|
@ -121,7 +117,7 @@ public class SandboxEndpointSimpleTests
|
|||
// using var jsonDoc = JsonDocument.Parse(response);
|
||||
// var root = jsonDoc.RootElement;
|
||||
|
||||
// // Ellenőrizzük, hogy van Message property
|
||||
// // Ellen<EFBFBD>rizz<EFBFBD>k, hogy van Message property
|
||||
// Assert.IsTrue(root.TryGetProperty("Message", out var messageElement) ||
|
||||
// root.TryGetProperty("message", out messageElement),
|
||||
// "Response should contain 'Message' property");
|
||||
|
|
@ -141,13 +137,13 @@ public class SandboxEndpointSimpleTests
|
|||
// using var jsonDoc = JsonDocument.Parse(response);
|
||||
// var root = jsonDoc.RootElement;
|
||||
|
||||
// // Ellenőrizzük az Id-t
|
||||
// // Ellen<EFBFBD>rizz<EFBFBD>k az Id-t
|
||||
// Assert.IsTrue(root.TryGetProperty("Id", out var idElement) ||
|
||||
// root.TryGetProperty("id", out idElement),
|
||||
// "Response should contain 'Id' property");
|
||||
// Assert.AreEqual(42, idElement.GetInt32(), "Id should be 42");
|
||||
|
||||
// // Ellenőrizzük a Name-et
|
||||
// // Ellen<EFBFBD>rizz<EFBFBD>k a Name-et
|
||||
// Assert.IsTrue(root.TryGetProperty("Name", out var nameElement) ||
|
||||
// root.TryGetProperty("name", out nameElement),
|
||||
// "Response should contain 'Name' property");
|
||||
|
|
@ -167,13 +163,13 @@ public class SandboxEndpointSimpleTests
|
|||
// using var jsonDoc = JsonDocument.Parse(response);
|
||||
// var root = jsonDoc.RootElement;
|
||||
|
||||
// // Ellenőrizzük, hogy tömb-e
|
||||
// // Ellen<EFBFBD>rizz<EFBFBD>k, hogy t<>mb-e
|
||||
// Assert.AreEqual(JsonValueKind.Array, root.ValueKind, "Response should be an array");
|
||||
// Assert.IsTrue(root.GetArrayLength() > 0, "Array should have items");
|
||||
|
||||
// Console.WriteLine($"[GetTestItems] Received {root.GetArrayLength()} items");
|
||||
|
||||
// // Ellenőrizzük az első elemet
|
||||
// // Ellen<EFBFBD>rizz<EFBFBD>k az els<6C> elemet
|
||||
// var firstItem = root[0];
|
||||
// Assert.IsTrue(firstItem.TryGetProperty("Id", out _) || firstItem.TryGetProperty("id", out _),
|
||||
// "Item should have 'Id' property");
|
||||
|
|
@ -187,8 +183,8 @@ public class SandboxEndpointSimpleTests
|
|||
//#region EREDETI BUSINESS ENDPOINT TESZTEK - KIKOMMENTEZVE
|
||||
|
||||
//// ===========================================
|
||||
//// === Az alábbi tesztek az eredeti 3 endpoint-ot tesztelik ===
|
||||
//// === Visszaállításhoz: töröld a kommenteket és regisztráld az endpoint-okat a Program.cs-ben ===
|
||||
//// === Az al<EFBFBD>bbi tesztek az eredeti 3 endpoint-ot tesztelik ===
|
||||
//// === Vissza<EFBFBD>ll<EFBFBD>t<EFBFBD>shoz: t<>r<EFBFBD>ld a kommenteket <20>s regisztr<74>ld az endpoint-okat a Program.cs-ben ===
|
||||
//// ===========================================
|
||||
|
||||
//// [TestMethod]
|
||||
|
|
@ -260,13 +256,13 @@ public class SandboxEndpointSimpleTests
|
|||
// await connection.StartAsync();
|
||||
// Assert.AreEqual(HubConnectionState.Connected, connection.State, $"Failed to connect to SignalR hub for {endpointName}");
|
||||
|
||||
// // Készítsük el a request data-t
|
||||
// // Ha nincs paraméter, null-t küldünk (nem üres byte tömböt!)
|
||||
// // K<EFBFBD>sz<EFBFBD>ts<EFBFBD>k el a request data-t
|
||||
// // Ha nincs param<EFBFBD>ter, null-t k<>ld<6C>nk (nem <20>res byte t<>mb<6D>t!)
|
||||
// byte[]? requestData = parameter != null
|
||||
// ? Encoding.UTF8.GetBytes(JsonSerializer.Serialize(parameter))
|
||||
// : null;
|
||||
|
||||
// // A Hub metódus neve: OnReceiveMessage (3 paraméter: messageTag, messageBytes, requestId)
|
||||
// // A Hub met<EFBFBD>dus neve: OnReceiveMessage (3 param<61>ter: messageTag, messageBytes, requestId)
|
||||
// await connection.InvokeAsync("OnReceiveMessage", tag, requestData, (int?)null);
|
||||
|
||||
// var completed = await Task.WhenAny(responseReceived.Task, Task.Delay(15000));
|
||||
|
|
@ -276,7 +272,7 @@ public class SandboxEndpointSimpleTests
|
|||
// Console.WriteLine($"[{endpointName}] Response tag: {receivedTag}");
|
||||
// Console.WriteLine($"[{endpointName}] Response JSON: {receivedJson?.Substring(0, Math.Min(500, receivedJson?.Length ?? 0))}...");
|
||||
|
||||
// // Ellenőrizzük, hogy valid JSON-e (ha van adat)
|
||||
// // Ellen<EFBFBD>rizz<EFBFBD>k, hogy valid JSON-e (ha van adat)
|
||||
// if (!string.IsNullOrEmpty(receivedJson))
|
||||
// {
|
||||
// try
|
||||
|
|
|
|||
|
|
@ -5,7 +5,3 @@ Demo models for Toon serialization testing.
|
|||
## Key Files
|
||||
|
||||
- **`ToonTestData.cs`** — TestOrder, TestCustomer, TestOrderItem, TestProduct models with one-to-many relationships and back-references for testing complex object graph serialization.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
using AyCode.Core.Enums;
|
||||
using AyCode.Core.Loggers;
|
||||
using AyCode.Core.Serializers.Binaries;
|
||||
using AyCode.Services.SignalRs;
|
||||
using FruitBank.Common;
|
||||
using FruitBank.Common.Loggers;
|
||||
using FruitBankHybrid.Shared.Services.Loggers;
|
||||
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||
using Microsoft.AspNetCore.Http.Connections;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
|
||||
namespace FruitBankHybrid.Shared.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Test-only factory for <see cref="FruitBankSignalRClient"/>. Builds a <c>HubConnectionBuilder</c>
|
||||
/// with the same connection settings a production <c>Program.cs</c> would use, wires a logger factory
|
||||
/// backed by a single <c>SignaRClientLogItemWriter</c> (test-unit AppType, Detail level),
|
||||
/// and uses <see cref="BinaryProtocolMode.AsyncSegment"/> for the protocol.
|
||||
/// </summary>
|
||||
internal static class TestSignalRClientFactory
|
||||
{
|
||||
public static FruitBankSignalRClient Create(string testCategoryName)
|
||||
{
|
||||
var logWriters = new List<IAcLogWriterClientBase>
|
||||
{
|
||||
new SignaRClientLogItemWriter(AppType.TestUnit, LogLevel.Detail, testCategoryName)
|
||||
};
|
||||
|
||||
Func<string, LoggerClient> loggerFactory =
|
||||
categoryName => new LoggerClient(categoryName, logWriters.ToArray());
|
||||
|
||||
var connectionOptions = new AcHubConnectionOptions
|
||||
{
|
||||
Url = $"{FruitBankConstClient.BaseUrl}/{FruitBankConstClient.DefaultHubName}",
|
||||
TransportMaxBufferSize = 30_000_000,
|
||||
ApplicationMaxBufferSize = 30_000_000,
|
||||
CloseTimeout = TimeSpan.FromSeconds(10),
|
||||
KeepAliveInterval = TimeSpan.FromSeconds(60),
|
||||
ServerTimeout = TimeSpan.FromSeconds(180),
|
||||
SkipNegotiation = true,
|
||||
Transports = HttpTransportType.WebSockets,
|
||||
UseAutomaticReconnect = true,
|
||||
UseStatefulReconnect = true
|
||||
};
|
||||
|
||||
var logger = loggerFactory(nameof(FruitBankSignalRClient));
|
||||
|
||||
var hubBuilder = new HubConnectionBuilder().AddAcDefaults(logger, connectionOptions);
|
||||
hubBuilder.AddAcBinaryProtocol(opts => opts.ProtocolMode = BinaryProtocolMode.AsyncSegment);
|
||||
|
||||
return new FruitBankSignalRClient(hubBuilder, loggerFactory);
|
||||
}
|
||||
}
|
||||
|
|
@ -24,26 +24,26 @@ namespace FruitBankHybrid.Shared.Tests;
|
|||
|
||||
//1. "Headered List" (A biztonságos táblázatosítás)
|
||||
|
||||
//Az LLM-eknek nem kell minden sorban megismételni a mezőneveket, ha a lista elején egyszer definiálod a sorrendet. Ez nem találgatás, hanem egy lokális "szerződés".
|
||||
//Az LLM-eknek nem kell minden sorban megismételni a mezőneveket, ha a lista elején egyszer definiálod a sorrendet.Ez nem találgatás, hanem egy lokális "szerződés".
|
||||
|
||||
//Hagyományos (pazarló):
|
||||
//Kódrészlet
|
||||
|
||||
//OrderItemDtos = [
|
||||
// OrderItemDto { Id = 120, Quantity = 10, ProductName = "Áfonya" }
|
||||
// OrderItemDto { Id = 121, Quantity = 5, ProductName = "Narancs" }
|
||||
//OrderItemDto { Id = 121, Quantity = 5, ProductName = "Narancs" }
|
||||
//]
|
||||
|
||||
//Optimalizált (pontos és tömör):
|
||||
//Optimalizált(pontos és tömör) :
|
||||
//Kódrészlet
|
||||
|
||||
//OrderItemDtos: OrderItemDto[] = [
|
||||
// [ Id, Quantity, ProductName ]
|
||||
// [ 120, 10, "Áfonya" ]
|
||||
// [ 121, 5, "Narancs" ]
|
||||
// [Id, Quantity, ProductName]
|
||||
// [120, 10, "Áfonya"]
|
||||
// [121, 5, "Narancs"]
|
||||
//]
|
||||
|
||||
// Miért jó ez? Az LLM a fejléc alapján (mint egy CSV-nél) rendeli hozzá az értékeket a típushoz. Mivel a típus (OrderItemDto) ott van a definícióban, a szemantikai kapcsolat nem vész el.
|
||||
// Miért jó ez? Az LLM a fejléc alapján(mint egy CSV-nél) rendeli hozzá az értékeket a típushoz.Mivel a típus (OrderItemDto) ott van a definícióban, a szemantikai kapcsolat nem vész el.
|
||||
|
||||
//2. Típus-öröklődés a listákban
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ namespace FruitBankHybrid.Shared.Tests;
|
|||
|
||||
//// A 'ProductDto' elhagyható az objektum elől, mert a sémából tudja
|
||||
//ProductDto = {
|
||||
// Id = 1
|
||||
// Id = 1
|
||||
// Name = "Áfonya..."
|
||||
// GenericAttributes = [
|
||||
// { Id = 99, Key = "NetWeight", Value = "178.3" }
|
||||
|
|
@ -62,13 +62,13 @@ namespace FruitBankHybrid.Shared.Tests;
|
|||
// ]
|
||||
//}
|
||||
|
||||
//3. Alapértelmezett értékek elhagyása (Implicit Defaults)
|
||||
//3. Alapértelmezett értékek elhagyása(Implicit Defaults)
|
||||
|
||||
//Ha egy mező értéke megegyezik a @types-ban definiált default-value-val, vagy null/0/false, akkor azt teljesen hagyd ki a @data részből.
|
||||
//Ha egy mező értéke megegyezik a @types - ban definiált default-value-val, vagy null/0/false, akkor azt teljesen hagyd ki a @data részből.
|
||||
|
||||
// Szabály: Ami nincs ott, az az alapértelmezett.
|
||||
|
||||
// Token megtakarítás: A FruitBank példádban a GenericAttributes = <GenericAttributeDto[]> (count: 0) [] sorok rengeteg helyet foglalnak. Ha üres, egyszerűen ne küldd el a mezőt.
|
||||
// Token megtakarítás: A FruitBank példádban a GenericAttributes = < GenericAttributeDto[] > (count: 0)[] sorok rengeteg helyet foglalnak. Ha üres, egyszerűen ne küldd el a mezőt.
|
||||
|
||||
//4. String Table helyett: "Object Anchoring"
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ namespace FruitBankHybrid.Shared.Tests;
|
|||
|
||||
// Első alkalommal: ProductDto { ... }
|
||||
|
||||
// Minden további alkalommal: ProductDto = @ProductDto:1
|
||||
//Minden további alkalommal: ProductDto = @ProductDto:1
|
||||
|
||||
//[ToonIgnore][ToonDataIgnore]
|
||||
[ToonDescription(Purpose = "Container model for Shipping, Order")]
|
||||
|
|
@ -101,11 +101,7 @@ public sealed class ToonTests
|
|||
{
|
||||
if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
||||
|
||||
_signalRClient = new FruitBankSignalRClient(new List<IAcLogWriterClientBase>
|
||||
{
|
||||
//new ConsoleLogWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests)),
|
||||
new SignaRClientLogItemWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests))
|
||||
});
|
||||
_signalRClient = TestSignalRClientFactory.Create(nameof(FruitBankClientTests));
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
|
@ -134,8 +130,8 @@ public sealed class ToonTests
|
|||
a.Orders = (await _signalRClient.GetAllOrderDtos())!.Where(x=>x.CreatedOnUtc > DateTime.UtcNow.AddDays(-70)).ToList();
|
||||
a.Shippings = (await _signalRClient.GetShippings())!.Where(x=>x.Created > DateTime.UtcNow.AddDays(-70)).ToList();
|
||||
|
||||
//var toon = AcToonSerializer.Serialize(a, FruitBankConstClient.DomainDescription, AcToonSerializerOptions.Default);
|
||||
var toon = AcToonSerializer.SerializeTypeMetadata<FullProcessModel>(FruitBankConstClient.DomainDescription);
|
||||
var toon = AcToonSerializer.Serialize(a, FruitBankConstClient.DomainDescription, AcToonSerializerOptions.Default);
|
||||
//var toon = AcToonSerializer.SerializeTypeMetadata<FullProcessModel>(FruitBankConstClient.DomainDescription);
|
||||
|
||||
Console.WriteLine(toon);
|
||||
Assert.IsNotEmpty(toon);
|
||||
|
|
|
|||
|
|
@ -46,19 +46,19 @@
|
|||
<DxTabPage Text="Rendelés tételek">
|
||||
<GridDetailOrderItemDto OrderItemDtos="orderDto.OrderItemDtos" IsMasterGrid="false" />
|
||||
</DxTabPage>
|
||||
<DxTabPage Text="Mérések" Visible="@LoggedInModel.IsDeveloper">
|
||||
<DxTabPage Text="Mérések" Visible="@LoggedInModel.IsAdministrator">
|
||||
@{
|
||||
var orderItemPalletDtos = orderDto?.OrderItemDtos.SelectMany(oi => oi.OrderItemPallets).ToList() ?? [];
|
||||
<GridDetailOrderItemPallets OrderItemPallets="orderItemPalletDtos" IsMasterGrid="false"/>
|
||||
}
|
||||
</DxTabPage>
|
||||
<DxTabPage Text="Rendelés jegyzetek" Visible="@LoggedInModel.IsDeveloper">
|
||||
<DxTabPage Text="Rendelés jegyzetek" Visible="@LoggedInModel.IsAdministrator">
|
||||
@{
|
||||
// var orderItemPalletDtos = orderDto?.OrderItemDtos.SelectMany(oi => oi.OrderItemPallets).ToList() ?? [];
|
||||
// <GridDetailOrderItemPallets OrderItemPallets="orderItemPalletDtos" IsMasterGrid="false" />
|
||||
}
|
||||
</DxTabPage>
|
||||
<DxTabPage Text="Speciális jellemzők" Visible="@LoggedInModel.IsDeveloper">
|
||||
<DxTabPage Text="Speciális jellemzők" Visible="@LoggedInModel.IsAdministrator">
|
||||
@{
|
||||
var genericAttributeDtos = new AcObservableCollection<GenericAttributeDto>(orderDto.GenericAttributes);
|
||||
<GridGenericAttribute ParentDataItem="@orderDto" GenericAttributes="@genericAttributeDtos" />
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)"/>
|
||||
}
|
||||
</ToolbarTemplate>
|
||||
<GroupSummary>
|
||||
@* <GroupSummary>
|
||||
<DxGridSummaryItem SummaryType="GridSummaryItemType.Sum"
|
||||
FieldName="Quantity"
|
||||
FooterColumnName="Quantity" />
|
||||
|
|
@ -84,7 +84,23 @@
|
|||
<DxGridSummaryItem SummaryType="GridSummaryItemType.Sum"
|
||||
FieldName="PriceInclTax"
|
||||
FooterColumnName="PriceInclTax" />
|
||||
</GroupSummary> *@
|
||||
<GroupSummary>
|
||||
<DxGridSummaryItem SummaryType="GridSummaryItemType.Count"
|
||||
FieldName="Id"
|
||||
ValueDisplayFormat="'{0} rendelés'" />
|
||||
<DxGridSummaryItem SummaryType="GridSummaryItemType.Count"
|
||||
FieldName="Id"
|
||||
FooterColumnName="Id" />
|
||||
<DxGridSummaryItem SummaryType="GridSummaryItemType.Sum"
|
||||
FieldName="OrderTotal"
|
||||
FooterColumnName="OrderTotal" />
|
||||
</GroupSummary>
|
||||
<TotalSummary>
|
||||
<DxGridSummaryItem SummaryType="GridSummaryItemType.Count"
|
||||
FieldName="Id"
|
||||
FooterColumnName="Id" />
|
||||
</TotalSummary>
|
||||
</MgGridBase>
|
||||
|
||||
@code {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@
|
|||
<DxListEditorColumn FieldName="@nameof(Shipping.ShippingDate)" />
|
||||
<DxListEditorColumn FieldName="@nameof(Shipping.LicencePlate)" />
|
||||
<DxListEditorColumn FieldName="@nameof(Shipping.Comment)" />
|
||||
<DxListEditorColumn FieldName="@nameof(Shipping.CargoCompany)" />
|
||||
</Columns>
|
||||
</DxComboBoxSettings>
|
||||
</EditSettings>
|
||||
|
|
|
|||
|
|
@ -239,8 +239,10 @@
|
|||
var quantityOnDocument = (int)e.Grid.GetRowValue(e.VisibleIndex, nameof(ShippingItem.QuantityOnDocument));
|
||||
var measuredQuantity = (int)e.Grid.GetRowValue(e.VisibleIndex, nameof(ShippingItem.MeasuredQuantity));
|
||||
|
||||
if (quantityOnDocument > 0 && quantityOnDocument > measuredQuantity) e.CssClass = "text-danger";
|
||||
//else if (quantityOnDocument <= measuredQuantity) e.CssClass = "text-success";
|
||||
if (quantityOnDocument > 0 && quantityOnDocument > measuredQuantity)
|
||||
{ e.CssClass = "text-danger"; }
|
||||
else if (quantityOnDocument < measuredQuantity)
|
||||
{ e.CssClass = "text-success"; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,52 @@
|
|||
# Grids
|
||||
|
||||
Domain-specific DevExpress grid components, one per entity type. All extend MgGridBase for layout persistence.
|
||||
Domain-specific grid components, one per entity type. All inherit `FruitBankGridBase<TEntity>`.
|
||||
|
||||
> For the MgGrid framework reference see: `AyCode.Blazor/AyCode.Blazor.Components/docs/MGGRID/README.md`
|
||||
|
||||
## FruitBankGridBase
|
||||
|
||||
`FruitBankGridBase<TDataItem>` is the project-specific adapter that fixes the generic parameters:
|
||||
|
||||
```
|
||||
MgGridBase<SignalRDataSourceObservable<TDataItem>, TDataItem, int, LoggerClient>
|
||||
```
|
||||
|
||||
Adds these defaults in `OnParametersSet` (based on `IsMasterGrid`):
|
||||
|
||||
| Setting | Master | Detail |
|
||||
|---|---|---|
|
||||
| `SizeMode` | `Small` | `Small` |
|
||||
| `ShowGroupPanel` | `true` | `false` |
|
||||
| `ShowSearchBox` | `true` | `false` |
|
||||
| `ShowFilterRow` | `true` | `false` |
|
||||
| `FilterMenuButtonDisplayMode` | `Never` | `Always` |
|
||||
| `DetailRowDisplayMode` | `Auto` | `Never` |
|
||||
| `DetailExpandButtonDisplayMode` | `Auto` | `Never` |
|
||||
| `PagerVisible` | `true` | `true` |
|
||||
| `PageSize` | 20 (Small) / 15 | 10 |
|
||||
| `AllowColumnReorder` | `true` | `true` |
|
||||
| `AllowGroup` | `true` | `false` |
|
||||
| `EditMode` | `EditRow` | `EditRow` |
|
||||
| `FocusedRowEnabled` | `true` | `true` |
|
||||
| `ColumnResizeMode` | `NextColumn` | `NextColumn` |
|
||||
| `PageSizeSelectorVisible` | `true` | `true` |
|
||||
|
||||
Also adds `OnCustomizeElement`: alternating row colors (`.alt-item`), header background (`#E6E6E6`), `hideDetailButton` for non-admin users.
|
||||
|
||||
## Legacy MgGridBase
|
||||
|
||||
`Components/MgGridBase.cs` — a non-generic legacy class that directly extends `DxGrid` and implements `IMgGridBase`. Used by older pages that predate the generic `MgGridBase<…>`. New grids should use `FruitBankGridBase<TEntity>` instead.
|
||||
|
||||
## Subfolders
|
||||
|
||||
| Folder | Entity |
|
||||
|---|---|
|
||||
| `GenericAttributes/` | GridGenericAttributeBase — context-based attribute grids |
|
||||
| `OrderItems/` | GridOrderItem — order item grid (partial) |
|
||||
| `Partners/` | GridPartnerBase — partner grid with SignalR |
|
||||
| `Products/` | GridStockQuantityHistoryDtoBase — stock history visualization |
|
||||
| `ShippingDocuments/` | GridShippingDocumentBase — shipping document management |
|
||||
| `ShippingItems/` | GridShippingItemBase — shipping item detail with context awareness |
|
||||
| `Shippings/` | GridShippingBase — master shipping grid |
|
||||
| `StockTakingItems/` | GridStockTakingItemBase — stock taking item grid |
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
| Folder | Entity | Notes |
|
||||
|---|---|---|
|
||||
| `GenericAttributes/` | `GridGenericAttributeBase` | Context-based (ContextIds: EntityId, KeyGroup, StoreId). Parent type switching: Product, Order, OrderItem |
|
||||
| `OrderItems/` | `GridOrderItem` | Commented out — placeholder |
|
||||
| `Partners/` | `GridPartnerBase` | Simple master grid with CRUD tags |
|
||||
| `Products/` | `GridStockQuantityHistoryDtoBase` | Detail grid under ProductDto |
|
||||
| `ShippingDocuments/` | `GridShippingDocumentBase` | Parent type switching: Shipping, Product, Partner. Sets ContextIds/KeyFieldNameToParentId per parent type |
|
||||
| `ShippingItems/` | `GridShippingItemBase` | Parent type switching: ShippingDocument, Shipping, Partner |
|
||||
| `Shippings/` | `GridShippingBase` | Simple master grid with CRUD tags |
|
||||
| `StockTakingItems/` | `GridStockTakingItemBase` | Simple master grid, GetAll only |
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ DevExpress Blazor grid wrappers, pallet measurement components, and toast notifi
|
|||
|
||||
## Key Files (Root)
|
||||
|
||||
- **`MgGridBase.cs`** — Base class for all DevExpress grids with layout persistence via localStorage.
|
||||
- **`MgGridBase.cs`** — Legacy non-generic grid base (directly extends `DxGrid`). Used by older pages. New grids should use `FruitBankGridBase<TEntity>` — see [`Grids/README.md`](Grids/README.md).
|
||||
- **`GridProductDto.cs`** — Product data grid component.
|
||||
- **`OrderNotificationToast.razor`** — Toast notification for order updates.
|
||||
- **Pallet components** — PalletItemComponent.razor, GridShippingItemPallets.razor, GridDetailOrderItemPallets.razor.
|
||||
|
|
@ -16,7 +16,3 @@ DevExpress Blazor grid wrappers, pallet measurement components, and toast notifi
|
|||
| [`Grids/`](Grids/README.md) | Domain-specific grid components by entity type |
|
||||
| [`FileUploads/`](FileUploads/README.md) | File upload components |
|
||||
| [`StockTakings/`](StockTakings/README.md) | Stock taking UI components |
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,3 @@ Client-side in-memory table cache using ConcurrentDictionary for offline/fast da
|
|||
## Key Files
|
||||
|
||||
- **`DatabaseClient.cs`** — (~250 lines) Local client-side database with typed tables (Shipping, ShippingDocument, ShippingItem, etc.). ProductDtoTable and OrderDtoTable with semaphore-based async loading. DatabaseTableBase<T> generic base. ObjectLock for thread-safe type-based locking. LoadingPanelVisibility global flag.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,3 @@ DevExpress dialog helper extensions.
|
|||
## Key Files
|
||||
|
||||
- **`DevexpressComponentExtensions.cs`** — ShowMessageBoxAsync() and ShowConfirmBoxAsync() for DevExpress dialogs.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
|
|
@ -72,4 +72,21 @@
|
|||
<ItemGroup>
|
||||
<Folder Include="Components\Toolbars\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="**\README.md" Exclude="$(DefaultItemExcludes)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- appsettings.json is the canonical config source for Web / Web.Client / MAUI hosts.
|
||||
They each pull it directly from disk (Web/Web.Client via <Target Copy>, MAUI via <EmbeddedResource>).
|
||||
Suppress the Razor SDK's auto-publish content behavior so the file does NOT flow into
|
||||
dependent projects' publish output — that would collide with each host's own copy
|
||||
(NETSDK1152 "multiple publish output files with the same relative path"). -->
|
||||
<Content Update="appsettings.json">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
<Pack>false</Pack>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -7,7 +7,3 @@ Application shell: root layout, navigation menu, auto-login, and toast notificat
|
|||
- **`MainLayout.razor`** — Root layout with navigation menu.
|
||||
- **`MainLayout.razor.cs`** — SignalR message handling, auto-login on first render, toast notification for orders, login/logout handling, navigation guards.
|
||||
- **`NavMenu.razor`** — Navigation component.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,3 @@ View models for measuring pages.
|
|||
## Key Files
|
||||
|
||||
- **`MeasuringDateSelectorModel.cs`** — Date picker model: ShippingId, DateTime, IsMeasured flag.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,3 @@ Routed Blazor pages for the FruitBank application.
|
|||
- **`MeasuringIn.razor.cs`** — Shipping measurement: calendar date picker, item detail, pallet recording.
|
||||
- **`MeasuringOut.razor.cs`** — Order measurement/audit: measurement tracking, approval workflow, RevisorId assignment.
|
||||
- **`StockTaking.razor.cs`** — Inventory management: stock taking sessions, item reconciliation.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,14 @@
|
|||
# FruitBankHybrid.Shared
|
||||
|
||||
@project {
|
||||
type = "product"
|
||||
own-dep-projects = [
|
||||
"AyCode.Core, AyCode.Entities, AyCode.Interfaces, AyCode.Models, AyCode.Services, AyCode.Services.Server, AyCode.Utils (in AyCode.Core repo)",
|
||||
"AyCode.Blazor.Components (in AyCode.Blazor repo)",
|
||||
"Mango.Nop.Core (in Mango.Nop Libraries repo)"
|
||||
]
|
||||
}
|
||||
|
||||
Main Blazor UI library shared across all three deployment targets (Server, WASM, MAUI). Contains pages, DevExpress grid components, SignalR client, measurement service, and layout.
|
||||
|
||||
## Folder Structure
|
||||
|
|
@ -18,11 +27,8 @@ Main Blazor UI library shared across all three deployment targets (Server, WASM,
|
|||
|
||||
- **`_Imports.razor`** — Global Blazor imports.
|
||||
- **`Routes.razor`** — Route definitions.
|
||||
- **`appsettings.json`** — Canonical configuration source for all three hosts (Web, Web.Client, MAUI). Edit ONLY here. Pull mechanism per host: see `docs/ARCHITECTURE.md` (in repo root) → "Shared Configuration".
|
||||
|
||||
## Target Framework
|
||||
|
||||
.NET 10.0 with AOT compilation and WASM IL stripping enabled.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,3 @@ Custom logger implementations for the FruitBank client.
|
|||
## Key Files
|
||||
|
||||
- **`LoggerClient.cs`** — Non-generic and generic `LoggerClient<T>` extending AyCode logger base.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,3 @@ Business logic, SignalR client, measurement helpers, and platform abstractions.
|
|||
|---|---|
|
||||
| [`Loggers/`](Loggers/README.md) | LoggerClient and LoggerClient<T> extending AyCode logger |
|
||||
| [`SignalRs/`](SignalRs/README.md) | FruitBankSignalRClient hub client + DataSource wrappers |
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -19,20 +19,16 @@ using Microsoft.AspNetCore.SignalR.Client;
|
|||
using Nop.Core.Domain.Customers;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ServiceModel.Channels;
|
||||
using AyCode.Core.Serializers;
|
||||
using Mango.Nop.Core.Entities;
|
||||
|
||||
namespace FruitBankHybrid.Shared.Services.SignalRs
|
||||
{
|
||||
public class FruitBankSignalRClient : AcSignalRClientBase, IFruitBankDataControllerClient, ICustomOrderSignalREndpointClient, IStockSignalREndpointClient
|
||||
{
|
||||
public FruitBankSignalRClient( /*IServiceProvider serviceProvider, */ IEnumerable<IAcLogWriterClientBase> logWriters) : base($"{FruitBankConstClient.BaseUrl}/{FruitBankConstClient.DefaultHubName}", new LoggerClient(nameof(FruitBankSignalRClient), logWriters.ToArray()))
|
||||
public FruitBankSignalRClient(IHubConnectionBuilder hubBuilder, Func<string, LoggerClient> loggerFactory)
|
||||
: base(hubBuilder, loggerFactory(nameof(FruitBankSignalRClient)))
|
||||
{
|
||||
//var hubConnection = new HubConnectionBuilder()
|
||||
// .WithUrl("fullHubName")
|
||||
// .WithAutomaticReconnect()
|
||||
// .WithStatefulReconnect()
|
||||
// .WithKeepAliveInterval(TimeSpan.FromSeconds(60))
|
||||
// .WithServerTimeout(TimeSpan.FromSeconds(120))
|
||||
EnableBinaryDiagnostics = FruitBankConstClient.SignalRSerializerDiagnosticLog;
|
||||
ConstHelper.NameByValue<SignalRTags>(0);
|
||||
}
|
||||
|
|
@ -42,10 +38,14 @@ namespace FruitBankHybrid.Shared.Services.SignalRs
|
|||
/// </summary>
|
||||
public event Func<int, SignalResponseDataMessage?, Task> OnMessageReceived = null!;
|
||||
|
||||
protected override async Task MessageReceived(int messageTag, byte[] messageBytes)
|
||||
protected override async Task MessageReceived(int messageTag, SignalParams signalParams, object data)
|
||||
{
|
||||
var responseDataMessage = messageBytes.BinaryTo<SignalResponseDataMessage>();
|
||||
|
||||
var responseDataMessage = new SignalResponseDataMessage
|
||||
{
|
||||
Status = signalParams.Status,
|
||||
DataSerializerType = AcSerializerType.Binary,
|
||||
RawResponseData = data
|
||||
};
|
||||
await OnMessageReceived(messageTag, responseDataMessage);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,3 @@ Main SignalR hub client and data source wrappers.
|
|||
|
||||
- **`FruitBankSignalRClient.cs`** — (~343 lines) Central hub client for ALL server communication. Methods for Partners, Shippings, ShippingItems, ShippingDocuments, Orders, OrderItems, OrderItemPallets, Products, StockTaking, GenericAttributes, Authentication.
|
||||
- **`SignalRDataSource.cs`** — `SignalRDataSourceList<T>` and `SignalRDataSourceObservable<T>` wrappers for DevExpress grid binding.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AyCode": {
|
||||
"ProjectId": "aad53443-2ee2-4650-8a99-97e907265e4e",
|
||||
"Urls": {
|
||||
"BaseUrl": "https://localhost:59579",
|
||||
"ApiBaseUrl": "https://localhost:59579"
|
||||
},
|
||||
"Logger": {
|
||||
"AppType": "Server",
|
||||
"LogLevel": "Detail",
|
||||
"LogWriters": [
|
||||
{
|
||||
"LogLevel": "Detail",
|
||||
"LogWriterType": "FruitBank.Common.Server.Services.Loggers.ConsoleLogWriter, FruitBank.Common.Server, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
},
|
||||
"AcHubConnection": {
|
||||
"Url": "https://localhost:59579/fbHub",
|
||||
"TransportMaxBufferSize": 30000000,
|
||||
"ApplicationMaxBufferSize": 30000000,
|
||||
"CloseTimeout": "00:00:10",
|
||||
"KeepAliveInterval": "00:01:00",
|
||||
"ServerTimeout": "00:03:00",
|
||||
"SkipNegotiation": true,
|
||||
"Transports": "WebSockets",
|
||||
"UseAutomaticReconnect": true,
|
||||
"UseStatefulReconnect": true
|
||||
},
|
||||
"AcBinaryHubProtocol": {
|
||||
"ProtocolMode": "AsyncSegment",
|
||||
"BufferSize": 4096,
|
||||
"FlushPolicy": "DoubleBuffered",
|
||||
"FlushTimeout": "00:00:10"
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,27 @@
|
|||
<ProjectReference Include="..\FruitBankHybrid.Shared\FruitBankHybrid.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Shared appsettings.json synced into wwwroot at build time.
|
||||
Approach: pre-build Copy Target (not <Content Include Link=...>), because:
|
||||
- <Content Include="..\..." Link="wwwroot\..."/> triggers StaticWebAssets.Normalize() "Illegal characters"
|
||||
- pre-normalizing via [System.IO.Path]::GetFullPath(...) still fails (the absolute path also trips the validator)
|
||||
- a pre-build Copy creates a physical wwwroot/appsettings.json which the StaticWebAssets auto-discovery
|
||||
picks up naturally, same as any other wwwroot file
|
||||
BeforeTargets lists multiple early targets to ensure the Copy runs before static-asset discovery,
|
||||
regardless of which one triggers first in a given SDK version.
|
||||
NOTE: a clean build (delete obj/) is required the first time, because the static-asset manifest
|
||||
is cached in obj/ and stale entries persist across incremental builds.
|
||||
The physical wwwroot/appsettings.json is a build artifact — commit or gitignore per team policy;
|
||||
edits should always be made in FruitBankHybrid.Shared/appsettings.json (the canonical source). -->
|
||||
<Target Name="CopySharedAppSettings"
|
||||
BeforeTargets="CollectPackageReferences;AssignTargetPaths;ResolveStaticWebAssetsInputs;BeforeBuild"
|
||||
Inputs="..\FruitBankHybrid.Shared\appsettings.json"
|
||||
Outputs="wwwroot\appsettings.json">
|
||||
<Copy SourceFiles="..\FruitBankHybrid.Shared\appsettings.json"
|
||||
DestinationFiles="wwwroot\appsettings.json"
|
||||
SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="AyCode.Core">
|
||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.dll</HintPath>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using AyCode.Core.Loggers;
|
||||
using AyCode.Core.Serializers.Binaries;
|
||||
using AyCode.Services.SignalRs;
|
||||
using FruitBank.Common;
|
||||
using FruitBank.Common.Loggers;
|
||||
|
|
@ -6,11 +7,13 @@ using FruitBank.Common.Models;
|
|||
using FruitBank.Common.Services;
|
||||
using FruitBankHybrid.Shared.Databases;
|
||||
using FruitBankHybrid.Shared.Services;
|
||||
using FruitBankHybrid.Shared.Services.Loggers;
|
||||
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||
using FruitBankHybrid.Web.Client.Services;
|
||||
using FruitBankHybrid.Web.Client.Services.Loggers;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
|
||||
|
|
@ -20,21 +23,55 @@ builder.Services.AddDevExpressBlazor(configure => configure.SizeMode = DevExpres
|
|||
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
||||
builder.Services.AddSingleton<ISecureCredentialService, WebSecureCredentialService>();
|
||||
|
||||
//#if DEBUG
|
||||
#if DEBUG
|
||||
builder.Services.AddSingleton<IAcLogWriterClientBase, BrowserConsoleLogWriter>();
|
||||
//#endif
|
||||
#endif
|
||||
builder.Services.AddSingleton<IAcLogWriterClientBase, SignaRClientLogItemWriter>();
|
||||
builder.Services.AddSingleton<LoggedInModel>(sp => new LoggedInModel(sp.GetRequiredService<ISecureCredentialService>()));
|
||||
|
||||
// Bind SignalR options from wwwroot/appsettings.json (loaded automatically by WebAssemblyHostBuilder) —
|
||||
// single Configure call per options type, combining section Bind with runtime overrides.
|
||||
builder.Services.Configure<AcHubConnectionOptions>(opts => builder.Configuration.GetSection("AcHubConnection").Bind(opts));
|
||||
|
||||
builder.Services.Configure<AcBinaryHubProtocolOptions>(opts =>
|
||||
{
|
||||
builder.Configuration.GetSection("AcBinaryHubProtocol").Bind(opts);
|
||||
|
||||
// WASM safety net: AsyncSegment send-path is unsupported here — Validate() would throw.
|
||||
// Downgrade if appsettings.json accidentally specifies it.
|
||||
if (opts.ProtocolMode == BinaryProtocolMode.AsyncSegment) opts.ProtocolMode = BinaryProtocolMode.Segment;
|
||||
});
|
||||
|
||||
// Logger options + framework factory. LoggerClient instances are created per caller category,
|
||||
// with AppType+LogLevel from appsettings and writers resolved from DI via IAcLogWriterClientBase.
|
||||
builder.Services.Configure<AcLoggerOptions>(builder.Configuration.GetSection("AyCode:Logger"));
|
||||
builder.Services.AddAcLoggerFactory<LoggerClient, IAcLogWriterClientBase>();
|
||||
|
||||
// HubConnectionBuilder — transient so each consumer gets a fresh builder to Build().
|
||||
// All connection and protocol configuration flows from appsettings.json via IOptions<T>;
|
||||
// AddAcDefaults (framework) applies AcHubConnectionOptions and bridges the provided logger into SignalR's internal pipeline.
|
||||
// NOTE: AcBinaryHubProtocolOptions is resolved from the OUTER service provider and passed
|
||||
// explicitly — HubConnectionBuilder's inner DI cannot see outer services.Configure<T>() registrations.
|
||||
builder.Services.AddTransient<IHubConnectionBuilder>(sp =>
|
||||
{
|
||||
var loggerFactory = sp.GetRequiredService<Func<string, LoggerClient>>();
|
||||
var connectionOpts = sp.GetRequiredService<IOptions<AcHubConnectionOptions>>().Value;
|
||||
var protocolOpts = sp.GetRequiredService<IOptions<AcBinaryHubProtocolOptions>>().Value;
|
||||
|
||||
var logger = loggerFactory(nameof(FruitBankSignalRClient));
|
||||
var hubBuilder = new HubConnectionBuilder().AddAcDefaults(logger, connectionOpts);
|
||||
|
||||
hubBuilder.AddAcBinaryProtocol(protocolOpts);
|
||||
return hubBuilder;
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<LoggedInModel>(sp =>
|
||||
new LoggedInModel(sp.GetRequiredService<ISecureCredentialService>()));
|
||||
builder.Services.AddSingleton<FruitBankSignalRClient>();
|
||||
builder.Services.AddSingleton<DatabaseClient>();
|
||||
|
||||
builder.Services.AddSingleton<IAcLogWriterClientBase, SignaRClientLogItemWriter>();
|
||||
|
||||
#if DEBUG
|
||||
if (FruitBankConstClient.SignalRSerializerDiagnosticLog)
|
||||
{
|
||||
SignalResponseDataMessage.DiagnosticLogger = message => { Console.WriteLine(message); };
|
||||
//SignalResponseDataMessage.DiagnosticLogger = message => { Console.WriteLine(message); };
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
# FruitBankHybrid.Web.Client
|
||||
|
||||
@project {
|
||||
type = "product"
|
||||
own-dep-projects = [
|
||||
"AyCode.Core, AyCode.Core.Server, AyCode.Entities, AyCode.Entities.Server, AyCode.Interfaces, AyCode.Interfaces.Server, AyCode.Models, AyCode.Models.Server, AyCode.Services, AyCode.Services.Server, AyCode.Utils (in AyCode.Core repo)",
|
||||
"Mango.Nop.Core (in Mango.Nop Libraries repo)"
|
||||
]
|
||||
}
|
||||
|
||||
Blazor WebAssembly client running in the browser after server prerendering. .NET 10.0.
|
||||
|
||||
## Folder Structure
|
||||
|
|
@ -12,7 +20,3 @@ Blazor WebAssembly client running in the browser after server prerendering. .NET
|
|||
|
||||
- **`Program.cs`** — WASM startup, DI registration.
|
||||
- **`_Imports.razor`** — Global imports.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,3 @@ WASM-specific service implementations.
|
|||
## Subfolders
|
||||
|
||||
- **`Loggers/BrowserConsoleLogWriter.cs`** — Browser console logging via JS interop.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,44 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AyCode": {
|
||||
"ProjectId": "aad53443-2ee2-4650-8a99-97e907265e4e",
|
||||
"Urls": {
|
||||
"BaseUrl": "https://localhost:59579",
|
||||
"ApiBaseUrl": "https://localhost:59579"
|
||||
},
|
||||
"Logger": {
|
||||
"AppType": "Server",
|
||||
"LogLevel": "Detail",
|
||||
"LogWriters": [
|
||||
{
|
||||
"LogLevel": "Detail",
|
||||
"LogWriterType": "FruitBank.Common.Server.Services.Loggers.ConsoleLogWriter, FruitBank.Common.Server, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
},
|
||||
"AcHubConnection": {
|
||||
"Url": "https://localhost:59579/fbHub",
|
||||
"TransportMaxBufferSize": 30000000,
|
||||
"ApplicationMaxBufferSize": 30000000,
|
||||
"CloseTimeout": "00:00:10",
|
||||
"KeepAliveInterval": "00:01:00",
|
||||
"ServerTimeout": "00:03:00",
|
||||
"SkipNegotiation": true,
|
||||
"Transports": "WebSockets",
|
||||
"UseAutomaticReconnect": true,
|
||||
"UseStatefulReconnect": true
|
||||
},
|
||||
"AcBinaryHubProtocol": {
|
||||
"ProtocolMode": "AsyncSegment",
|
||||
"BufferSize": 4096,
|
||||
"FlushPolicy": "DoubleBuffered",
|
||||
"FlushTimeout": "00:00:10"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,3 @@ Blazor Server app shell components.
|
|||
- **`App.razor`** — Root component: DevExpress theme, asset configuration, render mode.
|
||||
- **`_Imports.razor`** — Global imports.
|
||||
- **`Pages/Error.razor`** — Error page with request ID tracking.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -85,6 +85,19 @@
|
|||
<Folder Include="Services\SignalRs\" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Shared appsettings.json copied into project root at build time.
|
||||
Why a Copy target instead of <Content Include="..\..." Link="appsettings.json" CopyToOutputDirectory="PreserveNewest"/>:
|
||||
the Link approach only copies the file to bin/Debug output, but ASP.NET Core's WebApplicationBuilder
|
||||
in development reads appsettings.json from the ContentRoot (= project directory), not from the output folder.
|
||||
Materializing the file physically in the project root makes it discoverable by the default configuration
|
||||
loader in every run mode (F5 / dotnet run / published), and the SDK auto-include for appsettings*.json
|
||||
takes care of copy-to-output from there. -->
|
||||
<Target Name="CopySharedAppSettings" BeforeTargets="BeforeBuild" Inputs="..\FruitBankHybrid.Shared\appsettings.json" Outputs="appsettings.json">
|
||||
<Copy SourceFiles="..\FruitBankHybrid.Shared\appsettings.json"
|
||||
DestinationFiles="appsettings.json"
|
||||
SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
|
||||
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,14 +1,18 @@
|
|||
using AyCode.Core.Loggers;
|
||||
using AyCode.Core.Serializers.Binaries;
|
||||
using AyCode.Services.SignalRs;
|
||||
using FruitBank.Common;
|
||||
using FruitBank.Common.Models;
|
||||
using FruitBank.Common.Services;
|
||||
using FruitBank.Common.Server.Services.Loggers;
|
||||
using FruitBank.Common.Server.Services.SignalRs;
|
||||
using FruitBankHybrid.Shared.Databases;
|
||||
using FruitBankHybrid.Shared.Services;
|
||||
using FruitBankHybrid.Shared.Services.Loggers;
|
||||
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||
using FruitBankHybrid.Web.Components;
|
||||
using FruitBankHybrid.Web.Services;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
|
|
@ -16,13 +20,49 @@ builder.Services.AddRazorComponents().AddInteractiveWebAssemblyComponents();
|
|||
builder.Services.AddDevExpressBlazor(configure => configure.SizeMode = DevExpress.Blazor.SizeMode.Medium);
|
||||
builder.Services.AddMvc();
|
||||
|
||||
builder.Services.AddSignalR(options => options.MaximumReceiveMessageSize = 256 * 1024);
|
||||
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
||||
builder.Services.AddSingleton<ISecureCredentialService, ServerSecureCredentialService>();
|
||||
|
||||
builder.Services.AddSingleton<IAcLogWriterBase, ConsoleLogWriter>();
|
||||
builder.Services.AddSingleton<LoggedInModel>(sp =>
|
||||
new LoggedInModel(sp.GetRequiredService<ISecureCredentialService>()));
|
||||
|
||||
// Logger options + framework factory. LoggerClient instances are created per caller category,
|
||||
// with AppType+LogLevel from appsettings and writers resolved from DI via IAcLogWriterBase.
|
||||
builder.Services.Configure<AcLoggerOptions>(builder.Configuration.GetSection("AyCode:Logger"));
|
||||
builder.Services.AddAcLoggerFactory<LoggerClient>();
|
||||
|
||||
builder.Services.AddSingleton<LoggedInModel>(sp => new LoggedInModel(sp.GetRequiredService<ISecureCredentialService>()));
|
||||
|
||||
// Bind SignalR options from appsettings.json — single Configure call per options type.
|
||||
// The lambda runs the appsettings Bind first, then any runtime overrides (e.g. the WASM safety net).
|
||||
builder.Services.Configure<AcHubConnectionOptions>(opts => builder.Configuration.GetSection("AcHubConnection").Bind(opts));
|
||||
|
||||
builder.Services.Configure<AcBinaryHubProtocolOptions>(opts =>
|
||||
{
|
||||
builder.Configuration.GetSection("AcBinaryHubProtocol").Bind(opts);
|
||||
|
||||
// Platform safety net: on WebAssembly the AsyncSegment send-path is unsupported
|
||||
// (Validate() would throw). No-op on this server host, but matches the contract.
|
||||
if (OperatingSystem.IsBrowser() && opts.ProtocolMode == BinaryProtocolMode.AsyncSegment)
|
||||
opts.ProtocolMode = BinaryProtocolMode.Segment;
|
||||
});
|
||||
|
||||
// HubConnectionBuilder — transient so each consumer gets a fresh builder to Build().
|
||||
// All connection and protocol configuration flows from appsettings.json via IOptions<T>;
|
||||
// AddAcDefaults (framework) applies AcHubConnectionOptions and bridges the provided logger into SignalR's internal pipeline.
|
||||
// NOTE: AcBinaryHubProtocolOptions is resolved from the OUTER service provider and passed
|
||||
// explicitly — HubConnectionBuilder's inner DI cannot see outer services.Configure<T>() registrations.
|
||||
builder.Services.AddTransient<IHubConnectionBuilder>(sp =>
|
||||
{
|
||||
var loggerFactory = sp.GetRequiredService<Func<string, LoggerClient>>();
|
||||
var connectionOpts = sp.GetRequiredService<IOptions<AcHubConnectionOptions>>().Value;
|
||||
var protocolOpts = sp.GetRequiredService<IOptions<AcBinaryHubProtocolOptions>>().Value;
|
||||
|
||||
var logger = loggerFactory(nameof(FruitBankSignalRClient));
|
||||
var hubBuilder = new HubConnectionBuilder().AddAcDefaults(logger, connectionOpts);
|
||||
|
||||
hubBuilder.AddAcBinaryProtocol(protocolOpts);
|
||||
return hubBuilder;
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<FruitBankSignalRClient>();
|
||||
builder.Services.AddSingleton<DatabaseClient>();
|
||||
|
|
@ -45,9 +85,6 @@ else
|
|||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.MapHub<LoggerSignalRHub>($"/{FruitBankConstClient.LoggerHubName}");
|
||||
app.MapHub<DevAdminSignalRHub>($"/{FruitBankConstClient.DefaultHubName}");
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseStaticFiles();
|
||||
|
|
@ -57,8 +94,6 @@ app.MapStaticAssets();
|
|||
app.MapRazorComponents<App>()
|
||||
//.AddInteractiveServerRenderMode()
|
||||
.AddInteractiveWebAssemblyRenderMode()
|
||||
.AddAdditionalAssemblies(
|
||||
typeof(FruitBankHybrid.Shared._Imports).Assembly,
|
||||
typeof(FruitBankHybrid.Web.Client._Imports).Assembly);
|
||||
.AddAdditionalAssemblies(typeof(FruitBankHybrid.Shared._Imports).Assembly, typeof(FruitBankHybrid.Web.Client._Imports).Assembly);
|
||||
|
||||
app.Run();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
# FruitBankHybrid.Web
|
||||
|
||||
@project {
|
||||
type = "product"
|
||||
own-dep-projects = [
|
||||
"AyCode.Core, AyCode.Core.Server, AyCode.Entities, AyCode.Entities.Server, AyCode.Interfaces, AyCode.Interfaces.Server, AyCode.Models, AyCode.Models.Server, AyCode.Services, AyCode.Services.Server, AyCode.Utils (in AyCode.Core repo)",
|
||||
"Mango.Nop.Core (in Mango.Nop Libraries repo)"
|
||||
]
|
||||
}
|
||||
|
||||
ASP.NET Core Blazor Server host. Serves the web interface, hosts SignalR hubs, and supports interactive WebAssembly rendering.
|
||||
|
||||
## Folder Structure
|
||||
|
|
@ -13,7 +21,3 @@ ASP.NET Core Blazor Server host. Serves the web interface, hosts SignalR hubs, a
|
|||
## Key Files (Root)
|
||||
|
||||
- **`Program.cs`** — DI, SignalR hub mapping (DevAdminSignalRHub, LoggerSignalRHub), 256KB max message size, DevExpress Fluent theme, static asset versioning.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,3 @@ Server-side service implementations.
|
|||
|
||||
- **`FormFactor.cs`** — Returns "Web" as form factor.
|
||||
- **`ServerSecureCredentialService.cs`** — No-op implementation (clients handle credential storage).
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +1,44 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AyCode": {
|
||||
"ProjectId": "aad53443-2ee2-4650-8a99-97e907265e4e",
|
||||
"Urls": {
|
||||
"BaseUrl": "https://localhost:59579",
|
||||
"ApiBaseUrl": "https://localhost:59579"
|
||||
},
|
||||
"Logger": {
|
||||
"AppType": "Server",
|
||||
"LogLevel": "Detail",
|
||||
"LogWriters": [
|
||||
{
|
||||
"LogLevel": "Detail",
|
||||
"LogWriterType": "FruitBank.Common.Server.Services.Loggers.ConsoleLogWriter, FruitBank.Common.Server, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
"AyCode": {
|
||||
"ProjectId": "aad53443-2ee2-4650-8a99-97e907265e4e",
|
||||
"Urls": {
|
||||
"BaseUrl": "https://localhost:7144",
|
||||
"ApiBaseUrl": "https://localhost:7144"
|
||||
},
|
||||
"Logger": {
|
||||
"AppType": "Server",
|
||||
"LogLevel": "Detail",
|
||||
"LogWriters": [
|
||||
{
|
||||
"LogLevel": "Detail",
|
||||
"LogWriterType": "FruitBank.Common.Server.Services.Loggers.ConsoleLogWriter, FruitBank.Common.Server, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
"AcHubConnection": {
|
||||
"Url": "https://localhost:59579/fbHub",
|
||||
"TransportMaxBufferSize": 30000000,
|
||||
"ApplicationMaxBufferSize": 30000000,
|
||||
"CloseTimeout": "00:00:10",
|
||||
"KeepAliveInterval": "00:01:00",
|
||||
"ServerTimeout": "00:03:00",
|
||||
"SkipNegotiation": true,
|
||||
"Transports": "WebSockets",
|
||||
"UseAutomaticReconnect": true,
|
||||
"UseStatefulReconnect": true
|
||||
},
|
||||
"AcBinaryHubProtocol": {
|
||||
"ProtocolMode": "AsyncSegment",
|
||||
"BufferSize": 4096,
|
||||
"FlushPolicy": "DoubleBuffered",
|
||||
"FlushTimeout": "00:00:10"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.0.11222.15
|
||||
|
|
@ -33,10 +33,21 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
ProjectSection(SolutionItems) = preProject
|
||||
..\..\..\Aycode\Source\AyCode.Blazor\AyCode.Blazor.Components\Components\Grids\MgGridSignalRDataSource.txt = ..\..\..\Aycode\Source\AyCode.Blazor\AyCode.Blazor.Components\Components\Grids\MgGridSignalRDataSource.txt
|
||||
SqlSchemaCompare_Dev_to_Prod.scmp = SqlSchemaCompare_Dev_to_Prod.scmp
|
||||
.github\copilot-instructions.md = .github\copilot-instructions.md
|
||||
CLAUDE.md = CLAUDE.md
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Blazor.Components.Tests", "..\..\..\Aycode\Source\AyCode.Blazor\AyCode.Blazor.Components.Tests\AyCode.Blazor.Components.Tests.csproj", "{5EFD44C6-DC9E-FEB8-F229-3E07C2E224FA}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{B7D3E8A1-F4C2-4E9D-A6B5-1C3D5E7F9A2B}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
docs\ARCHITECTURE.md = docs\ARCHITECTURE.md
|
||||
docs\CONVENTIONS.md = docs\CONVENTIONS.md
|
||||
docs\GLOSSARY.md = docs\GLOSSARY.md
|
||||
docs\SCHEMA.md = docs\SCHEMA.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Core", "..\..\..\Aycode\Source\AyCode.Core\AyCode.Core\AyCode.Core.csproj", "{EC0E3D9A-40DE-52EB-9E66-CFFBB36B5326}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Core.Serializers.SourceGenerator", "..\..\..\Aycode\Source\AyCode.Core\AyCode.Core.Serializers.SourceGenerator\AyCode.Core.Serializers.SourceGenerator.csproj", "{1C882DAC-5027-BD65-9F22-A5FFF813FA36}"
|
||||
|
|
|
|||
|
|
@ -19,8 +19,9 @@
|
|||
<ApplicationId>com.mango.fruitbank</ApplicationId>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>1.0.1</ApplicationDisplayVersion>
|
||||
<ApplicationDisplayVersion>1.0.2</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
<PackageVersion>$(ApplicationDisplayVersion)</PackageVersion>
|
||||
|
||||
<RunAOTCompilation>false</RunAOTCompilation>
|
||||
<WasmStripILAfterAOT>true</WasmStripILAfterAOT>
|
||||
|
|
@ -78,6 +79,12 @@
|
|||
<AndroidKeyStore>True</AndroidKeyStore>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Shared appsettings.json linked from FruitBankHybrid.Shared, embedded so MauiAppBuilder can load via GetManifestResourceStream.
|
||||
LogicalName preserves the original manifest resource name so the loader in MauiProgram.cs doesn't need changes. -->
|
||||
<EmbeddedResource Include="..\FruitBankHybrid.Shared\appsettings.json" Link="appsettings.json" LogicalName="FruitBankHybrid.appsettings.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- App Icon -->
|
||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
|
||||
|
|
@ -131,11 +138,11 @@
|
|||
<Reference Include="AyCode.Entities">
|
||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.dll</HintPath>
|
||||
</Reference>
|
||||
<!--<Reference Include="Mango.Nop.Core">
|
||||
<HintPath>..\..\NopCommerce.Common\4.70\Libraries\Mango.Nop.Core\bin\FruitBank\Debug\net9.0\Mango.Nop.Core.dll</HintPath>
|
||||
<Reference Include="Mango.Nop.Core">
|
||||
<HintPath>..\..\NopCommerce.Common\4.70\Libraries\Mango.Nop.Core\bin\FruitBank\$(Configuration)\net9.0\Mango.Nop.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Nop.Core">
|
||||
<HintPath>..\..\NopCommerce.Common\4.70\Libraries\Mango.Nop.Core\bin\FruitBank\Debug\net9.0\Nop.Core.dll</HintPath>
|
||||
<!--<Reference Include="Nop.Core">
|
||||
<HintPath>..\..\NopCommerce.Common\4.70\Libraries\Mango.Nop.Core\bin\FruitBank\$(Configuration)\net9.0\Nop.Core.dll</HintPath>
|
||||
</Reference>-->
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using AyCode.Core.Loggers;
|
||||
using AyCode.Services.SignalRs;
|
||||
using FruitBank.Common.Loggers;
|
||||
using FruitBank.Common.Models;
|
||||
using FruitBank.Common.Services;
|
||||
|
|
@ -6,9 +7,15 @@ using FruitBankHybrid.Services;
|
|||
using FruitBankHybrid.Services.Loggers;
|
||||
using FruitBankHybrid.Shared.Databases;
|
||||
using FruitBankHybrid.Shared.Services;
|
||||
using FruitBankHybrid.Shared.Services.Loggers;
|
||||
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||
//using DevExpress.Maui;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FruitBankHybrid
|
||||
{
|
||||
|
|
@ -27,22 +34,59 @@ namespace FruitBankHybrid
|
|||
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||
});
|
||||
|
||||
// Load embedded appsettings.json — MAUI has no automatic config file discovery,
|
||||
// so the JSON is shipped as an EmbeddedResource (see FruitBankHybrid.csproj).
|
||||
using (var appsettingsStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("FruitBankHybrid.appsettings.json"))
|
||||
{
|
||||
if (appsettingsStream is not null)
|
||||
{
|
||||
var jsonConfig = new ConfigurationBuilder().AddJsonStream(appsettingsStream).Build();
|
||||
builder.Configuration.AddConfiguration(jsonConfig);
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
builder.Services.AddSingleton<IAcLogWriterClientBase, BrowserConsoleLogWriter>();
|
||||
#endif
|
||||
builder.Services.AddSingleton<IAcLogWriterClientBase, SignaRClientLogItemWriter>();
|
||||
|
||||
// Logger options + framework factory. LoggerClient instances are created per caller category,
|
||||
// with AppType+LogLevel from appsettings and writers resolved from DI via IAcLogWriterClientBase.
|
||||
builder.Services.Configure<AcLoggerOptions>(builder.Configuration.GetSection("AyCode:Logger"));
|
||||
builder.Services.AddAcLoggerFactory<LoggerClient, IAcLogWriterClientBase>();
|
||||
|
||||
// Bind SignalR options from configuration.
|
||||
// Precedence: code default → appsettings.json (this line) → any later Configure<T> action.
|
||||
builder.Services.Configure<AcHubConnectionOptions>(builder.Configuration.GetSection("AcHubConnection"));
|
||||
builder.Services.Configure<AcBinaryHubProtocolOptions>(builder.Configuration.GetSection("AcBinaryHubProtocol"));
|
||||
|
||||
// Add device-specific services used by the FruitBankHybrid.Shared project
|
||||
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
||||
builder.Services.AddSingleton<ISecureCredentialService, MauiSecureCredentialService>();
|
||||
|
||||
#if DEBUG
|
||||
builder.Services.AddSingleton<IAcLogWriterClientBase, BrowserConsoleLogWriter>();
|
||||
#endif
|
||||
builder.Services.AddSingleton<LoggedInModel>(sp => new LoggedInModel(sp.GetRequiredService<ISecureCredentialService>()));
|
||||
|
||||
// SignalR HubConnectionBuilder — transient so each consumer gets a fresh builder to Build().
|
||||
// All connection and protocol configuration flows from appsettings.json via IOptions<T>;
|
||||
// AddAcDefaults (framework) applies AcHubConnectionOptions and bridges the provided logger into SignalR's internal pipeline.
|
||||
// NOTE: AcBinaryHubProtocolOptions is resolved from the OUTER service provider and passed
|
||||
// explicitly — HubConnectionBuilder's inner DI cannot see outer services.Configure<T>() registrations.
|
||||
builder.Services.AddTransient<IHubConnectionBuilder>(sp =>
|
||||
{
|
||||
var loggerFactory = sp.GetRequiredService<Func<string, LoggerClient>>();
|
||||
var connectionOpts = sp.GetRequiredService<IOptions<AcHubConnectionOptions>>().Value;
|
||||
var protocolOpts = sp.GetRequiredService<IOptions<AcBinaryHubProtocolOptions>>().Value;
|
||||
|
||||
var logger = loggerFactory(nameof(FruitBankSignalRClient));
|
||||
var hubBuilder = new HubConnectionBuilder().AddAcDefaults(logger, connectionOpts);
|
||||
|
||||
hubBuilder.AddAcBinaryProtocol(protocolOpts);
|
||||
return hubBuilder;
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<LoggedInModel>(sp =>
|
||||
new LoggedInModel(sp.GetRequiredService<ISecureCredentialService>()));
|
||||
builder.Services.AddSingleton<FruitBankSignalRClient>();
|
||||
builder.Services.AddSingleton<DatabaseClient>();
|
||||
|
||||
builder.Services.AddSingleton<IAcLogWriterClientBase, SignaRClientLogItemWriter>();
|
||||
|
||||
|
||||
builder.Services.AddMauiBlazorWebView();
|
||||
builder.Services.AddDevExpressBlazor(configure => configure.SizeMode = DevExpress.Blazor.SizeMode.Medium);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,3 @@ Per-platform entry points for MAUI.
|
|||
- **`iOS/`** — AppDelegate.cs, Program.cs.
|
||||
- **`MacCatalyst/`** — AppDelegate.cs, Program.cs.
|
||||
- **`Windows/`** — App.xaml.cs (WinUI entry point).
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
# FruitBankHybrid
|
||||
|
||||
@project {
|
||||
type = "product"
|
||||
own-dep-projects = [
|
||||
"AyCode.Core, AyCode.Services, AyCode.Entities (in AyCode.Core repo)",
|
||||
"Mango.Nop.Core (in Mango.Nop Libraries repo)"
|
||||
]
|
||||
}
|
||||
|
||||
.NET MAUI Hybrid cross-platform app hosting Blazor components via BlazorWebView. Targets Android (API 33+), iOS (15.0+), and Windows.
|
||||
|
||||
## Folder Structure
|
||||
|
|
@ -16,7 +24,3 @@
|
|||
- **`MauiProgram.cs`** — DI registration, DevExpress init, SignalR client setup.
|
||||
- **`MainPage.xaml.cs`** — BlazorWebView host page.
|
||||
- **`App.xaml.cs`** — MAUI Application entry point.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,3 @@ MAUI platform-specific service implementations.
|
|||
## Subfolders
|
||||
|
||||
- **`Loggers/BrowserConsoleLogWriter.cs`** — Browser console logging bridge for BlazorWebView.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify code in this folder, update this README to reflect the changes. If you notice the README content does not match the current code, automatically update the README to match the code.
|
||||
|
|
|
|||
41
README.md
41
README.md
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
nopCommerce plugin for FruitBank, a fruit & vegetable wholesaler. Manages supplier inbound delivery (Shipping), outgoing orders (Order), warehouse weighing, and inventory stocktaking. Runs as Blazor Server, Blazor WASM, and MAUI Hybrid (Android/iOS/Windows).
|
||||
|
||||
nopCommerce 4.80.9 requires it
|
||||
|
||||
## LLM Context
|
||||
|
||||
Domain rules and critical pitfalls live in a single file: [`.github/copilot-instructions.md`](.github/copilot-instructions.md)
|
||||
|
|
@ -12,26 +14,33 @@ Domain rules and critical pitfalls live in a single file: [`.github/copilot-inst
|
|||
| Claude Code | ✅ `CLAUDE.md` → references above | None |
|
||||
| Cursor / Windsurf | ✅ `README.md` | Read `copilot-instructions.md` via @file |
|
||||
|
||||
Detailed docs: [`docs/`](docs/) — GLOSSARY.md, ARCHITECTURE.md, CONVENTIONS.md, SCHEMA.md
|
||||
Detailed docs: `docs/` — GLOSSARY.md, ARCHITECTURE.md, CONVENTIONS.md. Domain model schema (TOON) lives in the plugin: `NopCommerce.Common/4.70/Plugins/Nop.Plugin.Misc.AIPlugin/docs/SCHEMA.md`
|
||||
|
||||
## Solution Structure
|
||||
|
||||
| Project | Purpose | README |
|
||||
|---|---|---|
|
||||
| [`FruitBank.Common`](FruitBank.Common/README.md) | Shared domain: entities, DTOs, interfaces, SignalR tags, measurement helpers | [README](FruitBank.Common/README.md) |
|
||||
| [`FruitBank.Common.Server`](FruitBank.Common.Server/README.md) | Server-side: SignalR hubs, broadcast service, logging, nopCommerce integration | [README](FruitBank.Common.Server/README.md) |
|
||||
| [`FruitBankHybrid.Shared`](FruitBankHybrid.Shared/README.md) | Blazor UI: pages, grids, SignalR client, measurement service, layout | [README](FruitBankHybrid.Shared/README.md) |
|
||||
| [`FruitBankHybrid.Shared.Common`](FruitBankHybrid.Shared.Common/README.md) | Shared common library (placeholder) | [README](FruitBankHybrid.Shared.Common/README.md) |
|
||||
| [`FruitBankHybrid`](FruitBankHybrid/README.md) | MAUI Hybrid app: Android, iOS, Windows | [README](FruitBankHybrid/README.md) |
|
||||
| [`FruitBankHybrid.Web`](FruitBankHybrid.Web/README.md) | Blazor Server host with SignalR hubs | [README](FruitBankHybrid.Web/README.md) |
|
||||
| [`FruitBankHybrid.Web.Client`](FruitBankHybrid.Web.Client/README.md) | Blazor WebAssembly client | [README](FruitBankHybrid.Web.Client/README.md) |
|
||||
| Project | TFM | Purpose | README |
|
||||
|---|---|---|---|
|
||||
| [`FruitBank.Common`](FruitBank.Common/README.md) | net9.0 | Shared domain: entities, DTOs, interfaces, SignalR tags, measurement helpers | [README](FruitBank.Common/README.md) |
|
||||
| [`FruitBank.Common.Server`](FruitBank.Common.Server/README.md) | net9.0 | Server-side: SignalR hubs, broadcast service, logging, nopCommerce integration | [README](FruitBank.Common.Server/README.md) |
|
||||
| [`FruitBankHybrid.Shared`](FruitBankHybrid.Shared/README.md) | net10.0 | Blazor UI: pages, grids, SignalR client, measurement service, layout | [README](FruitBankHybrid.Shared/README.md) |
|
||||
| [`FruitBankHybrid.Shared.Common`](FruitBankHybrid.Shared.Common/README.md) | net10.0 | Shared common library (placeholder) | [README](FruitBankHybrid.Shared.Common/README.md) |
|
||||
| [`FruitBankHybrid`](FruitBankHybrid/README.md) | net10.0‑android/ios/win | MAUI Hybrid app: Android, iOS, Windows | [README](FruitBankHybrid/README.md) |
|
||||
| [`FruitBankHybrid.Web`](FruitBankHybrid.Web/README.md) | net10.0 | Blazor Server host with SignalR hubs | [README](FruitBankHybrid.Web/README.md) |
|
||||
| [`FruitBankHybrid.Web.Client`](FruitBankHybrid.Web.Client/README.md) | net10.0 | Blazor WebAssembly client | [README](FruitBankHybrid.Web.Client/README.md) |
|
||||
|
||||
### Test Projects
|
||||
|
||||
| Project | Purpose | README |
|
||||
| Project | TFM | Purpose | README |
|
||||
|---|---|---|---|
|
||||
| [`FruitBankHybrid.Shared.Tests`](FruitBankHybrid.Shared.Tests/README.md) | net10.0 | Integration + serialization tests (SignalR, JSON, Toon, bunit) | [README](FruitBankHybrid.Shared.Tests/README.md) |
|
||||
|
||||
### External Dependencies
|
||||
|
||||
All projects reference these via **DLL** (not ProjectReference). Full source is available in sibling directories:
|
||||
|
||||
| Repo | Path | Key Docs |
|
||||
|---|---|---|
|
||||
| [`FruitBankHybrid.Shared.Tests`](FruitBankHybrid.Shared.Tests/README.md) | Integration + serialization tests (SignalR, JSON, Toon, bunit) | [README](FruitBankHybrid.Shared.Tests/README.md) |
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify this solution's structure, update this README to reflect the changes.
|
||||
| **AyCode.Core** (net9.0) | `../../../Aycode/Source/AyCode.Core/` | [copilot-instructions](../../../Aycode/Source/AyCode.Core/.github/copilot-instructions.md), [ARCHITECTURE](../../../Aycode/Source/AyCode.Core/docs/ARCHITECTURE.md) |
|
||||
| **AyCode.Blazor** (net10.0) | `../../../Aycode/Source/AyCode.Blazor/` | [copilot-instructions](../../../Aycode/Source/AyCode.Blazor/.github/copilot-instructions.md), [MGGRID](../../../Aycode/Source/AyCode.Blazor/AyCode.Blazor.Components/docs/MGGRID/README.md) |
|
||||
| **Mango.Nop Libraries** (net9.0) | `../NopCommerce.Common/4.70/Libraries/` | [copilot-instructions](../NopCommerce.Common/4.70/Libraries/.github/copilot-instructions.md), [ARCHITECTURE](../NopCommerce.Common/4.70/Libraries/docs/ARCHITECTURE.md) |
|
||||
| **FruitBank Plugin** (net9.0) | `../NopCommerce.Common/4.70/Plugins/Nop.Plugin.Misc.AIPlugin/` | [README](../NopCommerce.Common/4.70/Plugins/Nop.Plugin.Misc.AIPlugin/README.md) |
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,38 +1,44 @@
|
|||
# Conventions
|
||||
|
||||
> For core framework conventions see: `CONVENTIONS.md` (in AyCode.Core repo)
|
||||
> For UI framework conventions see: `CONVENTIONS.md` (in AyCode.Blazor repo)
|
||||
|
||||
## Naming
|
||||
|
||||
- **fb prefix** on database tables: `fbPallet`, `fbShipping`, `fbShippingItem`, etc.
|
||||
- **Dto suffix** for DTOs wrapping nopCommerce entities: `OrderDto`, `OrderItemDto`, `ProductDto`.
|
||||
- **XxxItemPallet** for measurement records: `ShippingItemPallet`, `OrderItemPallet`, `StockTakingItemPallet`.
|
||||
- **Grid prefix** for Blazor grid components: `GridPartnerBase`, `GridShippingBase`, etc.
|
||||
- **GridXxxBase** = C# code-behind class inheriting `FruitBankGridBase<TDataItem>`.
|
||||
- **GridXxx.razor** = Razor markup using `<GridXxxBase>` with `<Columns>` and `<DetailRowTemplate>`.
|
||||
- **OnGrid prefix** for MgGridBase event parameters: `OnGridItemDeleting`, `OnGridEditModelSaving`, `OnGridFocusedRowChanged`, etc. (avoids collision with DxGrid base events).
|
||||
- **SignalRTags** constants use numeric ranges by domain (see `FruitBank.Common/SignalRs/`).
|
||||
|
||||
## XML Documentation
|
||||
|
||||
`<summary>` — brief, developer-facing, readable in VS IntelliSense tooltip. NO implementation details, NO wire-format / byte-level / perf specifics — those live in `docs/TOPIC/*.md`. Add `<example>` only when usage is non-obvious; otherwise omit.
|
||||
|
||||
## Patterns
|
||||
|
||||
- **MeasuringItemPalletBase** as abstract base for all three measurement hierarchies.
|
||||
- **GenericAttributes** for extending nopCommerce entities with custom data (IsMeasurable, Tare, AverageWeight).
|
||||
- **Composition interfaces** for measurement traits: IMeasuringValues = IMeasuringWeights + IMeasuringQuantity.
|
||||
- **DevExpress DxGrid** with `AcSignalRDataSource` for real-time grid data.
|
||||
- **MgGridBase** for grid layout persistence via localStorage.
|
||||
- **MgGridBase** — canonical grid base from AyCode.Blazor (see `AyCode.Blazor.Components/docs/MGGRID/README.md` (in AyCode.Blazor repo)). Provides SignalR CRUD, layout persistence, master-detail, InfoPanel, fullscreen.
|
||||
- **FruitBankGridBase** — project adapter that fixes `TId=int`, `TLoggerClient=LoggerClient`, adds per-user layout and master/detail defaults.
|
||||
- **FruitBankSignalRClient** as single hub client for all server communication.
|
||||
- **DatabaseClient** for client-side caching with ConcurrentDictionary tables.
|
||||
|
||||
## Critical Rules
|
||||
### Grid Creation Checklist
|
||||
|
||||
- **MeasuringStatus.Finnished** — intentional legacy typo. Do NOT fix.
|
||||
- **Shipping = INBOUND, Order = OUTBOUND** — never confuse directions.
|
||||
- **Pallet = measurement record** — always created, even for non-measurable products.
|
||||
- **NetWeight is calculated, never stored** — GrossWeight − PalletWeight − (TrayQuantity × TareWeight).
|
||||
- **DLL references** to AyCode projects are intentional — do not convert to ProjectReference.
|
||||
- Do not suggest removal/rollback as a solution — find a fix.
|
||||
1. Create `GridXxxBase.cs` inheriting `FruitBankGridBase<TEntity>`.
|
||||
2. Set CRUD tags in constructor: `GetAllMessageTag`, `AddMessageTag`, `UpdateMessageTag`, `RemoveMessageTag`.
|
||||
3. Create `GridXxx.razor` with `<GridXxxBase>`, `<Columns>`, optional `<DetailRowTemplate>`.
|
||||
4. Wrap in `<MgGridWithInfoPanel>` if InfoPanel is needed.
|
||||
5. For detail grids: set `ParentDataItem`, `KeyFieldNameToParentId`, `ContextIds`.
|
||||
|
||||
## UI (Hungarian Locale)
|
||||
|
||||
- Status labels and UI text are in Hungarian.
|
||||
- MeasuringStatus display: "Nincs elkezdve", "Elkezdve", "Kész", "Auditált".
|
||||
- Date format follows Hungarian conventions.
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you establish new conventions, document them here.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
# Glossary / Fogalomtár
|
||||
|
||||
> For core framework glossary see: `GLOSSARY.md` (in AyCode.Core repo)
|
||||
> For UI framework glossary see: `GLOSSARY.md` (in AyCode.Blazor repo)
|
||||
> For core measurement system rules and common domain traps, see: `../FruitBank.Common/docs/GLOSSARY.md`
|
||||
|
||||
Domain terminology for the FruitBank system. **Read this before making changes.**
|
||||
|
||||
## Business Domain
|
||||
|
|
@ -15,30 +19,6 @@ Domain terminology for the FruitBank system. **Read this before making changes.*
|
|||
| **StockTaking** | Leltározás | Inventory session that freezes logical stock and reconciles with physical count. |
|
||||
| **GenericAttribute** | Generikus attribútum | nopCommerce polymorphic key-value store. KeyGroup = owner type, EntityId = owner ID. |
|
||||
|
||||
## Measurement System
|
||||
|
||||
| Term | Definition |
|
||||
|---|---|
|
||||
| **IsMeasurable** | Product-level flag. If `false`: weights = 0.0, only TrayQuantity matters. A Pallet record is still created. |
|
||||
| **NetWeight** | `GrossWeight − PalletWeight − (TrayQuantity × TareWeight)` — universal formula across all three hierarchies. |
|
||||
| **TrayQuantity** | Always recorded, regardless of measurability. Count of trays/crates. |
|
||||
| **GrossWeight** | Total weight including pallet and packaging. 0.0 if not measurable. |
|
||||
| **PalletWeight** | Weight of the physical pallet. 0.0 if goods arrive without one. |
|
||||
| **TareWeight** | Weight of a single tray/crate. Used in NetWeight calculation. |
|
||||
| **AverageWeight** | Per-pallet average: `NetWeight / TrayQuantity`. Validated against threshold. |
|
||||
| **MeasuringStatus** | NotStarted(0) → Started(10) → **Finnished**(20) → Audited(30). Note: "Finnished" is intentional. |
|
||||
| **RevisorId** | Quality auditor's Customer ID. OrderItemPallet becomes "Audited" when RevisorId > 0. |
|
||||
|
||||
## Three Measurement Hierarchies
|
||||
|
||||
All share `MeasuringItemPalletBase` with the same NetWeight formula:
|
||||
|
||||
| Flow | Parent | Pallet Record | Extra |
|
||||
|---|---|---|---|
|
||||
| **Inbound (Shipping)** | ShippingItem | ShippingItemPallet | Declared vs measured discrepancy |
|
||||
| **Outbound (Order)** | OrderItemDto | OrderItemPallet | RevisorId for audit |
|
||||
| **Inventory (StockTaking)** | StockTakingItem | StockTakingItemPallet | QuantityDiff for stock adjustment |
|
||||
|
||||
## nopCommerce Entities
|
||||
|
||||
These are **NOT custom FruitBank entities** — they come from nopCommerce:
|
||||
|
|
@ -48,17 +28,10 @@ FruitBank extends them via:
|
|||
- **DTOs** (OrderDto, OrderItemDto, ProductDto) that wrap nopCommerce entities with measurement properties
|
||||
- **GenericAttributes** for storing custom values (IsMeasurable, Tare, AverageWeight, etc.)
|
||||
|
||||
## Common Traps
|
||||
## UI / Grid Components
|
||||
|
||||
| Trap | Correct Behavior |
|
||||
For MgGrid framework terms (MgGridBase, MgGridWithInfoPanel, MgGridToolbarBase, MgGridDataColumn, MgGridInfoPanel, IMgGridBase, etc.) see `GLOSSARY.md` (in AyCode.Blazor repo) and `AyCode.Blazor.Components/docs/MGGRID/README.md` (in AyCode.Blazor repo).
|
||||
|
||||
| Term | Definition |
|
||||
|---|---|
|
||||
| "Pallet" = physical pallet | ❌ It's a measurement record. Always created. |
|
||||
| Shipping = outgoing | ❌ Shipping = INBOUND. Order = OUTBOUND. |
|
||||
| Fix "Finnished" spelling | ❌ Intentional legacy typo. Do NOT fix. |
|
||||
| IsMeasurable=false means no Pallet | ❌ Pallet is always created, weights just = 0.0 |
|
||||
| NetWeight is stored | ❌ It's calculated: GrossWeight − PalletWeight − (TrayQuantity × TareWeight) |
|
||||
| GenericAttribute is simple | ❌ It's polymorphic: KeyGroup determines which entity type owns the record |
|
||||
|
||||
---
|
||||
|
||||
> **LLM Maintenance:** If you modify domain terminology or introduce new business concepts, update this glossary.
|
||||
| **FruitBankGridBase** | Project-level adapter: fixes `TSignalRDataSource=SignalRDataSourceObservable`, `TId=int`, `TLoggerClient=LoggerClient`. Adds per-user layout, master/detail defaults. See `FruitBankHybrid.Shared/Components/Grids/README.md`. |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
# FruitBankHybridApp documentation
|
||||
|
||||
Top-level documentation for the `FruitBankHybridApp` repo (Layer 3 — FruitBank MAUI/Blazor Hybrid client).
|
||||
|
||||
## Reference docs (flat)
|
||||
|
||||
- [`ARCHITECTURE.md`](ARCHITECTURE.md) — Repo architecture overview
|
||||
- [`CONVENTIONS.md`](CONVENTIONS.md) — Coding conventions
|
||||
- [`GLOSSARY.md`](GLOSSARY.md) — Domain glossary (FruitBank terms: Shipping, Order, StockTaking, MeasuringStatus, etc.)
|
||||
|
||||
## Sub-projects with docs
|
||||
|
||||
- `FruitBank.Common/docs/` — Common glossary (shared across Hybrid client-side)
|
||||
|
||||
## Navigation
|
||||
|
||||
Per the AI Agent Core Protocol (folder navigation rule), start from this README when browsing `docs/`. Single-file reference docs remain flat; multi-file topics would live in named subfolders (none currently at this level).
|
||||
|
||||
## See also
|
||||
|
||||
- **Server-side plugin**: `../../NopCommerce.Common/4.70/Plugins/Nop.Plugin.Misc.AIPlugin/docs/README.md`
|
||||
- **Base framework** (AyCode.Core, AyCode.Blazor): see those repos' `docs/` folders.
|
||||
635
docs/SCHEMA.md
635
docs/SCHEMA.md
|
|
@ -1,635 +0,0 @@
|
|||
# Domain Model Schema (Toon Format)
|
||||
|
||||
> Full domain model in [Toon](../../AyCode.Core/AyCode.Core/Serializers/Toons/README.md) (Token-Oriented Object Notation) format.
|
||||
> This is the authoritative schema for entities, DTOs, and enums in the FruitBank domain.
|
||||
|
||||
@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", "DocumentType", "Files", "Pallet", "ProductDto", "Customer", "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
|
||||
|
||||
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<double>('AverageWeight')"
|
||||
constraints: "readonly, not-mapped"
|
||||
AverageWeightTreshold: double
|
||||
business-logic: "get => GenericAttributes.GetValueOrDefault<double>('AverageWeightTreshold')"
|
||||
constraints: "readonly, not-mapped"
|
||||
GenericAttributes: List<GenericAttributeDto>
|
||||
navigation: "one-to-many"
|
||||
IncomingQuantity: int
|
||||
business-logic: "get => GenericAttributes.GetValueOrDefault<int>('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<bool>('IsMeasurable')"
|
||||
constraints: "not-mapped"
|
||||
NetWeight: double
|
||||
business-logic: "get => GenericAttributes.GetValueOrDefault<double>('NetWeight')"
|
||||
constraints: "not-mapped"
|
||||
Tare: double
|
||||
business-logic: "get => GenericAttributes.GetValueOrDefault<double>('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
|
||||
|
||||
FullProcessModel: "Object of type FullProcessModel"
|
||||
table-name: "FullProcessModel"
|
||||
purpose: "Container model for Shipping, Order"
|
||||
Orders: List<OrderDto>
|
||||
navigation: "one-to-many"
|
||||
Shippings: List<Shipping>
|
||||
navigation: "one-to-many"
|
||||
StockTakings: List<StockTaking>
|
||||
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<DateTime>('DateOfReceipt')"
|
||||
constraints: "readonly, not-mapped"
|
||||
DateOfReceiptOrCreated: DateTime
|
||||
constraints: "readonly, not-mapped"
|
||||
GenericAttributes: List<GenericAttributeDto>
|
||||
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<OrderItemDto>
|
||||
other-key: "OrderId"
|
||||
navigation: "one-to-many"
|
||||
inverse-property: "OrderDto"
|
||||
OrderNotes: List<OrderNote>
|
||||
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<GenericAttributeDto>
|
||||
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<OrderItemPallet>
|
||||
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. NOTE: Despite the 'Pallet' name, this is a general measurement record that is ALWAYS created for every item."
|
||||
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?
|
||||
GrossWeight: double
|
||||
IsMeasured: bool
|
||||
Modified: DateTime
|
||||
ModifierId: int?
|
||||
NetWeight: double
|
||||
business-logic: "get => GrossWeight - PalletWeight - (TrayQuantity * TareWeight)"
|
||||
constraints: "readonly, not-mapped"
|
||||
PalletWeight: double
|
||||
TareWeight: double
|
||||
TrayQuantity: int
|
||||
Id: int
|
||||
primary-key: true
|
||||
|
||||
Partner: "Business partner with address and tax information"
|
||||
table-name: "fbPartner"
|
||||
purpose: "External supplier providing goods"
|
||||
CertificationNumber: string
|
||||
City: string
|
||||
Country: string
|
||||
County: string
|
||||
Created: DateTime
|
||||
Modified: DateTime
|
||||
Name: string
|
||||
PostalCode: string
|
||||
ShippingDocuments: List<ShippingDocument>
|
||||
navigation: "one-to-many"
|
||||
State: string
|
||||
Street: string
|
||||
TaxId: string
|
||||
Id: int
|
||||
primary-key: true
|
||||
|
||||
Shipping: "Inbound delivery event at warehouse"
|
||||
table-name: "fbShipping"
|
||||
CargoCompany: string
|
||||
Comment: string
|
||||
Created: DateTime
|
||||
IsAllMeasured: bool
|
||||
LicencePlate: string
|
||||
MeasuredDate: DateTime?
|
||||
Modified: DateTime
|
||||
ShippingDate: DateTime
|
||||
ShippingDocuments: List<ShippingDocument>
|
||||
navigation: "one-to-many"
|
||||
Id: int
|
||||
primary-key: true
|
||||
|
||||
ShippingDocument: "Supplier delivery note or invoice"
|
||||
table-name: "fbShippingDocument"
|
||||
Comment: string
|
||||
Country: string
|
||||
Created: DateTime
|
||||
DocumentIdNumber: string
|
||||
IsAllMeasured: bool
|
||||
Modified: DateTime
|
||||
Partner: Partner
|
||||
foreign-key: "PartnerId"
|
||||
navigation: "many-to-one"
|
||||
PartnerId: int
|
||||
PdfFileName: string
|
||||
Shipping: Shipping
|
||||
foreign-key: "ShippingId"
|
||||
navigation: "many-to-one"
|
||||
ShippingDate: DateTime
|
||||
ShippingDocumentToFiles: List<ShippingDocumentToFiles>
|
||||
navigation: "one-to-many"
|
||||
ShippingId: int?
|
||||
ShippingItems: List<ShippingItem>
|
||||
navigation: "one-to-many"
|
||||
TotalPallets: int
|
||||
Id: int
|
||||
primary-key: true
|
||||
|
||||
ShippingDocumentToFiles: "Links documents to files with type"
|
||||
table-name: "fbShippingDocumentToFiles"
|
||||
Created: DateTime
|
||||
DocumentType: DocumentType
|
||||
DocumentTypeId: int
|
||||
FilesId: int
|
||||
Modified: DateTime
|
||||
ShippingDocument: ShippingDocument
|
||||
foreign-key: "ShippingDocumentId"
|
||||
navigation: "many-to-one"
|
||||
ShippingDocumentFile: Files
|
||||
foreign-key: "FilesId"
|
||||
navigation: "many-to-one"
|
||||
ShippingDocumentId: int
|
||||
Id: int
|
||||
primary-key: true
|
||||
|
||||
ShippingItem: "Product line on shipping document"
|
||||
table-name: "fbShippingItem"
|
||||
Created: DateTime
|
||||
GrossWeightOnDocument: double
|
||||
HungarianName: string
|
||||
IsMeasurable: bool
|
||||
IsMeasured: bool
|
||||
MeasuredGrossWeight: double
|
||||
MeasuredNetWeight: double
|
||||
MeasuredQuantity: int
|
||||
MeasuringCount: int
|
||||
MeasuringStatus: MeasuringStatus
|
||||
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?
|
||||
QuantityOnDocument: int
|
||||
ShippingDocument: ShippingDocument
|
||||
foreign-key: "ShippingDocumentId"
|
||||
navigation: "many-to-one"
|
||||
ShippingDocumentId: int
|
||||
ShippingItemPallets: List<ShippingItemPallet>
|
||||
navigation: "one-to-many"
|
||||
UnitPriceOnDocument: double
|
||||
Id: int
|
||||
primary-key: true
|
||||
|
||||
ShippingItemPallet: "Measurement record for incoming goods"
|
||||
table-name: "fbShippingItemPallet"
|
||||
purpose: "Always created even without physical pallet"
|
||||
ShippingItem: ShippingItem
|
||||
foreign-key: "ShippingItemId"
|
||||
navigation: "many-to-one"
|
||||
ShippingItemId: int
|
||||
Created: DateTime
|
||||
CreatorId: int?
|
||||
GrossWeight: double
|
||||
IsMeasured: bool
|
||||
MeasuringStatus: MeasuringStatus
|
||||
constraints: "readonly, not-mapped"
|
||||
Modified: DateTime
|
||||
ModifierId: int?
|
||||
NetWeight: double
|
||||
business-logic: "get => GrossWeight - PalletWeight - (TrayQuantity * TareWeight)"
|
||||
constraints: "readonly, not-mapped"
|
||||
PalletWeight: double
|
||||
TareWeight: double
|
||||
TrayQuantity: int
|
||||
Id: int
|
||||
primary-key: true
|
||||
|
||||
StockTaking: "Inventory session record"
|
||||
table-name: "fbStockTaking"
|
||||
Created: DateTime
|
||||
Creator: int
|
||||
IsClosed: bool
|
||||
Modified: DateTime
|
||||
StartDateTime: DateTime
|
||||
StockTakingItems: List<StockTakingItem>
|
||||
navigation: "one-to-many"
|
||||
Id: int
|
||||
primary-key: true
|
||||
|
||||
StockTakingItem: "Line item for product reconciliation"
|
||||
table-name: "fbStockTakingItem"
|
||||
purpose: "Reconciles snapshot quantity with physical count"
|
||||
InProcessOrdersQuantity: int
|
||||
IsInvalid: bool
|
||||
constraints: "readonly, not-mapped"
|
||||
IsMeasurable: bool
|
||||
IsRequiredForMeasuring: bool
|
||||
constraints: "readonly, not-mapped"
|
||||
MeasuredNetWeight: double
|
||||
NetWeightDiff: double
|
||||
constraints: "readonly, not-mapped"
|
||||
OriginalNetWeight: double
|
||||
QuantityDiff: int
|
||||
constraints: "readonly, not-mapped"
|
||||
StockTakingItemPallets: List<StockTakingItemPallet>
|
||||
navigation: "one-to-many"
|
||||
TotalOriginalQuantity: int
|
||||
constraints: "readonly, not-mapped"
|
||||
Created: DateTime
|
||||
IsMeasured: bool
|
||||
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"
|
||||
StockTakingId: int
|
||||
Id: int
|
||||
primary-key: true
|
||||
|
||||
StockTakingItemPallet: "Weight record for inventory item"
|
||||
table-name: "fbStockTakingItemPallet"
|
||||
purpose: "Mandatory for every inventory item, even non-measurable"
|
||||
StockTakingItem: StockTakingItem
|
||||
foreign-key: "StockTakingItemId"
|
||||
navigation: "many-to-one"
|
||||
StockTakingItemId: int
|
||||
Created: DateTime
|
||||
CreatorId: int?
|
||||
GrossWeight: double
|
||||
IsMeasured: bool
|
||||
MeasuringStatus: MeasuringStatus
|
||||
constraints: "readonly, not-mapped"
|
||||
Modified: DateTime
|
||||
ModifierId: int?
|
||||
NetWeight: double
|
||||
business-logic: "get => GrossWeight - PalletWeight - (TrayQuantity * TareWeight)"
|
||||
constraints: "readonly, not-mapped"
|
||||
PalletWeight: double
|
||||
TareWeight: double
|
||||
TrayQuantity: int
|
||||
Id: int
|
||||
primary-key: true
|
||||
}
|
||||
Loading…
Reference in New Issue