Compare commits
No commits in common. "FruitBank_v0.0.8.0" and "main" have entirely different histories.
|
|
@ -3,58 +3,7 @@
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(dir:*)",
|
"Bash(dir:*)",
|
||||||
"Bash(dotnet list:*)",
|
"Bash(dotnet list:*)",
|
||||||
"Bash(find:*)",
|
"Bash(find:*)"
|
||||||
"Bash(grep:*)",
|
|
||||||
"Bash(dotnet test:*)",
|
|
||||||
"Bash(dotnet build:*)",
|
|
||||||
"Bash(ls:*)",
|
|
||||||
"Bash(while read:*)",
|
|
||||||
"Bash(do sed -i '1a using AyCode.Core.Serializers.Toons;\\\\n' \"$f\")",
|
|
||||||
"Bash(done)",
|
|
||||||
"Bash(rm \"C:/Users/Fullepi/.claude/projects/H--Applications-Mango-Source-FruitBankHybridApp/memory/feedback_framework_docs_no_consumer_types.md\")",
|
|
||||||
"Bash(cd \"H:/Applications/Aycode/Source/AyCode.Core/AyCode.Services.Server/bin/FruitBank\" && ls -la --time-style=full-iso Debug/net9.0/AyCode.Core.dll Release/net9.0/AyCode.Core.dll 2>&1)",
|
|
||||||
"WebSearch",
|
|
||||||
"Bash(dotnet --version)",
|
|
||||||
"Bash(curl -sI -m 8 https://ekaer.nav.gov.hu/)",
|
|
||||||
"Bash(echo \"---curl-exit:$?\")",
|
|
||||||
"Bash(dotnet tool *)",
|
|
||||||
"Read(//c/Users/Fullepi/Downloads/ekaer/Generated/AyCode.Services.Nav.Ekaer.Models//**)",
|
|
||||||
"WebFetch(domain:github.com)",
|
|
||||||
"Bash(curl -s -m 15 https://raw.githubusercontent.com/nav-gov-hu/Common/master/README.md)",
|
|
||||||
"Bash(curl -s -m 15 \"https://api.github.com/repos/nav-gov-hu/Common/git/trees/master?recursive=1\")",
|
|
||||||
"Bash(curl -s -m 15 \"https://api.github.com/repos/nav-gov-hu/Common\")",
|
|
||||||
"Bash(curl -s -m 15 \"https://api.github.com/repos/nav-gov-hu/Common/git/trees/main?recursive=1\")",
|
|
||||||
"Bash(sed -E 's/\"path\": *\"//;s/\"$//')",
|
|
||||||
"Bash(curl -s -m 15 https://raw.githubusercontent.com/nav-gov-hu/Common/main/README_en.adoc)",
|
|
||||||
"WebFetch(domain:ekaer.nav.gov.hu)",
|
|
||||||
"Bash(curl -s -m 15 https://raw.githubusercontent.com/nav-gov-hu/Common/main/schemas/src/main/resources/xsd/hu/gov/nav/schemas/NTCA/2.0/common/authservice.xsd)",
|
|
||||||
"Bash(echo \"=== Nav/docs \\(amit a user bemásolt\\) ===\" *)",
|
|
||||||
"Bash(cp \"C:/Users/Fullepi/Downloads/ekaer/ekaermanagement.xsd\" \"H:/Applications/Aycode/Source/AyCode.Core/AyCode.Services/Nav/docs/\")",
|
|
||||||
"Bash(cp \"C:/Users/Fullepi/Downloads/ekaer/common.xsd\" \"H:/Applications/Aycode/Source/AyCode.Core/AyCode.Services/Nav/docs/\")",
|
|
||||||
"Bash(pdftotext -layout \"H:/Applications/Aycode/Source/AyCode.Core/AyCode.Services/Nav/docs/eKAERManagementService_2.2.pdf\" \"C:/Users/Fullepi/Downloads/ekaer/ekaer_2.2.txt\")",
|
|
||||||
"Bash(sed -n '1,70p' \"C:/Users/Fullepi/Downloads/ekaer/ekaer_2.2.txt\")",
|
|
||||||
"Bash(pdftotext -layout -enc UTF-8 H:/Applications/Aycode/Source/AyCode.Core/AyCode.Services/Nav/docs/eKAERManagementService_2.2.pdf C:/Users/Fullepi/Downloads/ekaer/ekaer_2.2.txt)",
|
|
||||||
"Bash(cp C:/Users/Fullepi/Downloads/ekaer/Generated/AyCode.Services.Nav.Ekaer.Models/*.cs H:/Applications/Aycode/Source/AyCode.Core/AyCode.Services/Nav/Ekaer/Models/)",
|
|
||||||
"Bash(echo '=== bemásolva: __CMDSUB_OUTPUT__')",
|
|
||||||
"Bash(cp C:/Users/Fullepi/Downloads/ekaer/Generated2/AyCode.Services.Nav.Ekaer.Models/*.cs H:/Applications/Aycode/Source/AyCode.Core/AyCode.Services/Nav/Ekaer/Models/)",
|
|
||||||
"Bash(cp C:/Users/Fullepi/Downloads/ekaer/Generated2/AyCode.Services.Nav.Ekaer.Models.Common/*.cs H:/Applications/Aycode/Source/AyCode.Core/AyCode.Services/Nav/Ekaer/Models/Common/)",
|
|
||||||
"Bash(sha512sum)",
|
|
||||||
"Bash(awk '{print toupper\\($1\\)}')",
|
|
||||||
"Bash(git -C \"H:/Applications/Aycode/Source/AyCode.Core\" status --short)",
|
|
||||||
"Bash(git -C \"H:/Applications/Mango/Source/FruitBankHybridApp\" status --short)",
|
|
||||||
"Bash(git -C \"H:/Applications/Mango/Source/NopCommerce.Common\" status --short)",
|
|
||||||
"Bash(Get-ChildItem -Path \"H:\\\\Applications\\\\Mango\\\\Source\\\\NopCommerce.Common\\\\4.70\\\\Plugins\\\\Nop.Plugin.Misc.AIPlugin\" -Directory)",
|
|
||||||
"Bash(Select-Object Name)",
|
|
||||||
"Bash(ICargo)",
|
|
||||||
"Bash(IShipping\")",
|
|
||||||
"Bash(ls -la \"H:\\\\Applications\\\\Mango\\\\Source\\\\NopCommerce.Common\\\\4.70\\\\Plugins\\\\Nop.Plugin.Misc.AIPlugin\" 2>/dev/null | head -30)",
|
|
||||||
"PowerShell(ls \"H:\\\\Applications\\\\Mango\\\\Source\\\\FruitBankHybridApp\\\\FruitBank.Common\\\\Interfaces\\\\\" | where {$_.Name -match 'IPartner|ICargo|IShipping'} | select Name)",
|
|
||||||
"Bash(find \"H:\\\\Applications\\\\FruitBankHybridApp\\\\FruitBank.Common\\\\Source\" -type f -name \"*Shipping*.cs\" 2>/dev/null | head -10)",
|
|
||||||
"Bash(find \"H:\\\\Applications\\\\Aycode\\\\Source\\\\AyCode.Core\\\\AyCode.Services\" -type d | head -20)",
|
|
||||||
"Bash(rm -f H:/Applications/Mango/Source/NopCommerce.Common/4.70/Plugins/Nop.Plugin.Misc.AIPlugin/Services/Ekaer/EkaerMappingOptions.cs H:/Applications/Mango/Source/NopCommerce.Common/4.70/Plugins/Nop.Plugin.Misc.AIPlugin/Services/Ekaer/IShippingToEkaerMapper.cs H:/Applications/Mango/Source/NopCommerce.Common/4.70/Plugins/Nop.Plugin.Misc.AIPlugin/Services/Ekaer/ShippingToEkaerMapper.cs)",
|
|
||||||
"Bash(rmdir \"H:/Applications/Mango/Source/NopCommerce.Common/4.70/Plugins/Nop.Plugin.Misc.AIPlugin/Services/Ekaer\")",
|
|
||||||
"Bash(rm -f \"H:/Applications/Mango/Source/NopCommerce.Common/4.70/Plugins/Nop.Plugin.Misc.AIPlugin/Infrastructure/NavCredentials.cs\" *)",
|
|
||||||
"Bash(rm -f \"H:/Applications/Aycode/Source/AyCode.Core/AyCode.Entities/IPostalParty.cs\" *)"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
# Topic Codes — FruitBankHybridApp (`FBANKAPP`)
|
|
||||||
|
|
||||||
Per-repo topic registry for the FruitBankHybridApp product, per the **per-repo extension convention** in `AyCode.Core/.github/skills/docs-check/references/TOPIC_CODES.md`. Lists ONLY this repo's own topic codes; inherited (ACCORE / AyCode.Blazor / Mango.Nop) topics are reached through `@repo.own-dep-repos`.
|
|
||||||
|
|
||||||
**Foundational conventions are defined once at the framework layer — not restated here:**
|
|
||||||
- ID format, type codes (`I`/`T`/`B`/`C`), ID rules, Status vocabulary, archival, registry-maintenance workflow → `AyCode.Core/.github/skills/docs-check/references/TOPIC_CODES.md`
|
|
||||||
- `<PREFIX>` (this repo = `FBANKAPP`, from the `@repo.prefix` in `.github/copilot-instructions.md`) + `<RAND>` spec → `AyCode.Core/.github/REPO_PREFIXES.md`
|
|
||||||
|
|
||||||
## This repo's own topic codes
|
|
||||||
|
|
||||||
| Code | Topic | Scope | Docs location |
|
|
||||||
|----------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------|
|
|
||||||
| `DMODEL` | DATA-MODEL | FruitBank entitások adatmodell-normalizálása: nopCommerce referencia-FK-k szabad string helyett (Country/Currency), azonosító-szétválasztások (GTIN/VTSZ), és hasonló átmeneti adatmodell-megoldások. **Általános** — nem funkció-specifikus (pl. nem EKÁER). | `FruitBank.Common/docs/DATAMODEL/` |
|
|
||||||
File diff suppressed because one or more lines are too long
17
CLAUDE.md
17
CLAUDE.md
|
|
@ -1,17 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
||||||
## 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
|
|
||||||
|
|
||||||
## 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`
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
@ -19,13 +19,13 @@ namespace FruitBank.Common.Server
|
||||||
/// DateTime generic attribute on Product.
|
/// DateTime generic attribute on Product.
|
||||||
/// The start of the window during which this product is visible for preordering.
|
/// The start of the window during which this product is visible for preordering.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string PreOrderWindowStart = "PreOrderWindowStart";
|
public const string PreorderWindowStart = "PreorderWindowStart";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DateTime generic attribute on Product.
|
/// DateTime generic attribute on Product.
|
||||||
/// The end of the window during which this product is visible for preordering.
|
/// The end of the window during which this product is visible for preordering.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string PreOrderWindowEnd = "PreOrderWindowEnd";
|
public const string PreorderWindowEnd = "PreorderWindowEnd";
|
||||||
|
|
||||||
static FruitBankConst()
|
static FruitBankConst()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
# Interfaces
|
|
||||||
|
|
||||||
Server-side marker interfaces extending the shared Common interfaces. Used for DI registration and type safety.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`IFruitBankDataControllerServer.cs`** — Extends IFruitBankDataControllerCommon. Empty server marker.
|
|
||||||
- **`ICustomOrderSignalREndpointServer.cs`** — Extends ICustomOrderSignalREndpointCommon. Empty server marker.
|
|
||||||
- **`IStockSignalREndpointServer.cs`** — Extends IStockSignalREndpointCommon. Empty server marker.
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
# 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
|
|
||||||
|
|
||||||
| Folder | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| [`Interfaces/`](Interfaces/README.md) | Server-side endpoint marker interfaces |
|
|
||||||
| [`Services/`](Services/README.md) | SignalR hubs, broadcast service, logging |
|
|
||||||
|
|
||||||
## Key Files (Root)
|
|
||||||
|
|
||||||
- **`FruitBankConst.cs`** — Server constants: project GUID, role system names ("Measuring", "MeasuringRevisor"), product attribute "IsMeasurable", project salt.
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
- nopCommerce via Mango.Nop.Core
|
|
||||||
- AyCode.Core, AyCode.Services.Server (DLL references)
|
|
||||||
- Microsoft.AspNetCore.SignalR
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
using AyCode.Services.Nav.Ekaer;
|
|
||||||
using AyCode.Services.Nav.Ekaer.Models;
|
|
||||||
using FruitBank.Common.Entities;
|
|
||||||
using FruitBank.Common.Services.Ekaer;
|
|
||||||
|
|
||||||
namespace FruitBank.Common.Server.Services.Ekaer;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IFruitBankEkaerService"/>
|
|
||||||
/// <remarks>
|
|
||||||
/// A teljes lánc: <c>map</c> (<see cref="IShippingToEkaerMapper"/>, FruitBank.Common) →
|
|
||||||
/// <c>validate → send</c> (<see cref="IEkaerSubmitService"/>, AyCode.Services). A NAV-fiók hitelesítő adatait
|
|
||||||
/// (<c>INavCredentials</c>) és a saját cégadatokat (<see cref="EkaerCompanyInfo"/>) a DI szolgáltatja.
|
|
||||||
/// </remarks>
|
|
||||||
public sealed class FruitBankEkaerService : IFruitBankEkaerService
|
|
||||||
{
|
|
||||||
private readonly IShippingToEkaerMapper _mapper;
|
|
||||||
private readonly IEkaerSubmitService _submitService;
|
|
||||||
private readonly EkaerCompanyInfo _company;
|
|
||||||
|
|
||||||
public FruitBankEkaerService(IShippingToEkaerMapper mapper, IEkaerSubmitService submitService, EkaerCompanyInfo company)
|
|
||||||
{
|
|
||||||
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
|
|
||||||
_submitService = submitService ?? throw new ArgumentNullException(nameof(submitService));
|
|
||||||
_company = company ?? throw new ArgumentNullException(nameof(company));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<EkaerSubmitResult> SubmitShippingAsync(Shipping shipping, OperationType operation = OperationType.Create, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(shipping);
|
|
||||||
|
|
||||||
// map (FruitBank.Common) → submit: validate → send (AyCode.Services)
|
|
||||||
var operations = _mapper.MapShipping(shipping, _company, operation);
|
|
||||||
return _submitService.SubmitAsync(operations, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
using AyCode.Services.Nav.Ekaer;
|
|
||||||
using AyCode.Services.Nav.Ekaer.Models;
|
|
||||||
using FruitBank.Common.Entities;
|
|
||||||
|
|
||||||
namespace FruitBank.Common.Server.Services.Ekaer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A FruitBank szerver-oldali EKÁER-fogyasztója: egy <see cref="Shipping"/>-et leképez EKÁER tradeCard-okra
|
|
||||||
/// (a mapperrel), majd beküldi (az általános submit-orchestrátorral). Ez a vékony, projekt-specifikus réteg;
|
|
||||||
/// az általános NAV/EKÁER logika (validátor, submit, manage) az <c>AyCode.Services</c>-ben él.
|
|
||||||
/// </summary>
|
|
||||||
public interface IFruitBankEkaerService
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Leképezi és beküldi a szállítmányt. Az eredmény vagy validációs hibák (nem ment ki kérés),
|
|
||||||
/// vagy a NAV-válasz — lásd <see cref="EkaerSubmitResult"/>.
|
|
||||||
/// </summary>
|
|
||||||
Task<EkaerSubmitResult> SubmitShippingAsync(Shipping shipping, OperationType operation = OperationType.Create, CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
# Loggers
|
|
||||||
|
|
||||||
Server-side logging implementations.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`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.
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# Services
|
|
||||||
|
|
||||||
Server-side SignalR hubs, real-time broadcast, and logging infrastructure.
|
|
||||||
|
|
||||||
## Subfolders
|
|
||||||
|
|
||||||
| Folder | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| [`Loggers/`](Loggers/README.md) | Console and API controller log writers |
|
|
||||||
| [`SignalRs/`](SignalRs/README.md) | SignalR hubs and broadcast service |
|
|
||||||
|
|
@ -13,6 +13,22 @@ using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
namespace FruitBank.Common.Server.Services.SignalRs;
|
namespace FruitBank.Common.Server.Services.SignalRs;
|
||||||
|
|
||||||
|
//public class DevAdminSignalRHub : AcWebSignalRHubWithSessionBase<SignalRTags, Logger<DevAdminSignalRHub>>
|
||||||
|
//{
|
||||||
|
// public DevAdminSignalRHub(IConfiguration configuration, IEnumerable<IAcLogWriterBase> logWriters)
|
||||||
|
// : base(configuration, new Logger<DevAdminSignalRHub>(logWriters.ToArray()))
|
||||||
|
// {
|
||||||
|
// Logger.Info("DevAdminSignalRHub");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public Task ReceiveMessage(int messageTag, byte[]? message, int? requestId)
|
||||||
|
// {
|
||||||
|
|
||||||
|
// Clients.All.SendAsync("TestMessage", "Hello from server");
|
||||||
|
// }
|
||||||
|
|
||||||
|
//}
|
||||||
|
|
||||||
public class DevAdminSignalRHub : AcWebSignalRHubWithSessionBase<SignalRTags, Logger<DevAdminSignalRHub>>
|
public class DevAdminSignalRHub : AcWebSignalRHubWithSessionBase<SignalRTags, Logger<DevAdminSignalRHub>>
|
||||||
{
|
{
|
||||||
public DevAdminSignalRHub(IConfiguration configuration, IFruitBankDataControllerServer fruitBankDataController/*, SessionService sessionService*/,
|
public DevAdminSignalRHub(IConfiguration configuration, IFruitBankDataControllerServer fruitBankDataController/*, SessionService sessionService*/,
|
||||||
|
|
@ -21,13 +37,11 @@ public class DevAdminSignalRHub : AcWebSignalRHubWithSessionBase<SignalRTags, Lo
|
||||||
{
|
{
|
||||||
EnableBinaryDiagnostics = FruitBankConstClient.SignalRSerializerDiagnosticLog;
|
EnableBinaryDiagnostics = FruitBankConstClient.SignalRSerializerDiagnosticLog;
|
||||||
SerializerOptions = new AcBinarySerializerOptions();
|
SerializerOptions = new AcBinarySerializerOptions();
|
||||||
|
//SerializerOptions = new AcJsonSerializerOptions();
|
||||||
|
|
||||||
// Use the new lazy Registry - no reflection at construction time
|
DynamicMethodCallModels.Add(new AcDynamicMethodCallModel<SignalRAttribute>(fruitBankDataController));
|
||||||
DynamicMethodRegistry.CahcheSizeCapacity = 3;
|
DynamicMethodCallModels.Add(new AcDynamicMethodCallModel<SignalRAttribute>(customOrderSignalREndpoint));
|
||||||
|
DynamicMethodCallModels.Add(new AcDynamicMethodCallModel<SignalRAttribute>(stockSignalREndpointServer));
|
||||||
DynamicMethodRegistry.Register(fruitBankDataController);
|
|
||||||
DynamicMethodRegistry.Register(customOrderSignalREndpoint);
|
|
||||||
DynamicMethodRegistry.Register(stockSignalREndpointServer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LogContextUserNameAndId()
|
protected override void LogContextUserNameAndId()
|
||||||
|
|
@ -35,5 +49,83 @@ public class DevAdminSignalRHub : AcWebSignalRHubWithSessionBase<SignalRTags, Lo
|
||||||
return;
|
return;
|
||||||
base.LogContextUserNameAndId();
|
base.LogContextUserNameAndId();
|
||||||
}
|
}
|
||||||
// ...existing commented code...
|
//public override Task OnReceiveMessage(int messageTag, byte[]? message, int? requestId)
|
||||||
|
//{
|
||||||
|
// return ProcessOnReceiveMessage(messageTag, message, requestId, async tagName =>
|
||||||
|
// {
|
||||||
|
// switch (messageTag)
|
||||||
|
// {
|
||||||
|
// case SignalRTags.GetAddress:
|
||||||
|
// //var id = Guid.Parse((string)message!.MessagePackTo<SignalPostJsonDataMessage<IdMessage>>().PostData.Ids[0]);
|
||||||
|
// var id = message!.MessagePackTo<SignalPostJsonDataMessage<IdMessage>>().PostData.Ids[0].JsonTo<Guid[]>()![0];
|
||||||
|
|
||||||
|
// var address = await _adminDal.GetAddressByIdAsync(id);
|
||||||
|
// await ResponseToCaller(messageTag, new SignalResponseJsonMessage(messageTag, SignalResponseStatus.Success, address), requestId);
|
||||||
|
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// case SignalRTags.GetAddressesByContextId:
|
||||||
|
// //id = Guid.Parse((string)message!.MessagePackTo<SignalPostJsonDataMessage<IdMessage>>().PostData.Ids[0]);
|
||||||
|
// id = message!.MessagePackTo<SignalPostJsonDataMessage<IdMessage>>().PostData.Ids[0].JsonTo<Guid[]>()![0];
|
||||||
|
|
||||||
|
// address = await _adminDal.GetAddressByIdAsync(id);
|
||||||
|
// await ResponseToCaller(messageTag, new SignalResponseJsonMessage(messageTag, SignalResponseStatus.Success, new List<Address> { address! }), requestId);
|
||||||
|
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// case SignalRTags.UpdateAddress:
|
||||||
|
// address = message!.MessagePackTo<SignalPostJsonDataMessage<Address>>().PostData;
|
||||||
|
|
||||||
|
// await _adminDal.UpdateAddressAsync(address);
|
||||||
|
// await ResponseToCaller(messageTag, new SignalResponseJsonMessage(messageTag, SignalResponseStatus.Success, address), requestId);
|
||||||
|
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// case SignalRTags.UpdateProfile:
|
||||||
|
// var profile = message!.MessagePackTo<SignalPostJsonDataMessage<Profile>>().PostData;
|
||||||
|
|
||||||
|
// await _adminDal.UpdateProfileAsync(profile);
|
||||||
|
// await ResponseToCaller(messageTag, new SignalResponseJsonMessage(messageTag, SignalResponseStatus.Success, profile), requestId);
|
||||||
|
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// //case SignalRTags.GetTransfersAsync:
|
||||||
|
// // await ResponseToCaller(messageTag, new SignalResponseJsonMessage(SignalResponseStatus.Success, await _transferDataApiController.GetTransfers()), requestId);
|
||||||
|
|
||||||
|
// // return;
|
||||||
|
|
||||||
|
// //case SignalRTags.GetPropertiesByOwnerIdAsync:
|
||||||
|
// // var ownerId = message!.MessagePackTo<SignalRequestByIdMessage>().Id;
|
||||||
|
|
||||||
|
// // await ResponseToCaller(messageTag, new SignalResponseJsonMessage(SignalResponseStatus.Success, await _serviceProviderApiController.GetServiceProvidersByOwnerId(ownerId)), requestId);
|
||||||
|
|
||||||
|
// // return;
|
||||||
|
|
||||||
|
// //case SignalRTags.UpdateTransferAsync:
|
||||||
|
// // var transfer = message!.MessagePackTo<SignalPostJsonDataMessage<Transfer>>().PostData;
|
||||||
|
|
||||||
|
// // await _transferDataApiController.UpdateTransfer(transfer);
|
||||||
|
// // await ResponseToCaller(messageTag, new SignalResponseJsonMessage(SignalResponseStatus.Success, transfer), requestId);
|
||||||
|
|
||||||
|
// // return;
|
||||||
|
|
||||||
|
// //case SignalRTags.GetCompaniesAsync:
|
||||||
|
// // await ResponseToCaller(messageTag, new SignalResponseJsonMessage(SignalResponseStatus.Success, await _serviceProviderApiController.GetServiceProviders()), requestId);
|
||||||
|
|
||||||
|
// // return;
|
||||||
|
// //case SignalRTags.UpdateCompanyAsync:
|
||||||
|
|
||||||
|
// // var updateCompany = message!.MessagePackTo<SignalPostJsonDataMessage<Company>>().PostData;
|
||||||
|
|
||||||
|
// // await _serviceProviderApiController.UpdateServiceProvider(updateCompany);
|
||||||
|
// // await ResponseToCaller(messageTag, new SignalResponseJsonMessage(SignalResponseStatus.Success, updateCompany), requestId);
|
||||||
|
|
||||||
|
// // return;
|
||||||
|
|
||||||
|
// default:
|
||||||
|
// Logger.Error($"Server OnReceiveMessage; messageTag not found! {tagName}");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# SignalRs
|
|
||||||
|
|
||||||
SignalR hub implementations and real-time broadcast service.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`DevAdminSignalRHub.cs`** — Main admin hub. Dependencies: IConfiguration, IFruitBankDataControllerServer, ICustomOrderSignalREndpointServer, IStockSignalREndpointServer. Registers all three endpoint interfaces with DynamicMethodRegistry. Uses AcBinary serialization.
|
|
||||||
- **`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.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# Databases
|
|
||||||
|
|
||||||
Local in-memory database abstraction for offline/cached data using ConcurrentDictionary.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`DatabaseLocalBase.cs`** — Abstract base with generic table management for IEntityInt entities. Thread-safe AddTable, GetRows, GetRow, AddRow, AddRows, DeleteRow.
|
|
||||||
|
|
@ -1,15 +1,7 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using Mango.Nop.Core.Dtos;
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using Mango.Nop.Core.Dtos;
|
|
||||||
using Nop.Core.Domain.Common;
|
|
||||||
using Nop.Core.Domain.Orders;
|
|
||||||
|
|
||||||
namespace FruitBank.Common.Dtos;
|
namespace FruitBank.Common.Dtos;
|
||||||
|
|
||||||
[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)])]
|
|
||||||
public class GenericAttributeDto : MgGenericAttributeDto
|
public class GenericAttributeDto : MgGenericAttributeDto
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using AyCode.Core.Extensions;
|
using AyCode.Core.Extensions;
|
||||||
using AyCode.Core.Serializers.Attributes;
|
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using AyCode.Utils.Extensions;
|
using AyCode.Utils.Extensions;
|
||||||
using FruitBank.Common.Entities;
|
using FruitBank.Common.Entities;
|
||||||
using FruitBank.Common.Enums;
|
using FruitBank.Common.Enums;
|
||||||
|
|
@ -22,10 +20,6 @@ using System.Linq.Expressions;
|
||||||
|
|
||||||
namespace FruitBank.Common.Dtos;
|
namespace FruitBank.Common.Dtos;
|
||||||
|
|
||||||
[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)])]
|
|
||||||
public class OrderDto : MgOrderDto<OrderItemDto, ProductDto>, IOrderDto
|
public class OrderDto : MgOrderDto<OrderItemDto, ProductDto>, IOrderDto
|
||||||
{
|
{
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
|
@ -37,7 +31,6 @@ public class OrderDto : MgOrderDto<OrderItemDto, ProductDto>, IOrderDto
|
||||||
public List<GenericAttributeDto> GenericAttributes { get; set; }
|
public List<GenericAttributeDto> GenericAttributes { get; set; }
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => Id > 0 && OrderItemDtos.Count > 0 && OrderItemDtos.All(x => x.IsMeasured)", Constraints = "[#SmartTypeConstraints], readonly")]
|
|
||||||
public bool IsMeasured
|
public bool IsMeasured
|
||||||
{
|
{
|
||||||
get => IsMeasuredAndValid();
|
get => IsMeasuredAndValid();
|
||||||
|
|
@ -45,7 +38,6 @@ public class OrderDto : MgOrderDto<OrderItemDto, ProductDto>, IOrderDto
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => OrderItemDtos.Any(oi => oi.IsMeasurable)", Constraints = "[#SmartTypeConstraints], readonly")]
|
|
||||||
public bool IsMeasurable
|
public bool IsMeasurable
|
||||||
{
|
{
|
||||||
get => OrderItemDtos.Any(oi => oi.IsMeasurable);
|
get => OrderItemDtos.Any(oi => oi.IsMeasurable);
|
||||||
|
|
@ -73,23 +65,18 @@ public class OrderDto : MgOrderDto<OrderItemDto, ProductDto>, IOrderDto
|
||||||
public DateTime DateOfReceiptOrCreated => DateOfReceipt ?? CreatedOnUtc;
|
public DateTime DateOfReceiptOrCreated => DateOfReceipt ?? CreatedOnUtc;
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrNull<DateTime>('DateOfReceipt')")]
|
|
||||||
public DateTime? DateOfReceipt => GenericAttributes.GetValueOrNull<DateTime>(nameof(IOrderDto.DateOfReceipt));
|
public DateTime? DateOfReceipt => GenericAttributes.GetValueOrNull<DateTime>(nameof(IOrderDto.DateOfReceipt));
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault('RevisorId', 0)")]
|
|
||||||
public int RevisorId => GenericAttributes.GetValueOrDefault(nameof(IOrderDto.RevisorId), 0);
|
public int RevisorId => GenericAttributes.GetValueOrDefault(nameof(IOrderDto.RevisorId), 0);
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault('MeasurementOwnerId', 0)")]
|
|
||||||
public int MeasurementOwnerId => GenericAttributes.GetValueOrDefault(nameof(IOrderDto.MeasurementOwnerId), 0);
|
public int MeasurementOwnerId => GenericAttributes.GetValueOrDefault(nameof(IOrderDto.MeasurementOwnerId), 0);
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => OrderItemDtos.Count > 0 && OrderItemDtos.All(oi => oi.IsAudited)")]
|
|
||||||
public bool IsAllOrderItemAudited => OrderItemDtos.Count > 0 && OrderItemDtos.All(oi => oi.IsAudited);
|
public bool IsAllOrderItemAudited => OrderItemDtos.Count > 0 && OrderItemDtos.All(oi => oi.IsAudited);
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => OrderItemDtos.All(oi => oi.AverageWeightIsValid)")]
|
|
||||||
public bool IsAllOrderItemAvgWeightValid => OrderItemDtos.All(oi => oi.AverageWeightIsValid);
|
public bool IsAllOrderItemAvgWeightValid => OrderItemDtos.All(oi => oi.AverageWeightIsValid);
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
|
@ -114,7 +101,6 @@ public class OrderDto : MgOrderDto<OrderItemDto, ProductDto>, IOrderDto
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => OrderStatus == OrderStatus.Complete")]
|
|
||||||
public bool IsComplete => OrderStatus == OrderStatus.Complete;
|
public bool IsComplete => OrderStatus == OrderStatus.Complete;
|
||||||
|
|
||||||
public bool HasMeasuringAccess(int? customerId, bool isRevisorUser = false)
|
public bool HasMeasuringAccess(int? customerId, bool isRevisorUser = false)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using FruitBank.Common.Entities;
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Entities;
|
|
||||||
using FruitBank.Common.Enums;
|
using FruitBank.Common.Enums;
|
||||||
using FruitBank.Common.Interfaces;
|
using FruitBank.Common.Interfaces;
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
|
|
@ -16,10 +14,6 @@ using System.Linq.Expressions;
|
||||||
|
|
||||||
namespace FruitBank.Common.Dtos;
|
namespace FruitBank.Common.Dtos;
|
||||||
|
|
||||||
[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)])]
|
|
||||||
public class OrderItemDto : MgOrderItemDto<ProductDto>, IOrderItemDto
|
public class OrderItemDto : MgOrderItemDto<ProductDto>, IOrderItemDto
|
||||||
{
|
{
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
|
@ -37,7 +31,6 @@ public class OrderItemDto : MgOrderItemDto<ProductDto>, IOrderItemDto
|
||||||
public OrderDto OrderDto { get; set; }
|
public OrderDto OrderDto { get; set; }
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(Purpose = "Status flag", BusinessRule = "get => IsMeasuredAndValid()")]
|
|
||||||
public bool IsMeasured
|
public bool IsMeasured
|
||||||
{
|
{
|
||||||
get => IsMeasuredAndValid();
|
get => IsMeasuredAndValid();
|
||||||
|
|
@ -45,7 +38,6 @@ public class OrderItemDto : MgOrderItemDto<ProductDto>, IOrderItemDto
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(Purpose = "Status flag", BusinessRule = "get => ProductDto!.IsMeasurable")]
|
|
||||||
public bool IsMeasurable
|
public bool IsMeasurable
|
||||||
{
|
{
|
||||||
get => ProductDto!.IsMeasurable;
|
get => ProductDto!.IsMeasurable;
|
||||||
|
|
@ -53,7 +45,6 @@ public class OrderItemDto : MgOrderItemDto<ProductDto>, IOrderItemDto
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => OrderItemPallets.Sum(x => x.TrayQuantity)")]
|
|
||||||
public int TrayQuantity
|
public int TrayQuantity
|
||||||
{
|
{
|
||||||
get => OrderItemPallets.Sum(x => x.TrayQuantity);
|
get => OrderItemPallets.Sum(x => x.TrayQuantity);
|
||||||
|
|
@ -61,7 +52,6 @@ public class OrderItemDto : MgOrderItemDto<ProductDto>, IOrderItemDto
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => double.Round(OrderItemPallets.Sum(x => x.NetWeight), 1)")]
|
|
||||||
public double NetWeight
|
public double NetWeight
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -79,7 +69,6 @@ public class OrderItemDto : MgOrderItemDto<ProductDto>, IOrderItemDto
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => double.Round(OrderItemPallets.Sum(x => x.NetWeight), 1)")]
|
|
||||||
public double GrossWeight
|
public double GrossWeight
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -97,23 +86,18 @@ public class OrderItemDto : MgOrderItemDto<ProductDto>, IOrderItemDto
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => IsMeasurable && OrderItemPallets.Count > 0 ? double.Round(OrderItemPallets.Sum(oip => oip.AverageWeight) / OrderItemPallets.Count, 1) : 0d")]
|
|
||||||
public double AverageWeight => IsMeasurable && OrderItemPallets.Count > 0 ? double.Round(OrderItemPallets.Sum(oip => oip.AverageWeight) / OrderItemPallets.Count, 1) : 0d;
|
public double AverageWeight => IsMeasurable && OrderItemPallets.Count > 0 ? double.Round(OrderItemPallets.Sum(oip => oip.AverageWeight) / OrderItemPallets.Count, 1) : 0d;
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => IsMeasurable ? double.Round(ProductDto!.AverageWeight - AverageWeight, 1) : 0")]
|
|
||||||
public double AverageWeightDifference => IsMeasurable ? double.Round(ProductDto!.AverageWeight - AverageWeight, 1) : 0;
|
public double AverageWeightDifference => IsMeasurable ? double.Round(ProductDto!.AverageWeight - AverageWeight, 1) : 0;
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => !IsMeasurable || (ProductDto!.AverageWeight > 0 && ((AverageWeightDifference / ProductDto!.AverageWeight) * 100) < ProductDto!.AverageWeightTreshold)")]
|
|
||||||
public bool AverageWeightIsValid => !IsMeasurable || (ProductDto!.AverageWeight > 0 && Math.Abs((AverageWeightDifference / ProductDto!.AverageWeight) * 100) < ProductDto!.AverageWeightTreshold);
|
public bool AverageWeightIsValid => !IsMeasurable || (ProductDto!.AverageWeight > 0 && Math.Abs((AverageWeightDifference / ProductDto!.AverageWeight) * 100) < ProductDto!.AverageWeightTreshold);
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(Purpose = "Status flag", BusinessRule = "get => OrderItemPallets.Count > 0 && OrderItemPallets.All(oip => oip.IsAudited)")]
|
|
||||||
public bool IsAudited => OrderItemPallets.Count > 0 && OrderItemPallets.All(oip => oip.IsAudited);
|
public bool IsAudited => OrderItemPallets.Count > 0 && OrderItemPallets.All(oip => oip.IsAudited);
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => complex conditional logic based on IsAudited, IsMeasured, and OrderItemPallets status")]
|
|
||||||
public MeasuringStatus MeasuringStatus
|
public MeasuringStatus MeasuringStatus
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,26 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using FruitBank.Common.Interfaces;
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Interfaces;
|
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Dtos;
|
using Mango.Nop.Core.Dtos;
|
||||||
using Mango.Nop.Core.Extensions;
|
using Mango.Nop.Core.Extensions;
|
||||||
using Mango.Nop.Core.Interfaces.ForeignKeys;
|
using Mango.Nop.Core.Interfaces.ForeignKeys;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Nop.Core.Domain.Catalog;
|
|
||||||
|
|
||||||
//using Nop.Core.Domain.Catalog;
|
//using Nop.Core.Domain.Catalog;
|
||||||
using Nop.Core.Domain.Common;
|
using Nop.Core.Domain.Common;
|
||||||
using Nop.Core.Domain.Orders;
|
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
namespace FruitBank.Common.Dtos;
|
namespace FruitBank.Common.Dtos;
|
||||||
|
|
||||||
[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)])]
|
|
||||||
public class ProductDto : MgProductDto, IProductDto
|
public class ProductDto : MgProductDto, IProductDto
|
||||||
{
|
{
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
private static Expression<Func<ProductDto, GenericAttributeDto, bool>> RelationWithGenericAttribute => (orderItemDto, genericAttributeDto) =>
|
private static Expression<Func<ProductDto, GenericAttributeDto, bool>> RelationWithGenericAttribute => (orderItemDto, genericAttributeDto) =>
|
||||||
orderItemDto.Id == genericAttributeDto.EntityId && genericAttributeDto.KeyGroup == nameof(Product);// nameof(Product);
|
orderItemDto.Id == genericAttributeDto.EntityId && genericAttributeDto.KeyGroup == "Product";// nameof(Product);
|
||||||
|
|
||||||
|
|
||||||
[Association(ThisKey = nameof(Id), OtherKey = nameof(GenericAttributeDto.EntityId), ExpressionPredicate = nameof(RelationWithGenericAttribute), CanBeNull = false)]
|
[Association(ThisKey = nameof(Id), OtherKey = nameof(GenericAttributeDto.EntityId), ExpressionPredicate = nameof(RelationWithGenericAttribute), CanBeNull = false)]
|
||||||
public List<GenericAttributeDto> GenericAttributes { get; set; }
|
public List<GenericAttributeDto> GenericAttributes { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A nopCommerce <c>Product.Gtin</c> oszlopa. <b>Átmenetileg az EKÁER VTSZ-t (vámtarifaszámot) tárolja</b> —
|
|
||||||
/// a jövőbeli <c>ShippingToEkaerMapper</c> innen olvassa a <c>tradeCardItem.productVtsz</c> értékét.
|
|
||||||
/// ⚠️ A GTIN ≠ VTSZ (a GTIN globális kereskedelmi cikkszám, a VTSZ vámtarifaszám). Külön mezőbe választandó —
|
|
||||||
/// lásd <c>FruitBank.Common/docs/DATAMODEL/DATAMODEL_ISSUES.md#fbankapp-dmodel-i-p6x4</c>.
|
|
||||||
/// </summary>
|
|
||||||
[LinqToDB.Mapping.Column(nameof(Product.Gtin))]
|
|
||||||
[ToonDescription(Purpose = "nopCommerce Gtin column — holds the EKÁER VTSZ (customs tariff number) used as the trade-card item productVtsz in NAV road-freight reporting.")]
|
|
||||||
public string? Gtin { get; set; }
|
|
||||||
|
|
||||||
public ProductDto() :base()
|
public ProductDto() :base()
|
||||||
{ }
|
{ }
|
||||||
public ProductDto(int productId) : base(productId)
|
public ProductDto(int productId) : base(productId)
|
||||||
|
|
@ -48,7 +29,6 @@ public class ProductDto : MgProductDto, IProductDto
|
||||||
//{ }
|
//{ }
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(Purpose = "Master flag: if false, the system bypasses weight validation but still creates one Measurement Record (PalletItem) with TrayQuantity.", BusinessRule = "get => GenericAttributes.GetValueOrDefault<bool>('IsMeasurable')")]
|
|
||||||
public bool IsMeasurable
|
public bool IsMeasurable
|
||||||
{
|
{
|
||||||
get => GenericAttributes.GetValueOrDefault<bool>(nameof(IMeasurable.IsMeasurable));
|
get => GenericAttributes.GetValueOrDefault<bool>(nameof(IMeasurable.IsMeasurable));
|
||||||
|
|
@ -63,7 +43,6 @@ public class ProductDto : MgProductDto, IProductDto
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault<double>('Tare')")]
|
|
||||||
public double Tare
|
public double Tare
|
||||||
{
|
{
|
||||||
get => GenericAttributes.GetValueOrDefault<double>(nameof(ITare.Tare));
|
get => GenericAttributes.GetValueOrDefault<double>(nameof(ITare.Tare));
|
||||||
|
|
@ -72,7 +51,6 @@ public class ProductDto : MgProductDto, IProductDto
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault<double>('NetWeight')")]
|
|
||||||
public double NetWeight
|
public double NetWeight
|
||||||
{
|
{
|
||||||
get => GenericAttributes.GetValueOrDefault<double>(nameof(IMeasuringNetWeight.NetWeight));
|
get => GenericAttributes.GetValueOrDefault<double>(nameof(IMeasuringNetWeight.NetWeight));
|
||||||
|
|
@ -80,7 +58,6 @@ public class ProductDto : MgProductDto, IProductDto
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault<int>('IncomingQuantity')")]
|
|
||||||
public int IncomingQuantity
|
public int IncomingQuantity
|
||||||
{
|
{
|
||||||
get => GenericAttributes.GetValueOrDefault<int>(nameof(IIncomingQuantity.IncomingQuantity));
|
get => GenericAttributes.GetValueOrDefault<int>(nameof(IIncomingQuantity.IncomingQuantity));
|
||||||
|
|
@ -88,22 +65,19 @@ public class ProductDto : MgProductDto, IProductDto
|
||||||
//set
|
//set
|
||||||
//{
|
//{
|
||||||
// var ga = GenericAttributes.FirstOrDefault(ga => ga.Key == nameof(IIncomingQuantity.IncomingQuantity)) ??
|
// var ga = GenericAttributes.FirstOrDefault(ga => ga.Key == nameof(IIncomingQuantity.IncomingQuantity)) ??
|
||||||
// GenericAttributes.AddNewGenericAttribute(nameof(Product), nameof(IIncomingQuantity.IncomingQuantity), value.ToString(), Id);
|
// GenericAttributes.AddNewGenericAttribute("Product", nameof(IIncomingQuantity.IncomingQuantity), value.ToString(), Id);
|
||||||
|
|
||||||
// ga.Value = value.ToString();
|
// ga.Value = value.ToString();
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => StockQuantity + IncomingQuantity")]
|
|
||||||
public int AvailableQuantity => StockQuantity + IncomingQuantity;
|
public int AvailableQuantity => StockQuantity + IncomingQuantity;
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault<double>('AverageWeight')")]
|
|
||||||
public double AverageWeight => GenericAttributes.GetValueOrDefault<double>(nameof(IProductDto.AverageWeight));
|
public double AverageWeight => GenericAttributes.GetValueOrDefault<double>(nameof(IProductDto.AverageWeight));
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault<double>('AverageWeightTreshold')")]
|
|
||||||
public double AverageWeightTreshold => GenericAttributes.GetValueOrDefault<double>(nameof(IProductDto.AverageWeightTreshold));
|
public double AverageWeightTreshold => GenericAttributes.GetValueOrDefault<double>(nameof(IProductDto.AverageWeightTreshold));
|
||||||
|
|
||||||
public bool HasMeasuringValues() => Id > 0 && NetWeight != 0 && IsMeasurable;
|
public bool HasMeasuringValues() => Id > 0 && NetWeight != 0 && IsMeasurable;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
# Dtos
|
|
||||||
|
|
||||||
Binary-serializable DTOs for efficient SignalR communication. All marked with `[AcBinarySerializable]`.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`OrderDto.cs`** — Order with items, measurement status, auditing, receipt date, GenericAttributes. Computed: IsMeasured, IsComplete, MeasuringStatus, RevisorId.
|
|
||||||
- **`OrderItemDto.cs`** — Order line item with OrderItemPallet collection. Computed: NetWeight, GrossWeight, AverageWeight, AverageWeightIsValid, IsMeasured, IsAudited.
|
|
||||||
- **`ProductDto.cs`** — Product with GenericAttribute-backed properties: IsMeasurable, Tare, AverageWeight, AverageWeightTreshold, IncomingQuantity, NetWeight.
|
|
||||||
- **`StockQuantityHistoryDto.cs`** — Stock history with net weight adjustments and inconsistency detection.
|
|
||||||
- **`GenericAttributeDto.cs`** — Key-value attribute wrapper. Polymorphic: KeyGroup = owner type, EntityId = owner ID.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
@ -1,31 +1,22 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using FruitBank.Common.Interfaces;
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Interfaces;
|
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Dtos;
|
using Mango.Nop.Core.Dtos;
|
||||||
using Mango.Nop.Core.Entities;
|
using Mango.Nop.Core.Entities;
|
||||||
using Mango.Nop.Core.Interfaces;
|
using Mango.Nop.Core.Interfaces;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Nop.Core.Domain.Catalog;
|
using Nop.Core.Domain.Catalog;
|
||||||
using Nop.Core.Domain.Common;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using FruitBank.Common.Entities;
|
|
||||||
|
|
||||||
namespace FruitBank.Common.Dtos
|
namespace FruitBank.Common.Dtos
|
||||||
{
|
{
|
||||||
[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)])]
|
|
||||||
public class StockQuantityHistoryDto : MgStockQuantityHistoryDto<ProductDto>, IStockQuantityHistoryDto
|
public class StockQuantityHistoryDto : MgStockQuantityHistoryDto<ProductDto>, IStockQuantityHistoryDto
|
||||||
{
|
{
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => StockQuantityHistoryExt?.StockQuantityHistoryId")]
|
|
||||||
public int? StockQuantityHistoryId
|
public int? StockQuantityHistoryId
|
||||||
{
|
{
|
||||||
get => StockQuantityHistoryExt?.StockQuantityHistoryId;
|
get => StockQuantityHistoryExt?.StockQuantityHistoryId;
|
||||||
|
|
@ -33,7 +24,6 @@ namespace FruitBank.Common.Dtos
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => StockQuantityHistoryExt?.NetWeightAdjustment")]
|
|
||||||
public double? NetWeightAdjustment
|
public double? NetWeightAdjustment
|
||||||
{
|
{
|
||||||
get => StockQuantityHistoryExt?.NetWeightAdjustment;
|
get => StockQuantityHistoryExt?.NetWeightAdjustment;
|
||||||
|
|
@ -41,7 +31,6 @@ namespace FruitBank.Common.Dtos
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => StockQuantityHistoryExt?.NetWeight")]
|
|
||||||
public double? NetWeight
|
public double? NetWeight
|
||||||
{
|
{
|
||||||
get => StockQuantityHistoryExt?.NetWeight;
|
get => StockQuantityHistoryExt?.NetWeight;
|
||||||
|
|
@ -49,7 +38,6 @@ namespace FruitBank.Common.Dtos
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(Purpose = "Status flag", BusinessRule = "get => StockQuantityHistoryExt?.IsInconsistent ?? false")]
|
|
||||||
public bool IsInconsistent
|
public bool IsInconsistent
|
||||||
{
|
{
|
||||||
get => StockQuantityHistoryExt?.IsInconsistent ?? false;
|
get => StockQuantityHistoryExt?.IsInconsistent ?? false;
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Interfaces;
|
|
||||||
using LinqToDB.Mapping;
|
|
||||||
using Mango.Nop.Core.Entities;
|
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
|
||||||
|
|
||||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
|
||||||
[ToonDescription("Transport / haulage company (carrier) with its vehicle fleet", Purpose = "A carrier that delivers goods to the warehouse and owns the CargoTrucks (both trucks and trailers). Distinct from Partner, which is the goods supplier. Name, address and tax fields are inherited from PartnerBase.")]
|
|
||||||
[Table(Name = FruitBankConstClient.CargoPartnerDbTableName)]
|
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.CargoPartnerDbTableName)]
|
|
||||||
public sealed class CargoPartner : PartnerBase, ICargoPartner
|
|
||||||
{
|
|
||||||
[Association(ThisKey = nameof(Id), OtherKey = nameof(CargoTruck.CargoPartnerId), CanBeNull = true)]
|
|
||||||
public List<CargoTruck>? CargoTrucks { get; set; }
|
|
||||||
|
|
||||||
//[Association(ThisKey = nameof(Id), OtherKey = nameof(Shipping.CargoPartnerId), CanBeNull = true)]
|
|
||||||
//public List<Shipping>? Shippings { get; set; }
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -4,7 +4,7 @@ using Mango.Nop.Core.Entities;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
public sealed class CustomerCredit: MgEntityBase, IEntityComment
|
public class CustomerCredit: MgEntityBase, IEntityComment
|
||||||
{
|
{
|
||||||
public int CustomerId { get; set; }
|
public int CustomerId { get; set; }
|
||||||
public decimal CreditLimit { get; set; }
|
public decimal CreditLimit { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,12 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using FruitBank.Common.Interfaces;
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Interfaces;
|
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Entities;
|
using Mango.Nop.Core.Entities;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
[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)]
|
[Table(Name = FruitBankConstClient.FilesDbTableName)]
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.FilesDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.FilesDbTableName)]
|
||||||
public sealed class Files : MgEntityBase, IFiles
|
public class Files : MgEntityBase, IFiles
|
||||||
{
|
{
|
||||||
public string FileName { get; set; }
|
public string FileName { get; set; }
|
||||||
public string FileSubPath { get; set; }
|
public string FileSubPath { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Dtos;
|
using FruitBank.Common.Dtos;
|
||||||
using FruitBank.Common.Enums;
|
using FruitBank.Common.Enums;
|
||||||
using FruitBank.Common.Interfaces;
|
using FruitBank.Common.Interfaces;
|
||||||
|
|
@ -10,8 +8,6 @@ using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
[ToonDescription("Base class for pallet measurements with net weight calculation",
|
|
||||||
Purpose = "Technically named 'Pallet' for legacy reasons, but represents a General Measurement Record. It is ALWAYS created for every item. If the product is not measurable, weights are 0 and only TrayQuantity is used.")]
|
|
||||||
public abstract class MeasuringItemPalletBase : MgEntityBase, IMeasuringItemPalletBase
|
public abstract class MeasuringItemPalletBase : MgEntityBase, IMeasuringItemPalletBase
|
||||||
{
|
{
|
||||||
private double _palletWeight;
|
private double _palletWeight;
|
||||||
|
|
@ -20,12 +16,9 @@ public abstract class MeasuringItemPalletBase : MgEntityBase, IMeasuringItemPall
|
||||||
|
|
||||||
[NotColumn]
|
[NotColumn]
|
||||||
protected int ForeignItemId;
|
protected int ForeignItemId;
|
||||||
|
|
||||||
[NotColumn]
|
[NotColumn]
|
||||||
[ToonDescription(BusinessRule = "get => ForeignItemId", Constraints = "[#SmartTypeConstraints]")]
|
|
||||||
public int ForeignKey => ForeignItemId;
|
public int ForeignKey => ForeignItemId;
|
||||||
|
|
||||||
[ToonDescription(Purpose = "Always recorded, regardless of measurability")]
|
|
||||||
public int TrayQuantity { get; set; }
|
public int TrayQuantity { get; set; }
|
||||||
|
|
||||||
[Column(DataType = DataType.DecFloat)]
|
[Column(DataType = DataType.DecFloat)]
|
||||||
|
|
@ -36,7 +29,6 @@ public abstract class MeasuringItemPalletBase : MgEntityBase, IMeasuringItemPall
|
||||||
}
|
}
|
||||||
|
|
||||||
[Column(DataType = DataType.DecFloat)]
|
[Column(DataType = DataType.DecFloat)]
|
||||||
[ToonDescription(Purpose = "Weight of the physical pallet if used; 0.0 if goods arrive without a pallet")]
|
|
||||||
public double PalletWeight
|
public double PalletWeight
|
||||||
{
|
{
|
||||||
get => _palletWeight;
|
get => _palletWeight;
|
||||||
|
|
@ -44,7 +36,6 @@ public abstract class MeasuringItemPalletBase : MgEntityBase, IMeasuringItemPall
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, System.ComponentModel.DataAnnotations.Schema.NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, System.ComponentModel.DataAnnotations.Schema.NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => GrossWeight - PalletWeight - (TrayQuantity * TareWeight)", Constraints = "[#SmartTypeConstraints], readonly")]
|
|
||||||
public double NetWeight
|
public double NetWeight
|
||||||
{
|
{
|
||||||
get => CalculateNetWeight();
|
get => CalculateNetWeight();
|
||||||
|
|
@ -52,7 +43,6 @@ public abstract class MeasuringItemPalletBase : MgEntityBase, IMeasuringItemPall
|
||||||
}
|
}
|
||||||
|
|
||||||
[Column(DataType = DataType.DecFloat, CanBeNull = false)]
|
[Column(DataType = DataType.DecFloat, CanBeNull = false)]
|
||||||
[ToonDescription(Purpose = "Measured gross weight; 0.0 if product is not measurable")]
|
|
||||||
public double GrossWeight
|
public double GrossWeight
|
||||||
{
|
{
|
||||||
get => _grossWeight;
|
get => _grossWeight;
|
||||||
|
|
@ -70,7 +60,6 @@ public abstract class MeasuringItemPalletBase : MgEntityBase, IMeasuringItemPall
|
||||||
public DateTime Modified { get; set; }
|
public DateTime Modified { get; set; }
|
||||||
|
|
||||||
[NotColumn, System.ComponentModel.DataAnnotations.Schema.NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, System.ComponentModel.DataAnnotations.Schema.NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => IsMeasured ? MeasuringStatus.Finnished : Id > 0 ? MeasuringStatus.Started : MeasuringStatus.NotStarted")]
|
|
||||||
public virtual MeasuringStatus MeasuringStatus => IsMeasured ? MeasuringStatus.Finnished : Id > 0 ? MeasuringStatus.Started : MeasuringStatus.NotStarted;
|
public virtual MeasuringStatus MeasuringStatus => IsMeasured ? MeasuringStatus.Finnished : Id > 0 ? MeasuringStatus.Started : MeasuringStatus.NotStarted;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Dtos;
|
using FruitBank.Common.Dtos;
|
||||||
using FruitBank.Common.Enums;
|
using FruitBank.Common.Enums;
|
||||||
using FruitBank.Common.Interfaces;
|
using FruitBank.Common.Interfaces;
|
||||||
|
|
@ -12,11 +10,9 @@ using Table = LinqToDB.Mapping.TableAttribute;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
[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)]
|
[Table(Name = FruitBankConstClient.OrderItemPalletDbTableName)]
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.OrderItemPalletDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.OrderItemPalletDbTableName)]
|
||||||
public sealed class OrderItemPallet : MeasuringItemPalletBase, IOrderItemPallet
|
public class OrderItemPallet : MeasuringItemPalletBase, IOrderItemPallet
|
||||||
{
|
{
|
||||||
public int OrderItemId
|
public int OrderItemId
|
||||||
{
|
{
|
||||||
|
|
@ -24,11 +20,7 @@ public sealed class OrderItemPallet : MeasuringItemPalletBase, IOrderItemPallet
|
||||||
set => ForeignItemId = value;
|
set => ForeignItemId = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[ToonDescription(Purpose = "User/Customer ID of the quality auditor")]
|
|
||||||
public int RevisorId { get; set; }
|
public int RevisorId { get; set; }
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
|
||||||
[ToonDescription(Purpose = "Status flag", BusinessRule = "get => RevisorId > 0")]
|
|
||||||
public bool IsAudited => RevisorId > 0;
|
public bool IsAudited => RevisorId > 0;
|
||||||
|
|
||||||
//[JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
//[JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
|
@ -36,7 +28,6 @@ public sealed class OrderItemPallet : MeasuringItemPalletBase, IOrderItemPallet
|
||||||
public OrderItemDto? OrderItemDto { get; set; }
|
public OrderItemDto? OrderItemDto { get; set; }
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => IsAudited ? MeasuringStatus.Audited : base.MeasuringStatus")]
|
|
||||||
public override MeasuringStatus MeasuringStatus => IsAudited ? MeasuringStatus.Audited : base.MeasuringStatus;
|
public override MeasuringStatus MeasuringStatus => IsAudited ? MeasuringStatus.Audited : base.MeasuringStatus;
|
||||||
public override double CalculateNetWeight() => base.CalculateNetWeight();
|
public override double CalculateNetWeight() => base.CalculateNetWeight();
|
||||||
|
|
||||||
|
|
@ -46,7 +37,6 @@ public sealed class OrderItemPallet : MeasuringItemPalletBase, IOrderItemPallet
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => double.Round(NetWeight / TrayQuantity, 1)")]
|
|
||||||
public double AverageWeight => double.Round(NetWeight / TrayQuantity, 1);
|
public double AverageWeight => double.Round(NetWeight / TrayQuantity, 1);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,13 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using FruitBank.Common.Interfaces;
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Interfaces;
|
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Entities;
|
using Mango.Nop.Core.Entities;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
|
||||||
[ToonDescription("Pallet type definition with size and weight")]
|
|
||||||
[Table(Name = FruitBankConstClient.PalletDbTableName)]
|
[Table(Name = FruitBankConstClient.PalletDbTableName)]
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PalletDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PalletDbTableName)]
|
||||||
public sealed class Pallet : MgEntityBase, IPallet
|
public class Pallet : MgEntityBase, IPallet
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Size { get; set; }
|
public string Size { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,29 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using FruitBank.Common.Interfaces;
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Interfaces;
|
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Entities;
|
using Mango.Nop.Core.Entities;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
[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)]
|
[Table(Name = FruitBankConstClient.PartnerDbTableName)]
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PartnerDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PartnerDbTableName)]
|
||||||
public sealed class Partner : PartnerBase, IPartner
|
public class Partner : MgEntityBase, IPartner
|
||||||
{
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string TaxId { get; set; }
|
||||||
|
public string CertificationNumber { get; set; }
|
||||||
|
|
||||||
|
public string PostalCode { get; set; }
|
||||||
|
public string Country { get; set; }
|
||||||
|
public string State { get; set; }
|
||||||
|
public string County { get; set; }
|
||||||
|
public string City { get; set; }
|
||||||
|
public string Street { get; set; }
|
||||||
|
|
||||||
[Association(ThisKey = nameof(Id), OtherKey = nameof(ShippingDocument.ShippingId), CanBeNull = true)]
|
[Association(ThisKey = nameof(Id), OtherKey = nameof(ShippingDocument.ShippingId), CanBeNull = true)]
|
||||||
public List<ShippingDocument>? ShippingDocuments { get; set; }
|
public List<ShippingDocument>? ShippingDocuments { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
[SkipValuesOnUpdate]
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
public DateTime Modified { get; set; }
|
||||||
}
|
}
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using AyCode.Entities;
|
|
||||||
using FruitBank.Common.Interfaces;
|
|
||||||
using LinqToDB.Mapping;
|
|
||||||
using Mango.Nop.Core.Entities;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
using AyCode.Core.Interfaces;
|
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
|
||||||
|
|
||||||
public abstract class PartnerBase : MgEntityBase, IPartnerBase
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string TaxId { get; set; }
|
|
||||||
public string CertificationNumber { get; set; }
|
|
||||||
|
|
||||||
public string CountryCode { get; set; }
|
|
||||||
|
|
||||||
[ToonDescription(Purpose = "ISO 4217 currency code the company trades and settles in with this partner (e.g. EUR, HUF). For supplier partners it is the source currency for converting shipping-item values to example: HUF in NAV EKÁER reporting.")]
|
|
||||||
public string Currency { get; set; }
|
|
||||||
public string PostalCode { get; set; }
|
|
||||||
public string Country { get; set; }
|
|
||||||
public string State { get; set; }
|
|
||||||
public string County { get; set; }
|
|
||||||
public string City { get; set; }
|
|
||||||
public string Street { get; set; }
|
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
|
||||||
[ToonDescription(Purpose = "The PostalCode + City + Street joined into a single-line postal address (non-empty parts).")]
|
|
||||||
public string? FullAddress => this.ComposeFullAddress();
|
|
||||||
|
|
||||||
|
|
||||||
[SkipValuesOnUpdate]
|
|
||||||
public DateTime Created { get; set; }
|
|
||||||
public DateTime Modified { get; set; }
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,35 +1,17 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using FruitBank.Common.Enums;
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Enums;
|
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Entities;
|
using Mango.Nop.Core.Entities;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
|
||||||
[Table(Name = FruitBankConstClient.PreOrderItemDbTableName)]
|
[Table(Name = FruitBankConstClient.PreOrderItemDbTableName)]
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PreOrderItemDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PreOrderItemDbTableName)]
|
||||||
[ToonDescription("Single product line of a customer preorder with fulfilment tracking", Purpose = "A requested product line within a PreOrder. Tracks requested versus cumulatively fulfilled quantity as incoming stock is allocated across one or more shipping-document conversion runs.")]
|
public class PreorderItem : MgEntityBase
|
||||||
public sealed class PreOrderItem : MgEntityBase
|
|
||||||
{
|
{
|
||||||
[ToonDescription(Purpose = "FK to the parent PreOrder.")]
|
public int PreorderId { get; set; }
|
||||||
public int PreOrderId { get; set; }
|
|
||||||
|
|
||||||
[ToonDescription(Purpose = "FK to the nopCommerce Product being preordered.")]
|
|
||||||
public int ProductId { get; set; }
|
public int ProductId { get; set; }
|
||||||
|
|
||||||
[ToonDescription(Purpose = "Quantity of the product the customer requested.", Constraints = "positive")]
|
|
||||||
public int RequestedQuantity { get; set; }
|
public int RequestedQuantity { get; set; }
|
||||||
|
|
||||||
[ToonDescription(Purpose = "Quantity allocated from incoming stock so far; accumulates across conversion runs until it reaches RequestedQuantity.",
|
|
||||||
BusinessRule = "this >= 0 && this <= RequestedQuantity")]
|
|
||||||
public int FulfilledQuantity { get; set; }
|
public int FulfilledQuantity { get; set; }
|
||||||
|
|
||||||
[ToonDescription(Purpose = "Gross unit price locked at preorder time. Used as the order-item price on conversion for non-measurable products; measurable products are priced 0 at conversion and weighed afterwards.",
|
|
||||||
Constraints = "non-negative")]
|
|
||||||
public decimal UnitPriceInclTax { get; set; }
|
public decimal UnitPriceInclTax { get; set; }
|
||||||
|
public PreorderItemStatus Status { get; set; }
|
||||||
[ToonDescription(Purpose = "Item lifecycle: Pending / Fulfilled (fully allocated) / PartiallyFulfilled (partly allocated) / Dropped (expired or no incoming stock).",
|
|
||||||
BusinessRule = "set during conversion: FulfilledQuantity >= RequestedQuantity ? Fulfilled : FulfilledQuantity > 0 ? PartiallyFulfilled : Dropped; stays Pending until first allocation")]
|
|
||||||
public PreOrderItemStatus Status { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
# Entities
|
|
||||||
|
|
||||||
Domain entities for inbound/outbound goods tracking and inventory. All map to `fb`-prefixed database tables.
|
|
||||||
|
|
||||||
## Shipping (Inbound)
|
|
||||||
|
|
||||||
- **`Shipping.cs`** — Physical delivery event (truck arrival). Table: `fbShipping`.
|
|
||||||
- **`ShippingDocument.cs`** — Supplier delivery note/invoice. Table: `fbShippingDocument`.
|
|
||||||
- **`ShippingItem.cs`** — Product line on document with declared vs measured discrepancies. Table: `fbShippingItem`.
|
|
||||||
- **`ShippingItemPallet.cs`** — Measurement record for incoming goods. Table: `fbShippingItemPallet`.
|
|
||||||
- **`ShippingDocumentToFiles.cs`** — Many-to-many link: document ↔ file with DocumentType. Table: `fbShippingDocumentToFiles`.
|
|
||||||
- **`Partner.cs`** — External supplier with address and tax info. Table: `fbPartner`.
|
|
||||||
|
|
||||||
## Cargo / Logistics
|
|
||||||
|
|
||||||
- **`CargoPartner.cs`** — Freight/haulage partner (carrier). Distinct from `Partner` (supplier) — this is the transport side. Has `CargoTrucks` and `Shippings` collections. Table: `fbCargoPartner`.
|
|
||||||
- **`CargoTruck.cs`** — Individual truck belonging to a `CargoPartner` (`LicencePlate`, `CountryCode`, `IsTrailer` for trailers). Table: `fbCargoTruck`.
|
|
||||||
|
|
||||||
## Order (Outbound)
|
|
||||||
|
|
||||||
- **`OrderItemPallet.cs`** — Measurement record for outgoing goods with RevisorId for audit. Table: `fbOrderItemPallet`.
|
|
||||||
|
|
||||||
## Inventory
|
|
||||||
|
|
||||||
- **`StockTaking.cs`** — Inventory session record. Table: `fbStockTaking`.
|
|
||||||
- **`StockTakingItem.cs`** — Line item reconciling snapshot vs measured quantities. Table: `fbStockTakingItem`.
|
|
||||||
- **`StockTakingItemPallet.cs`** — Measurement record for inventory. Table: `fbStockTakingItemPallet`.
|
|
||||||
- **`StockQuantityHistoryExt.cs`** — Extended weight metadata for stock reconciliation.
|
|
||||||
|
|
||||||
## Shared
|
|
||||||
|
|
||||||
- **`MeasuringItemPalletBase.cs`** — Abstract base for all three measurement hierarchies. Defines NetWeight formula, validation methods, CreatorId/ModifierId tracking.
|
|
||||||
- **`Pallet.cs`** — Physical pallet type definition (name, size, weight). Table: `fbPallet`.
|
|
||||||
- **`Files.cs`** — Uploaded file with OCR-extracted RawText. Table: `fbFiles`.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
@ -1,22 +1,14 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using AyCode.Interfaces.EntityComment;
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using AyCode.Interfaces.EntityComment;
|
|
||||||
using FruitBank.Common.Interfaces;
|
using FruitBank.Common.Interfaces;
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Entities;
|
using Mango.Nop.Core.Entities;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
|
||||||
[ToonDescription("Shipping record with documents and measurement tracking", Purpose = "Inbound delivery event (truck arrival) at the warehouse. Created early and filled progressively — carrier, truck and trailer are assigned later.")]
|
|
||||||
[Table(Name = FruitBankConstClient.ShippingDbTableName)]
|
[Table(Name = FruitBankConstClient.ShippingDbTableName)]
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingDbTableName)]
|
||||||
public sealed class Shipping : MgEntityBase, IShipping, IEntityComment
|
public class Shipping : MgEntityBase, IShipping, IEntityComment
|
||||||
{
|
{
|
||||||
public int? CargoPartnerId { get; set; }
|
|
||||||
public int? CargoTruckId { get; set; }
|
|
||||||
public int? CargoTrailerId { get; set; }
|
|
||||||
|
|
||||||
public DateTime ShippingDate { get; set; } = DateTime.Now;
|
public DateTime ShippingDate { get; set; } = DateTime.Now;
|
||||||
public string LicencePlate { get; set; }
|
public string LicencePlate { get; set; }
|
||||||
public bool IsAllMeasured { get; set; }
|
public bool IsAllMeasured { get; set; }
|
||||||
|
|
@ -25,18 +17,6 @@ public sealed class Shipping : MgEntityBase, IShipping, IEntityComment
|
||||||
|
|
||||||
public DateTime? MeasuredDate { get; set; }
|
public DateTime? MeasuredDate { get; set; }
|
||||||
|
|
||||||
[ToonDescription(Purpose = "Carrier (transport company); assigned later, null until known. Supplier is separate — see ShippingDocument.Partner.")]
|
|
||||||
[Association(ThisKey = nameof(CargoPartnerId), OtherKey = nameof(CargoPartner.Id), CanBeNull = true)]
|
|
||||||
public CargoPartner CargoPartner { get; set; }
|
|
||||||
|
|
||||||
[ToonDescription(Purpose = "Tractor unit (CargoTruck, IsTrailer=false) from the carrier's fleet; assigned later, null until known.")]
|
|
||||||
[Association(ThisKey = nameof(CargoTruckId), OtherKey = nameof(CargoTruck.Id), CanBeNull = true)]
|
|
||||||
public CargoTruck CargoTruck { get; set; }
|
|
||||||
|
|
||||||
[ToonDescription(Purpose = "Trailer (CargoTruck table, IsTrailer=true); optional, assigned later — null if none or not yet known.")]
|
|
||||||
[Association(ThisKey = nameof(CargoTrailerId), OtherKey = nameof(CargoTruck.Id), CanBeNull = true)]
|
|
||||||
public CargoTruck CargoTrailer { get; set; }
|
|
||||||
|
|
||||||
[Association(ThisKey = nameof(Id), OtherKey = nameof(ShippingDocument.ShippingId), CanBeNull = true)]
|
[Association(ThisKey = nameof(Id), OtherKey = nameof(ShippingDocument.ShippingId), CanBeNull = true)]
|
||||||
public List<ShippingDocument>? ShippingDocuments { get; set; }
|
public List<ShippingDocument>? ShippingDocuments { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,13 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using System.Collections.ObjectModel;
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Interfaces;
|
using FruitBank.Common.Interfaces;
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Entities;
|
using Mango.Nop.Core.Entities;
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
|
||||||
[ToonDescription("Shipping document with partner, items and files", Purpose = "Supplier's delivery note or invoice for the shipment; reconciles paper data with measured reality. Populated progressively — much entered at order time, the rest as it becomes known.")]
|
|
||||||
[Table(Name = FruitBankConstClient.ShippingDocumentDbTableName)]
|
[Table(Name = FruitBankConstClient.ShippingDocumentDbTableName)]
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingDocumentDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingDocumentDbTableName)]
|
||||||
public sealed class ShippingDocument : MgEntityBase, IShippingDocument
|
public class ShippingDocument : MgEntityBase, IShippingDocument
|
||||||
{
|
{
|
||||||
public int PartnerId { get; set; }
|
public int PartnerId { get; set; }
|
||||||
public int? ShippingId { get; set; }
|
public int? ShippingId { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,21 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Interfaces;
|
using FruitBank.Common.Interfaces;
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Entities;
|
using Mango.Nop.Core.Entities;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
[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)]
|
[LinqToDB.Mapping.Table(Name = FruitBankConstClient.ShippingDocumentToFilesDbTableName)]
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingDocumentToFilesDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingDocumentToFilesDbTableName)]
|
||||||
public sealed class ShippingDocumentToFiles : MgEntityBase, IShippingDocumentToFiles
|
public class ShippingDocumentToFiles : MgEntityBase, IShippingDocumentToFiles
|
||||||
{
|
{
|
||||||
public int FilesId { get; set; }
|
public int FilesId { get; set; }
|
||||||
|
|
||||||
public int ShippingDocumentId { get; set; }
|
public int ShippingDocumentId { get; set; }
|
||||||
|
|
||||||
[ToonDescription(Constraints = "enum-reference: DocumentType")]
|
|
||||||
public int DocumentTypeId { get; set; }
|
public int DocumentTypeId { get; set; }
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => DocumentTypeId")]
|
|
||||||
public DocumentType DocumentType
|
public DocumentType DocumentType
|
||||||
{
|
{
|
||||||
get => (DocumentType)DocumentTypeId;
|
get => (DocumentType)DocumentTypeId;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using AyCode.Core.Interfaces;
|
using AyCode.Core.Interfaces;
|
||||||
using AyCode.Core.Serializers.Attributes;
|
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Dtos;
|
using FruitBank.Common.Dtos;
|
||||||
using FruitBank.Common.Enums;
|
using FruitBank.Common.Enums;
|
||||||
using FruitBank.Common.Interfaces;
|
using FruitBank.Common.Interfaces;
|
||||||
|
|
@ -12,15 +10,13 @@ using Nop.Core.Domain.Customers;
|
||||||
using Nop.Core.Domain.Orders;
|
using Nop.Core.Domain.Orders;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using Column = LinqToDB.Mapping.ColumnAttribute;
|
|
||||||
//using Nop.Core.Domain.Catalog;
|
//using Nop.Core.Domain.Catalog;
|
||||||
using DataType = LinqToDB.DataType;
|
using DataType = LinqToDB.DataType;
|
||||||
|
using Column = LinqToDB.Mapping.ColumnAttribute;
|
||||||
using Table = LinqToDB.Mapping.TableAttribute;
|
using Table = LinqToDB.Mapping.TableAttribute;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
[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)]
|
[Table(Name = FruitBankConstClient.ShippingItemDbTableName)]
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingItemDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingItemDbTableName)]
|
||||||
public class ShippingItem : MgEntityBase, IShippingItem
|
public class ShippingItem : MgEntityBase, IShippingItem
|
||||||
|
|
@ -33,8 +29,10 @@ public class ShippingItem : MgEntityBase, IShippingItem
|
||||||
public string NameOnDocument { get; set; }
|
public string NameOnDocument { get; set; }
|
||||||
public string HungarianName { get; set; }
|
public string HungarianName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// get => ProductDto?.Name ?? Name
|
||||||
|
/// </summary>
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => ProductDto?.Name ?? Name")]
|
|
||||||
public string ProductName => ProductDto?.Name ?? Name;
|
public string ProductName => ProductDto?.Name ?? Name;
|
||||||
|
|
||||||
public int PalletsOnDocument { get; set; }
|
public int PalletsOnDocument { get; set; }
|
||||||
|
|
@ -86,7 +84,6 @@ public class ShippingItem : MgEntityBase, IShippingItem
|
||||||
public DateTime Modified { get; set; }
|
public DateTime Modified { get; set; }
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => complex conditional logic based on IsMeasured and ShippingItemPallets status")]
|
|
||||||
public MeasuringStatus MeasuringStatus
|
public MeasuringStatus MeasuringStatus
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using FruitBank.Common.Interfaces;
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Interfaces;
|
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
@ -8,11 +6,9 @@ using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
[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)]
|
[LinqToDB.Mapping.Table(Name = FruitBankConstClient.ShippingItemPalletDbTableName)]
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingItemPalletDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingItemPalletDbTableName)]
|
||||||
public sealed class ShippingItemPallet : MeasuringItemPalletBase, IShippingItemPallet
|
public class ShippingItemPallet : MeasuringItemPalletBase, IShippingItemPallet
|
||||||
{
|
{
|
||||||
public int ShippingItemId
|
public int ShippingItemId
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using AyCode.Interfaces.Entities;
|
||||||
using AyCode.Core.Serializers.Toons;
|
using AyCode.Interfaces.TimeStampInfo;
|
||||||
using AyCode.Interfaces.Entities;
|
using FruitBank.Common;
|
||||||
|
using FruitBank.Common.Interfaces;
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Entities;
|
using Nop.Core.Domain.Catalog;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities
|
namespace Mango.Nop.Core.Entities
|
||||||
{
|
{
|
||||||
public interface IStockQuantityHistoryExt : IEntityInt
|
public interface IStockQuantityHistoryExt : IEntityInt
|
||||||
{
|
{
|
||||||
|
|
@ -15,11 +21,9 @@ namespace FruitBank.Common.Entities
|
||||||
public bool IsInconsistent { get; set; }
|
public bool IsInconsistent { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
|
||||||
[Table(Name = FruitBankConstClient.StockQuantityHistoryExtDbTableName)]
|
[Table(Name = FruitBankConstClient.StockQuantityHistoryExtDbTableName)]
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockQuantityHistoryExtDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockQuantityHistoryExtDbTableName)]
|
||||||
[ToonDescription("Extended weight-metadata for StockQuantityHistory", Purpose = "Validates quantity deltas against measured weight to detect inconsistencies")]
|
public class StockQuantityHistoryExt : MgEntityBase, IStockQuantityHistoryExt
|
||||||
public sealed class StockQuantityHistoryExt : MgEntityBase, IStockQuantityHistoryExt
|
|
||||||
{
|
{
|
||||||
public int StockQuantityHistoryId { get; set; }
|
public int StockQuantityHistoryId { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using LinqToDB.Mapping;
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using LinqToDB.Mapping;
|
|
||||||
using Mango.Nop.Core.Entities;
|
using Mango.Nop.Core.Entities;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
|
||||||
[ToonDescription("Inventory session record", Purpose = "Orchestrates inventory sessions by freezing logical stock states")]
|
|
||||||
[Table(Name = FruitBankConstClient.StockTakingDbTableName)]
|
[Table(Name = FruitBankConstClient.StockTakingDbTableName)]
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockTakingDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockTakingDbTableName)]
|
||||||
public sealed class StockTaking : MgStockTaking<StockTakingItem>
|
public class StockTaking : MgStockTaking<StockTakingItem>
|
||||||
{
|
{
|
||||||
public override bool IsReadyForClose()
|
public override bool IsReadyForClose()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,17 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using FruitBank.Common.Dtos;
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Dtos;
|
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Entities;
|
using Mango.Nop.Core.Entities;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Column = LinqToDB.Mapping.ColumnAttribute;
|
using Column = LinqToDB.Mapping.ColumnAttribute;
|
||||||
using Table = LinqToDB.Mapping.TableAttribute;
|
using Table = LinqToDB.Mapping.TableAttribute;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
[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)]
|
[Table(Name = FruitBankConstClient.StockTakingItemDbTableName)]
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockTakingItemDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockTakingItemDbTableName)]
|
||||||
public sealed class StockTakingItem : MgStockTakingItem<StockTaking, ProductDto>
|
public class StockTakingItem : MgStockTakingItem<StockTaking, ProductDto>
|
||||||
{
|
{
|
||||||
public bool IsMeasurable { get; set; }
|
public bool IsMeasurable { get; set; }
|
||||||
|
|
||||||
|
|
@ -25,34 +21,27 @@ public sealed class StockTakingItem : MgStockTakingItem<StockTaking, ProductDto>
|
||||||
[Column(DataType = DataType.DecFloat, CanBeNull = false)]
|
[Column(DataType = DataType.DecFloat, CanBeNull = false)]
|
||||||
public double MeasuredNetWeight { get; set; }
|
public double MeasuredNetWeight { get; set; }
|
||||||
|
|
||||||
[ToonDescription(Purpose = "Reserved stock buffer (not yet shipped) to prevent double-deduction during closing")]
|
|
||||||
public int InProcessOrdersQuantity { get; set; }
|
public int InProcessOrdersQuantity { get; set; }
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => OriginalStockQuantity + InProcessOrdersQuantity", Purpose = "Snapshot of total logical stock at session start")]
|
|
||||||
public int TotalOriginalQuantity => OriginalStockQuantity + InProcessOrdersQuantity;
|
public int TotalOriginalQuantity => OriginalStockQuantity + InProcessOrdersQuantity;
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(Purpose = "Final adjustment value for Product.StockQuantity", BusinessRule = "get => IsMeasured ? MeasuredStockQuantity - TotalOriginalQuantity : 0")]
|
|
||||||
public int QuantityDiff => IsMeasured ? MeasuredStockQuantity - TotalOriginalQuantity : 0;
|
public int QuantityDiff => IsMeasured ? MeasuredStockQuantity - TotalOriginalQuantity : 0;
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => IsMeasurable && IsMeasured ? double.Round(MeasuredNetWeight - OriginalNetWeight, 1) : 0d")]
|
|
||||||
public double NetWeightDiff => IsMeasurable && IsMeasured ? double.Round(MeasuredNetWeight - OriginalNetWeight, 1) : 0d;
|
public double NetWeightDiff => IsMeasurable && IsMeasured ? double.Round(MeasuredNetWeight - OriginalNetWeight, 1) : 0d;
|
||||||
|
|
||||||
[Association(ThisKey = nameof(Id), OtherKey = nameof(StockTakingItemPallet.StockTakingItemId), CanBeNull = true)]
|
[Association(ThisKey = nameof(Id), OtherKey = nameof(StockTakingItemPallet.StockTakingItemId), CanBeNull = true)]
|
||||||
public List<StockTakingItemPallet>? StockTakingItemPallets { get; set; }
|
public List<StockTakingItemPallet>? StockTakingItemPallets { get; set; }
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(Purpose = "Status flag", BusinessRule = "get => !IsInvalid && (TotalOriginalQuantity != 0 || OriginalNetWeight != 0)")]
|
|
||||||
public bool IsRequiredForMeasuring => !IsInvalid && (TotalOriginalQuantity != 0 || OriginalNetWeight != 0);
|
public bool IsRequiredForMeasuring => !IsInvalid && (TotalOriginalQuantity != 0 || OriginalNetWeight != 0);
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(Purpose = "Status flag", BusinessRule = "get => TotalOriginalQuantity < 0")]
|
|
||||||
public bool IsInvalid => TotalOriginalQuantity < 0;
|
public bool IsInvalid => TotalOriginalQuantity < 0;
|
||||||
|
|
||||||
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[ToonDescription(BusinessRule = "get => conditional string based on IsInvalid, IsMeasured, IsRequiredForMeasuring")]
|
|
||||||
public string DisplayText
|
public string DisplayText
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using FruitBank.Common.Dtos;
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common.Dtos;
|
|
||||||
using FruitBank.Common.Interfaces;
|
using FruitBank.Common.Interfaces;
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Entities;
|
using Mango.Nop.Core.Entities;
|
||||||
|
|
@ -14,11 +12,9 @@ public interface IStockTakingItemPallet : IMeasuringItemPalletBase
|
||||||
public StockTakingItem? StockTakingItem{ get; set; }
|
public StockTakingItem? StockTakingItem{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[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)]
|
[LinqToDB.Mapping.Table(Name = FruitBankConstClient.StockTakingItemPalletDbTableName)]
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockTakingItemPalletDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockTakingItemPalletDbTableName)]
|
||||||
public sealed class StockTakingItemPallet : MeasuringItemPalletBase, IStockTakingItemPallet
|
public class StockTakingItemPallet : MeasuringItemPalletBase, IStockTakingItemPallet
|
||||||
{
|
{
|
||||||
public int StockTakingItemId
|
public int StockTakingItemId
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FruitBank.Common.Enums;
|
namespace FruitBank.Common.Enums;
|
||||||
|
|
||||||
public enum PreOrderItemStatus
|
public enum PreorderItemStatus
|
||||||
{
|
{
|
||||||
Pending = 0,
|
Pending = 0,
|
||||||
Fulfilled = 10,
|
Fulfilled = 10,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace FruitBank.Common.Enums;
|
namespace FruitBank.Common.Enums;
|
||||||
|
|
||||||
public enum PreOrderStatus
|
public enum PreorderStatus
|
||||||
{
|
{
|
||||||
Pending = 0,
|
Pending = 0,
|
||||||
Confirmed = 10,
|
Confirmed = 10,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# Enums
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -40,10 +39,4 @@
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
</Project>
|
||||||
<ProjectReference Include="..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Core.Serializers.SourceGenerator\AyCode.Core.Serializers.SourceGenerator.csproj"
|
|
||||||
OutputItemType="Analyzer"
|
|
||||||
ReferenceOutputAssembly="false" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
|
||||||
|
|
@ -44,15 +44,11 @@ public static class FruitBankConstClient
|
||||||
public const string StockTakingDbTableName = "fbStockTaking";
|
public const string StockTakingDbTableName = "fbStockTaking";
|
||||||
public const string StockTakingItemDbTableName = "fbStockTakingItem";
|
public const string StockTakingItemDbTableName = "fbStockTakingItem";
|
||||||
public const string StockTakingItemPalletDbTableName = "fbStockTakingItemPallet";
|
public const string StockTakingItemPalletDbTableName = "fbStockTakingItemPallet";
|
||||||
|
|
||||||
public const string CustomerCreditDbTableName = "fbCustomerCredit";
|
public const string CustomerCreditDbTableName = "fbCustomerCredit";
|
||||||
public const string PreOrderDbTableName = "fbPreOrder";
|
public const string PreOrderDbTableName = "fbPreorder";
|
||||||
public const string PreOrderItemDbTableName = "fbPreOrderItem";
|
public const string PreOrderItemDbTableName = "fbPreorderItem";
|
||||||
|
|
||||||
public const string CargoPartnerDbTableName = "fbCargoPartner";
|
|
||||||
public const string CargoTruckDbTableName = "fbCargoTruck";
|
|
||||||
|
|
||||||
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") };
|
//public static Guid[] DevAdminIds = new Guid[2] { Guid.Parse("dcf451d2-cc4c-4ac2-8c1f-da00041be1fd"), Guid.Parse("4cbaed43-2465-4d99-84f1-c8bc6b7025f7") };
|
||||||
//public static Guid[] SysAdmins = new Guid[3]
|
//public static Guid[] SysAdmins = new Guid[3]
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
# Helpers
|
|
||||||
|
|
||||||
Measurement aggregation utilities.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`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.
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
using AyCode.Interfaces.Entities;
|
|
||||||
using AyCode.Interfaces.TimeStampInfo;
|
|
||||||
using FruitBank.Common.Entities;
|
|
||||||
|
|
||||||
namespace FruitBank.Common.Interfaces;
|
|
||||||
|
|
||||||
public interface ICargoTruck : IEntityInt, ITimeStampInfo
|
|
||||||
{
|
|
||||||
public int CargoPartnerId { get; set; }
|
|
||||||
|
|
||||||
public CargoPartner CargoPartner { get; set; }
|
|
||||||
|
|
||||||
public string CountryCode { get; set; }
|
|
||||||
public string LicencePlate { get; set; }
|
|
||||||
|
|
||||||
public bool IsTrailer { get; set; }
|
|
||||||
|
|
||||||
public string? CargoPartnerName { get; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using FruitBank.Common.Dtos;
|
using FruitBank.Common.Dtos;
|
||||||
using FruitBank.Common.Entities;
|
using FruitBank.Common.Entities;
|
||||||
using FruitBank.Common.Models;
|
using FruitBank.Common.Models;
|
||||||
using FruitBank.Common.SignalRs;
|
|
||||||
using Mango.Nop.Core.Dtos;
|
using Mango.Nop.Core.Dtos;
|
||||||
using Mango.Nop.Core.Entities;
|
using Mango.Nop.Core.Entities;
|
||||||
using Mango.Nop.Core.Models;
|
using Mango.Nop.Core.Models;
|
||||||
|
|
@ -20,21 +19,6 @@ public interface IFruitBankDataControllerCommon
|
||||||
public Task<Partner?> UpdatePartner(Partner partner);
|
public Task<Partner?> UpdatePartner(Partner partner);
|
||||||
#endregion Partner
|
#endregion Partner
|
||||||
|
|
||||||
#region CargoPartner
|
|
||||||
public Task<List<CargoPartner>?> GetCargoPartners();
|
|
||||||
public Task<CargoPartner?> GetCargoPartnerById(int id);
|
|
||||||
public Task<CargoPartner?> AddCargoPartner(CargoPartner cargoPartner);
|
|
||||||
public Task<CargoPartner?> UpdateCargoPartner(CargoPartner cargoPartner);
|
|
||||||
#endregion CargoPartner
|
|
||||||
|
|
||||||
#region CargoTruck
|
|
||||||
public Task<List<CargoTruck>?> GetCargoTrucks();
|
|
||||||
public Task<CargoTruck?> GetCargoTruckById(int id);
|
|
||||||
public Task<List<CargoTruck>?> GetCargoTrucksByCargoPartnerId(int cargoPartnerId);
|
|
||||||
public Task<CargoTruck?> AddCargoTruck(CargoTruck cargoTruck);
|
|
||||||
public Task<CargoTruck?> UpdateCargoTruck(CargoTruck cargoTruck);
|
|
||||||
#endregion CargoTruck
|
|
||||||
|
|
||||||
#region Shipping
|
#region Shipping
|
||||||
public Task<List<Shipping>?> GetShippings();
|
public Task<List<Shipping>?> GetShippings();
|
||||||
Task<List<Shipping>?> GetNotMeasuredShippings();
|
Task<List<Shipping>?> GetNotMeasuredShippings();
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,20 @@
|
||||||
using AyCode.Core.Interfaces;
|
using AyCode.Interfaces.Entities;
|
||||||
using AyCode.Entities;
|
|
||||||
using AyCode.Interfaces.Entities;
|
|
||||||
using AyCode.Interfaces.TimeStampInfo;
|
using AyCode.Interfaces.TimeStampInfo;
|
||||||
using FruitBank.Common.Entities;
|
using FruitBank.Common.Entities;
|
||||||
|
|
||||||
namespace FruitBank.Common.Interfaces;
|
namespace FruitBank.Common.Interfaces;
|
||||||
|
|
||||||
public interface ICargoPartner : IPartnerBase
|
public interface IPartner : IEntityInt, ITimeStampInfo
|
||||||
{
|
|
||||||
List<CargoTruck>? CargoTrucks { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IPartner : IPartnerBase
|
|
||||||
{
|
|
||||||
List<ShippingDocument>? ShippingDocuments { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IPartnerBase : ICompanyInfoBase, IEntityInt, ITimeStampInfo
|
|
||||||
{
|
{
|
||||||
|
string Name { get; set; }
|
||||||
|
string TaxId { get; set; }
|
||||||
string CertificationNumber { get; set; }
|
string CertificationNumber { get; set; }
|
||||||
string Currency { get; set; }
|
string PostalCode { get; set; }
|
||||||
string Country { get; set; }
|
string Country { get; set; }
|
||||||
string State { get; set; }
|
string State { get; set; }
|
||||||
string County { get; set; }
|
string County { get; set; }
|
||||||
|
string City { get; set; }
|
||||||
|
string Street { get; set; }
|
||||||
|
|
||||||
|
List<ShippingDocument>? ShippingDocuments { get; set; }
|
||||||
}
|
}
|
||||||
|
|
@ -6,10 +6,6 @@ namespace FruitBank.Common.Interfaces;
|
||||||
|
|
||||||
public interface IShipping : IEntityInt, ITimeStampInfo//, IMeasured
|
public interface IShipping : IEntityInt, ITimeStampInfo//, IMeasured
|
||||||
{
|
{
|
||||||
public int? CargoPartnerId { get; set; }
|
|
||||||
public int? CargoTruckId { get; set; }
|
|
||||||
public int? CargoTrailerId { get; set; }
|
|
||||||
|
|
||||||
DateTime ShippingDate { get; set; }
|
DateTime ShippingDate { get; set; }
|
||||||
string LicencePlate { get; set; }
|
string LicencePlate { get; set; }
|
||||||
bool IsAllMeasured { get; set; }
|
bool IsAllMeasured { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using FruitBank.Common.Entities;
|
|
||||||
|
|
||||||
namespace FruitBank.Common.Interfaces
|
namespace FruitBank.Common.Interfaces
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# Interfaces
|
|
||||||
|
|
||||||
SignalR endpoint contracts, measurement composition traits, and entity interfaces.
|
|
||||||
|
|
||||||
## SignalR Endpoints
|
|
||||||
|
|
||||||
- **`IFruitBankDataControllerCommon.cs`** / **`Client.cs`** — Core CRUD: Partners, Shipping, ShippingDocuments, ShippingItems, ShippingItemPallets, Products, Customers, GenericAttributes.
|
|
||||||
- **`ICustomOrderSignalREndpointCommon.cs`** / **`Client.cs`** — Order operations: GetAllOrderDtos, GetPendingOrderDtos, OrderItem/Pallet management, StartMeasuring, SetOrderStatusToComplete.
|
|
||||||
- **`IStockSignalREndpointCommon.cs`** / **`Client.cs`** — Inventory: StockTaking, StockTakingItem, StockTakingItemPallet CRUD, CloseStockTaking.
|
|
||||||
|
|
||||||
## Measurement Traits (Composition Pattern)
|
|
||||||
|
|
||||||
- **`IMeasuringValues`** = IMeasuringWeights + IMeasuringQuantity
|
|
||||||
- **`IMeasuringWeights`** = IMeasuringNetWeight + IMeasuringGrossWeight
|
|
||||||
- **`IMeasurable`** — IsMeasurable flag
|
|
||||||
- **`IMeasured`** — IsMeasured flag
|
|
||||||
- **`IMeasurableStatus`** — MeasuringStatus property
|
|
||||||
- **`IMeasuringItemPalletBase`** — Full measurement contract with validation
|
|
||||||
|
|
||||||
## Entity & DTO Interfaces
|
|
||||||
|
|
||||||
- **`IPallet`**, **`IPartner`**, **`IShipping`**, **`IShippingDocument`**, **`IShippingItem`**, **`IShippingItemPallet`**, **`IFiles`**
|
|
||||||
- **`IOrderDto`**, **`IOrderItemDto`**, **`IProductDto`**, **`IStockQuantityHistoryDto`**
|
|
||||||
- **`ITare`**, **`IAvailableQuantity`**, **`IIncomingQuantity`** — Quantity/weight property interfaces
|
|
||||||
|
|
||||||
## Service Interfaces
|
|
||||||
|
|
||||||
- **`IMeasurementServiceBase<TLogger>`** — Base service marker
|
|
||||||
- **`ISecureCredentialService`** — Save/retrieve/clear credentials with 2-day expiration
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# Loggers
|
|
||||||
|
|
||||||
SignalR client-to-server log writer.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`SignaRClientLogItemWriter.cs`** — Routes client logs to `{BaseUrl}/loggerHub` via SignalR. Configurable by AppType and LogLevel.
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
# Models
|
|
||||||
|
|
||||||
Application and view models for UI state management.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`LoggedInModel.cs`** — Authentication state: IsLoggedIn, IsRevisor, IsAdministrator, IsDeveloper. Auto-login from stored credentials (2-day expiration). Customer and role management.
|
|
||||||
- **`MeasuringAttributeValues.cs`** — IMeasuringAttributeValues implementation: Id, NetWeight, IsMeasurable, HasMeasuringValues().
|
|
||||||
- **`MeasuringModel.cs`** — ViewModel aggregating Shipping + Partners + ShippingItems + ShippingDocuments.
|
|
||||||
|
|
||||||
## Subfolders
|
|
||||||
|
|
||||||
- **`SignalRs/SignalRMessageToClientWithText<T>.cs`** — Generic message wrapper with optional text and typed content.
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
# 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
|
|
||||||
|
|
||||||
| Folder | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| [`Databases/`](Databases/README.md) | Local in-memory database abstraction for offline/cached data |
|
|
||||||
| [`Dtos/`](Dtos/README.md) | Binary-serializable DTOs for Order, OrderItem, Product, StockQuantityHistory |
|
|
||||||
| [`Entities/`](Entities/README.md) | Domain entities: Shipping, Partner, measurement pallets, inventory |
|
|
||||||
| [`Enums/`](Enums/README.md) | MeasuringStatus and DocumentType enums |
|
|
||||||
| [`Helpers/`](Helpers/README.md) | Measurement aggregation utilities |
|
|
||||||
| [`Interfaces/`](Interfaces/README.md) | SignalR endpoint contracts, measurement traits, entity interfaces |
|
|
||||||
| [`Loggers/`](Loggers/README.md) | SignalR client log writer |
|
|
||||||
| [`Models/`](Models/README.md) | Authentication state, measurement view models |
|
|
||||||
| [`Services/`](Services/README.md) | Measurement service base, credential persistence |
|
|
||||||
| [`SignalRs/`](SignalRs/README.md) | SignalR method tags (numeric constants) |
|
|
||||||
|
|
||||||
## Key Files (Root)
|
|
||||||
|
|
||||||
- **`FruitBankConstClient.cs`** — Global constants: BaseUrl, SignalR hubs, database table names, email templates, system settings.
|
|
||||||
- **`DocumentType.cs`** — Enum: ShippingDocument, OrderConfirmation, Invoice.
|
|
||||||
|
|
||||||
## Key Domain Concepts
|
|
||||||
|
|
||||||
- **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` for full terminology
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
using AyCode.Services.Nav.Ekaer;
|
|
||||||
using AyCode.Services.Nav.Ekaer.Models;
|
|
||||||
using FruitBank.Common.Entities;
|
|
||||||
|
|
||||||
namespace FruitBank.Common.Services.Ekaer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// FruitBank domain → NAV EKÁER tradeCard leképezés. Egy bejövő <see cref="Shipping"/>-ből
|
|
||||||
/// EKÁER tradeCard műveleteket állít elő (dokumentumonként egyet).
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// A NAV-protokollt és az authentikációt NEM kezeli — az az <c>AyCode.Services.Nav</c> réteg felelőssége.
|
|
||||||
/// A feladót (beszállító <c>Partner</c>) és a saját céget (<see cref="EkaerCompanyInfo"/>) egységesen,
|
|
||||||
/// <see cref="AyCode.Entities.ICompanyInfoBase"/>-ként kezeli.
|
|
||||||
/// </remarks>
|
|
||||||
public interface IShippingToEkaerMapper
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Leképezi a <paramref name="shipping"/> minden <c>ShippingDocument</c>-jét egy-egy EKÁER tradeCard műveletre.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="shipping">A bejövő szállítmány (fuvarozó/jármű a Shipping szintjén, eladó/tételek a dokumentum szintjén).</param>
|
|
||||||
/// <param name="company">A bejelentő saját cégadatai (címzett bejövő relációban) + a lerakodási hely.</param>
|
|
||||||
/// <param name="operation">A tradeCard művelet típusa. Alapértelmezés: <see cref="OperationType.Create"/>.</param>
|
|
||||||
IReadOnlyList<TradeCardOperationType> MapShipping(Shipping shipping, EkaerCompanyInfo company, OperationType operation = OperationType.Create);
|
|
||||||
}
|
|
||||||
|
|
@ -1,162 +0,0 @@
|
||||||
using AyCode.Core.Interfaces;
|
|
||||||
using AyCode.Entities;
|
|
||||||
using AyCode.Services.Nav.Ekaer;
|
|
||||||
using AyCode.Services.Nav.Ekaer.Models;
|
|
||||||
using FruitBank.Common.Entities;
|
|
||||||
using TradeReasonType = AyCode.Services.Nav.Ekaer.Models.Common.TradeReasonType;
|
|
||||||
using TradeType = AyCode.Services.Nav.Ekaer.Models.Common.TradeType;
|
|
||||||
|
|
||||||
namespace FruitBank.Common.Services.Ekaer;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IShippingToEkaerMapper"/>
|
|
||||||
/// <remarks>
|
|
||||||
/// Tiszta (állapotmentes) leképező. A feladót és a saját céget egységesen <see cref="ICompanyInfoBase"/>-ként kezeli.
|
|
||||||
/// A <c>TradeType</c>/<c>TradeReasonType</c> enumokat aliasszal hozzuk be a <c>Models.TradeCardType</c> osztály
|
|
||||||
/// és a <c>Models.Common.TradeCardType</c> enum névütközése miatt.
|
|
||||||
/// </remarks>
|
|
||||||
public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
|
|
||||||
{
|
|
||||||
/// <summary>A NAV EKÁER magyar rendszer — a „belföld" mindig HU; minden más feladó-ország import.</summary>
|
|
||||||
private const string HomeCountry = "HU";
|
|
||||||
|
|
||||||
public IReadOnlyList<TradeCardOperationType> MapShipping(Shipping shipping, EkaerCompanyInfo company, OperationType operation = OperationType.Create)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(shipping);
|
|
||||||
ArgumentNullException.ThrowIfNull(company);
|
|
||||||
|
|
||||||
var operations = new List<TradeCardOperationType>();
|
|
||||||
var index = 0;
|
|
||||||
|
|
||||||
// Granularitás: egy ShippingDocument → egy tradeCard (lásd EKAER_TODO #5).
|
|
||||||
foreach (var document in shipping.ShippingDocuments ?? [])
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
operations.Add(new TradeCardOperationType
|
|
||||||
{
|
|
||||||
Index = index,
|
|
||||||
Operation = operation,
|
|
||||||
TradeCard = BuildTradeCard(shipping, document, company),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return operations;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TradeCardType BuildTradeCard(Shipping shipping, ShippingDocument document, EkaerCompanyInfo company)
|
|
||||||
{
|
|
||||||
var seller = document.Partner; // a beszállító (feladó) — ICompanyInfoBase
|
|
||||||
|
|
||||||
var tradeCard = new TradeCardType
|
|
||||||
{
|
|
||||||
TradeType = ResolveTradeType(seller),
|
|
||||||
ModByCarrierEnabled = false, // mi jelentünk; a fuvarozó alapból nem módosíthat
|
|
||||||
|
|
||||||
// Feladó / eladó = a beszállító
|
|
||||||
SellerName = seller?.Name,
|
|
||||||
SellerVatNumber = NormalizeVatNumber(seller?.TaxId),
|
|
||||||
SellerCountry = NormalizeCountryCode(seller?.CountryCode, 2),
|
|
||||||
SellerAddress = Truncate(seller?.FullAddress, 200),
|
|
||||||
|
|
||||||
// Címzett = a bejelentő saját cége (bejövő relációban)
|
|
||||||
DestinationName = company.Name,
|
|
||||||
DestinationVatNumber = NormalizeVatNumber(company.TaxId),
|
|
||||||
DestinationCountry = NormalizeCountryCode(company.CountryCode, 2),
|
|
||||||
DestinationAddress = Truncate(company.FullAddress, 200),
|
|
||||||
|
|
||||||
// Fuvarozó (Shipping.CargoPartner). Regisztrált EKAER-azonosító nincs, csak szöveges név.
|
|
||||||
CarrierText = shipping.CargoPartner?.Name,
|
|
||||||
|
|
||||||
// Lerakodás = saját telephely (a cégadatból); felrakodás = a beszállító telephelye.
|
|
||||||
UnloadLocation = company.UnloadLocation,
|
|
||||||
LoadLocation = BuildLoadLocation(seller),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Vonó jármű + vontatmány: az EKÁER külön bejegyzésként kéri (vehicle / vehicle2).
|
|
||||||
if (shipping.CargoTruck != null) tradeCard.Vehicle = BuildVehicle(shipping.CargoTruck);
|
|
||||||
if (shipping.CargoTrailer != null) tradeCard.Vehicle2 = BuildVehicle(shipping.CargoTrailer);
|
|
||||||
|
|
||||||
foreach (var item in document.ShippingItems ?? []) tradeCard.Items.Add(BuildItem(item));
|
|
||||||
return tradeCard;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Belföldi feladó (HU) → <c>D</c> (belföld-belföld), egyébként → <c>I</c> (import). A NAV EKÁER magyar,
|
|
||||||
/// így a belföld mindig HU; az export (<c>E</c>) jelenleg nincs leképezve (lásd EKAER_TODO #1, #7).
|
|
||||||
/// </summary>
|
|
||||||
private static TradeType ResolveTradeType(ICompanyInfoBase? seller)
|
|
||||||
=> string.Equals(seller?.CountryCode, HomeCountry, StringComparison.OrdinalIgnoreCase)
|
|
||||||
? TradeType.D
|
|
||||||
: TradeType.I;
|
|
||||||
|
|
||||||
private static TradeCardItemType BuildItem(ShippingItem item) => new()
|
|
||||||
{
|
|
||||||
ItemExternalId = item.Id.ToString(),
|
|
||||||
// Bejövő áru = beszerzés → A. (Enum: S=értékesítés, A=beszerzés, W=bérmunka, O=egyéb.) Lásd EKAER_TODO #9.
|
|
||||||
TradeReason = TradeReasonType.A,
|
|
||||||
ProductVtsz = item.ProductDto?.Gtin, // VTSZ — átmenetileg a Gtin oszlopban (FBANKAPP-DMODEL-I-P6X4)
|
|
||||||
ProductName = item.ProductName,
|
|
||||||
Weight = (decimal)item.MeasuredGrossWeight, // bruttó tömeg kg-ban (lásd EKAER_TODO #4)
|
|
||||||
// Value (HUF): a deviza/FX tisztázásáig NEM töltjük (a mező opcionális). Lásd EKAER_TODO #3, #10.
|
|
||||||
};
|
|
||||||
|
|
||||||
private static BasicVehicleDetailType BuildVehicle(CargoTruck truck) => new()
|
|
||||||
{
|
|
||||||
PlateNumber = NormalizePlateNumber(truck.LicencePlate),
|
|
||||||
Country = NormalizeCountryCode(truck.CountryCode, 3),
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Felrakodási hely a beszállító adataiból. (Magyar feladónál a NAV a Phone/Email-t is kéri, ami az
|
|
||||||
/// entitásban nincs — lásd EKAER_TODO #6.)
|
|
||||||
/// </summary>
|
|
||||||
private static LocationType? BuildLoadLocation(ICompanyInfoBase? seller)
|
|
||||||
{
|
|
||||||
if (seller is null) return null;
|
|
||||||
return new LocationType
|
|
||||||
{
|
|
||||||
Name = seller.Name,
|
|
||||||
VatNumber = NormalizeVatNumber(seller.TaxId),
|
|
||||||
Country = NormalizeCountryCode(seller.CountryCode, 2),
|
|
||||||
ZipCode = NormalizeZipCode(seller.PostalCode),
|
|
||||||
City = seller.City,
|
|
||||||
Street = seller.Street,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Adószám normalizálása. Pattern: <c>[0-9A-Z-]{1,15}</c>.</summary>
|
|
||||||
private static string? NormalizeVatNumber(string? value)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(value)) return null;
|
|
||||||
var cleaned = new string([.. value.ToUpperInvariant().Where(c => char.IsAsciiLetterOrDigit(c) || c == '-')]);
|
|
||||||
return Truncate(EmptyToNull(cleaned), 15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Országkód normalizálása. Pattern: <c>[A-Z]{1,maxLen}</c> (seller/location: 2, jármű: 3).</summary>
|
|
||||||
private static string? NormalizeCountryCode(string? value, int maxLen)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(value)) return null;
|
|
||||||
var cleaned = new string([.. value.ToUpperInvariant().Where(char.IsAsciiLetter)]);
|
|
||||||
return Truncate(EmptyToNull(cleaned), maxLen);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Rendszám normalizálása. Pattern: <c>[A-Z0-9ÖŐÜŰ]{4,15}</c> — kötőjel/szóköz NEM engedett.</summary>
|
|
||||||
private static string? NormalizePlateNumber(string? value)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(value)) return null;
|
|
||||||
var cleaned = new string([.. value.ToUpperInvariant().Where(c => char.IsAsciiLetterOrDigit(c) || c is 'Ö' or 'Ő' or 'Ü' or 'Ű')]);
|
|
||||||
return Truncate(EmptyToNull(cleaned), 15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Irányítószám normalizálása. Pattern: <c>[A-Z0-9 -]{2,10}</c> vagy üres.</summary>
|
|
||||||
private static string? NormalizeZipCode(string? value)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(value)) return null;
|
|
||||||
var cleaned = new string([.. value.ToUpperInvariant().Where(c => char.IsAsciiLetterOrDigit(c) || c is ' ' or '-')]);
|
|
||||||
return Truncate(EmptyToNull(cleaned), 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string? EmptyToNull(string? value) => string.IsNullOrEmpty(value) ? null : value;
|
|
||||||
|
|
||||||
private static string? Truncate(string? value, int maxLen)
|
|
||||||
=> value is null ? null : value.Length <= maxLen ? value : value[..maxLen];
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# Services
|
|
||||||
|
|
||||||
Business logic services and credential management.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`MeasurementServiceBase.cs`** — Abstract base with generic TLogger injection.
|
|
||||||
- **`ISecureCredentialService.cs`** — Interface: SaveCredentialsAsync (2-day expiry), GetCredentialsAsync, ClearCredentialsAsync. StoredCredentials sealed record.
|
|
||||||
|
|
||||||
Platform implementations: MAUI → SecureStorage, Web → obfuscated localStorage, Server → no-op.
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
# SignalRs
|
|
||||||
|
|
||||||
SignalR method identifiers as numeric constants for type-safe client-server communication.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`SignalRTags.cs`** — Constant int tags organized by domain:
|
|
||||||
- **0-10:** System (GetMeasuringModels)
|
|
||||||
- **20-27:** Partner CRUD
|
|
||||||
- **40-66:** Shipping, ShippingDocument, ShippingItem
|
|
||||||
- **70-83:** Customer, Product
|
|
||||||
- **94-98:** ShippingItemPallet
|
|
||||||
- **111-138:** Order (OrderDto, OrderItemDto, OrderItemPallet)
|
|
||||||
- **150-151:** StockQuantityHistory
|
|
||||||
- **160-169:** GenericAttribute
|
|
||||||
- **170-179:** StockTaking
|
|
||||||
- **195-200:** Authentication
|
|
||||||
- **500+:** Server→client notifications (SendOrderChanged, SendShippingChanged, etc.)
|
|
||||||
- **1000+:** Diagnostic/Logging
|
|
||||||
|
|
@ -15,17 +15,6 @@ public class SignalRTags : AcSignalRTags
|
||||||
public const int AddPartner = 25;
|
public const int AddPartner = 25;
|
||||||
public const int UpdatePartner = 26;
|
public const int UpdatePartner = 26;
|
||||||
|
|
||||||
public const int GetCargoPartners = 30;
|
|
||||||
public const int GetCargoPartnerById = 31;
|
|
||||||
public const int AddCargoPartner = 32;
|
|
||||||
public const int UpdateCargoPartner = 33;
|
|
||||||
|
|
||||||
public const int GetCargoTrucks = 35;
|
|
||||||
public const int GetCargoTrucksByCargoPartnerId = 36;
|
|
||||||
public const int GetCargoTruckById = 37;
|
|
||||||
public const int AddCargoTruck = 38;
|
|
||||||
public const int UpdateCargoTruck = 39;
|
|
||||||
|
|
||||||
public const int GetShippings = 40;
|
public const int GetShippings = 40;
|
||||||
public const int GetNotMeasuredShippings = 41;
|
public const int GetNotMeasuredShippings = 41;
|
||||||
public const int GetShippingById = 42;
|
public const int GetShippingById = 42;
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
# DATA-MODEL — Known Issues
|
|
||||||
|
|
||||||
> Companion to [`README.md`](README.md). Topic `DMODEL`, prefix `FBANKAPP` → entry IDs `FBANKAPP-DMODEL-I-<RAND>` (issue) / `-B-` (bug).
|
|
||||||
> ID format, Status vocabulary, type codes, archival → `../../../.github/TOPIC_CODES.md` (→ framework registry).
|
|
||||||
|
|
||||||
Scope: a FruitBank entitások adatmodell-normalizálási teendői (nopCommerce referencia-FK-k, azonosító-szétválasztások).
|
|
||||||
|
|
||||||
## Active entries
|
|
||||||
|
|
||||||
## FBANKAPP-DMODEL-I-K3D9: A Partner-entitások nopCommerce referencia-mezői szabad string-ek, FK helyett
|
|
||||||
|
|
||||||
**Status:** Open · **Priority:** P3 · **Type:** I (adatmodell / normalizálás)
|
|
||||||
|
|
||||||
A `PartnerBase` (és így `Partner` / `CargoPartner`) + a `CargoTruck` a nopCommerce referencia-adatait **szabad string-ként** tárolja, ahelyett hogy a megfelelő nopCommerce tábla **Id-jára FK-zna**:
|
|
||||||
|
|
||||||
| Mező | Jelenlegi | Helyes (hosszú táv) |
|
|
||||||
|---|---|---|
|
|
||||||
| `CountryCode` (string) | szabad szöveg (pl. `"HU"`) | nopCommerce **`Country.Id`** FK; a megjelenített kód a `Country.TwoLetterIsoCode`-ból — `PartnerBase` + `CargoTruck` |
|
|
||||||
| `Currency` (string) | szabad szöveg (pl. `"EUR"`) | nopCommerce **`Currency.Id`** FK; a megjelenített kód a `Currency.CurrencyCode`-ból — `PartnerBase` |
|
|
||||||
|
|
||||||
**Hatás:** működik (a string-eket kézzel töltjük), de **nincs hivatkozás-integritás**, és a kódok elgépelhetők / inkonzisztensek lehetnek. Az EKÁER-leképezés a string pontosságára támaszkodik (`seller`/`destination`/`vehicle` `Country`, és a value→HUF deviza forrás-pénzneme).
|
|
||||||
|
|
||||||
**Javítási irány:** FK-oszlop (`CountryId`, `CurrencyId`) + navigation a nopCommerce táblákra; a megjelenített/exportált kód a referencia-entitásból.
|
|
||||||
|
|
||||||
**Affected:**
|
|
||||||
- `FruitBank.Common/Entities/PartnerBase.cs` → `CountryCode`, `Currency`
|
|
||||||
- `FruitBank.Common/Entities/CargoTruck.cs` → `CountryCode`
|
|
||||||
- felhasználó: `FruitBank.Common/Services/Ekaer/ShippingToEkaerMapper.cs` (a `*Country` + a deviza-konverzió pontossága)
|
|
||||||
|
|
||||||
## FBANKAPP-DMODEL-I-P6X4: A `Product.Gtin` átmenetileg a VTSZ-t tárolja — szétválasztandó
|
|
||||||
|
|
||||||
**Status:** Open · **Priority:** P3 · **Type:** I (adatmodell / átmeneti megoldás)
|
|
||||||
|
|
||||||
> **Supersedes** `MGFBANKPLUG-EKAER-I-T3X8` — áthelyezve az EKÁER-topicból ide, mert a GTIN/VTSZ szétválasztás **általános** adatmodell-kérdés (a GTIN globális termékazonosító, a VTSZ vámtarifaszám), nem EKÁER-specifikus.
|
|
||||||
|
|
||||||
Az EKÁER `tradeCardItem.productVtsz` (kötelező, 8 jegyű vámtarifaszám) forrása jelenleg a nopCommerce **`Product.Gtin`** oszlop (a `ProductDto.Gtin`-en keresztül). A GTIN és a VTSZ **fogalmilag különböző**:
|
|
||||||
- **GTIN** — globális kereskedelmi cikkszám (vonalkód-azonosító, EAN/UPC).
|
|
||||||
- **VTSZ** — vámtarifaszám (a termék vám-/statisztikai besorolása).
|
|
||||||
|
|
||||||
Egy termékhez a kettő nem azonos; a `Gtin` oszlop VTSZ-ként való használata **átmeneti** megoldás az EKÁER-integráció beindításához.
|
|
||||||
|
|
||||||
**Hatás:** jelenleg nincs üzemszerű gond (a `Gtin` mező szabad, és a VTSZ-t tölthetjük bele). Hosszú távon viszont, ha a valódi GTIN-re is szükség lesz, a kettő ütközik.
|
|
||||||
|
|
||||||
**Javítási irány:** külön `Vtsz` mező/`GenericAttribute` a `Product`-on, és a `ShippingToEkaerMapper` onnan olvasson — a `Gtin` maradjon a valódi GTIN.
|
|
||||||
|
|
||||||
**Affected:**
|
|
||||||
- `FruitBank.Common/Dtos/ProductDto.cs` → `Gtin` property (a `[Column(nameof(Product.Gtin))]` jelöléssel, summary-ban megjelölve)
|
|
||||||
- `FruitBank.Common/Services/Ekaer/ShippingToEkaerMapper.cs` → a `productVtsz` forrása
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
# DATA-MODEL — FruitBank adatmodell-normalizálás
|
|
||||||
|
|
||||||
Topic `DMODEL`, prefix `FBANKAPP` → entry ID-k `FBANKAPP-DMODEL-I-<RAND>` (issue) / `-T-` (TODO) / `-B-` (bug).
|
|
||||||
ID-formátum, Status, type-kódok, archiválás → `../../../.github/TOPIC_CODES.md` (→ framework registry).
|
|
||||||
|
|
||||||
A FruitBank entitások (`Partner`, `CargoPartner`, `CargoTruck`, `ProductDto`) **adatmodell-átmenetiségei**: olyan mezők, amelyek hosszú távon a nopCommerce referencia-tábláira FK-znának, vagy külön mezőbe válnának — de jelenleg szabad string / átmeneti megoldás.
|
|
||||||
|
|
||||||
> Ezek **általános** adatmodell-kérdések, NEM funkció-specifikusak. Az EKÁER-, pre-order- stb. doksik csak **hivatkoznak** ide (nem duplikálják).
|
|
||||||
|
|
||||||
## Companion fájlok
|
|
||||||
|
|
||||||
- [`DATAMODEL_ISSUES.md`](DATAMODEL_ISSUES.md) — az aktív adatmodell-issue-k.
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
# 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 |
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
# 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 +0,0 @@
|
||||||
# FruitBankHybrid.Shared.Common
|
|
||||||
|
|
||||||
@project {
|
|
||||||
type = "product"
|
|
||||||
}
|
|
||||||
|
|
||||||
Shared common library. Currently a placeholder — no source files yet. .NET 10.0 with AOT enabled.
|
|
||||||
|
|
@ -1,215 +0,0 @@
|
||||||
using AyCode.Services.Nav.Ekaer;
|
|
||||||
using AyCode.Services.Nav.Ekaer.Models;
|
|
||||||
using FruitBank.Common.Dtos;
|
|
||||||
using FruitBank.Common.Entities;
|
|
||||||
using FruitBank.Common.Services.Ekaer;
|
|
||||||
using TradeReasonType = AyCode.Services.Nav.Ekaer.Models.Common.TradeReasonType;
|
|
||||||
using TradeType = AyCode.Services.Nav.Ekaer.Models.Common.TradeType;
|
|
||||||
|
|
||||||
namespace FruitBankHybrid.Shared.Tests.Ekaer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unit tesztek a <see cref="ShippingToEkaerMapper"/>-re — a FruitBank <c>Shipping</c> → NAV EKÁER tradeCard
|
|
||||||
/// leképezésre. Tisztán memóriában felépített entitásokon fut (nincs hálózat/DB), determinisztikus.
|
|
||||||
/// </summary>
|
|
||||||
[TestClass]
|
|
||||||
public sealed class ShippingToEkaerMapperTests
|
|
||||||
{
|
|
||||||
private static readonly ShippingToEkaerMapper Mapper = new();
|
|
||||||
|
|
||||||
// ---- Helpers ------------------------------------------------------------
|
|
||||||
|
|
||||||
private static Shipping CreateShipping(string sellerCountry = "HU", string plate = "ABC-123", bool withTrailer = true)
|
|
||||||
{
|
|
||||||
var item = new ShippingItem
|
|
||||||
{
|
|
||||||
Id = 1,
|
|
||||||
Name = "Alma",
|
|
||||||
ProductDto = new ProductDto { Gtin = "08081010", Name = "Alma" },
|
|
||||||
MeasuredGrossWeight = 123.5,
|
|
||||||
MeasuredQuantity = 10,
|
|
||||||
UnitPriceOnDocument = 5.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
var document = new ShippingDocument
|
|
||||||
{
|
|
||||||
Country = sellerCountry,
|
|
||||||
Partner = new Partner
|
|
||||||
{
|
|
||||||
Name = "Beszállító Kft",
|
|
||||||
TaxId = "12345678-2-42",
|
|
||||||
CountryCode = sellerCountry,
|
|
||||||
PostalCode = "1011",
|
|
||||||
City = "Budapest",
|
|
||||||
Street = "Fő utca 1",
|
|
||||||
},
|
|
||||||
ShippingItems = [item],
|
|
||||||
};
|
|
||||||
|
|
||||||
var shipping = new Shipping
|
|
||||||
{
|
|
||||||
CargoPartner = new CargoPartner { Name = "Fuvaros Zrt", CountryCode = "HU" },
|
|
||||||
CargoTruck = new CargoTruck { LicencePlate = plate, CountryCode = "HU", IsTrailer = false },
|
|
||||||
ShippingDocuments = [document],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (withTrailer)
|
|
||||||
shipping.CargoTrailer = new CargoTruck { LicencePlate = "XYZ-789", CountryCode = "HU", IsTrailer = true };
|
|
||||||
|
|
||||||
return shipping;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static EkaerCompanyInfo CreateCompany() => new()
|
|
||||||
{
|
|
||||||
Name = "FruitBank Kft",
|
|
||||||
TaxId = "98765432-2-41",
|
|
||||||
CountryCode = "HU",
|
|
||||||
PostalCode = "1102",
|
|
||||||
City = "Budapest",
|
|
||||||
Street = "Raktar utca 5",
|
|
||||||
UnloadLocation = new LocationType
|
|
||||||
{
|
|
||||||
Name = "FruitBank Raktár",
|
|
||||||
VatNumber = "98765432-2-41",
|
|
||||||
Country = "HU",
|
|
||||||
ZipCode = "1102",
|
|
||||||
City = "Budapest",
|
|
||||||
Street = "Raktar utca",
|
|
||||||
StreetNumber = "5",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---- Granularitás / index ----------------------------------------------
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void MapShipping_ProducesOneOperationPerDocument_WithSequentialIndex()
|
|
||||||
{
|
|
||||||
var shipping = CreateShipping();
|
|
||||||
shipping.ShippingDocuments!.Add(new ShippingDocument
|
|
||||||
{
|
|
||||||
Country = "HU",
|
|
||||||
Partner = new Partner { Name = "Másik Beszállító", CountryCode = "HU" },
|
|
||||||
ShippingItems = [],
|
|
||||||
});
|
|
||||||
|
|
||||||
var ops = Mapper.MapShipping(shipping, CreateCompany());
|
|
||||||
|
|
||||||
Assert.AreEqual(2, ops.Count, "dokumentumonként egy tradeCard");
|
|
||||||
Assert.AreEqual(1, ops[0].Index);
|
|
||||||
Assert.AreEqual(2, ops[1].Index);
|
|
||||||
Assert.AreEqual(OperationType.Create, ops[0].Operation, "alapértelmezett művelet: Create");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void MapShipping_NullDocuments_ReturnsEmpty()
|
|
||||||
{
|
|
||||||
var ops = Mapper.MapShipping(new Shipping { ShippingDocuments = null }, CreateCompany());
|
|
||||||
Assert.AreEqual(0, ops.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void MapShipping_HonorsExplicitOperation()
|
|
||||||
{
|
|
||||||
var ops = Mapper.MapShipping(CreateShipping(), CreateCompany(), OperationType.Modify);
|
|
||||||
Assert.AreEqual(OperationType.Modify, ops[0].Operation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- tradeType irány ----------------------------------------------------
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void MapShipping_DomesticSeller_TradeTypeDomestic()
|
|
||||||
{
|
|
||||||
var ops = Mapper.MapShipping(CreateShipping(sellerCountry: "HU"), CreateCompany());
|
|
||||||
Assert.AreEqual(TradeType.D, ops[0].TradeCard.TradeType, "belföldi (HU) feladó → D");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void MapShipping_ForeignSeller_TradeTypeImport()
|
|
||||||
{
|
|
||||||
var ops = Mapper.MapShipping(CreateShipping(sellerCountry: "DE"), CreateCompany());
|
|
||||||
Assert.AreEqual(TradeType.I, ops[0].TradeCard.TradeType, "nem-HU feladó → I (import) — a NAV EKÁER magyar");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Tétel-leképezés ----------------------------------------------------
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void MapShipping_MapsItemFields()
|
|
||||||
{
|
|
||||||
var item = Mapper.MapShipping(CreateShipping(), CreateCompany())[0].TradeCard.Items[0];
|
|
||||||
|
|
||||||
Assert.AreEqual("08081010", item.ProductVtsz, "productVtsz = ProductDto.Gtin");
|
|
||||||
Assert.AreEqual("Alma", item.ProductName);
|
|
||||||
Assert.AreEqual(123.5m, item.Weight, "weight = MeasuredGrossWeight (bruttó)");
|
|
||||||
Assert.AreEqual(TradeReasonType.A, item.TradeReason, "bejövő áru = beszerzés = A");
|
|
||||||
Assert.IsNull(item.Value, "a HUF érték a deviza tisztázásáig nincs kitöltve");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Eladó (seller*) ----------------------------------------------------
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void MapShipping_MapsSellerFromPartner()
|
|
||||||
{
|
|
||||||
var tradeCard = Mapper.MapShipping(CreateShipping(), CreateCompany())[0].TradeCard;
|
|
||||||
|
|
||||||
Assert.AreEqual("Beszállító Kft", tradeCard.SellerName);
|
|
||||||
Assert.AreEqual("12345678-2-42", tradeCard.SellerVatNumber);
|
|
||||||
Assert.AreEqual("HU", tradeCard.SellerCountry);
|
|
||||||
StringAssert.Contains(tradeCard.SellerAddress, "Budapest", "a sellerAddress a Partner FullAddress-éből jön");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void MapShipping_MapsDestinationAndUnloadFromCompany()
|
|
||||||
{
|
|
||||||
var company = CreateCompany();
|
|
||||||
var tradeCard = Mapper.MapShipping(CreateShipping(), company)[0].TradeCard;
|
|
||||||
|
|
||||||
Assert.AreEqual("FruitBank Kft", tradeCard.DestinationName);
|
|
||||||
Assert.AreEqual("HU", tradeCard.DestinationCountry);
|
|
||||||
StringAssert.Contains(tradeCard.DestinationAddress, "Budapest", "a destinationAddress a company FullAddress-éből jön");
|
|
||||||
Assert.AreSame(company.UnloadLocation, tradeCard.UnloadLocation, "a lerakodási hely a cégadatból jön");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void MapShipping_MapsCarrierTextFromCargoPartner()
|
|
||||||
{
|
|
||||||
var tradeCard = Mapper.MapShipping(CreateShipping(), CreateCompany())[0].TradeCard;
|
|
||||||
Assert.AreEqual("Fuvaros Zrt", tradeCard.CarrierText);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Járművek + rendszám-normalizálás ----------------------------------
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void MapShipping_NormalizesLicencePlate_RemovesHyphenAndUppercases()
|
|
||||||
{
|
|
||||||
var tradeCard = Mapper.MapShipping(CreateShipping(plate: "abc-123"), CreateCompany())[0].TradeCard;
|
|
||||||
Assert.AreEqual("ABC123", tradeCard.Vehicle!.PlateNumber, "a NAV pattern nem enged kötőjelet, és nagybetűs");
|
|
||||||
Assert.AreEqual("HU", tradeCard.Vehicle.Country);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void MapShipping_WithTrailer_MapsVehicle2()
|
|
||||||
{
|
|
||||||
var tradeCard = Mapper.MapShipping(CreateShipping(withTrailer: true), CreateCompany())[0].TradeCard;
|
|
||||||
Assert.IsNotNull(tradeCard.Vehicle, "vonó jármű");
|
|
||||||
Assert.IsNotNull(tradeCard.Vehicle2, "vontatmány");
|
|
||||||
Assert.AreEqual("XYZ789", tradeCard.Vehicle2!.PlateNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void MapShipping_NoTrailer_Vehicle2Null()
|
|
||||||
{
|
|
||||||
var tradeCard = Mapper.MapShipping(CreateShipping(withTrailer: false), CreateCompany())[0].TradeCard;
|
|
||||||
Assert.IsNotNull(tradeCard.Vehicle);
|
|
||||||
Assert.IsNull(tradeCard.Vehicle2, "nincs pótkocsi → nincs vehicle2");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Védőkorlátok -------------------------------------------------------
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void MapShipping_NullShipping_Throws()
|
|
||||||
=> Assert.ThrowsExactly<ArgumentNullException>(() => Mapper.MapShipping(null!, CreateCompany()));
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void MapShipping_NullCompany_Throws()
|
|
||||||
=> Assert.ThrowsExactly<ArgumentNullException>(() => Mapper.MapShipping(CreateShipping(), null!));
|
|
||||||
}
|
|
||||||
|
|
@ -31,7 +31,11 @@ namespace FruitBankHybrid.Shared.Tests
|
||||||
{
|
{
|
||||||
if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
||||||
|
|
||||||
_signalRClient = TestSignalRClientFactory.Create(nameof(FruitBankClientTests));
|
_signalRClient = new FruitBankSignalRClient(new List<IAcLogWriterClientBase>
|
||||||
|
{
|
||||||
|
//new ConsoleLogWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests)),
|
||||||
|
new SignaRClientLogItemWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Partner
|
#region Partner
|
||||||
|
|
@ -42,22 +46,22 @@ namespace FruitBankHybrid.Shared.Tests
|
||||||
var partners = await _signalRClient.GetPartners();
|
var partners = await _signalRClient.GetPartners();
|
||||||
|
|
||||||
Assert.IsNotNull(partners);
|
Assert.IsNotNull(partners);
|
||||||
Assert.IsNotEmpty(partners);
|
Assert.IsTrue(partners.Count != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
//[TestMethod]
|
//[DataTestMethod]
|
||||||
//[DataRow(1)]
|
//[DataRow(1)]
|
||||||
public async Task<Partner> GetPartnerByIdTest(int partnerId)
|
public async Task<Partner> GetPartnerByIdTest(int partnerId)
|
||||||
{
|
{
|
||||||
var partner = await _signalRClient.GetPartnerById(partnerId);
|
var partner = await _signalRClient.GetPartnerById(partnerId);
|
||||||
|
|
||||||
Assert.IsNotNull(partner);
|
Assert.IsNotNull(partner);
|
||||||
Assert.AreEqual(partnerId, partner.Id);
|
Assert.IsTrue(partner.Id == partnerId);
|
||||||
|
|
||||||
return partner;
|
return partner;
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[DataTestMethod]
|
||||||
[DataRow(2)]
|
[DataRow(2)]
|
||||||
public async Task UpdatePartnerTest(int partnerId)
|
public async Task UpdatePartnerTest(int partnerId)
|
||||||
{
|
{
|
||||||
|
|
@ -75,108 +79,10 @@ namespace FruitBankHybrid.Shared.Tests
|
||||||
partner = await _signalRClient.UpdatePartner(partner);
|
partner = await _signalRClient.UpdatePartner(partner);
|
||||||
|
|
||||||
Assert.IsNotNull(partner);
|
Assert.IsNotNull(partner);
|
||||||
Assert.AreEqual(partnerId, partner.Id);
|
Assert.IsTrue(partner.Id == partnerId);
|
||||||
}
|
}
|
||||||
#endregion Partner
|
#endregion Partner
|
||||||
|
|
||||||
#region CargoPartner
|
|
||||||
[TestMethod]
|
|
||||||
public async Task GetCargoPartnersTest()
|
|
||||||
{
|
|
||||||
var partners = await _signalRClient.GetCargoPartners();
|
|
||||||
|
|
||||||
Assert.IsNotNull(partners);
|
|
||||||
Assert.IsNotEmpty(partners);
|
|
||||||
}
|
|
||||||
//[TestMethod]
|
|
||||||
//[DataRow(1)]
|
|
||||||
public async Task<CargoPartner> GetCargoPartnerByIdTest(int cargoPartnerId)
|
|
||||||
{
|
|
||||||
var cargoPartner = await _signalRClient.GetCargoPartnerById(cargoPartnerId);
|
|
||||||
|
|
||||||
Assert.IsNotNull(cargoPartner);
|
|
||||||
Assert.AreEqual(cargoPartnerId, cargoPartner.Id);
|
|
||||||
|
|
||||||
return cargoPartner;
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
[DataRow(1)]
|
|
||||||
public async Task UpdateCargoPartnerTest(int cargoPartnerId)
|
|
||||||
{
|
|
||||||
var cargoPartner = await GetCargoPartnerByIdTest(cargoPartnerId);
|
|
||||||
|
|
||||||
var newName = GetFixtureName(cargoPartner.Name);
|
|
||||||
|
|
||||||
cargoPartner.Name = newName;
|
|
||||||
cargoPartner = await _signalRClient.UpdateCargoPartner(cargoPartner);
|
|
||||||
|
|
||||||
Assert.IsNotNull(cargoPartner);
|
|
||||||
Assert.IsTrue(cargoPartner.Name == newName);
|
|
||||||
|
|
||||||
cargoPartner.Name = GetOriginalName(cargoPartner.Name);
|
|
||||||
cargoPartner = await _signalRClient.UpdateCargoPartner(cargoPartner);
|
|
||||||
|
|
||||||
Assert.IsNotNull(cargoPartner);
|
|
||||||
Assert.AreEqual(cargoPartnerId, cargoPartner.Id);
|
|
||||||
}
|
|
||||||
#endregion CargoPartner
|
|
||||||
|
|
||||||
#region CargoTruck
|
|
||||||
[TestMethod]
|
|
||||||
public async Task GetCargoTrucksTest()
|
|
||||||
{
|
|
||||||
var cargoTrucks = await _signalRClient.GetCargoTrucks();
|
|
||||||
|
|
||||||
Assert.IsNotNull(cargoTrucks);
|
|
||||||
Assert.IsNotEmpty(cargoTrucks);
|
|
||||||
}
|
|
||||||
|
|
||||||
//[TestMethod]
|
|
||||||
//[DataRow(1)]
|
|
||||||
public async Task<CargoTruck> GetCargoTruckByIdTest(int cargoTruckId)
|
|
||||||
{
|
|
||||||
var cargoTruck = await _signalRClient.GetCargoTruckById(cargoTruckId);
|
|
||||||
|
|
||||||
Assert.IsNotNull(cargoTruck);
|
|
||||||
Assert.AreEqual(cargoTruckId, cargoTruck.Id);
|
|
||||||
|
|
||||||
return cargoTruck;
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
[DataRow(1)]
|
|
||||||
public async Task GetCargoTrucksByCargoPartnerIdTest(int cargoPartnerId)
|
|
||||||
{
|
|
||||||
var cargoTrucks = await _signalRClient.GetCargoTrucksByCargoPartnerId(cargoPartnerId);
|
|
||||||
|
|
||||||
Assert.IsNotNull(cargoTrucks);
|
|
||||||
Assert.IsNotEmpty(cargoTrucks);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
[DataRow(1)]
|
|
||||||
public async Task UpdateCargoTruckTest(int cargoTruckId)
|
|
||||||
{
|
|
||||||
var cargoTruck = await GetCargoTruckByIdTest(cargoTruckId);
|
|
||||||
|
|
||||||
var newLicencePlate = GetFixtureName(cargoTruck.LicencePlate);
|
|
||||||
|
|
||||||
cargoTruck.LicencePlate = newLicencePlate;
|
|
||||||
cargoTruck = await _signalRClient.UpdateCargoTruck(cargoTruck);
|
|
||||||
|
|
||||||
Assert.IsNotNull(cargoTruck);
|
|
||||||
Assert.IsTrue(cargoTruck.LicencePlate == newLicencePlate);
|
|
||||||
|
|
||||||
cargoTruck.LicencePlate = GetOriginalName(cargoTruck.LicencePlate);
|
|
||||||
cargoTruck = await _signalRClient.UpdateCargoTruck(cargoTruck);
|
|
||||||
|
|
||||||
Assert.IsNotNull(cargoTruck);
|
|
||||||
Assert.AreEqual(cargoTruckId, cargoTruck.Id);
|
|
||||||
}
|
|
||||||
#endregion CargoTruck
|
|
||||||
|
|
||||||
#region Shipping
|
#region Shipping
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task GetShippingsTest()
|
public async Task GetShippingsTest()
|
||||||
|
|
@ -184,7 +90,7 @@ namespace FruitBankHybrid.Shared.Tests
|
||||||
var shippings = await _signalRClient.GetShippings();
|
var shippings = await _signalRClient.GetShippings();
|
||||||
|
|
||||||
Assert.IsNotNull(shippings);
|
Assert.IsNotNull(shippings);
|
||||||
Assert.IsNotEmpty(shippings);
|
Assert.IsTrue(shippings.Count != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
@ -554,7 +460,7 @@ namespace FruitBankHybrid.Shared.Tests
|
||||||
[DataRow(5, true)]
|
[DataRow(5, true)]
|
||||||
//[DataRow(6, false)]
|
//[DataRow(6, false)]
|
||||||
[DataRow(33, true)]
|
[DataRow(33, true)]
|
||||||
//[DataRow(64, false)]
|
[DataRow(64, false)]
|
||||||
[DataRow(7, true)]
|
[DataRow(7, true)]
|
||||||
public async Task GetProductDtoByIdTest(int productId, bool isMeasurableExcepted)
|
public async Task GetProductDtoByIdTest(int productId, bool isMeasurableExcepted)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,20 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="bunit" Version="2.4.2" />
|
<PackageReference Include="bunit" Version="2.2.2" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Core\AyCode.Core.csproj" />
|
|
||||||
<ProjectReference Include="..\FruitBank.Common\FruitBank.Common.csproj" />
|
<ProjectReference Include="..\FruitBank.Common\FruitBank.Common.csproj" />
|
||||||
<ProjectReference Include="..\FruitBankHybrid.Shared.Common\FruitBankHybrid.Shared.Common.csproj" />
|
<ProjectReference Include="..\FruitBankHybrid.Shared.Common\FruitBankHybrid.Shared.Common.csproj" />
|
||||||
<ProjectReference Include="..\FruitBankHybrid.Shared\FruitBankHybrid.Shared.csproj" />
|
<ProjectReference Include="..\FruitBankHybrid.Shared\FruitBankHybrid.Shared.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Reference Include="AyCode.Core">
|
||||||
|
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\Debug\net9.0\AyCode.Core.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="AyCode.Entities">
|
<Reference Include="AyCode.Entities">
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\Debug\net9.0\AyCode.Entities.dll</HintPath>
|
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\Debug\net9.0\AyCode.Entities.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,10 @@ public sealed class JsonExtensionTests
|
||||||
{
|
{
|
||||||
if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
||||||
|
|
||||||
_signalRClient = TestSignalRClientFactory.Create(nameof(FruitBankClientTests));
|
_signalRClient = new FruitBankSignalRClient(new List<IAcLogWriterClientBase>
|
||||||
|
{
|
||||||
|
new SignaRClientLogItemWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,14 @@ using AyCode.Core.Extensions;
|
||||||
using AyCode.Core.Loggers;
|
using AyCode.Core.Loggers;
|
||||||
using FruitBank.Common;
|
using FruitBank.Common;
|
||||||
using FruitBank.Common.Dtos;
|
using FruitBank.Common.Dtos;
|
||||||
using FruitBank.Common.Entities;
|
|
||||||
using FruitBank.Common.Loggers;
|
using FruitBank.Common.Loggers;
|
||||||
using FruitBankHybrid.Shared.Services.SignalRs;
|
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Nop.Core.Domain.Common;
|
|
||||||
using Nop.Core.Domain.Orders;
|
using Nop.Core.Domain.Orders;
|
||||||
using Nop.Core.Domain.Payments;
|
using Nop.Core.Domain.Payments;
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using AyCode.Core.Serializers.Toons;
|
using FruitBank.Common.Entities;
|
||||||
|
using Nop.Core.Domain.Common;
|
||||||
|
|
||||||
namespace FruitBankHybrid.Shared.Tests;
|
namespace FruitBankHybrid.Shared.Tests;
|
||||||
|
|
||||||
|
|
@ -28,20 +26,23 @@ public sealed class OrderClientTests
|
||||||
{
|
{
|
||||||
if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
||||||
|
|
||||||
_signalRClient = TestSignalRClientFactory.Create(nameof(OrderClientTests));
|
_signalRClient = new FruitBankSignalRClient(new List<IAcLogWriterClientBase>
|
||||||
|
{
|
||||||
|
//new ConsoleLogWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests)),
|
||||||
|
new SignaRClientLogItemWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task GetAllStockTakings()
|
public async Task GetAllStockTakings()
|
||||||
{
|
{
|
||||||
var stockTakings = await _signalRClient.GetStockTakings(true);
|
var stockTakings = await _signalRClient.GetStockTakings(false);
|
||||||
|
|
||||||
Assert.IsNotNull(stockTakings);
|
Assert.IsNotNull(stockTakings);
|
||||||
Assert.IsNotEmpty(stockTakings);
|
Assert.IsTrue(stockTakings.Count != 0);
|
||||||
|
|
||||||
Assert.IsTrue(stockTakings.All(o => o.StockTakingItems != null));
|
Assert.IsTrue(stockTakings.All(o => o.StockTakingItems.All(oi => oi.Product != null && oi.Product.Id == oi.ProductId)));
|
||||||
Assert.IsTrue(stockTakings.All(o => o.StockTakingItems!.All(oi => oi.Product != null && oi.Product.Id == oi.ProductId)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
@ -50,9 +51,9 @@ public sealed class OrderClientTests
|
||||||
var stockTakingItems = await _signalRClient.GetStockTakingItems();
|
var stockTakingItems = await _signalRClient.GetStockTakingItems();
|
||||||
|
|
||||||
Assert.IsNotNull(stockTakingItems);
|
Assert.IsNotNull(stockTakingItems);
|
||||||
Assert.IsNotEmpty(stockTakingItems);
|
Assert.IsTrue(stockTakingItems.Count != 0);
|
||||||
|
|
||||||
Assert.IsTrue(stockTakingItems.All(oi => oi is { StockTaking: not null, Product: not null } && oi.Product.Id == oi.ProductId));
|
Assert.IsTrue(stockTakingItems.All(oi => oi.StockTaking != null && oi.Product != null && oi.Product.Id == oi.ProductId));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
@ -76,18 +77,6 @@ public sealed class OrderClientTests
|
||||||
Assert.IsTrue(orderDtos.All(o => o.OrderItemDtos.All(oi => oi.ProductDto != null && oi.ProductDto.Id == oi.ProductId)));
|
Assert.IsTrue(orderDtos.All(o => o.OrderItemDtos.All(oi => oi.ProductDto != null && oi.ProductDto.Id == oi.ProductId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task GetAllOrderDtosByFilter()
|
|
||||||
{
|
|
||||||
//Queryable? filter = dto => dto.Id == 15;
|
|
||||||
var orderDtos = await _signalRClient.GetAllOrderDtos();
|
|
||||||
|
|
||||||
Assert.IsNotNull(orderDtos);
|
|
||||||
Assert.IsTrue(orderDtos.Count != 0);
|
|
||||||
|
|
||||||
Assert.IsTrue(orderDtos.All(o => o.OrderItemDtos.All(oi => oi.ProductDto != null && oi.ProductDto.Id == oi.ProductId)));
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow(1)]
|
[DataRow(1)]
|
||||||
[DataRow(2)]
|
[DataRow(2)]
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# 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
|
|
||||||
|
|
||||||
| Folder | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| [`TestData/`](TestData/README.md) | Test models for Toon serialization |
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`MSTestSettings.cs`** — Parallel test execution at MethodLevel.
|
|
||||||
- **`FruitBankClientTests.cs`** — (~667 lines) Full SignalR integration: Partner, Shipping, ShippingItem, ShippingDocument, Customer, Product, Order, Login tests. Localhost-only safety check.
|
|
||||||
- **`OrderClientTests.cs`** — Order and StockTaking retrieval/manipulation tests.
|
|
||||||
- **`JsonExtensionTests.cs`** — (~715 lines) JSON $id/$ref reference handling, 5-level hierarchies, circular references, DeepPopulateWithMerge.
|
|
||||||
- **`StockTakingSerializerTests.cs`** — Binary serialization round-trips, null collection handling, binary format analysis.
|
|
||||||
- **`ToonTests.cs`** — (~465 lines) Toon format: metadata generation, reference markers, type uniqueness, navigation metadata, property descriptions.
|
|
||||||
- **`SandboxEndpointSimpleTests.cs`** — Endpoint connectivity and SignalR negotiate tests.
|
|
||||||
- **`GridPartnerBaseTests.cs`** — Grid component tests (disabled).
|
|
||||||
- **`GridPartnerRazorTests.cs`** — bunit Blazor rendering tests (disabled).
|
|
||||||
|
|
@ -1,316 +1,320 @@
|
||||||
//using AyCode.Core.Enums;
|
using AyCode.Core.Enums;
|
||||||
//using AyCode.Core.Loggers;
|
using AyCode.Core.Loggers;
|
||||||
//using AyCode.Utils.Extensions;
|
using AyCode.Utils.Extensions;
|
||||||
//using FruitBank.Common;
|
using FruitBank.Common;
|
||||||
//using FruitBank.Common.Dtos;
|
using FruitBank.Common.Dtos;
|
||||||
//using FruitBank.Common.Entities;
|
using FruitBank.Common.Entities;
|
||||||
//using FruitBank.Common.Interfaces;
|
using FruitBank.Common.Interfaces;
|
||||||
//using FruitBank.Common.Loggers;
|
using FruitBank.Common.Loggers;
|
||||||
//using FruitBankHybrid.Shared.Services.SignalRs;
|
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||||
//using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
//using FruitBank.Common.SignalRs;
|
using FruitBank.Common.SignalRs;
|
||||||
//using AyCode.Services.SignalRs;
|
using AyCode.Services.SignalRs;
|
||||||
|
|
||||||
//namespace FruitBankHybrid.Shared.Tests;
|
namespace FruitBankHybrid.Shared.Tests;
|
||||||
|
|
||||||
///// <summary>
|
/// <summary>
|
||||||
///// Teszt a TestSignalREndpoint-hoz.
|
/// Teszt a TestSignalREndpoint-hoz.
|
||||||
///// FONTOS: A SANDBOX-ot manu<6E>lisan kell elind<6E>tani a tesztek futtat<61>sa el<65>tt!
|
/// FONTOS: A SANDBOX-ot manuálisan kell elindítani a tesztek futtatása elõtt!
|
||||||
///// Ind<6E>t<EFBFBD>s: dotnet run --project Mango.Sandbox.EndPoints --urls http://localhost:59579
|
/// Indítás: dotnet run --project Mango.Sandbox.EndPoints --urls http://localhost:59579
|
||||||
///// </summary>
|
/// </summary>
|
||||||
//[TestClass]
|
[TestClass]
|
||||||
//public class SandboxEndpointSimpleTests
|
public class SandboxEndpointSimpleTests
|
||||||
//{
|
{
|
||||||
// private static readonly string SandboxUrl = FruitBankConstClient.BaseUrl; //"http://localhost:59579";
|
private static readonly string SandboxUrl = FruitBankConstClient.BaseUrl; //"http://localhost:59579";
|
||||||
// private static readonly string HubUrl = $"{SandboxUrl}/fbHub";
|
private static readonly string HubUrl = $"{SandboxUrl}/fbHub";
|
||||||
|
|
||||||
// // Teszt SignalR Tags (TestSignalRTags-b<>l)
|
// Teszt SignalR Tags (TestSignalRTags-bõl)
|
||||||
// private const int PingTag = SignalRTags.PingTag;
|
private const int PingTag = SignalRTags.PingTag;
|
||||||
// private const int EchoTag = SignalRTags.EchoTag;
|
private const int EchoTag = SignalRTags.EchoTag;
|
||||||
// private const int GetTestItemsTag = 9003;
|
private const int GetTestItemsTag = 9003;
|
||||||
|
|
||||||
// private FruitBankSignalRClient _signalRClient = null!;
|
private FruitBankSignalRClient _signalRClient = null!;
|
||||||
|
|
||||||
// [TestInitialize]
|
[TestInitialize]
|
||||||
// public void TestInit()
|
public void TestInit()
|
||||||
// {
|
{
|
||||||
// if (!SandboxUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTEL<45>NK!");
|
if (!SandboxUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
||||||
|
|
||||||
// _signalRClient = TestSignalRClientFactory.Create(nameof(SandboxEndpointSimpleTests));
|
_signalRClient = new FruitBankSignalRClient(new List<IAcLogWriterClientBase>
|
||||||
// }
|
{
|
||||||
|
//new ConsoleLogWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests)),
|
||||||
|
new SignaRClientLogItemWriter(AppType.TestUnit, LogLevel.Detail, nameof(SandboxEndpointSimpleTests))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// #region HTTP Endpoint Tests
|
#region HTTP Endpoint Tests
|
||||||
|
|
||||||
// [TestMethod]
|
[TestMethod]
|
||||||
// public async Task HealthEndpoint_ReturnsSuccess()
|
public async Task HealthEndpoint_ReturnsSuccess()
|
||||||
// {
|
{
|
||||||
// using var httpClient = new HttpClient();
|
using var httpClient = new HttpClient();
|
||||||
// var response = await httpClient.GetAsync($"{SandboxUrl}/health");
|
var response = await httpClient.GetAsync($"{SandboxUrl}/health");
|
||||||
// Assert.IsTrue(response.IsSuccessStatusCode, $"Health endpoint returned {response.StatusCode}");
|
Assert.IsTrue(response.IsSuccessStatusCode, $"Health endpoint returned {response.StatusCode}");
|
||||||
// }
|
}
|
||||||
|
|
||||||
// [TestMethod]
|
[TestMethod]
|
||||||
// public async Task RootEndpoint_ReturnsSandboxIsRunning()
|
public async Task RootEndpoint_ReturnsSandboxIsRunning()
|
||||||
// {
|
{
|
||||||
// using var httpClient = new HttpClient();
|
using var httpClient = new HttpClient();
|
||||||
// var response = await httpClient.GetStringAsync(SandboxUrl);
|
var response = await httpClient.GetStringAsync(SandboxUrl);
|
||||||
// Assert.AreEqual("SANDBOX is running!", response);
|
Assert.AreEqual("SANDBOX is running!", response);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// #endregion
|
#endregion
|
||||||
|
|
||||||
// #region SignalR Connection Tests
|
#region SignalR Connection Tests
|
||||||
|
|
||||||
// [TestMethod]
|
[TestMethod]
|
||||||
// public async Task SignalR_Negotiate_ReturnsSuccess()
|
public async Task SignalR_Negotiate_ReturnsSuccess()
|
||||||
// {
|
{
|
||||||
// using var httpClient = new HttpClient();
|
using var httpClient = new HttpClient();
|
||||||
// var response = await httpClient.PostAsync($"{HubUrl}/negotiate?negotiateVersion=1", null);
|
var response = await httpClient.PostAsync($"{HubUrl}/negotiate?negotiateVersion=1", null);
|
||||||
// Assert.IsTrue(response.IsSuccessStatusCode, $"SignalR negotiate returned {response.StatusCode}");
|
Assert.IsTrue(response.IsSuccessStatusCode, $"SignalR negotiate returned {response.StatusCode}");
|
||||||
// }
|
}
|
||||||
|
|
||||||
// [TestMethod]
|
[TestMethod]
|
||||||
// public async Task SignalR_Connect_Succeeds()
|
public async Task SignalR_Connect_Succeeds()
|
||||||
// {
|
{
|
||||||
// var testItems = await _signalRClient.GetAllAsync<List<TestItem>>(GetTestItemsTag);
|
var testItems = await _signalRClient.GetAllAsync<List<TestItem>>(GetTestItemsTag);
|
||||||
// Assert.IsNotNull(testItems);
|
Assert.IsNotNull(testItems);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// public class TestItem
|
public class TestItem
|
||||||
// {
|
{
|
||||||
// public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
// public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
// public decimal Value { get; set; }
|
public decimal Value { get; set; }
|
||||||
// }
|
}
|
||||||
|
|
||||||
////[TestMethod]
|
//[TestMethod]
|
||||||
// //public async Task SignalR_Connect_Succeeds()
|
//public async Task SignalR_Connect_Succeeds()
|
||||||
// //{
|
//{
|
||||||
// // var connection = new HubConnectionBuilder()
|
// var connection = new HubConnectionBuilder()
|
||||||
// // .WithUrl(HubUrl)
|
// .WithUrl(HubUrl)
|
||||||
// // .Build();
|
// .Build();
|
||||||
|
|
||||||
// // try
|
// try
|
||||||
// // {
|
// {
|
||||||
// // await connection.StartAsync();
|
// await connection.StartAsync();
|
||||||
// // Assert.AreEqual(HubConnectionState.Connected, connection.State);
|
// Assert.AreEqual(HubConnectionState.Connected, connection.State);
|
||||||
// // }
|
// }
|
||||||
// // finally
|
// finally
|
||||||
// // {
|
// {
|
||||||
// // await connection.StopAsync();
|
// await connection.StopAsync();
|
||||||
// // }
|
// }
|
||||||
// //}
|
//}
|
||||||
|
|
||||||
// //#endregion
|
//#endregion
|
||||||
|
|
||||||
// //#region TestSignalREndpoint Tests
|
//#region TestSignalREndpoint Tests
|
||||||
|
|
||||||
// //[TestMethod]
|
//[TestMethod]
|
||||||
// //public async Task SignalR_Ping_ReturnsResponse()
|
//public async Task SignalR_Ping_ReturnsResponse()
|
||||||
// //{
|
//{
|
||||||
// // var testMessage = "Hello SignalR!";
|
// var testMessage = "Hello SignalR!";
|
||||||
// // await TestSignalREndpoint(PingTag, testMessage, "Ping", response =>
|
// await TestSignalREndpoint(PingTag, testMessage, "Ping", response =>
|
||||||
// // {
|
// {
|
||||||
// // Assert.IsNotNull(response, "Response should not be null");
|
// Assert.IsNotNull(response, "Response should not be null");
|
||||||
|
|
||||||
// // // Parse JSON response
|
// // Parse JSON response
|
||||||
// // using var jsonDoc = JsonDocument.Parse(response);
|
// using var jsonDoc = JsonDocument.Parse(response);
|
||||||
// // var root = jsonDoc.RootElement;
|
// var root = jsonDoc.RootElement;
|
||||||
|
|
||||||
// // // Ellen<65>rizz<7A>k, hogy van Message property
|
// // Ellenõrizzük, hogy van Message property
|
||||||
// // Assert.IsTrue(root.TryGetProperty("Message", out var messageElement) ||
|
// Assert.IsTrue(root.TryGetProperty("Message", out var messageElement) ||
|
||||||
// // root.TryGetProperty("message", out messageElement),
|
// root.TryGetProperty("message", out messageElement),
|
||||||
// // "Response should contain 'Message' property");
|
// "Response should contain 'Message' property");
|
||||||
|
|
||||||
// // Console.WriteLine($"[Ping] Received message: {messageElement.GetString()}");
|
// Console.WriteLine($"[Ping] Received message: {messageElement.GetString()}");
|
||||||
// // });
|
// });
|
||||||
// //}
|
//}
|
||||||
|
|
||||||
// //[TestMethod]
|
//[TestMethod]
|
||||||
// //public async Task SignalR_Echo_ReturnsEchoedData()
|
//public async Task SignalR_Echo_ReturnsEchoedData()
|
||||||
// //{
|
//{
|
||||||
// // var request = new { Id = 42, Name = "TestName" };
|
// var request = new { Id = 42, Name = "TestName" };
|
||||||
// // await TestSignalREndpoint(EchoTag, request, "Echo", response =>
|
// await TestSignalREndpoint(EchoTag, request, "Echo", response =>
|
||||||
// // {
|
// {
|
||||||
// // Assert.IsNotNull(response, "Response should not be null");
|
// Assert.IsNotNull(response, "Response should not be null");
|
||||||
|
|
||||||
// // using var jsonDoc = JsonDocument.Parse(response);
|
// using var jsonDoc = JsonDocument.Parse(response);
|
||||||
// // var root = jsonDoc.RootElement;
|
// var root = jsonDoc.RootElement;
|
||||||
|
|
||||||
// // // Ellen<65>rizz<7A>k az Id-t
|
// // Ellenõrizzük az Id-t
|
||||||
// // Assert.IsTrue(root.TryGetProperty("Id", out var idElement) ||
|
// Assert.IsTrue(root.TryGetProperty("Id", out var idElement) ||
|
||||||
// // root.TryGetProperty("id", out idElement),
|
// root.TryGetProperty("id", out idElement),
|
||||||
// // "Response should contain 'Id' property");
|
// "Response should contain 'Id' property");
|
||||||
// // Assert.AreEqual(42, idElement.GetInt32(), "Id should be 42");
|
// Assert.AreEqual(42, idElement.GetInt32(), "Id should be 42");
|
||||||
|
|
||||||
// // // Ellen<65>rizz<7A>k a Name-et
|
// // Ellenõrizzük a Name-et
|
||||||
// // Assert.IsTrue(root.TryGetProperty("Name", out var nameElement) ||
|
// Assert.IsTrue(root.TryGetProperty("Name", out var nameElement) ||
|
||||||
// // root.TryGetProperty("name", out nameElement),
|
// root.TryGetProperty("name", out nameElement),
|
||||||
// // "Response should contain 'Name' property");
|
// "Response should contain 'Name' property");
|
||||||
// // Assert.AreEqual("TestName", nameElement.GetString(), "Name should be 'TestName'");
|
// Assert.AreEqual("TestName", nameElement.GetString(), "Name should be 'TestName'");
|
||||||
|
|
||||||
// // Console.WriteLine($"[Echo] Received: Id={idElement.GetInt32()}, Name={nameElement.GetString()}");
|
// Console.WriteLine($"[Echo] Received: Id={idElement.GetInt32()}, Name={nameElement.GetString()}");
|
||||||
// // });
|
// });
|
||||||
// //}
|
//}
|
||||||
|
|
||||||
// //[TestMethod]
|
//[TestMethod]
|
||||||
// //public async Task SignalR_GetTestItems_ReturnsItemList()
|
//public async Task SignalR_GetTestItems_ReturnsItemList()
|
||||||
// //{
|
//{
|
||||||
// // await TestSignalREndpoint(GetTestItemsTag, null, "GetTestItems", response =>
|
// await TestSignalREndpoint(GetTestItemsTag, null, "GetTestItems", response =>
|
||||||
// // {
|
// {
|
||||||
// // Assert.IsNotNull(response, "Response should not be null");
|
// Assert.IsNotNull(response, "Response should not be null");
|
||||||
|
|
||||||
// // using var jsonDoc = JsonDocument.Parse(response);
|
// using var jsonDoc = JsonDocument.Parse(response);
|
||||||
// // var root = jsonDoc.RootElement;
|
// var root = jsonDoc.RootElement;
|
||||||
|
|
||||||
// // // Ellen<65>rizz<7A>k, hogy t<>mb-e
|
// // Ellenõrizzük, hogy tömb-e
|
||||||
// // Assert.AreEqual(JsonValueKind.Array, root.ValueKind, "Response should be an array");
|
// Assert.AreEqual(JsonValueKind.Array, root.ValueKind, "Response should be an array");
|
||||||
// // Assert.IsTrue(root.GetArrayLength() > 0, "Array should have items");
|
// Assert.IsTrue(root.GetArrayLength() > 0, "Array should have items");
|
||||||
|
|
||||||
// // Console.WriteLine($"[GetTestItems] Received {root.GetArrayLength()} items");
|
// Console.WriteLine($"[GetTestItems] Received {root.GetArrayLength()} items");
|
||||||
|
|
||||||
// // // Ellen<65>rizz<7A>k az els<6C> elemet
|
// // Ellenõrizzük az elsõ elemet
|
||||||
// // var firstItem = root[0];
|
// var firstItem = root[0];
|
||||||
// // Assert.IsTrue(firstItem.TryGetProperty("Id", out _) || firstItem.TryGetProperty("id", out _),
|
// Assert.IsTrue(firstItem.TryGetProperty("Id", out _) || firstItem.TryGetProperty("id", out _),
|
||||||
// // "Item should have 'Id' property");
|
// "Item should have 'Id' property");
|
||||||
// // Assert.IsTrue(firstItem.TryGetProperty("Name", out _) || firstItem.TryGetProperty("name", out _),
|
// Assert.IsTrue(firstItem.TryGetProperty("Name", out _) || firstItem.TryGetProperty("name", out _),
|
||||||
// // "Item should have 'Name' property");
|
// "Item should have 'Name' property");
|
||||||
// // });
|
// });
|
||||||
// //}
|
//}
|
||||||
|
|
||||||
// //#endregion
|
//#endregion
|
||||||
|
|
||||||
// //#region EREDETI BUSINESS ENDPOINT TESZTEK - KIKOMMENTEZVE
|
//#region EREDETI BUSINESS ENDPOINT TESZTEK - KIKOMMENTEZVE
|
||||||
|
|
||||||
// //// ===========================================
|
//// ===========================================
|
||||||
// //// === Az al<61>bbi tesztek az eredeti 3 endpoint-ot tesztelik ===
|
//// === Az alábbi tesztek az eredeti 3 endpoint-ot tesztelik ===
|
||||||
// //// === Vissza<7A>ll<6C>t<EFBFBD>shoz: t<>r<EFBFBD>ld a kommenteket <20>s regisztr<74>ld az endpoint-okat a Program.cs-ben ===
|
//// === Visszaállításhoz: töröld a kommenteket és regisztráld az endpoint-okat a Program.cs-ben ===
|
||||||
// //// ===========================================
|
//// ===========================================
|
||||||
|
|
||||||
// //// [TestMethod]
|
//// [TestMethod]
|
||||||
// //// public async Task SignalR_GetMeasuringUsers_ReturnsJson()
|
//// public async Task SignalR_GetMeasuringUsers_ReturnsJson()
|
||||||
// //// {
|
//// {
|
||||||
// //// await TestSignalREndpoint(GetMeasuringUsersTag, null, "GetMeasuringUsers");
|
//// await TestSignalREndpoint(GetMeasuringUsersTag, null, "GetMeasuringUsers");
|
||||||
// //// }
|
//// }
|
||||||
|
|
||||||
// //// [TestMethod]
|
//// [TestMethod]
|
||||||
// //// public async Task SignalR_GetStockQuantityHistoryDtos_ReturnsJson()
|
//// public async Task SignalR_GetStockQuantityHistoryDtos_ReturnsJson()
|
||||||
// //// {
|
//// {
|
||||||
// //// await TestSignalREndpoint(GetStockQuantityHistoryDtosTag, null, "GetStockQuantityHistoryDtos");
|
//// await TestSignalREndpoint(GetStockQuantityHistoryDtosTag, null, "GetStockQuantityHistoryDtos");
|
||||||
// //// }
|
//// }
|
||||||
|
|
||||||
// //// [TestMethod]
|
//// [TestMethod]
|
||||||
// //// public async Task SignalR_GetStockQuantityHistoryDtosByProductId_ReturnsJson()
|
//// public async Task SignalR_GetStockQuantityHistoryDtosByProductId_ReturnsJson()
|
||||||
// //// {
|
//// {
|
||||||
// //// // ProductId = 10
|
//// // ProductId = 10
|
||||||
// //// await TestSignalREndpoint(GetStockQuantityHistoryDtosByProductIdTag, 10, "GetStockQuantityHistoryDtosByProductId");
|
//// await TestSignalREndpoint(GetStockQuantityHistoryDtosByProductIdTag, 10, "GetStockQuantityHistoryDtosByProductId");
|
||||||
// //// }
|
//// }
|
||||||
|
|
||||||
// //// [TestMethod]
|
//// [TestMethod]
|
||||||
// //// public async Task SignalR_GetShippingDocumentsByShippingId_ReturnsJson()
|
//// public async Task SignalR_GetShippingDocumentsByShippingId_ReturnsJson()
|
||||||
// //// {
|
//// {
|
||||||
// //// // ShippingId = 5
|
//// // ShippingId = 5
|
||||||
// //// await TestSignalREndpoint(GetShippingDocumentsByShippingIdTag, 5, "GetShippingDocumentsByShippingId");
|
//// await TestSignalREndpoint(GetShippingDocumentsByShippingIdTag, 5, "GetShippingDocumentsByShippingId");
|
||||||
// //// }
|
//// }
|
||||||
|
|
||||||
// //// [TestMethod]
|
//// [TestMethod]
|
||||||
// //// public async Task SignalR_GetOrderDtoById_ReturnsJson()
|
//// public async Task SignalR_GetOrderDtoById_ReturnsJson()
|
||||||
// //// {
|
//// {
|
||||||
// //// // OrderId = 15
|
//// // OrderId = 15
|
||||||
// //// await TestSignalREndpoint(GetOrderDtoByIdTag, 15, "GetOrderDtoById");
|
//// await TestSignalREndpoint(GetOrderDtoByIdTag, 15, "GetOrderDtoById");
|
||||||
// //// }
|
//// }
|
||||||
|
|
||||||
// //// [TestMethod]
|
//// [TestMethod]
|
||||||
// //// public async Task SignalR_GetStockTakingItemsById_ReturnsJson()
|
//// public async Task SignalR_GetStockTakingItemsById_ReturnsJson()
|
||||||
// //// {
|
//// {
|
||||||
// //// // StockTakingItemId = 200
|
//// // StockTakingItemId = 200
|
||||||
// //// await TestSignalREndpoint(GetStockTakingItemsByIdTag, 200, "GetStockTakingItemsById");
|
//// await TestSignalREndpoint(GetStockTakingItemsByIdTag, 200, "GetStockTakingItemsById");
|
||||||
// //// }
|
//// }
|
||||||
|
|
||||||
// //#endregion
|
//#endregion
|
||||||
|
|
||||||
// //#region Helper Methods
|
//#region Helper Methods
|
||||||
|
|
||||||
// //private async Task TestSignalREndpoint(int tag, object? parameter, string endpointName, Action<string?>? validateResponse = null)
|
//private async Task TestSignalREndpoint(int tag, object? parameter, string endpointName, Action<string?>? validateResponse = null)
|
||||||
// //{
|
//{
|
||||||
// // var connection = new HubConnectionBuilder()
|
// var connection = new HubConnectionBuilder()
|
||||||
// // .WithUrl(HubUrl)
|
// .WithUrl(HubUrl)
|
||||||
// // .Build();
|
// .Build();
|
||||||
|
|
||||||
// // string? receivedJson = null;
|
// string? receivedJson = null;
|
||||||
// // int receivedTag = -1;
|
// int receivedTag = -1;
|
||||||
// // var responseReceived = new TaskCompletionSource<bool>();
|
// var responseReceived = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
// // connection.On<int, byte[]>("ReceiveMessage", (responseTag, data) =>
|
// connection.On<int, byte[]>("ReceiveMessage", (responseTag, data) =>
|
||||||
// // {
|
// {
|
||||||
// // receivedTag = responseTag;
|
// receivedTag = responseTag;
|
||||||
// // if (data != null && data.Length > 0)
|
// if (data != null && data.Length > 0)
|
||||||
// // {
|
// {
|
||||||
// // receivedJson = Encoding.UTF8.GetString(data);
|
// receivedJson = Encoding.UTF8.GetString(data);
|
||||||
// // }
|
// }
|
||||||
// // responseReceived.TrySetResult(true);
|
// responseReceived.TrySetResult(true);
|
||||||
// // });
|
// });
|
||||||
|
|
||||||
// // try
|
// try
|
||||||
// // {
|
// {
|
||||||
// // await connection.StartAsync();
|
// await connection.StartAsync();
|
||||||
// // Assert.AreEqual(HubConnectionState.Connected, connection.State, $"Failed to connect to SignalR hub for {endpointName}");
|
// Assert.AreEqual(HubConnectionState.Connected, connection.State, $"Failed to connect to SignalR hub for {endpointName}");
|
||||||
|
|
||||||
// // // K<>sz<73>ts<74>k el a request data-t
|
// // Készítsük el a request data-t
|
||||||
// // // Ha nincs param<61>ter, null-t k<>ld<6C>nk (nem <20>res byte t<>mb<6D>t!)
|
// // Ha nincs paraméter, null-t küldünk (nem üres byte tömböt!)
|
||||||
// // byte[]? requestData = parameter != null
|
// byte[]? requestData = parameter != null
|
||||||
// // ? Encoding.UTF8.GetBytes(JsonSerializer.Serialize(parameter))
|
// ? Encoding.UTF8.GetBytes(JsonSerializer.Serialize(parameter))
|
||||||
// // : null;
|
// : null;
|
||||||
|
|
||||||
// // // A Hub met<65>dus neve: OnReceiveMessage (3 param<61>ter: messageTag, messageBytes, requestId)
|
// // A Hub metódus neve: OnReceiveMessage (3 paraméter: messageTag, messageBytes, requestId)
|
||||||
// // await connection.InvokeAsync("OnReceiveMessage", tag, requestData, (int?)null);
|
// await connection.InvokeAsync("OnReceiveMessage", tag, requestData, (int?)null);
|
||||||
|
|
||||||
// // var completed = await Task.WhenAny(responseReceived.Task, Task.Delay(15000));
|
// var completed = await Task.WhenAny(responseReceived.Task, Task.Delay(15000));
|
||||||
|
|
||||||
// // if (completed == responseReceived.Task)
|
// if (completed == responseReceived.Task)
|
||||||
// // {
|
// {
|
||||||
// // Console.WriteLine($"[{endpointName}] Response tag: {receivedTag}");
|
// Console.WriteLine($"[{endpointName}] Response tag: {receivedTag}");
|
||||||
// // Console.WriteLine($"[{endpointName}] Response JSON: {receivedJson?.Substring(0, Math.Min(500, receivedJson?.Length ?? 0))}...");
|
// Console.WriteLine($"[{endpointName}] Response JSON: {receivedJson?.Substring(0, Math.Min(500, receivedJson?.Length ?? 0))}...");
|
||||||
|
|
||||||
// // // Ellen<65>rizz<7A>k, hogy valid JSON-e (ha van adat)
|
// // Ellenõrizzük, hogy valid JSON-e (ha van adat)
|
||||||
// // if (!string.IsNullOrEmpty(receivedJson))
|
// if (!string.IsNullOrEmpty(receivedJson))
|
||||||
// // {
|
// {
|
||||||
// // try
|
// try
|
||||||
// // {
|
// {
|
||||||
// // using var jsonDoc = JsonDocument.Parse(receivedJson);
|
// using var jsonDoc = JsonDocument.Parse(receivedJson);
|
||||||
// // Assert.IsTrue(
|
// Assert.IsTrue(
|
||||||
// // jsonDoc.RootElement.ValueKind == JsonValueKind.Array ||
|
// jsonDoc.RootElement.ValueKind == JsonValueKind.Array ||
|
||||||
// // jsonDoc.RootElement.ValueKind == JsonValueKind.Object ||
|
// jsonDoc.RootElement.ValueKind == JsonValueKind.Object ||
|
||||||
// // jsonDoc.RootElement.ValueKind == JsonValueKind.Null,
|
// jsonDoc.RootElement.ValueKind == JsonValueKind.Null,
|
||||||
// // $"[{endpointName}] Response is not a valid JSON");
|
// $"[{endpointName}] Response is not a valid JSON");
|
||||||
|
|
||||||
// // // Custom validation
|
// // Custom validation
|
||||||
// // validateResponse?.Invoke(receivedJson);
|
// validateResponse?.Invoke(receivedJson);
|
||||||
// // }
|
// }
|
||||||
// // catch (JsonException ex)
|
// catch (JsonException ex)
|
||||||
// // {
|
// {
|
||||||
// // Assert.Fail($"[{endpointName}] Invalid JSON response: {ex.Message}");
|
// Assert.Fail($"[{endpointName}] Invalid JSON response: {ex.Message}");
|
||||||
// // }
|
// }
|
||||||
// // }
|
// }
|
||||||
// // }
|
// }
|
||||||
// // else
|
// else
|
||||||
// // {
|
// {
|
||||||
// // Assert.AreEqual(HubConnectionState.Connected, connection.State,
|
// Assert.AreEqual(HubConnectionState.Connected, connection.State,
|
||||||
// // $"[{endpointName}] Connection was closed - check SANDBOX logs for DI errors");
|
// $"[{endpointName}] Connection was closed - check SANDBOX logs for DI errors");
|
||||||
// // }
|
// }
|
||||||
// // }
|
// }
|
||||||
// // catch (Exception ex)
|
// catch (Exception ex)
|
||||||
// // {
|
// {
|
||||||
// // Assert.Fail($"[{endpointName}] SignalR error: {ex.Message}. Check SANDBOX logs for missing DI registrations.");
|
// Assert.Fail($"[{endpointName}] SignalR error: {ex.Message}. Check SANDBOX logs for missing DI registrations.");
|
||||||
// // }
|
// }
|
||||||
// // finally
|
// finally
|
||||||
// // {
|
// {
|
||||||
// // if (connection.State == HubConnectionState.Connected)
|
// if (connection.State == HubConnectionState.Connected)
|
||||||
// // {
|
// {
|
||||||
// // await connection.StopAsync();
|
// await connection.StopAsync();
|
||||||
// // }
|
// }
|
||||||
// // }
|
// }
|
||||||
// //}
|
//}
|
||||||
|
|
||||||
// #endregion
|
#endregion
|
||||||
//}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# TestData
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
namespace FruitBankHybrid.Shared.Tests.TestData;
|
|
||||||
|
|
||||||
// Demo entity-k a teszteléshez
|
|
||||||
public class TestOrder
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public int CustomerId { get; set; }
|
|
||||||
public TestCustomer? Customer { get; set; }
|
|
||||||
public List<TestOrderItem> OrderItems { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TestCustomer
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string Name { get; set; } = "";
|
|
||||||
public List<TestOrder> Orders { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TestOrderItem
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public int OrderId { get; set; }
|
|
||||||
public TestOrder? Order { get; set; }
|
|
||||||
public int ProductId { get; set; }
|
|
||||||
public TestProduct? Product { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TestProduct
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string Name { get; set; } = "";
|
|
||||||
public List<TestOrderItem> OrderItems { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,462 +0,0 @@
|
||||||
using AyCode.Core.Enums;
|
|
||||||
using AyCode.Core.Extensions;
|
|
||||||
using AyCode.Core.Loggers;
|
|
||||||
using AyCode.Core.Serializers.Binaries;
|
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
using AyCode.Core.Serializers.Toons;
|
|
||||||
using FruitBank.Common;
|
|
||||||
using FruitBank.Common.Dtos;
|
|
||||||
using FruitBank.Common.Entities;
|
|
||||||
using FruitBank.Common.Loggers;
|
|
||||||
using FruitBankHybrid.Shared.Services.SignalRs;
|
|
||||||
using Mango.Nop.Core.Entities;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Nop.Core.Domain.Catalog;
|
|
||||||
using Nop.Core.Domain.Common;
|
|
||||||
using Nop.Core.Domain.Orders;
|
|
||||||
using Nop.Core.Domain.Payments;
|
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Runtime.InteropServices.JavaScript;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
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".
|
|
||||||
|
|
||||||
//Hagyományos (pazarló):
|
|
||||||
//Kódrészlet
|
|
||||||
|
|
||||||
//OrderItemDtos = [
|
|
||||||
// OrderItemDto { Id = 120, Quantity = 10, ProductName = "Áfonya" }
|
|
||||||
//OrderItemDto { Id = 121, Quantity = 5, ProductName = "Narancs" }
|
|
||||||
//]
|
|
||||||
|
|
||||||
//Optimalizált(pontos és tömör) :
|
|
||||||
//Kódrészlet
|
|
||||||
|
|
||||||
//OrderItemDtos: OrderItemDto[] = [
|
|
||||||
// [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.
|
|
||||||
|
|
||||||
//2. Típus-öröklődés a listákban
|
|
||||||
|
|
||||||
//Ha a @types részben már leírtad, hogy az OrderItemDto.ProductDto mezője egy ProductDto típust vár, akkor a @data részben felesleges kiírni a típusnevet minden egyes elemnél.
|
|
||||||
|
|
||||||
//Példa:
|
|
||||||
//Kódrészlet
|
|
||||||
|
|
||||||
//// A 'ProductDto' elhagyható az objektum elől, mert a sémából tudja
|
|
||||||
//ProductDto = {
|
|
||||||
// Id = 1
|
|
||||||
// Name = "Áfonya..."
|
|
||||||
// GenericAttributes = [
|
|
||||||
// { Id = 99, Key = "NetWeight", Value = "178.3" }
|
|
||||||
// { Id = 100, Key = "GrossWeight", Value = "19" }
|
|
||||||
// ]
|
|
||||||
//}
|
|
||||||
|
|
||||||
//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.
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
//4. String Table helyett: "Object Anchoring"
|
|
||||||
|
|
||||||
//használd az objektum-referenciákat (amit a @ProductDto:1 jelöléssel már el is kezdett a rendszered).
|
|
||||||
|
|
||||||
//Ha ugyanaz a Product szerepel 5 különböző rendelési tételnél, ne írd le ötször.
|
|
||||||
|
|
||||||
// Első alkalommal: ProductDto { ... }
|
|
||||||
|
|
||||||
//Minden további alkalommal: ProductDto = @ProductDto:1
|
|
||||||
|
|
||||||
//[ToonIgnore][ToonDataIgnore]
|
|
||||||
[ToonDescription(Purpose = "Container model for Shipping, Order")]
|
|
||||||
public class FullProcessModel
|
|
||||||
{
|
|
||||||
public List<Shipping> Shippings { get; set; }
|
|
||||||
public List<OrderDto> Orders { get; set; }
|
|
||||||
public List<PreOrder> PreOrders { get; set; }
|
|
||||||
public List<StockTaking> StockTakings { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestClass]
|
|
||||||
public sealed class ToonTests
|
|
||||||
{
|
|
||||||
private const int CustomerIdAasdDsserverCom = 6; //aasd@dsserver.com
|
|
||||||
|
|
||||||
private FruitBankSignalRClient _signalRClient = null!;
|
|
||||||
|
|
||||||
[TestInitialize]
|
|
||||||
public void TestInit()
|
|
||||||
{
|
|
||||||
if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
|
||||||
|
|
||||||
_signalRClient = TestSignalRClientFactory.Create(nameof(FruitBankClientTests));
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
[TestMethod]
|
|
||||||
public async Task GetAnalyzeStringInternCandidatesLog()
|
|
||||||
{
|
|
||||||
var orders = (await _signalRClient.GetAllOrderDtos())!.Where(x=>x.CreatedOnUtc > DateTime.UtcNow.AddDays(-70)).ToList();
|
|
||||||
|
|
||||||
var options = AcBinarySerializerOptions.WithoutReferenceHandling;
|
|
||||||
//options.SetReferenceHandlingUnsafe(ReferenceHandlingMode.OnlyId);
|
|
||||||
var analysisLog = AcBinarySerializer.GetAnalyzeStringInternCandidatesLog(orders, options);
|
|
||||||
|
|
||||||
Assert.IsNotNull(analysisLog);
|
|
||||||
Assert.IsGreaterThan(0, analysisLog.Length);
|
|
||||||
|
|
||||||
// Print results sorted by occurrence count
|
|
||||||
Console.WriteLine(analysisLog.ToString());
|
|
||||||
Console.WriteLine();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task OrderDtoToToon()
|
|
||||||
{
|
|
||||||
var a = new FullProcessModel();
|
|
||||||
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);
|
|
||||||
|
|
||||||
Console.WriteLine(toon);
|
|
||||||
Assert.IsNotEmpty(toon);
|
|
||||||
// Note: @ref: only appears when the same object instance is referenced multiple times.
|
|
||||||
// Data from separate API calls typically don't share object instances.
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void GetMetaInfos()
|
|
||||||
{
|
|
||||||
var a = new FullProcessModel();
|
|
||||||
var toon = AcToonSerializer.SerializeTypeMetadata<FullProcessModel>(FruitBankConstClient.DomainDescription);
|
|
||||||
Console.WriteLine(toon);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void ReferenceHandling_WithSharedReferences_ShouldOutputRefMarkers()
|
|
||||||
{
|
|
||||||
// Create a simple test container with shared references
|
|
||||||
var sharedProduct = new ProductDto { Id = 1, Name = "Shared Product" };
|
|
||||||
|
|
||||||
// Create a container that references the same ProductDto twice
|
|
||||||
var container = new TestContainerWithSharedRefs
|
|
||||||
{
|
|
||||||
Product1 = sharedProduct,
|
|
||||||
Product2 = sharedProduct // Same instance, should create @ref
|
|
||||||
};
|
|
||||||
|
|
||||||
var toon = AcToonSerializer.Serialize(container, FruitBankConstClient.DomainDescription, AcToonSerializerOptions.Default);
|
|
||||||
|
|
||||||
Console.WriteLine(toon);
|
|
||||||
Assert.IsNotEmpty(toon);
|
|
||||||
Assert.IsTrue(toon.Contains("@ref:"), "ReferenceHandling should detect shared Product instance");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void ReferenceHandling_WithSharedIIdReferences_ShouldOutputRefMarkers()
|
|
||||||
{
|
|
||||||
// Create a simple test container with shared references
|
|
||||||
var sharedProduct = new ProductDto { Id = 1, Name = "Shared Product" };
|
|
||||||
var sharedProduct2 = new ProductDto { Id = 1, Name = "Shared Product" };
|
|
||||||
|
|
||||||
// Create a container that references the same ProductDto twice
|
|
||||||
var container = new TestContainerWithSharedRefs
|
|
||||||
{
|
|
||||||
Product1 = sharedProduct,
|
|
||||||
Product2 = sharedProduct2 // Same instance, should create @ref
|
|
||||||
};
|
|
||||||
|
|
||||||
var toon = AcToonSerializer.Serialize(container, FruitBankConstClient.DomainDescription, AcToonSerializerOptions.Default);
|
|
||||||
|
|
||||||
Console.WriteLine(toon);
|
|
||||||
Assert.IsNotEmpty(toon);
|
|
||||||
Assert.IsTrue(toon.Contains("@ref:"), "ReferenceHandling should detect shared Product instance");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void ToonTypes_ShouldNotContainList1OrGenericTypeNames()
|
|
||||||
{
|
|
||||||
var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>();
|
|
||||||
StringAssert.DoesNotMatch(toon, new System.Text.RegularExpressions.Regex(@"List`?1"), "A @meta.types vagy @types szekcióban nem szerepelhet List`1 vagy generikus típusnév.");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void ToonTypes_ShouldNotContainDuplicateTypeNames()
|
|
||||||
{
|
|
||||||
var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>();
|
|
||||||
var typesLine = toon.Split('\n').FirstOrDefault(x => x.TrimStart().StartsWith("types = ["));
|
|
||||||
Assert.IsNotNull(typesLine, "Nem található types lista a @meta szekcióban.");
|
|
||||||
var typeNames = typesLine.Substring(typesLine.IndexOf('[') + 1, typesLine.LastIndexOf(']') - typesLine.IndexOf('[') - 1)
|
|
||||||
.Split(',').Select(x => x.Trim(' ', '"')).Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
|
|
||||||
var duplicates = typeNames.GroupBy(x => x).Where(g => g.Count() > 1).Select(g => g.Key).ToList();
|
|
||||||
Assert.IsTrue(duplicates.Count == 0, $"A types listában duplikált típusnév található: {string.Join(", ", duplicates)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void ToonTypes_EachTypeShouldBeDefinedOnceInTypesSection()
|
|
||||||
{
|
|
||||||
var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>();
|
|
||||||
var typeDefLines = toon.Split('\n').Where(x => x.TrimEnd().EndsWith(": \"Object of type") || x.TrimEnd().EndsWith(": enum") || x.TrimEnd().EndsWith(": \"Object of type "));
|
|
||||||
var typeNames = typeDefLines.Select(x => x.Trim().Split(':')[0]).ToList();
|
|
||||||
var duplicates = typeNames.GroupBy(x => x).Where(g => g.Count() > 1).Select(g => g.Key).ToList();
|
|
||||||
Assert.IsTrue(duplicates.Count == 0, $"A @types szekcióban duplikált típusdefiníció található: {string.Join(", ", duplicates)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void ToonTypes_PropertyTypesShouldNotReferenceList1()
|
|
||||||
{
|
|
||||||
var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>();
|
|
||||||
var lines = toon.Split('\n');
|
|
||||||
foreach (var line in lines)
|
|
||||||
{
|
|
||||||
if (line.Trim().EndsWith(": List`1"))
|
|
||||||
{
|
|
||||||
Assert.Fail($"Property List`1 típusra hivatkozik: {line.Trim()}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void ToonTypes_PropertyDescriptions_ShouldNotBeRedundantOrMisleading()
|
|
||||||
{
|
|
||||||
var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>(FruitBankConstClient.DomainDescription);
|
|
||||||
var lines = toon.Split('\n');
|
|
||||||
foreach (var line in lines)
|
|
||||||
{
|
|
||||||
if (line.Trim().StartsWith("description:") && line.Contains("Collection of Object for"))
|
|
||||||
{
|
|
||||||
Assert.Fail($"Redundáns vagy félrevezető description: {line.Trim()}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void ToonTypes_NavigationMetadata_ShouldBeComplete()
|
|
||||||
{
|
|
||||||
var toon = AcToonSerializer.SerializeMetadata(FruitBankConstClient.DomainDescription, [typeof(Shipping), typeof(OrderDto), typeof(StockTaking), typeof(StockQuantityHistory), typeof(StockQuantityHistoryExt)]);
|
|
||||||
Console.WriteLine(toon);
|
|
||||||
Console.WriteLine("\n=== NAVIGATION METADATA ELLENŐRZÉS ===\n");
|
|
||||||
|
|
||||||
var lines = toon.Split('\n').Select(x => x.TrimEnd()).ToList();
|
|
||||||
|
|
||||||
// Ismerten egyirányú kapcsolatok - ezeknél nincs inverse property a másik oldalon
|
|
||||||
// Customer: NopCommerce domain entity, nincs benne Orders kollekció
|
|
||||||
// OrderNotes: OrderNote osztályban nincs Order navigation property
|
|
||||||
// ProductDto: nincs benne OrderItems kollekció
|
|
||||||
// GenericAttributes: polimorf kapcsolat ExpressionPredicate-tel, nincs inverse ÉS nincs egyértelmű FK
|
|
||||||
// FONTOS: Csak az inverse-property hiányát engedjük! Az other-key-nek léteznie kell!
|
|
||||||
var knownUnidirectionalNavigations = new HashSet<string>
|
|
||||||
{
|
|
||||||
"Customer",
|
|
||||||
"OrderNotes",
|
|
||||||
"ProductDto",
|
|
||||||
"GenericAttributes",
|
|
||||||
"ShippingDocumentFile",
|
|
||||||
"Pallet"
|
|
||||||
};
|
|
||||||
|
|
||||||
// GenericAttributes speciális eset - polimorf, nincs other-key sem
|
|
||||||
var knownPolymorphicNavigations = new HashSet<string>
|
|
||||||
{
|
|
||||||
"GenericAttributes"
|
|
||||||
};
|
|
||||||
|
|
||||||
// FK property-k NEM tartalmazhatnak foreign-key attribútumot
|
|
||||||
for (int i = 0; i < lines.Count; i++)
|
|
||||||
{
|
|
||||||
var line = lines[i].Trim();
|
|
||||||
if (line.EndsWith("Id: int") || line.EndsWith("Id: int?"))
|
|
||||||
{
|
|
||||||
int j = i + 1;
|
|
||||||
while (j < lines.Count)
|
|
||||||
{
|
|
||||||
if (lines[j].StartsWith(" ") && !lines[j].StartsWith(" "))
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (lines[j].StartsWith(" "))
|
|
||||||
{
|
|
||||||
var metaLine = lines[j].Trim();
|
|
||||||
if (metaLine.StartsWith("foreign-key:"))
|
|
||||||
{
|
|
||||||
Assert.Fail($"FK property nem tartalmazhat foreign-key attribútumot: {line} -> {metaLine}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
j++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine("✓ FK property-k nem tartalmaznak foreign-key attribútumot\n");
|
|
||||||
|
|
||||||
// Számoljuk meg a hiányzó navigation metadatokat
|
|
||||||
var missingMetadata = new List<string>();
|
|
||||||
var skippedUnidirectional = new List<string>();
|
|
||||||
|
|
||||||
for (int i = 0; i < lines.Count; i++)
|
|
||||||
{
|
|
||||||
var line = lines[i].Trim();
|
|
||||||
|
|
||||||
// Navigation property-k keresése
|
|
||||||
if (line.Contains(": ") &&
|
|
||||||
!line.Contains(": int") && !line.Contains(": string") &&
|
|
||||||
!line.Contains(": DateTime") && !line.Contains(": decimal") &&
|
|
||||||
!line.Contains(": bool") && !line.Contains(": Guid") &&
|
|
||||||
!line.Contains(": double") && !line.Contains(": float") &&
|
|
||||||
!line.Contains("description:") && !line.Contains("purpose:") &&
|
|
||||||
!line.Contains("navigation:") && !line.Contains("foreign-key:") &&
|
|
||||||
!line.Contains("table-name:") && !line.Contains("constraints:") &&
|
|
||||||
!line.Contains("inverse-property:") && !line.Contains("other-key:") &&
|
|
||||||
!line.Contains("primary-key:") && !line.Contains("examples:") &&
|
|
||||||
!line.Contains("@meta") && !line.Contains("@types") &&
|
|
||||||
!line.Contains("version") && !line.Contains("format") && !line.Contains("source-code-language") &&
|
|
||||||
!line.Contains("underlying-type:") && !line.Contains("default-value:") && !line.Contains("values:"))
|
|
||||||
{
|
|
||||||
var propName = line.Split(':')[0].Trim();
|
|
||||||
if (string.IsNullOrEmpty(propName) || propName == "types") continue;
|
|
||||||
|
|
||||||
// Következő sorok metadatainak összegyűjtése
|
|
||||||
var metadata = new HashSet<string>();
|
|
||||||
int j = i + 1;
|
|
||||||
while (j < lines.Count && lines[j].StartsWith(" ") && lines[j].Trim().Contains(':'))
|
|
||||||
{
|
|
||||||
var metaLine = lines[j].Trim();
|
|
||||||
if (metaLine.StartsWith("navigation:")) metadata.Add("navigation");
|
|
||||||
if (metaLine.StartsWith("foreign-key:")) metadata.Add("foreign-key");
|
|
||||||
if (metaLine.StartsWith("inverse-property:")) metadata.Add("inverse-property");
|
|
||||||
if (metaLine.StartsWith("other-key:")) metadata.Add("other-key");
|
|
||||||
j++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ha van navigation attribútum, ellenőrizzük a szükséges metadatokat
|
|
||||||
if (metadata.Contains("navigation"))
|
|
||||||
{
|
|
||||||
var navLine = lines.Skip(i + 1).FirstOrDefault(x => x.Trim().StartsWith("navigation:"));
|
|
||||||
if (navLine != null)
|
|
||||||
{
|
|
||||||
var isUnidirectional = knownUnidirectionalNavigations.Contains(propName);
|
|
||||||
var isPolymorphic = knownPolymorphicNavigations.Contains(propName);
|
|
||||||
|
|
||||||
if (navLine.Contains("many-to-one"))
|
|
||||||
{
|
|
||||||
if (!metadata.Contains("foreign-key"))
|
|
||||||
missingMetadata.Add($"{propName} (ManyToOne): hiányzik foreign-key");
|
|
||||||
|
|
||||||
if (!metadata.Contains("inverse-property"))
|
|
||||||
{
|
|
||||||
if (isUnidirectional)
|
|
||||||
skippedUnidirectional.Add($"{propName} (ManyToOne): egyirányú kapcsolat, nincs inverse");
|
|
||||||
else
|
|
||||||
missingMetadata.Add($"{propName} (ManyToOne): hiányzik inverse-property");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (navLine.Contains("one-to-many"))
|
|
||||||
{
|
|
||||||
// other-key: polimorf kapcsolatoknál nem kötelező
|
|
||||||
if (!metadata.Contains("other-key"))
|
|
||||||
{
|
|
||||||
if (isPolymorphic)
|
|
||||||
{
|
|
||||||
skippedUnidirectional.Add($"{propName} (OneToMany): polimorf kapcsolat, nincs other-key");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// DEBUG: részletes info
|
|
||||||
Console.WriteLine($"\n[DEBUG] {propName} (OneToMany) - other-key hiányzik!");
|
|
||||||
|
|
||||||
// Keressük meg a property típusát a Toon outputban
|
|
||||||
var propTypePart = line.Split(':').LastOrDefault()?.Trim() ?? "";
|
|
||||||
Console.WriteLine($" Property type: {propTypePart}");
|
|
||||||
|
|
||||||
// Ha List<X> formátum, keressük meg X-et
|
|
||||||
if (propTypePart.StartsWith("List<") && propTypePart.EndsWith(">"))
|
|
||||||
{
|
|
||||||
var elementTypeName = propTypePart.Substring(5, propTypePart.Length - 6);
|
|
||||||
Console.WriteLine($" Element type: {elementTypeName}");
|
|
||||||
|
|
||||||
// Keressük meg az element type definícióját
|
|
||||||
var elementTypeDefIndex = lines.FindIndex(l => l.Trim().StartsWith($"{elementTypeName}:"));
|
|
||||||
if (elementTypeDefIndex >= 0)
|
|
||||||
{
|
|
||||||
Console.WriteLine($" Element type definition found at line {elementTypeDefIndex}");
|
|
||||||
// Listázzuk ki az element type property-jeit amik "Id"-re végződnek
|
|
||||||
for (int k = elementTypeDefIndex + 1; k < lines.Count; k++)
|
|
||||||
{
|
|
||||||
var propLine = lines[k];
|
|
||||||
if (!propLine.StartsWith(" ")) break; // Új típus definíció
|
|
||||||
if (propLine.StartsWith(" ")) continue; // Metaadat, skip
|
|
||||||
|
|
||||||
var trimmed = propLine.Trim();
|
|
||||||
if (trimmed.EndsWith(": int") && trimmed.Contains("Id"))
|
|
||||||
{
|
|
||||||
Console.WriteLine($" FK candidate: {trimmed}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
missingMetadata.Add($"{propName} (OneToMany): hiányzik other-key");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!metadata.Contains("inverse-property"))
|
|
||||||
{
|
|
||||||
if (isUnidirectional)
|
|
||||||
skippedUnidirectional.Add($"{propName} (OneToMany): egyirányú kapcsolat, nincs inverse");
|
|
||||||
else
|
|
||||||
missingMetadata.Add($"{propName} (OneToMany): hiányzik inverse-property");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skippedUnidirectional.Count > 0)
|
|
||||||
{
|
|
||||||
Console.WriteLine("EGYIRÁNYÚ/POLIMORF KAPCSOLATOK (nem hiba):");
|
|
||||||
foreach (var skipped in skippedUnidirectional)
|
|
||||||
{
|
|
||||||
Console.WriteLine($" ℹ {skipped}");
|
|
||||||
}
|
|
||||||
Console.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (missingMetadata.Count > 0)
|
|
||||||
{
|
|
||||||
Console.WriteLine("HIÁNYZÓ METAADATOK:");
|
|
||||||
foreach (var missing in missingMetadata)
|
|
||||||
{
|
|
||||||
Console.WriteLine($" - {missing}");
|
|
||||||
}
|
|
||||||
Assert.Fail($"Hiányzó navigation metaadatok: {missingMetadata.Count} db");
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine("✓ Minden navigation property tartalmazza a szükséges metadatokat");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test helper class to verify reference handling with shared object instances.
|
|
||||||
/// </summary>
|
|
||||||
public class TestContainerWithSharedRefs
|
|
||||||
{
|
|
||||||
public ProductDto? Product1 { get; set; }
|
|
||||||
public ProductDto? Product2 { get; set; }
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Test debugger script for JsonExtensionTests
|
||||||
|
$projectPath = "H:\Applications\Mango\Source\FruitBankHybridApp"
|
||||||
|
Set-Location $projectPath
|
||||||
|
|
||||||
|
Write-Host "Building test project..."
|
||||||
|
dotnet build FruitBankHybrid.Shared.Tests/FruitBankHybrid.Shared.Tests.csproj -c Debug
|
||||||
|
|
||||||
|
Write-Host "`nRunning JsonExtensionTests..."
|
||||||
|
# Use --no-build to avoid the MSBuild conflict
|
||||||
|
dotnet test FruitBankHybrid.Shared.Tests/FruitBankHybrid.Shared.Tests.csproj `
|
||||||
|
--no-build `
|
||||||
|
-c Debug `
|
||||||
|
--filter "ClassName=FruitBankHybrid.Shared.Tests.JsonExtensionTests" `
|
||||||
|
2>&1 | Tee-Object -FilePath "test_results.txt"
|
||||||
|
|
||||||
|
Write-Host "`n=== Test Results ==="
|
||||||
|
Get-Content "test_results.txt" | Select-String -Pattern "FAILED|PASSED|Error|Assert" | tail -50
|
||||||
|
|
@ -276,13 +276,15 @@
|
||||||
|
|
||||||
private async Task Callback(ToolbarItemClickEventArgs obj)
|
private async Task Callback(ToolbarItemClickEventArgs obj)
|
||||||
{
|
{
|
||||||
if (windowVisible) await windowRef.CloseAsync();
|
if (windowVisible)
|
||||||
else await windowRef.ShowAsync();
|
await windowRef.CloseAsync();
|
||||||
|
else
|
||||||
|
await windowRef.ShowAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Callback2(WindowClosingEventArgs obj)
|
private void Callback2(WindowClosingEventArgs obj)
|
||||||
{
|
{
|
||||||
ReloadDataFromDb(true).Forget(_logger);
|
ReloadDataFromDb(true).Forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
@using System.Collections.ObjectModel
|
|
||||||
@using AyCode.Blazor.Components.Components.Grids
|
|
||||||
@using AyCode.Core.Helpers
|
|
||||||
@using AyCode.Core.Loggers
|
|
||||||
@using AyCode.Utils.Extensions
|
|
||||||
@using FruitBank.Common.Dtos
|
|
||||||
@using FruitBank.Common.Entities
|
|
||||||
@using FruitBankHybrid.Shared.Components.Grids.Shippings
|
|
||||||
@using FruitBankHybrid.Shared.Databases
|
|
||||||
@using FruitBankHybrid.Shared.Services.Loggers
|
|
||||||
@using FruitBankHybrid.Shared.Services.SignalRs
|
|
||||||
|
|
||||||
@inject IEnumerable<IAcLogWriterClientBase> LogWriters
|
|
||||||
@inject FruitBankSignalRClient FruitBankSignalRClient
|
|
||||||
|
|
||||||
<MgGridWithInfoPanel ShowInfoPanel="@IsMasterGrid">
|
|
||||||
<GridContent>
|
|
||||||
<GridCargoPartnerBase @ref="Grid"
|
|
||||||
DataSource="CargoPartners"
|
|
||||||
AutoSaveLayoutName="GridCargoPartner"
|
|
||||||
SignalRClient="FruitBankSignalRClient"
|
|
||||||
Logger="_logger"
|
|
||||||
CssClass="@GridCss"
|
|
||||||
ValidationEnabled="false"
|
|
||||||
OnGridFocusedRowChanged="Grid_FocusedRowChanged">
|
|
||||||
<Columns>
|
|
||||||
<DxGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
|
|
||||||
|
|
||||||
<DxGridDataColumn FieldName="Name" />
|
|
||||||
<DxGridDataColumn FieldName="TaxId" />
|
|
||||||
<DxGridDataColumn FieldName="CertificationNumber" />
|
|
||||||
<DxGridDataColumn FieldName="@nameof(CargoPartner.Currency)" />
|
|
||||||
<DxGridDataColumn FieldName="@nameof(CargoPartner.CountryCode)" />
|
|
||||||
<DxGridDataColumn FieldName="PostalCode" />
|
|
||||||
<DxGridDataColumn FieldName="@nameof(CargoPartner.Country)" />
|
|
||||||
<DxGridDataColumn FieldName="State" />
|
|
||||||
<DxGridDataColumn FieldName="County" />
|
|
||||||
<DxGridDataColumn FieldName="City" />
|
|
||||||
<DxGridDataColumn FieldName="Street" />
|
|
||||||
|
|
||||||
<DxGridDataColumn FieldName="Created" ReadOnly="true" />
|
|
||||||
<DxGridDataColumn FieldName="Modified" ReadOnly="true" />
|
|
||||||
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
|
|
||||||
</Columns>
|
|
||||||
<DetailRowTemplate>
|
|
||||||
@if (IsMasterGrid)
|
|
||||||
{
|
|
||||||
var cargoPartner = ((CargoPartner)context.DataItem);
|
|
||||||
var shippings = new AcObservableCollection<Shipping>(); //cargoPartner?.Shippings ?? [];
|
|
||||||
var cargoTrucks = cargoPartner?.CargoTrucks ?? [];
|
|
||||||
|
|
||||||
<DxTabs>
|
|
||||||
<DxTabPage Text="Kamionok">
|
|
||||||
@{
|
|
||||||
var observableCargoTruck = new AcObservableCollection<CargoTruck>(cargoTrucks);
|
|
||||||
<GridCargoTruck ParentDataItem="@cargoPartner" CargoTrucks="@observableCargoTruck"></GridCargoTruck>
|
|
||||||
}
|
|
||||||
</DxTabPage>
|
|
||||||
|
|
||||||
@* <DxTabPage Text="Szállítmányok">
|
|
||||||
@{
|
|
||||||
//var observableShippings = new AcObservableCollection<Shipping>(shippings);
|
|
||||||
<GridShipping Shippings="@shippings" IsMasterGrid="false" />
|
|
||||||
}
|
|
||||||
</DxTabPage>
|
|
||||||
*@ </DxTabs>
|
|
||||||
}
|
|
||||||
</DetailRowTemplate>
|
|
||||||
<ToolbarTemplate>
|
|
||||||
@if (IsMasterGrid)
|
|
||||||
{
|
|
||||||
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
|
|
||||||
}
|
|
||||||
</ToolbarTemplate>
|
|
||||||
</GridCargoPartnerBase>
|
|
||||||
</GridContent>
|
|
||||||
</MgGridWithInfoPanel>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
//[Inject] public required ObjectLock ObjectLock { get; set; }
|
|
||||||
[Inject] public required DatabaseClient Database { get; set; }
|
|
||||||
|
|
||||||
[Parameter] public bool IsMasterGrid { get; set; } = false;
|
|
||||||
[Parameter] public AcObservableCollection<CargoPartner>? CargoPartners { get; set; }
|
|
||||||
[Parameter] public AcObservableCollection<Shipping>? Shippings { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
const string ExportFileName = "ExportResult";
|
|
||||||
string GridSearchText = "";
|
|
||||||
bool EditItemsEnabled { get; set; }
|
|
||||||
int FocusedRowVisibleIndex { get; set; }
|
|
||||||
public GridCargoPartnerBase Grid { get; set; }
|
|
||||||
string GridCss => !IsMasterGrid ? "hide-toolbar" : string.Empty;
|
|
||||||
|
|
||||||
private int _activeTabIndex;
|
|
||||||
private LoggerClient<GridCargoPartner> _logger;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
_logger = new LoggerClient<GridCargoPartner>(LogWriters.ToArray());
|
|
||||||
await ReloadDataFromDb(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ReloadDataFromDb(bool forceReload = false)
|
|
||||||
{
|
|
||||||
if (!IsMasterGrid) return;
|
|
||||||
|
|
||||||
if (Grid == null) return;
|
|
||||||
|
|
||||||
using (await ObjectLock.GetSemaphore<CargoPartner>().UseWaitAsync())
|
|
||||||
if (forceReload) await Grid.ReloadDataSourceAsync();
|
|
||||||
|
|
||||||
if (forceReload) Grid.Reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task Grid_FocusedRowChanged(GridFocusedRowChangedEventArgs args)
|
|
||||||
{
|
|
||||||
if (Grid == null) return;
|
|
||||||
|
|
||||||
if (Grid.IsEditing() && !Grid.IsEditingNewRow())
|
|
||||||
await Grid.SaveChangesAsync();
|
|
||||||
|
|
||||||
FocusedRowVisibleIndex = args.VisibleIndex;
|
|
||||||
EditItemsEnabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
using AyCode.Core.Interfaces;
|
|
||||||
using DevExpress.Blazor;
|
|
||||||
using FruitBank.Common.Entities;
|
|
||||||
using FruitBank.Common.Interfaces;
|
|
||||||
using FruitBank.Common.SignalRs;
|
|
||||||
using FruitBankHybrid.Shared.Pages;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
|
|
||||||
namespace FruitBankHybrid.Shared.Components.Grids.Cargos;
|
|
||||||
|
|
||||||
public class GridCargoPartnerBase: FruitBankGridBase<CargoPartner>, IGrid
|
|
||||||
{
|
|
||||||
private bool _isFirstInitializeParameterCore;
|
|
||||||
private bool _isFirstInitializeParameters;
|
|
||||||
|
|
||||||
public GridCargoPartnerBase() : base()
|
|
||||||
{
|
|
||||||
GetAllMessageTag = SignalRTags.GetCargoPartners;
|
|
||||||
AddMessageTag = SignalRTags.AddCargoPartner;
|
|
||||||
UpdateMessageTag = SignalRTags.UpdateCargoPartner;
|
|
||||||
|
|
||||||
//RemoveMessageTag = SignalRTags.;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
await base.OnInitializedAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
|
||||||
{
|
|
||||||
base.OnParametersSet();
|
|
||||||
|
|
||||||
if (!_isFirstInitializeParameters)
|
|
||||||
{
|
|
||||||
//if (!IsMasterGrid && (ContextIds == null || ContextIds.Length == 0))
|
|
||||||
//{
|
|
||||||
// ContextIds = [ParentDataItem!.Id];
|
|
||||||
// GetAllMessageTag = SignalRTags.GetShippingItemsByDocumentId;
|
|
||||||
//}
|
|
||||||
|
|
||||||
_isFirstInitializeParameters = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task SetParametersAsyncCore(ParameterView parameters)
|
|
||||||
{
|
|
||||||
await base.SetParametersAsyncCore(parameters);
|
|
||||||
|
|
||||||
if (!_isFirstInitializeParameterCore)
|
|
||||||
{
|
|
||||||
//if (!IsMasterGrid && (ContextIds == null || ContextIds.Length == 0))
|
|
||||||
//{
|
|
||||||
// ContextIds = [ParentDataItem!.Id];
|
|
||||||
// GetAllMessageTag = SignalRTags.GetShippingItemsByDocumentId;
|
|
||||||
//}
|
|
||||||
|
|
||||||
//ShowFilterRow = true;
|
|
||||||
//ShowGroupPanel = true;
|
|
||||||
//AllowSort = false;
|
|
||||||
|
|
||||||
//etc...
|
|
||||||
|
|
||||||
_isFirstInitializeParameterCore = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
@using System.Collections.ObjectModel
|
|
||||||
@using AyCode.Blazor.Components.Components.Grids
|
|
||||||
@using AyCode.Core.Helpers
|
|
||||||
@using AyCode.Core.Interfaces
|
|
||||||
@using AyCode.Core.Loggers
|
|
||||||
@using AyCode.Utils.Extensions
|
|
||||||
@using FruitBank.Common.Dtos
|
|
||||||
@using FruitBank.Common.Entities
|
|
||||||
@using FruitBankHybrid.Shared.Components.Grids.Shippings
|
|
||||||
@using FruitBankHybrid.Shared.Databases
|
|
||||||
@using FruitBankHybrid.Shared.Services.Loggers
|
|
||||||
@using FruitBankHybrid.Shared.Services.SignalRs
|
|
||||||
|
|
||||||
@inject IEnumerable<IAcLogWriterClientBase> LogWriters
|
|
||||||
@inject FruitBankSignalRClient FruitBankSignalRClient
|
|
||||||
|
|
||||||
<MgGridWithInfoPanel ShowInfoPanel="@IsMasterGrid">
|
|
||||||
<GridContent>
|
|
||||||
<GridCargoTruckBase @ref="Grid"
|
|
||||||
DataSource="CargoTrucks"
|
|
||||||
ParentDataItem="ParentDataItem"
|
|
||||||
AutoSaveLayoutName="GridCargoTruck"
|
|
||||||
SignalRClient="FruitBankSignalRClient"
|
|
||||||
Logger="_logger"
|
|
||||||
CssClass="@GridCss"
|
|
||||||
ValidationEnabled="true"
|
|
||||||
OnGridFocusedRowChanged="Grid_FocusedRowChanged">
|
|
||||||
<Columns>
|
|
||||||
<DxGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
|
|
||||||
|
|
||||||
<DxGridDataColumn FieldName="@nameof(CargoTruck.LicencePlate)" />
|
|
||||||
<DxGridDataColumn FieldName="@nameof(CargoTruck.CountryCode)" />
|
|
||||||
<DxGridDataColumn FieldName="@nameof(CargoTruck.IsTrailer)" />
|
|
||||||
|
|
||||||
<DxGridDataColumn FieldName="Created" ReadOnly="true" />
|
|
||||||
<DxGridDataColumn FieldName="Modified" ReadOnly="true" />
|
|
||||||
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
|
|
||||||
</Columns>
|
|
||||||
<ToolbarTemplate>
|
|
||||||
@if (IsMasterGrid)
|
|
||||||
{
|
|
||||||
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
|
|
||||||
}
|
|
||||||
</ToolbarTemplate>
|
|
||||||
</GridCargoTruckBase>
|
|
||||||
</GridContent>
|
|
||||||
</MgGridWithInfoPanel>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
//[Inject] public required ObjectLock ObjectLock { get; set; }
|
|
||||||
[Inject] public required DatabaseClient Database { get; set; }
|
|
||||||
|
|
||||||
[Parameter] public AcObservableCollection<CargoTruck>? CargoTrucks { get; set; }
|
|
||||||
|
|
||||||
const string ExportFileName = "ExportResult";
|
|
||||||
string GridSearchText = "";
|
|
||||||
bool EditItemsEnabled { get; set; }
|
|
||||||
int FocusedRowVisibleIndex { get; set; }
|
|
||||||
public GridCargoTruckBase Grid { get; set; }
|
|
||||||
string GridCss => !IsMasterGrid ? "hide-toolbar" : string.Empty;
|
|
||||||
|
|
||||||
[Parameter] public IId<int>? ParentDataItem { get; set; }
|
|
||||||
|
|
||||||
public bool IsMasterGrid => ParentDataItem == null;
|
|
||||||
public bool ParentDataItemIsCargoPartner => (ParentDataItem is CargoPartner);
|
|
||||||
|
|
||||||
|
|
||||||
private int _activeTabIndex;
|
|
||||||
private LoggerClient<GridCargoTruck> _logger;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
_logger = new LoggerClient<GridCargoTruck>(LogWriters.ToArray());
|
|
||||||
await ReloadDataFromDb(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ReloadDataFromDb(bool forceReload = false)
|
|
||||||
{
|
|
||||||
if (!IsMasterGrid) return;
|
|
||||||
|
|
||||||
if (Grid == null) return;
|
|
||||||
|
|
||||||
using (await ObjectLock.GetSemaphore<CargoTruck>().UseWaitAsync())
|
|
||||||
if (forceReload) await Grid.ReloadDataSourceAsync();
|
|
||||||
|
|
||||||
if (forceReload) Grid.Reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task Grid_FocusedRowChanged(GridFocusedRowChangedEventArgs args)
|
|
||||||
{
|
|
||||||
if (Grid == null) return;
|
|
||||||
|
|
||||||
if (Grid.IsEditing() && !Grid.IsEditingNewRow())
|
|
||||||
await Grid.SaveChangesAsync();
|
|
||||||
|
|
||||||
FocusedRowVisibleIndex = args.VisibleIndex;
|
|
||||||
EditItemsEnabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
using AyCode.Core.Interfaces;
|
|
||||||
using AyCode.Utils.Extensions;
|
|
||||||
using DevExpress.Blazor;
|
|
||||||
using FruitBank.Common.Entities;
|
|
||||||
using FruitBank.Common.Interfaces;
|
|
||||||
using FruitBank.Common.SignalRs;
|
|
||||||
using FruitBankHybrid.Shared.Pages;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
|
|
||||||
namespace FruitBankHybrid.Shared.Components.Grids.Cargos;
|
|
||||||
|
|
||||||
public class GridCargoTruckBase: FruitBankGridBase<CargoTruck>, IGrid
|
|
||||||
{
|
|
||||||
private bool _isFirstInitializeParameterCore;
|
|
||||||
private bool _isFirstInitializeParameters;
|
|
||||||
|
|
||||||
public GridCargoTruckBase() : base()
|
|
||||||
{
|
|
||||||
//GetAllMessageTag = SignalRTags.GetCargoTrucks;
|
|
||||||
AddMessageTag = SignalRTags.AddCargoTruck;
|
|
||||||
UpdateMessageTag = SignalRTags.UpdateCargoTruck;
|
|
||||||
|
|
||||||
//RemoveMessageTag = SignalRTags.;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
if (GetAllMessageTag > 0) return;
|
|
||||||
|
|
||||||
if (IsMasterGrid) GetAllMessageTag = SignalRTags.GetCargoTrucks;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (ContextIds == null || ContextIds.Length == 0) ContextIds = [ParentDataItem!.Id];
|
|
||||||
|
|
||||||
switch (ParentDataItem)
|
|
||||||
{
|
|
||||||
case ICargoPartner:
|
|
||||||
GetAllMessageTag = SignalRTags.GetCargoTrucksByCargoPartnerId;
|
|
||||||
if (KeyFieldNameToParentId.IsNullOrWhiteSpace()) KeyFieldNameToParentId = nameof(CargoTruck.CargoPartnerId);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await base.OnInitializedAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
|
||||||
{
|
|
||||||
base.OnParametersSet();
|
|
||||||
|
|
||||||
if (!_isFirstInitializeParameters)
|
|
||||||
{
|
|
||||||
//if (!IsMasterGrid && (ContextIds == null || ContextIds.Length == 0))
|
|
||||||
//{
|
|
||||||
// ContextIds = [ParentDataItem!.Id];
|
|
||||||
// GetAllMessageTag = SignalRTags.GetShippingItemsByDocumentId;
|
|
||||||
//}
|
|
||||||
|
|
||||||
_isFirstInitializeParameters = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task SetParametersAsyncCore(ParameterView parameters)
|
|
||||||
{
|
|
||||||
await base.SetParametersAsyncCore(parameters);
|
|
||||||
|
|
||||||
if (!_isFirstInitializeParameterCore)
|
|
||||||
{
|
|
||||||
//if (!IsMasterGrid && (ContextIds == null || ContextIds.Length == 0))
|
|
||||||
//{
|
|
||||||
// ContextIds = [ParentDataItem!.Id];
|
|
||||||
// GetAllMessageTag = SignalRTags.GetShippingItemsByDocumentId;
|
|
||||||
//}
|
|
||||||
|
|
||||||
//ShowFilterRow = true;
|
|
||||||
//ShowGroupPanel = true;
|
|
||||||
//AllowSort = false;
|
|
||||||
|
|
||||||
//etc...
|
|
||||||
|
|
||||||
_isFirstInitializeParameterCore = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,7 +7,6 @@ using FruitBank.Common.Interfaces;
|
||||||
using FruitBank.Common.SignalRs;
|
using FruitBank.Common.SignalRs;
|
||||||
using FruitBankHybrid.Shared.Pages;
|
using FruitBankHybrid.Shared.Pages;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Nop.Core.Domain.Catalog;
|
|
||||||
using Nop.Core.Domain.Common;
|
using Nop.Core.Domain.Common;
|
||||||
using Nop.Core.Domain.Orders;
|
using Nop.Core.Domain.Orders;
|
||||||
|
|
||||||
|
|
@ -52,7 +51,7 @@ public class GridGenericAttributeBase: FruitBankGridBase<GenericAttributeDto>, I
|
||||||
switch (ParentDataItem)
|
switch (ParentDataItem)
|
||||||
{
|
{
|
||||||
case IProductDto:
|
case IProductDto:
|
||||||
if (!hasContextIdParameter) ContextIds![ContextKeyGroupIndex] = nameof(Product);
|
if (!hasContextIdParameter) ContextIds![ContextKeyGroupIndex] = "Product";
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case IOrderDto:
|
case IOrderDto:
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,6 @@
|
||||||
<DxGridDataColumn FieldName="Name" />
|
<DxGridDataColumn FieldName="Name" />
|
||||||
<DxGridDataColumn FieldName="TaxId" />
|
<DxGridDataColumn FieldName="TaxId" />
|
||||||
<DxGridDataColumn FieldName="CertificationNumber" />
|
<DxGridDataColumn FieldName="CertificationNumber" />
|
||||||
<DxGridDataColumn FieldName="@nameof(CargoPartner.Currency)" />
|
|
||||||
<DxGridDataColumn FieldName="@nameof(Partner.CountryCode)" />
|
|
||||||
<DxGridDataColumn FieldName="PostalCode" />
|
<DxGridDataColumn FieldName="PostalCode" />
|
||||||
<DxGridDataColumn FieldName="@nameof(Partner.Country)" />
|
<DxGridDataColumn FieldName="@nameof(Partner.Country)" />
|
||||||
<DxGridDataColumn FieldName="State" />
|
<DxGridDataColumn FieldName="State" />
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
# Grids
|
|
||||||
|
|
||||||
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 | 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 |
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
@using FruitBank.Common.Entities
|
@using FruitBank.Common.Entities
|
||||||
@using FruitBankHybrid.Shared.Components.Grids.Shippings
|
@using FruitBankHybrid.Shared.Components.Grids.Shippings
|
||||||
@using FruitBankHybrid.Shared.Databases
|
@using FruitBankHybrid.Shared.Databases
|
||||||
@using FruitBankHybrid.Shared.Extensions
|
|
||||||
@using FruitBankHybrid.Shared.Services.Loggers
|
@using FruitBankHybrid.Shared.Services.Loggers
|
||||||
@using FruitBankHybrid.Shared.Services.SignalRs
|
@using FruitBankHybrid.Shared.Services.SignalRs
|
||||||
|
|
||||||
|
|
@ -20,68 +19,11 @@
|
||||||
<GridContent>
|
<GridContent>
|
||||||
<GridShippingBase @ref="Grid" DataSource="Shippings" AutoSaveLayoutName="GridShipping" SignalRClient="FruitBankSignalRClient" Logger="_logger"
|
<GridShippingBase @ref="Grid" DataSource="Shippings" AutoSaveLayoutName="GridShipping" SignalRClient="FruitBankSignalRClient" Logger="_logger"
|
||||||
CssClass="@GridCss" ValidationEnabled="false"
|
CssClass="@GridCss" ValidationEnabled="false"
|
||||||
OnGridEditModelSaving="Grid_EditModelSaving"
|
|
||||||
OnGridFocusedRowChanged="Grid_FocusedRowChanged">
|
OnGridFocusedRowChanged="Grid_FocusedRowChanged">
|
||||||
<Columns>
|
<Columns>
|
||||||
<DxGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
|
<DxGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
|
||||||
<DxGridDataColumn FieldName="ShippingDate" Caption="Beérkezés" />
|
|
||||||
<DxGridDataColumn FieldName="CargoPartnerId" Caption="Fuvarozó">
|
|
||||||
<EditSettings>
|
|
||||||
<DxComboBoxSettings Data="CargoPartners"
|
|
||||||
ValueFieldName="Id"
|
|
||||||
TextFieldName="Name"
|
|
||||||
DropDownBodyCssClass="dd-body-class"
|
|
||||||
ListRenderMode="ListRenderMode.Entire"
|
|
||||||
SearchMode="ListSearchMode.AutoSearch"
|
|
||||||
SearchFilterCondition="ListSearchFilterCondition.Contains"
|
|
||||||
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto">
|
|
||||||
<Columns>
|
|
||||||
<DxListEditorColumn FieldName="@nameof(CargoPartner.Id)" />
|
|
||||||
<DxListEditorColumn FieldName="@nameof(CargoPartner.Name)" />
|
|
||||||
<DxListEditorColumn FieldName="@nameof(CargoPartner.TaxId)" />
|
|
||||||
</Columns>
|
|
||||||
</DxComboBoxSettings>
|
|
||||||
</EditSettings>
|
|
||||||
</DxGridDataColumn>
|
|
||||||
<DxGridDataColumn FieldName="CargoTruckId" Caption="Vontató">
|
|
||||||
<EditSettings>
|
|
||||||
<DxComboBoxSettings Data="CargoTrucks.Where(x => !x.IsTrailer)"
|
|
||||||
ValueFieldName="Id"
|
|
||||||
TextFieldName="LicencePlate"
|
|
||||||
DropDownBodyCssClass="dd-body-class"
|
|
||||||
ListRenderMode="ListRenderMode.Entire"
|
|
||||||
SearchMode="ListSearchMode.AutoSearch"
|
|
||||||
SearchFilterCondition="ListSearchFilterCondition.Contains"
|
|
||||||
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto">
|
|
||||||
<Columns>
|
|
||||||
<DxListEditorColumn FieldName="@nameof(CargoTruck.Id)" />
|
|
||||||
<DxListEditorColumn FieldName="@nameof(CargoTruck.CargoPartnerName)" />
|
|
||||||
<DxListEditorColumn FieldName="@nameof(CargoTruck.LicencePlate)" />
|
|
||||||
<DxListEditorColumn FieldName="@nameof(CargoTruck.IsTrailer)" />
|
|
||||||
</Columns>
|
|
||||||
</DxComboBoxSettings>
|
|
||||||
</EditSettings>
|
|
||||||
</DxGridDataColumn>
|
|
||||||
<DxGridDataColumn FieldName="CargoTrailerId" Caption="Utánfutó">
|
|
||||||
<EditSettings>
|
|
||||||
<DxComboBoxSettings Data="CargoTrucks.Where(x => x.IsTrailer)"
|
|
||||||
ValueFieldName="Id"
|
|
||||||
TextFieldName="LicencePlate"
|
|
||||||
DropDownBodyCssClass="dd-body-class"
|
|
||||||
ListRenderMode="ListRenderMode.Entire"
|
|
||||||
SearchMode="ListSearchMode.AutoSearch"
|
|
||||||
SearchFilterCondition="ListSearchFilterCondition.Contains"
|
|
||||||
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto">
|
|
||||||
<Columns>
|
|
||||||
<DxListEditorColumn FieldName="@nameof(CargoTruck.Id)" />
|
|
||||||
<DxListEditorColumn FieldName="@nameof(CargoTruck.CargoPartnerName)" />
|
|
||||||
<DxListEditorColumn FieldName="@nameof(CargoTruck.LicencePlate)" />
|
|
||||||
<DxListEditorColumn FieldName="@nameof(CargoTruck.IsTrailer)" />
|
|
||||||
</Columns>
|
|
||||||
</DxComboBoxSettings>
|
|
||||||
</EditSettings>
|
|
||||||
</DxGridDataColumn>
|
|
||||||
|
|
||||||
|
<DxGridDataColumn FieldName="ShippingDate" Caption="Beérkezés"/>
|
||||||
<DxGridDataColumn FieldName="LicencePlate" Caption="Rendszám" />
|
<DxGridDataColumn FieldName="LicencePlate" Caption="Rendszám" />
|
||||||
<DxGridDataColumn FieldName="Comment" Caption="Megjegyzés" ReadOnly="false" />
|
<DxGridDataColumn FieldName="Comment" Caption="Megjegyzés" ReadOnly="false" />
|
||||||
<DxGridDataColumn FieldName="CargoCompany" Caption="Fuvarozó" ReadOnly="false" />
|
<DxGridDataColumn FieldName="CargoCompany" Caption="Fuvarozó" ReadOnly="false" />
|
||||||
|
|
@ -129,11 +71,7 @@
|
||||||
|
|
||||||
[Parameter] public bool IsMasterGrid { get; set; } = false;
|
[Parameter] public bool IsMasterGrid { get; set; } = false;
|
||||||
[Parameter] public AcObservableCollection<Partner>? Partners { get; set; }
|
[Parameter] public AcObservableCollection<Partner>? Partners { get; set; }
|
||||||
[Parameter] public AcObservableCollection<CargoPartner>? CargoPartners { get; set; }
|
|
||||||
[Parameter] public AcObservableCollection<Shipping>? Shippings { get; set; }
|
[Parameter] public AcObservableCollection<Shipping>? Shippings { get; set; }
|
||||||
[Inject] public required IDialogService DialogService { get; set; } = null!;
|
|
||||||
|
|
||||||
public List<CargoTruck> CargoTrucks { get; set; } = [];
|
|
||||||
|
|
||||||
const string ExportFileName = "ExportResult";
|
const string ExportFileName = "ExportResult";
|
||||||
string GridSearchText = "";
|
string GridSearchText = "";
|
||||||
|
|
@ -153,23 +91,6 @@
|
||||||
|
|
||||||
private async Task ReloadDataFromDb(bool forceReload = false)
|
private async Task ReloadDataFromDb(bool forceReload = false)
|
||||||
{
|
{
|
||||||
using (await ObjectLock.GetSemaphore<CargoPartner>().UseWaitAsync())
|
|
||||||
{
|
|
||||||
if (CargoPartners == null) CargoPartners = new AcObservableCollection<CargoPartner>(await FruitBankSignalRClient.GetCargoPartners() ?? []);
|
|
||||||
else if (CargoPartners.Count == 0 || forceReload)
|
|
||||||
{
|
|
||||||
CargoPartners.Replace(await FruitBankSignalRClient.GetCargoPartners() ?? []);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CargoTrucks = CargoPartners.Where(x => x.CargoTrucks != null).SelectMany(x =>
|
|
||||||
{
|
|
||||||
if (x.CargoTrucks == null) return null;
|
|
||||||
|
|
||||||
foreach (var cargoTruck in x.CargoTrucks) cargoTruck.CargoPartner = x;
|
|
||||||
return x.CargoTrucks;
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
if (!IsMasterGrid) return;
|
if (!IsMasterGrid) return;
|
||||||
|
|
||||||
using (await ObjectLock.GetSemaphore<Partner>().UseWaitAsync())
|
using (await ObjectLock.GetSemaphore<Partner>().UseWaitAsync())
|
||||||
|
|
@ -200,23 +121,7 @@
|
||||||
EditItemsEnabled = true;
|
EditItemsEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task Grid_EditModelSaving(GridEditModelSavingEventArgs e)
|
protected async Task OnActiveTabChanged(int activeTabIndex)
|
||||||
{
|
|
||||||
var shipping = (Shipping)e.EditModel; // a szerkesztett sor
|
|
||||||
|
|
||||||
if (ValidateCargoConsistency(shipping, out var error)) return;
|
|
||||||
|
|
||||||
e.Cancel = true;
|
|
||||||
await DialogService.ShowMessageBoxAsync("Hibás adat", error!, MessageBoxRenderStyle.Danger);
|
|
||||||
// e.IsNew → add vs update
|
|
||||||
|
|
||||||
// ... before-save logika (validáció, számított mezők, CargoTrailer beállítás stb.)
|
|
||||||
|
|
||||||
// megszakítás, ha kell:
|
|
||||||
// e.Cancel = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async protected Task OnActiveTabChanged(int activeTabIndex)
|
|
||||||
{
|
{
|
||||||
_activeTabIndex = activeTabIndex;
|
_activeTabIndex = activeTabIndex;
|
||||||
return;
|
return;
|
||||||
|
|
@ -237,29 +142,5 @@
|
||||||
// break;
|
// break;
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ValidateCargoConsistency(Shipping shipping, out string? error)
|
|
||||||
{
|
|
||||||
error = null;
|
|
||||||
|
|
||||||
if (shipping.CargoTruckId != null)
|
|
||||||
{
|
|
||||||
var truck = CargoTrucks.FirstOrDefault(t => t.Id == shipping.CargoTruckId);
|
|
||||||
if (truck == null || truck.CargoPartnerId != shipping.CargoPartnerId)
|
|
||||||
{
|
|
||||||
error = $"A vontató ({truck?.LicencePlate}) nem a kiválasztott fuvarozóhoz tartozik!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shipping.CargoTrailerId == null) return true;
|
|
||||||
|
|
||||||
var trailer = CargoTrucks.FirstOrDefault(t => t.Id == shipping.CargoTrailerId);
|
|
||||||
if (trailer != null && trailer.CargoPartnerId == shipping.CargoPartnerId) return true;
|
|
||||||
|
|
||||||
error = $"Az utánfutó ({trailer?.LicencePlate}) nem a kiválasztott fuvarozóhoz tartozik!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Components
|
|
||||||
|
|
||||||
DevExpress Blazor grid wrappers, pallet measurement components, and toast notifications.
|
|
||||||
|
|
||||||
## Key Files (Root)
|
|
||||||
|
|
||||||
- **`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.
|
|
||||||
|
|
||||||
## Subfolders
|
|
||||||
|
|
||||||
| Folder | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| [`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 |
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
using AyCode.Core.Extensions;
|
using AyCode.Core.Extensions;
|
||||||
using AyCode.Core.Helpers;
|
|
||||||
using AyCode.Core.Interfaces;
|
using AyCode.Core.Interfaces;
|
||||||
using AyCode.Core.Loggers;
|
|
||||||
using AyCode.Interfaces.Entities;
|
using AyCode.Interfaces.Entities;
|
||||||
using AyCode.Services.SignalRs;
|
using AyCode.Services.SignalRs;
|
||||||
using AyCode.Utils.Extensions;
|
using AyCode.Utils.Extensions;
|
||||||
|
|
@ -17,14 +15,18 @@ using Nop.Core.Domain.Orders;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using AyCode.Core.Helpers;
|
||||||
|
|
||||||
namespace FruitBankHybrid.Shared.Databases;
|
namespace FruitBankHybrid.Shared.Databases;
|
||||||
|
|
||||||
//public class ShippingTableItem : Shipping { }
|
public class ShippingTableItem : Shipping
|
||||||
//public class ShippingDocumentTableItem : ShippingDocument { }
|
{ }
|
||||||
|
public class ShippingDocumentTableItem : ShippingDocument
|
||||||
|
{ }
|
||||||
public class ShippingItemTableItem : ShippingItem
|
public class ShippingItemTableItem : ShippingItem
|
||||||
{ }
|
{ }
|
||||||
//public class ShippingItemPalletTableItem : ShippingItemPallet { }
|
public class ShippingItemPalletTableItem : ShippingItemPallet
|
||||||
|
{ }
|
||||||
public class ProductDtoTableItem : ProductDto
|
public class ProductDtoTableItem : ProductDto
|
||||||
{
|
{
|
||||||
//[Newtonsoft.Json.JsonProperty]
|
//[Newtonsoft.Json.JsonProperty]
|
||||||
|
|
@ -43,12 +45,13 @@ public class ShippingItemTable : SignalRDataSourceList<ShippingItemTableItem>
|
||||||
{
|
{
|
||||||
private static readonly SignalRCrudTags SignalRCrudTags = new(SignalRTags.GetShippingItems, 0, SignalRTags.AddShippingItem, SignalRTags.UpdateShippingItem, 0);
|
private static readonly SignalRCrudTags SignalRCrudTags = new(SignalRTags.GetShippingItems, 0, SignalRTags.AddShippingItem, SignalRTags.UpdateShippingItem, 0);
|
||||||
|
|
||||||
public ShippingItemTable(AcSignalRClientBase signalRClient, IAcLoggerBase? logger, params object[]? contextIds) : this(signalRClient, SignalRCrudTags, logger, contextIds)
|
public ShippingItemTable(AcSignalRClientBase signalRClient, params object[]? contextIds)
|
||||||
|
: this(signalRClient, SignalRCrudTags, contextIds)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShippingItemTable(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, IAcLoggerBase? logger, params object[]? contextIds) : base(signalRClient, signalRCrudTags, logger, contextIds)
|
public ShippingItemTable(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, params object[]? contextIds) : base(signalRClient, signalRCrudTags, contextIds)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +111,7 @@ public class OrderDtoTable(FruitBankSignalRClient fruitBankSignalRClient) : AcOb
|
||||||
|
|
||||||
public abstract class DatabaseTableBase<TDataItem> : SignalRDataSourceObservable<TDataItem> where TDataItem : class, IId<int>
|
public abstract class DatabaseTableBase<TDataItem> : SignalRDataSourceObservable<TDataItem> where TDataItem : class, IId<int>
|
||||||
{
|
{
|
||||||
protected DatabaseTableBase(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, IAcLoggerBase? logger, params object[]? contextIds) : base(signalRClient, signalRCrudTags, logger, contextIds)
|
protected DatabaseTableBase(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, params object[]? contextIds) : base(signalRClient, signalRCrudTags, contextIds)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,7 +171,7 @@ public class DatabaseClient : DatabaseClientBase
|
||||||
_fruitBankSignalRClient = fruitBankSignalRClient;
|
_fruitBankSignalRClient = fruitBankSignalRClient;
|
||||||
|
|
||||||
AddTable(new ProductDtoTable(_fruitBankSignalRClient));
|
AddTable(new ProductDtoTable(_fruitBankSignalRClient));
|
||||||
AddTable(new ShippingItemTable(_fruitBankSignalRClient, null));
|
AddTable(new ShippingItemTable(_fruitBankSignalRClient));
|
||||||
AddTable(new OrderDtoTable(_fruitBankSignalRClient));
|
AddTable(new OrderDtoTable(_fruitBankSignalRClient));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# Databases
|
|
||||||
|
|
||||||
Client-side in-memory table cache using ConcurrentDictionary for offline/fast data access.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# Extensions
|
|
||||||
|
|
||||||
DevExpress dialog helper extensions.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`DevexpressComponentExtensions.cs`** — ShowMessageBoxAsync() and ShowConfirmBoxAsync() for DevExpress dialogs.
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
|
@ -72,21 +72,4 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Components\Toolbars\" />
|
<Folder Include="Components\Toolbars\" />
|
||||||
</ItemGroup>
|
</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>
|
</Project>
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
# Layout
|
|
||||||
|
|
||||||
Application shell: root layout, navigation menu, auto-login, and toast notifications.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`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.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# Models
|
|
||||||
|
|
||||||
View models for measuring pages.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`MeasuringDateSelectorModel.cs`** — Date picker model: ShippingId, DateTime, IsMeasured flag.
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
# Pages
|
|
||||||
|
|
||||||
Routed Blazor pages for the FruitBank application.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`Home.razor.cs`** — Landing page showing form factor and platform.
|
|
||||||
- **`Login.razor.cs`** — Authentication with user selection and password validation.
|
|
||||||
- **`ShippingsAdmin.razor.cs`** — Inbound shipping management with tabbed interface.
|
|
||||||
- **`OrdersAdmin.razor.cs`** — Outbound order management with product and order item tabs.
|
|
||||||
- **`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.
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
@using FruitBank.Common.Dtos
|
@using FruitBank.Common.Dtos
|
||||||
@using FruitBank.Common.Entities
|
@using FruitBank.Common.Entities
|
||||||
@using FruitBankHybrid.Shared.Components
|
@using FruitBankHybrid.Shared.Components
|
||||||
@using FruitBankHybrid.Shared.Components.Grids.Cargos
|
|
||||||
@using FruitBankHybrid.Shared.Components.Grids.Partners
|
@using FruitBankHybrid.Shared.Components.Grids.Partners
|
||||||
@using FruitBankHybrid.Shared.Components.Grids.Shippings
|
@using FruitBankHybrid.Shared.Components.Grids.Shippings
|
||||||
@using FruitBankHybrid.Shared.Databases
|
@using FruitBankHybrid.Shared.Databases
|
||||||
|
|
@ -26,9 +25,6 @@
|
||||||
<DxTabPage Text="Beszállítók" Visible="@(LoggedInModel.IsAdministrator)">
|
<DxTabPage Text="Beszállítók" Visible="@(LoggedInModel.IsAdministrator)">
|
||||||
<GridPartner @ref="gridPartner" Partners="@Partners" IsMasterGrid="true"></GridPartner>
|
<GridPartner @ref="gridPartner" Partners="@Partners" IsMasterGrid="true"></GridPartner>
|
||||||
</DxTabPage>
|
</DxTabPage>
|
||||||
<DxTabPage Text="Fuvarozók" Visible="@(LoggedInModel.IsAdministrator)">
|
|
||||||
<GridCargoPartner @ref="gridCargoPartner" CargoPartners="CargoPartners" IsMasterGrid="true"></GridCargoPartner>
|
|
||||||
</DxTabPage>
|
|
||||||
<DxTabPage Text="Szállítmányok">
|
<DxTabPage Text="Szállítmányok">
|
||||||
<GridShipping @ref="gridShipping" Shippings="@Shippings" Partners="@Partners" IsMasterGrid="true"></GridShipping>
|
<GridShipping @ref="gridShipping" Shippings="@Shippings" Partners="@Partners" IsMasterGrid="true"></GridShipping>
|
||||||
</DxTabPage>
|
</DxTabPage>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ using FruitBankHybrid.Shared.Services.Loggers;
|
||||||
using FruitBankHybrid.Shared.Services.SignalRs;
|
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||||
using Mango.Nop.Core.Loggers;
|
using Mango.Nop.Core.Loggers;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using FruitBankHybrid.Shared.Components.Grids.Cargos;
|
|
||||||
|
|
||||||
namespace FruitBankHybrid.Shared.Pages;
|
namespace FruitBankHybrid.Shared.Pages;
|
||||||
|
|
||||||
|
|
@ -30,7 +29,6 @@ public partial class ShippingsAdmin : ComponentBase
|
||||||
|
|
||||||
private GridProductDtoTemplate gridProductDtoTemplate;
|
private GridProductDtoTemplate gridProductDtoTemplate;
|
||||||
private GridPartner gridPartner;
|
private GridPartner gridPartner;
|
||||||
private GridCargoPartner gridCargoPartner;
|
|
||||||
private GridShipping gridShipping;
|
private GridShipping gridShipping;
|
||||||
private GridShippingDocument gridShippingDocument;
|
private GridShippingDocument gridShippingDocument;
|
||||||
private GridShippingItemTemplate gridShippingItemTemplate;
|
private GridShippingItemTemplate gridShippingItemTemplate;
|
||||||
|
|
@ -41,7 +39,6 @@ public partial class ShippingsAdmin : ComponentBase
|
||||||
public AcObservableCollection<ShippingItem> ShippingItems { get; set; } = [];
|
public AcObservableCollection<ShippingItem> ShippingItems { get; set; } = [];
|
||||||
public List<ShippingItemPallet> ShippingItemPallets { get; set; } = [];
|
public List<ShippingItemPallet> ShippingItemPallets { get; set; } = [];
|
||||||
public AcObservableCollection<Partner> Partners { get; set; } = [];
|
public AcObservableCollection<Partner> Partners { get; set; } = [];
|
||||||
public AcObservableCollection<CargoPartner> CargoPartners { get; set; } = [];
|
|
||||||
|
|
||||||
public bool AutoCollapseDetailRow { get; set; }
|
public bool AutoCollapseDetailRow { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
# 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
|
|
||||||
|
|
||||||
| Folder | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| [`Components/`](Components/README.md) | DevExpress grid wrappers, pallet components, notifications |
|
|
||||||
| [`Pages/`](Pages/README.md) | Routed pages: Login, ShippingsAdmin, OrdersAdmin, MeasuringIn/Out, StockTaking |
|
|
||||||
| [`Services/`](Services/README.md) | SignalR client, measurement service, form factor, loggers |
|
|
||||||
| [`Layout/`](Layout/README.md) | MainLayout with navigation, auto-login, toast notifications |
|
|
||||||
| [`Models/`](Models/README.md) | Date selector model for measuring pages |
|
|
||||||
| [`Extensions/`](Extensions/README.md) | DevExpress MessageBox/ConfirmBox helpers |
|
|
||||||
| [`Databases/`](Databases/README.md) | Client-side ConcurrentDictionary table cache |
|
|
||||||
|
|
||||||
## Key Files (Root)
|
|
||||||
|
|
||||||
- **`_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.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# Loggers
|
|
||||||
|
|
||||||
Custom logger implementations for the FruitBank client.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`LoggerClient.cs`** — Non-generic and generic `LoggerClient<T>` extending AyCode logger base.
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
# Services
|
|
||||||
|
|
||||||
Business logic, SignalR client, measurement helpers, and platform abstractions.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- **`IFormFactor.cs`** — Interface for device form factor detection.
|
|
||||||
- **`IMeasurementService.cs`** — Measurement operation interface.
|
|
||||||
- **`MeasurementService.cs`** — CSS styling for MeasuringStatus, pallet item creation/validation, status badge/text generation, shipping-level status calculation.
|
|
||||||
|
|
||||||
## Subfolders
|
|
||||||
|
|
||||||
| Folder | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| [`Loggers/`](Loggers/README.md) | LoggerClient and LoggerClient<T> extending AyCode logger |
|
|
||||||
| [`SignalRs/`](SignalRs/README.md) | FruitBankSignalRClient hub client + DataSource wrappers |
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue