Compare commits
15 Commits
0a1287ce67
...
2221d4a68e
| Author | SHA1 | Date |
|---|---|---|
|
|
2221d4a68e | |
|
|
47fd53cc4e | |
|
|
c722a7b242 | |
|
|
6f46aaebb0 | |
|
|
1e41632599 | |
|
|
9383041504 | |
|
|
f23aebff2d | |
|
|
ea29ad18bd | |
|
|
76cb8adbe6 | |
|
|
82f0f00196 | |
|
|
735eabf008 | |
|
|
b8f52e0c84 | |
|
|
9a0d5698f0 | |
|
|
3b22b07d02 | |
|
|
0083a7bd6e |
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Claude Code PreToolUse hook: DB-vedelem — csak _DEV nevu adatbazis erheto el shell-parancsbol.
|
||||||
|
# Bemenet: hook JSON a stdin-en; ha a parancs connection stringet tartalmaz (Initial Catalog= / Database=)
|
||||||
|
# es a DB-nev NEM tartalmazza a "_DEV"-et, a tool-hivast DENY-jal blokkolja.
|
||||||
|
|
||||||
|
$payloadText = [Console]::In.ReadToEnd()
|
||||||
|
if ([string]::IsNullOrWhiteSpace($payloadText)) { exit 0 }
|
||||||
|
|
||||||
|
try { $payload = $payloadText | ConvertFrom-Json } catch { exit 0 }
|
||||||
|
|
||||||
|
$command = $payload.tool_input.command
|
||||||
|
if ([string]::IsNullOrWhiteSpace($command)) { exit 0 }
|
||||||
|
|
||||||
|
$pattern = [regex]'(?i)(?:Initial\s+Catalog|Database)\s*=\s*([^;"''\s]+)'
|
||||||
|
|
||||||
|
foreach ($match in $pattern.Matches($command)) {
|
||||||
|
$dbName = $match.Groups[1].Value
|
||||||
|
if ($dbName -notmatch '(?i)_DEV') {
|
||||||
|
$reason = "Blokkolt: csak _DEV adatbazis modosithato! (talalt adatbazis: $dbName)"
|
||||||
|
$result = @{ hookSpecificOutput = @{ hookEventName = 'PreToolUse'; permissionDecision = 'deny'; permissionDecisionReason = $reason } }
|
||||||
|
Write-Output ($result | ConvertTo-Json -Depth 5 -Compress)
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Bash|PowerShell",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "powershell -NoProfile -ExecutionPolicy Bypass -File \"H:/Applications/Mango/Source/FruitBankHybridApp/.claude/hooks/db-dev-guard.ps1\"",
|
||||||
|
"timeout": 30,
|
||||||
|
"statusMessage": "DB guard (_DEV) ellenőrzés..."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,7 @@
|
||||||
|
using AyCode.Services.Nav;
|
||||||
using AyCode.Services.Nav.Ekaer;
|
using AyCode.Services.Nav.Ekaer;
|
||||||
using AyCode.Services.Nav.Ekaer.Models;
|
using AyCode.Services.Nav.Ekaer.Models;
|
||||||
|
using FruitBank.Common.Dtos;
|
||||||
using FruitBank.Common.Entities;
|
using FruitBank.Common.Entities;
|
||||||
using FruitBank.Common.Services.Ekaer;
|
using FruitBank.Common.Services.Ekaer;
|
||||||
|
|
||||||
|
|
@ -8,20 +10,22 @@ namespace FruitBank.Common.Server.Services.Ekaer;
|
||||||
/// <inheritdoc cref="IFruitBankEkaerService"/>
|
/// <inheritdoc cref="IFruitBankEkaerService"/>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// A teljes lánc: <c>map</c> (<see cref="IShippingToEkaerMapper"/>, FruitBank.Common) →
|
/// 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>validate → send</c> (<see cref="IEkaerSubmitService"/>, AyCode.Services). A saját cégadatot
|
||||||
/// (<c>INavCredentials</c>) és a saját cégadatokat (<see cref="EkaerCompanyInfo"/>) a DI szolgáltatja.
|
/// (<see cref="EkaerSettings.Company"/>) és a NAV-fiók hitelesítő adatait a DI szolgáltatja.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public sealed class FruitBankEkaerService : IFruitBankEkaerService
|
public sealed class FruitBankEkaerService : IFruitBankEkaerService
|
||||||
{
|
{
|
||||||
private readonly IShippingToEkaerMapper _mapper;
|
private readonly IShippingToEkaerMapper _mapper;
|
||||||
private readonly IEkaerSubmitService _submitService;
|
private readonly IEkaerSubmitService _submitService;
|
||||||
private readonly EkaerCompanyInfo _company;
|
private readonly IEkaerTradeCardValidator _validator;
|
||||||
|
private readonly IEkaerSettings _settings;
|
||||||
|
|
||||||
public FruitBankEkaerService(IShippingToEkaerMapper mapper, IEkaerSubmitService submitService, EkaerCompanyInfo company)
|
public FruitBankEkaerService(IShippingToEkaerMapper mapper, IEkaerSubmitService submitService, IEkaerTradeCardValidator validator, IEkaerSettings settings)
|
||||||
{
|
{
|
||||||
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
|
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
|
||||||
_submitService = submitService ?? throw new ArgumentNullException(nameof(submitService));
|
_submitService = submitService ?? throw new ArgumentNullException(nameof(submitService));
|
||||||
_company = company ?? throw new ArgumentNullException(nameof(company));
|
_validator = validator ?? throw new ArgumentNullException(nameof(validator));
|
||||||
|
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<EkaerSubmitResult> SubmitShippingAsync(Shipping shipping, OperationType operation = OperationType.Create, CancellationToken cancellationToken = default)
|
public Task<EkaerSubmitResult> SubmitShippingAsync(Shipping shipping, OperationType operation = OperationType.Create, CancellationToken cancellationToken = default)
|
||||||
|
|
@ -29,7 +33,65 @@ public sealed class FruitBankEkaerService : IFruitBankEkaerService
|
||||||
ArgumentNullException.ThrowIfNull(shipping);
|
ArgumentNullException.ThrowIfNull(shipping);
|
||||||
|
|
||||||
// map (FruitBank.Common) → submit: validate → send (AyCode.Services)
|
// map (FruitBank.Common) → submit: validate → send (AyCode.Services)
|
||||||
var operations = _mapper.MapShipping(shipping, _company, operation);
|
var operations = _mapper.MapShipping(shipping, _settings.Company, operation);
|
||||||
return _submitService.SubmitAsync(operations, cancellationToken);
|
return _submitService.SubmitAsync(operations, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EkaerHistory GenerateEkaerXmlDocument(ShippingDocument document, EkaerHistory? ekaerHistory = null)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(document);
|
||||||
|
ekaerHistory ??= new EkaerHistory { ForeignKey = document.Id, IsOutgoing = false };
|
||||||
|
|
||||||
|
var currency = document.Partner?.Currency;
|
||||||
|
return TryConfigError(ekaerHistory, currency) ?? Finalize(ekaerHistory, _mapper.MapDocument(document, _settings.Company), currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EkaerHistory GenerateEkaerXmlDocument(OrderDto order, EkaerHistory? ekaerHistory = null)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(order);
|
||||||
|
ekaerHistory ??= new EkaerHistory { ForeignKey = order.Id, IsOutgoing = true };
|
||||||
|
|
||||||
|
// Kimenő pénznem: jelenleg minden HUF (a deviza az OrderDto-ba kerül, amint bekötik) → ConversionRate = 1.
|
||||||
|
const string currency = "HUF";
|
||||||
|
return TryConfigError(ekaerHistory, currency) ?? Finalize(ekaerHistory, _mapper.MapOrder(order, _settings.Company), currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EkaerObligationResult EvaluateObligation(IReadOnlyCollection<ShippingDocument> documents)
|
||||||
|
=> EkaerReportability.Evaluate(_mapper.ToConsignment(documents, _settings.Company), _settings);
|
||||||
|
|
||||||
|
public EkaerObligationResult EvaluateObligation(OrderDto order)
|
||||||
|
=> EkaerReportability.Evaluate(_mapper.ToConsignment(order, _settings.Company), _settings);
|
||||||
|
|
||||||
|
/// <summary>Config-kapu: külföldi (nem HUF) feladónál az árfolyam kötelező — különben a leképezés ELŐTT
|
||||||
|
/// ValidationError (nincs félrevezető XML). <c>null</c> = rendben, mehet a generálás.</summary>
|
||||||
|
private EkaerHistory? TryConfigError(EkaerHistory ekaerHistory, string? currency)
|
||||||
|
{
|
||||||
|
if (EkaerValueCalculator.IsHuf(currency) || _settings.EurHufRate > 0) return null;
|
||||||
|
|
||||||
|
ekaerHistory.Status = EkaerStatus.ValidationError;
|
||||||
|
ekaerHistory.ErrorText = "EKÁER EUR-HUF árfolyam nincs konfigurálva (appsettings Ekaer:ExchangeRate:EurHuf) — a tétel-érték nem számolható.";
|
||||||
|
return ekaerHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Közös befejezés (bejövő/kimenő): validál, szerializál; az XML validációs hibánál IS tárolódik
|
||||||
|
/// (a detail-nézethez), és rögzíti a ténylegesen alkalmazott árfolyamot (HUF → 1, külföldi → FX-ráta).</summary>
|
||||||
|
private EkaerHistory Finalize(EkaerHistory ekaerHistory, TradeCardType tradeCard, string? currency)
|
||||||
|
{
|
||||||
|
var operation = new TradeCardOperationType { Index = 1, Operation = OperationType.Create, TradeCard = tradeCard };
|
||||||
|
var messages = _validator.Validate(operation);
|
||||||
|
|
||||||
|
ekaerHistory.XmlDoc = NavXmlHelper.Serialize(tradeCard);
|
||||||
|
ekaerHistory.ConversionRate = EkaerValueCalculator.ResolveRateToHuf(currency, _settings.EurHufRate);
|
||||||
|
// Blokkoló hiba → ValidationError (nem küldhető); csak warning → GeneratedWithWarning (küldhető, de pótlandó);
|
||||||
|
// semmi → Generated. Az üzeneteket súlyossággal prefixeljük, hogy a detail-nézet megkülönböztesse őket.
|
||||||
|
ekaerHistory.Status = messages.HasErrors() ? EkaerStatus.ValidationError
|
||||||
|
: messages.HasWarnings() ? EkaerStatus.GeneratedWithWarning
|
||||||
|
: EkaerStatus.Generated;
|
||||||
|
// Error-ok elöl, warningok hátul (a detail-nézet soronként színez); a prefix [Error]/[Warning] hordozza a szintet.
|
||||||
|
ekaerHistory.ErrorText = messages.Count == 0 ? null
|
||||||
|
: string.Join(Environment.NewLine, messages
|
||||||
|
.OrderByDescending(m => m.GetSeverity())
|
||||||
|
.Select(m => $"[{m.GetSeverity()}] {m.ErrorMessage}"));
|
||||||
|
return ekaerHistory;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
using AyCode.Services.Nav.Ekaer;
|
using AyCode.Services.Nav.Ekaer;
|
||||||
using AyCode.Services.Nav.Ekaer.Models;
|
using AyCode.Services.Nav.Ekaer.Models;
|
||||||
|
using FruitBank.Common.Dtos;
|
||||||
using FruitBank.Common.Entities;
|
using FruitBank.Common.Entities;
|
||||||
|
using FruitBank.Common.Services.Ekaer;
|
||||||
|
|
||||||
namespace FruitBank.Common.Server.Services.Ekaer;
|
namespace FruitBank.Common.Server.Services.Ekaer;
|
||||||
|
|
||||||
|
|
@ -16,4 +18,31 @@ public interface IFruitBankEkaerService
|
||||||
/// vagy a NAV-válasz — lásd <see cref="EkaerSubmitResult"/>.
|
/// vagy a NAV-válasz — lásd <see cref="EkaerSubmitResult"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<EkaerSubmitResult> SubmitShippingAsync(Shipping shipping, OperationType operation = OperationType.Create, CancellationToken cancellationToken = default);
|
Task<EkaerSubmitResult> SubmitShippingAsync(Shipping shipping, OperationType operation = OperationType.Create, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Egy szállítólevélből legenerálja az EKÁER tradeCard XML-t és validálja — a (meglévő vagy új)
|
||||||
|
/// <see cref="EkaerHistory"/> rekordot tölti: <c>XmlDoc</c> + <c>Status</c>
|
||||||
|
/// (<see cref="EkaerStatus.Generated"/> / <see cref="EkaerStatus.ValidationError"/>) + <c>ErrorText</c>.
|
||||||
|
/// NEM perzisztál és NEM hív NAV-ot — a mentés (upsert) a hívó SignalR endpoint dolga.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="document">A szállítólevél a betöltött gráffal (Partner, Items+ProductDto, Shipping→járművek).</param>
|
||||||
|
/// <param name="ekaerHistory">A dokumentum meglévő rekordja (újrageneráláskor); <c>null</c> → új rekord készül.</param>
|
||||||
|
EkaerHistory GenerateEkaerXmlDocument(ShippingDocument document, EkaerHistory? ekaerHistory = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Egy kimenő rendelésből (<see cref="OrderDto"/>) legenerálja az EKÁER tradeCard XML-t és validálja —
|
||||||
|
/// a (meglévő vagy új) <see cref="EkaerHistory"/> rekordot tölti (<c>IsOutgoing = true</c>). Mi vagyunk az
|
||||||
|
/// ELADÓ, a vevő a CÍMZETT (belföldi értékesítés). NEM perzisztál és NEM hív NAV-ot — a mentés a hívó dolga.
|
||||||
|
/// </summary>
|
||||||
|
EkaerHistory GenerateEkaerXmlDocument(OrderDto order, EkaerHistory? ekaerHistory = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Eldönti egy bejövő (Shipping, Partner) CSOPORT EKÁER-kötelezettségét: külföldi (a feladó és a címzett
|
||||||
|
/// országkódja eltér) → mindig kötelező; belföld → az AGGREGÁLT tömeg/érték a küszöbhöz; érvénytelen országkód →
|
||||||
|
/// <see cref="EkaerObligation.DataError"/>. A küszöb-summa a csoport ÖSSZES dokumentumának tételeire megy.
|
||||||
|
/// </summary>
|
||||||
|
EkaerObligationResult EvaluateObligation(IReadOnlyCollection<ShippingDocument> documents);
|
||||||
|
|
||||||
|
/// <summary>Eldönti egy kimenő rendelés EKÁER-kötelezettségét (ugyanaz a logika, a rendelés tételeire).</summary>
|
||||||
|
EkaerObligationResult EvaluateObligation(OrderDto order);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
using AyCode.Core.Serializers.Attributes;
|
||||||
|
|
||||||
|
namespace FruitBank.Common.Dtos;
|
||||||
|
|
||||||
|
/// <summary>A „hiányzó EKÁER-sorok létrehozása" (reconciliation gomb) eredménye a SignalR-dróton: hány Pending sor
|
||||||
|
/// jött létre, és a felhasználónak szóló sima string üzenetek (adathiány / formátumhiba miatt kihagyott jelöltek —
|
||||||
|
/// pl. érvénytelen országkód). A küszöb alatti, nem kötelező jelölteknél NINCS üzenet. A megjelenítés a hívó dolga.</summary>
|
||||||
|
[AcBinarySerializable(false, false, false, false, false, false)]
|
||||||
|
public sealed class EkaerCreateResult
|
||||||
|
{
|
||||||
|
public int CreatedCount { get; set; }
|
||||||
|
public List<string> Messages { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using AyCode.Core.Serializers.Attributes;
|
||||||
|
using AyCode.Core.Serializers.Toons;
|
||||||
|
using AyCode.Interfaces.TimeStampInfo;
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.Mapping;
|
||||||
|
using Mango.Nop.Core.Entities;
|
||||||
|
|
||||||
|
|
||||||
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
|
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||||
|
[ToonDescription("NAV EKÁER declaration lifecycle record", Purpose = "Work-queue and audit row for one EKÁER road-freight declaration — one row per tradeCard: an incoming ShippingDocument (delivery note) or a completed outgoing Order. Tracks the declaration through generation, validation and submission to the Hungarian tax authority (NAV).")]
|
||||||
|
[Table(Name = FruitBankConstClient.EkaerHistoryDbTableName)]
|
||||||
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.EkaerHistoryDbTableName)]
|
||||||
|
|
||||||
|
public sealed class EkaerHistory: MgEntityBase, ITimeStampInfo
|
||||||
|
{
|
||||||
|
[ToonDescription(Purpose = "Id of the source entity the declaration belongs to: ShippingDocument.Id when IsOutgoing is false (one declaration per delivery note, matching NAV's one-TCN-per-tradeCard granularity), Order id when IsOutgoing is true.")]
|
||||||
|
public int ForeignKey { get; set; }
|
||||||
|
|
||||||
|
[ToonDescription(Purpose = "Direction of the goods movement: false = incoming shipment (Shipping), true = outgoing delivery (Order).")]
|
||||||
|
public bool IsOutgoing { get; set; }
|
||||||
|
|
||||||
|
[ToonDescription(Purpose = "Lifecycle state of the declaration, stored as int (see EkaerStatus): 0 Pending (auto-created, not yet generated), 1 Generated (tradeCard XML produced and valid), 2 ValidationError (generation produced errors, see ErrorText), 3 Sent (accepted by NAV, EkaerNumber filled), 4 SendError (NAV call failed, see ErrorText).")]
|
||||||
|
public int StatusId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>A <see cref="StatusId"/> enum-nézete. A tárolt érték az int oszlop (StatusId) — a linq2db réteg
|
||||||
|
/// az enum-property-t nem perzisztálta (az oszlop kimaradt az insertből, a DB default írt 0-t), ezért a nop-minta:
|
||||||
|
/// int oszlop + nem-mappelt enum wrapper.</summary>
|
||||||
|
[NotColumn, System.ComponentModel.DataAnnotations.Schema.NotMapped, Newtonsoft.Json.JsonIgnore, JsonIgnore]
|
||||||
|
public EkaerStatus Status
|
||||||
|
{
|
||||||
|
get => (EkaerStatus)StatusId;
|
||||||
|
set => StatusId = (int)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ToonDescription(Purpose = "The generated NAV EKÁER tradeCard request XML exactly as it was (or will be) submitted — audit copy and source of the read-only detail view. Null until the first Generate.")]
|
||||||
|
public string? XmlDoc { get; set; }
|
||||||
|
|
||||||
|
[Column(DataType = DataType.DecFloat)]
|
||||||
|
[ToonDescription(Purpose = "The conversion rate actually applied when computing this declaration's item values to HUF: 1 for domestic (HUF) suppliers (no conversion), the FX rate (e.g. EUR→HUF) for foreign suppliers. Null before the first Generate. Currency-agnostic by design — works for any source currency. Audit trail: the invoice amount times this rate yields the HUF value in the tradeCard; the NAV schema has no rate field, so this column preserves how the value was derived.")]
|
||||||
|
public double? ConversionRate { get; set; }
|
||||||
|
|
||||||
|
[ToonDescription(Purpose = "The EKÁER number (TCN) assigned by NAV after a successful submission. Null until the declaration is accepted.")]
|
||||||
|
public string? EkaerNumber { get; set; }
|
||||||
|
|
||||||
|
[ToonDescription(Purpose = "When the declaration was successfully submitted to NAV. Null until sent.")]
|
||||||
|
public DateTime? SentDate { get; set; }
|
||||||
|
|
||||||
|
[ToonDescription(Purpose = "Validation or NAV submission error details for the ValidationError / SendError states. Null when the last operation succeeded.")]
|
||||||
|
public string? ErrorText { get; set; }
|
||||||
|
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
public DateTime Modified { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Az EKÁER-bejelentés életciklus + kimenet állapota. Append-only: új érték a végére, meglévő értéke nem
|
||||||
|
/// változhat (DB-ben int-ként tárolt). Az állapot egyetlen mező; a kategorizálás a <see cref="EkaerStatusExtensions"/>
|
||||||
|
/// segédekkel megy (külön oszlop nélkül) — a folyamat lineáris, ezért a single-enum állapotgép a tiszta illeszkedés.</summary>
|
||||||
|
public enum EkaerStatus
|
||||||
|
{
|
||||||
|
/// <summary>Automatikusan létrejött, még nem volt Generate.</summary>
|
||||||
|
Pending = 0,
|
||||||
|
/// <summary>A tradeCard XML legenerálva és valid — küldhető.</summary>
|
||||||
|
Generated = 1,
|
||||||
|
/// <summary>A Generate BLOKKOLÓ (error) hibákkal zárult (ErrorText) — a forrásadat javítandó, nem küldhető.</summary>
|
||||||
|
ValidationError = 2,
|
||||||
|
/// <summary>NAV által befogadva (EkaerNumber + SentDate töltve), hiánytalanul.</summary>
|
||||||
|
Sent = 3,
|
||||||
|
/// <summary>A NAV-hívás hibával zárult (ErrorText) — újraküldhető.</summary>
|
||||||
|
SendError = 4,
|
||||||
|
/// <summary>Legenerálva és KÜLDHETŐ, de warninggal (pl. hiányzó rendszám, ami a felrakodás megkezdéséig pótolható).</summary>
|
||||||
|
GeneratedWithWarning = 5,
|
||||||
|
/// <summary>NAV-hoz beküldve, de PÓTLÁSRA VÁR (pl. a rendszám a felrakodás megkezdéséig megadandó).</summary>
|
||||||
|
SentWithMissingData = 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Állapot-kategóriák — a tárolás egy mező (Status), a csoportosítás ezekkel (külön oszlop nélkül).</summary>
|
||||||
|
public static class EkaerStatusExtensions
|
||||||
|
{
|
||||||
|
/// <summary>Blokkoló hibás állapot (a bejelentés nem küldhető / a NAV-hívás bukott).</summary>
|
||||||
|
public static bool IsError(this EkaerStatus status) => status is EkaerStatus.ValidationError or EkaerStatus.SendError;
|
||||||
|
|
||||||
|
/// <summary>Beküldhető: legenerálva, blokkoló hiba nélkül (warninggal is) — innen mehet a NAV-hoz.</summary>
|
||||||
|
public static bool IsSubmittable(this EkaerStatus status) => status is EkaerStatus.Generated or EkaerStatus.GeneratedWithWarning;
|
||||||
|
|
||||||
|
/// <summary>A NAV-nál van (akár hiányosan).</summary>
|
||||||
|
public static bool IsSent(this EkaerStatus status) => status is EkaerStatus.Sent or EkaerStatus.SentWithMissingData;
|
||||||
|
|
||||||
|
/// <summary>Elküldve, de pótlásra vár (hiányos adat — pl. rendszám).</summary>
|
||||||
|
public static bool NeedsCompletion(this EkaerStatus status) => status is EkaerStatus.SentWithMissingData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>EkaerHistory-lekérdezés szűrő a tabokhoz, <see cref="FlagsAttribute"/>. A kategóriák diszjunkt
|
||||||
|
/// StatusId-partíciók, ezért kombinálhatók (pl. <c>ToSubmit | NeedsCompletion</c> = minden, ami még nincs lezárva).
|
||||||
|
/// <see cref="All"/> = 0 (nincs bit = nincs szűrő → minden), egyúttal a biztonságos default; szándékosan NEM nullable.
|
||||||
|
/// Vigyázat: <c>HasFlag(All)</c> mindig true (0 bit), ezért a szerver előbb a <c>== All</c> ágat nézi.</summary>
|
||||||
|
[Flags]
|
||||||
|
public enum EkaerHistoryFilter
|
||||||
|
{
|
||||||
|
/// <summary>Minden rekord (nincs szűrés). 0 = nincs bit → minden; egyúttal a biztonságos default.</summary>
|
||||||
|
All = 0,
|
||||||
|
/// <summary>Beküldésre váró: minden, ami még NINCS a NAV-nál (Pending/Generated/GeneratedWithWarning/ValidationError/SendError).</summary>
|
||||||
|
ToSubmit = 1,
|
||||||
|
/// <summary>Elküldött és hiánytalan (<see cref="EkaerStatus.Sent"/>).</summary>
|
||||||
|
Sent = 2,
|
||||||
|
/// <summary>Elküldve, de pótlásra vár (<see cref="EkaerStatus.SentWithMissingData"/>).</summary>
|
||||||
|
NeedsCompletion = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
//public sealed class EkaerHistoryShipping : EkaerHistoryBase
|
||||||
|
//{
|
||||||
|
// public int ShippingId
|
||||||
|
// {
|
||||||
|
// get => ForeignItemId;
|
||||||
|
// set => ForeignItemId = value;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// [Association(ThisKey = nameof(ShippingId), OtherKey = nameof(Shipping.Id), CanBeNull = true)]
|
||||||
|
// public Shipping? Shipping { get; set; }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public sealed class EkaerHistoryOrder : EkaerHistoryBase
|
||||||
|
//{
|
||||||
|
// public int ShippingId
|
||||||
|
// {
|
||||||
|
// get => ForeignItemId;
|
||||||
|
// set => ForeignItemId = value;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// [Association(ThisKey = nameof(ShippingId), OtherKey = nameof(Shipping.Id), CanBeNull = true)]
|
||||||
|
// public Shipping? Shipping { get; set; }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public abstract class EkaerHistoryBase : MgEntityBase, ITimeStampInfo
|
||||||
|
//{
|
||||||
|
// [NotColumn]
|
||||||
|
// protected int ForeignItemId;
|
||||||
|
|
||||||
|
// [NotColumn]
|
||||||
|
// [ToonDescription(BusinessRule = "get => ForeignItemId", Constraints = "[#SmartTypeConstraints]")]
|
||||||
|
// public int ForeignKey => ForeignItemId;
|
||||||
|
|
||||||
|
// public DateTime Created { get; set; }
|
||||||
|
// public DateTime Modified { get; set; }
|
||||||
|
//}
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
using AyCode.Core.Serializers.Toons;
|
using AyCode.Core.Serializers.Toons;
|
||||||
using FruitBank.Common.Interfaces;
|
using FruitBank.Common.Interfaces;
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Entities;
|
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
|
|
@ -12,6 +11,12 @@ namespace FruitBank.Common.Entities;
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PartnerDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PartnerDbTableName)]
|
||||||
public sealed class Partner : PartnerBase, IPartner
|
public sealed class Partner : PartnerBase, IPartner
|
||||||
{
|
{
|
||||||
[Association(ThisKey = nameof(Id), OtherKey = nameof(ShippingDocument.ShippingId), CanBeNull = true)]
|
[ToonDescription(Purpose = "Whether shipments from this supplier are subject to NAV EKÁER reporting. Default true; set false for partners that do not require EKÁER (e.g. multiple companies sharing a wholesale-market address where no road transport between them occurs).")]
|
||||||
|
public bool IsEkaer { get; set; } = true;
|
||||||
|
|
||||||
|
[Association(ThisKey = nameof(Id), OtherKey = nameof(PartnerDepot.PartnerId), CanBeNull = true)]
|
||||||
|
public List<PartnerDepot>? PartnerDepots { get; set; }
|
||||||
|
|
||||||
|
[Association(ThisKey = nameof(Id), OtherKey = nameof(ShippingDocument.PartnerId), CanBeNull = true)]
|
||||||
public List<ShippingDocument>? ShippingDocuments { get; set; }
|
public List<ShippingDocument>? ShippingDocuments { get; set; }
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
using AyCode.Core.Interfaces;
|
||||||
|
using AyCode.Core.Serializers.Attributes;
|
||||||
|
using AyCode.Interfaces.TimeStampInfo;
|
||||||
|
using LinqToDB.Mapping;
|
||||||
|
using Mango.Nop.Core.Entities;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
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")]
|
||||||
|
[LinqToDB.Mapping.Table(Name = FruitBankConstClient.PartnerDepotDbTableName)]
|
||||||
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PartnerDepotDbTableName)]
|
||||||
|
public sealed class PartnerDepot : MgEntityBase, ITimeStampInfo, ICompanyInfoBase
|
||||||
|
{
|
||||||
|
public int PartnerId { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set;}
|
||||||
|
|
||||||
|
[NotColumn, NotMapped, Newtonsoft.Json.JsonIgnore, JsonIgnore]
|
||||||
|
public string? TaxId => Partner?.TaxId;
|
||||||
|
|
||||||
|
public string CountryCode { get; set; }
|
||||||
|
public string PostalCode { get; set;}
|
||||||
|
public string City { get; set;}
|
||||||
|
public string Street { get; set;}
|
||||||
|
|
||||||
|
[NotColumn, NotMapped, Newtonsoft.Json.JsonIgnore, JsonIgnore]
|
||||||
|
public string FullAddress => this.ComposeFullAddress() ?? string.Empty;
|
||||||
|
|
||||||
|
[Association(ThisKey = nameof(PartnerId), OtherKey = nameof(Partner.Id), CanBeNull = true)]
|
||||||
|
public Partner? Partner { get; set; }
|
||||||
|
|
||||||
|
[SkipValuesOnUpdate]
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
public DateTime Modified { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ public sealed class ShippingDocument : MgEntityBase, IShippingDocument
|
||||||
{
|
{
|
||||||
public int PartnerId { get; set; }
|
public int PartnerId { get; set; }
|
||||||
public int? ShippingId { get; set; }
|
public int? ShippingId { get; set; }
|
||||||
|
public int? PartnerDepotId { get; set; }
|
||||||
|
|
||||||
public string DocumentIdNumber { get; set; }
|
public string DocumentIdNumber { get; set; }
|
||||||
public string PdfFileName { get; set; }
|
public string PdfFileName { get; set; }
|
||||||
|
|
@ -32,6 +33,12 @@ public sealed class ShippingDocument : MgEntityBase, IShippingDocument
|
||||||
[Association(ThisKey = nameof(PartnerId), OtherKey = nameof(Partner.Id), CanBeNull = true)]
|
[Association(ThisKey = nameof(PartnerId), OtherKey = nameof(Partner.Id), CanBeNull = true)]
|
||||||
public Partner? Partner { get; set; }
|
public Partner? Partner { get; set; }
|
||||||
|
|
||||||
|
// Csak az EKÁER felrakodási címhez kell. SZÁNDÉKOSAN nincs benne semelyik alapértelmezett loadRelations-ben:
|
||||||
|
// kizárólag a generálás/reconciliation kéri explicit `LoadWith(sd => sd.PartnerDepot)`-tal, így a többi
|
||||||
|
// lekérdezést/sorosítást nem terheli (betöltetlenül null marad). Legacy szállítólevélnél PartnerDepotId = null.
|
||||||
|
[Association(ThisKey = nameof(PartnerDepotId), OtherKey = nameof(PartnerDepot.Id), CanBeNull = true)]
|
||||||
|
public PartnerDepot? PartnerDepot { get; set; }
|
||||||
|
|
||||||
[Association(ThisKey = nameof(Id), OtherKey = nameof(ShippingItem.ShippingDocumentId), CanBeNull = true)]
|
[Association(ThisKey = nameof(Id), OtherKey = nameof(ShippingItem.ShippingDocumentId), CanBeNull = true)]
|
||||||
public List<ShippingItem>? ShippingItems { get; set; }
|
public List<ShippingItem>? ShippingItems { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ public static class FruitBankConstClient
|
||||||
public const string PalletDbTableName = "fbPallet";
|
public const string PalletDbTableName = "fbPallet";
|
||||||
public const string FilesDbTableName = "fbFiles";
|
public const string FilesDbTableName = "fbFiles";
|
||||||
public const string PartnerDbTableName = "fbPartner";
|
public const string PartnerDbTableName = "fbPartner";
|
||||||
|
public const string PartnerDepotDbTableName = "fbPartnerDepot";
|
||||||
|
|
||||||
|
public const string EkaerHistoryDbTableName = "fbEkaerHistory";
|
||||||
|
|
||||||
public const string OrderItemPalletDbTableName = "fbOrderItemPallet";
|
public const string OrderItemPalletDbTableName = "fbOrderItemPallet";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,26 @@ public interface IFruitBankDataControllerCommon
|
||||||
public Task<Partner?> UpdatePartner(Partner partner);
|
public Task<Partner?> UpdatePartner(Partner partner);
|
||||||
#endregion Partner
|
#endregion Partner
|
||||||
|
|
||||||
|
#region PartnerDepot
|
||||||
|
public Task<List<PartnerDepot>?> GetPartnerDepots();
|
||||||
|
public Task<PartnerDepot?> GetPartnerDepotById(int id);
|
||||||
|
public Task<List<PartnerDepot>?> GetPartnerDepotsByPartnerId(int partnerId);
|
||||||
|
public Task<PartnerDepot?> AddPartnerDepot(PartnerDepot partnerDepot);
|
||||||
|
public Task<PartnerDepot?> UpdatePartnerDepot(PartnerDepot partnerDepot);
|
||||||
|
#endregion PartnerDepot
|
||||||
|
|
||||||
|
#region EkaerHistory
|
||||||
|
public Task<List<EkaerHistory>?> GetEkaerHistories(EkaerHistoryFilter filter);
|
||||||
|
public Task<EkaerHistory?> GetEkaerHistoryById(int id);
|
||||||
|
public Task<List<EkaerHistory>?> GetEkaerHistoriesByForeignKey(int foreignKey);
|
||||||
|
public Task<EkaerHistory?> AddEkaerHistory(EkaerHistory ekaerHistory);
|
||||||
|
public Task<EkaerHistory?> UpdateEkaerHistory(EkaerHistory ekaerHistory);
|
||||||
|
public Task<EkaerHistory?> GenerateEkaerXmlDocument(int foreignKey, bool isOutgoing);
|
||||||
|
public Task<EkaerHistory?> CreateEkaerHistory(int foreignKey, bool isOutgoing);
|
||||||
|
public Task<EkaerCreateResult?> CreateMissingEkaerHistories(DateTime fromDate);
|
||||||
|
public Task<int> GetEkaerHistoryCount(EkaerHistoryFilter filter);
|
||||||
|
#endregion EkaerHistory
|
||||||
|
|
||||||
#region CargoPartner
|
#region CargoPartner
|
||||||
public Task<List<CargoPartner>?> GetCargoPartners();
|
public Task<List<CargoPartner>?> GetCargoPartners();
|
||||||
public Task<CargoPartner?> GetCargoPartnerById(int id);
|
public Task<CargoPartner?> GetCargoPartnerById(int id);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ public interface ICargoPartner : IPartnerBase
|
||||||
|
|
||||||
public interface IPartner : IPartnerBase
|
public interface IPartner : IPartnerBase
|
||||||
{
|
{
|
||||||
|
bool IsEkaer { get; set; }
|
||||||
|
List<PartnerDepot>? PartnerDepots { get; set; }
|
||||||
List<ShippingDocument>? ShippingDocuments { get; set; }
|
List<ShippingDocument>? ShippingDocuments { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ public interface IShippingDocument: IEntityInt, ITimeStampInfo//, IMeasured
|
||||||
{
|
{
|
||||||
public int PartnerId { get; set; }
|
public int PartnerId { get; set; }
|
||||||
public int? ShippingId { get; set; }
|
public int? ShippingId { get; set; }
|
||||||
|
public int? PartnerDepotId { get; set; }
|
||||||
|
|
||||||
public string DocumentIdNumber { get; set; }
|
public string DocumentIdNumber { get; set; }
|
||||||
public string PdfFileName { get; set; }
|
public string PdfFileName { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
using AyCode.Services.Nav.Ekaer.Models;
|
||||||
|
using TradeReasonType = AyCode.Services.Nav.Ekaer.Models.Common.TradeReasonType;
|
||||||
|
|
||||||
|
namespace FruitBank.Common.Services.Ekaer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Irány-független, normalizált szállítmány-modell. A bejövő (<c>ShippingDocument</c>-csoport) és a kimenő
|
||||||
|
/// (<c>OrderDto</c>) forrás EBBE képződik (a <see cref="IShippingToEkaerMapper"/> adapterei), és innen épül MIND a
|
||||||
|
/// NAV tradeCard (<see cref="IShippingToEkaerMapper.BuildTradeCard"/>), MIND a bejelentés-kötelezettség
|
||||||
|
/// (<see cref="EkaerReportability"/>). Így az irányfüggő tudás a két adapterre szorul, a közös logika egy helyen van.
|
||||||
|
/// Szerver-oldali köztes típus — NEM megy a SignalR-dróton.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A fel-/lerakodási hely és a jármű már a NAV-típus (<see cref="LocationType"/>, <see cref="BasicVehicleDetailType"/>),
|
||||||
|
/// mert a saját telephely (<c>company.Site</c>) kész LocationType (Phone/Email/FELIR-mezőkkel) — azt átalakítás nélkül,
|
||||||
|
/// MEZŐVESZTÉS nélkül kell átengedni. A normalizálás (kötelezettséghez számít) a feladó/címzett országkódjára és a
|
||||||
|
/// tételek tömeg/érték-aggregálására korlátozódik.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class EkaerConsignment
|
||||||
|
{
|
||||||
|
/// <summary>A forrás azonosítója (bejövőnél ShippingDocument.Id, kimenőnél Order.Id). Csoport-kiértékelésnél
|
||||||
|
/// (több dokumentum) az első forrásé — a kötelezettség-döntés nem használja.</summary>
|
||||||
|
public int ForeignKey { get; init; }
|
||||||
|
|
||||||
|
public bool IsOutgoing { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Számla-pénznem (ISO 4217). A tétel <see cref="EkaerLine.ValueHuf"/> már KISZÁMOLT HUF-ban.</summary>
|
||||||
|
public string? Currency { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Feladó / eladó (a kötelezettség az országkódját nézi).</summary>
|
||||||
|
public EkaerEndpoint Seller { get; init; } = new();
|
||||||
|
|
||||||
|
/// <summary>Címzett / vevő (a kötelezettség az országkódját nézi).</summary>
|
||||||
|
public EkaerEndpoint Buyer { get; init; } = new();
|
||||||
|
|
||||||
|
/// <summary>Felrakodási hely (NAV LocationType — pl. a saját telephely átengedve).</summary>
|
||||||
|
public LocationType? LoadLocation { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Lerakodási hely (NAV LocationType — pl. a saját telephely átengedve).</summary>
|
||||||
|
public LocationType? UnloadLocation { get; init; }
|
||||||
|
|
||||||
|
public IReadOnlyList<EkaerLine> Lines { get; init; } = [];
|
||||||
|
|
||||||
|
public BasicVehicleDetailType? Vehicle { get; init; }
|
||||||
|
public BasicVehicleDetailType? Trailer { get; init; }
|
||||||
|
public string? CarrierName { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Egy fél (feladó/címzett) normalizálatlan adatai a NAV seller/destination mezőkhöz ÉS a kötelezettség
|
||||||
|
/// országkód-vizsgálatához. (A cím egysoros; a tagolt fel-/lerakodási helyet a <see cref="EkaerConsignment.LoadLocation"/>
|
||||||
|
/// / <see cref="EkaerConsignment.UnloadLocation"/> hordozza.)</summary>
|
||||||
|
public sealed class EkaerEndpoint
|
||||||
|
{
|
||||||
|
public string? Name { get; init; }
|
||||||
|
public string? VatNumber { get; init; }
|
||||||
|
public string? CountryCode { get; init; }
|
||||||
|
public string? FullAddress { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Egy normalizált tétel. A <see cref="WeightKg"/> és a <see cref="ValueHuf"/> már KISZÁMOLT (HUF-ban),
|
||||||
|
/// hogy a küszöb-summa és a tradeCard ugyanazt használja (egyetlen érték-forrás).</summary>
|
||||||
|
public sealed class EkaerLine
|
||||||
|
{
|
||||||
|
public string ExternalId { get; init; } = string.Empty;
|
||||||
|
public string? Vtsz { get; init; }
|
||||||
|
public string? Name { get; init; }
|
||||||
|
public double WeightKg { get; init; }
|
||||||
|
public long? ValueHuf { get; init; }
|
||||||
|
public TradeReasonType TradeReason { get; init; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
using AyCode.Services.Nav.Ekaer;
|
||||||
|
|
||||||
|
namespace FruitBank.Common.Services.Ekaer;
|
||||||
|
|
||||||
|
/// <summary>Egy szállítmány EKÁER bejelentés-kötelezettsége.</summary>
|
||||||
|
public enum EkaerObligation
|
||||||
|
{
|
||||||
|
/// <summary>Kötelező bejelenteni (sort kell létrehozni).</summary>
|
||||||
|
Required,
|
||||||
|
/// <summary>Nem kötelező (belföld, küszöb alatt) — NEM hiba, nincs üzenet.</summary>
|
||||||
|
NotRequired,
|
||||||
|
/// <summary>A döntés nem hozható meg adathiány/formátumhiba miatt (pl. érvénytelen országkód) — NINCS sor, az okok az üzenetekben.</summary>
|
||||||
|
DataError,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>A kötelezettség-kiértékelés eredménye: a döntés + (csak DataError esetén) a felhasználónak szóló okok.</summary>
|
||||||
|
public sealed class EkaerObligationResult
|
||||||
|
{
|
||||||
|
public EkaerObligation Obligation { get; private init; }
|
||||||
|
public IReadOnlyList<string> Errors { get; private init; } = [];
|
||||||
|
|
||||||
|
public static EkaerObligationResult Required { get; } = new() { Obligation = EkaerObligation.Required };
|
||||||
|
public static EkaerObligationResult NotRequired { get; } = new() { Obligation = EkaerObligation.NotRequired };
|
||||||
|
public static EkaerObligationResult DataError(IReadOnlyList<string> errors) =>
|
||||||
|
new() { Obligation = EkaerObligation.DataError, Errors = errors };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EKÁER bejelentés-kötelezettség eldöntése egy normalizált <see cref="EkaerConsignment"/>-re — KÖZÖS a bejövő
|
||||||
|
/// (document-csoport) és a kimenő (order) ágon. A NAV mindkét irányban büntet (a felesleges bejelentés ÉS a kimaradás
|
||||||
|
/// is bírság), ezért: a bizonytalan adatot (érvénytelen országkód) NEM döntjük el magától → <see cref="EkaerObligation.DataError"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Sorrend: (1) az AGGREGÁLT tömeg/érték küszöb FELETT → Required, az országkódtól FÜGGETLENÜL — belföld+küszöb
|
||||||
|
/// felett és külföld egyaránt kötelező; az országkód-hibát ilyenkor a generate-validálás jelzi, a sor létrejön;
|
||||||
|
/// (2) küszöb ALATT → itt számít az ország: hiányzó/érvénytelen országkód → DataError (a foreign-vs-belföld nem
|
||||||
|
/// dönthető el); (3) küszöb alatt, érvényes országkódok: külföld (eltér) → Required, belföld (egyezik) → NotRequired.
|
||||||
|
/// A 13/2020. (XII. 23.) PM rendelet alapján a küszöb feladó→címzett→jármű relációra aggregált — a hívó már
|
||||||
|
/// így állítja össze a <see cref="EkaerConsignment.Lines"/>-t (bejövőnél a (Shipping, Partner) csoport tételei).
|
||||||
|
/// </remarks>
|
||||||
|
public static class EkaerReportability
|
||||||
|
{
|
||||||
|
public static EkaerObligationResult Evaluate(EkaerConsignment consignment, IEkaerSettings settings)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(consignment);
|
||||||
|
ArgumentNullException.ThrowIfNull(settings);
|
||||||
|
|
||||||
|
// (1) Küszöb FELETT → kötelező, FÜGGETLENÜL az országkódtól: belföld+küszöb felett ÉS külföld egyaránt kötelező.
|
||||||
|
// Az országkód-hibát ilyenkor NEM itt blokkoljuk — a sor létrejön, a hibát a generate-validálás jelzi.
|
||||||
|
var totalWeight = consignment.Lines.Sum(l => l.WeightKg);
|
||||||
|
var totalValueHuf = consignment.Lines.Sum(l => l.ValueHuf ?? 0L);
|
||||||
|
if (totalWeight >= settings.ThresholdWeightKg || totalValueHuf >= settings.ThresholdValueHuf)
|
||||||
|
return EkaerObligationResult.Required;
|
||||||
|
|
||||||
|
// (2) Küszöb ALATT: itt MÁR az ország dönt — csak a külföldi (eltérő országkód) reláció kötelező. Ehhez érvényes
|
||||||
|
// ISO-2 országkódok kellenek; hiányzó/érvénytelen → a foreign-vs-belföld nem dönthető el → DataError.
|
||||||
|
var subject = Subject(consignment);
|
||||||
|
var errors = new List<string>();
|
||||||
|
if (!IsValidCountry(consignment.Seller.CountryCode))
|
||||||
|
errors.Add($"{subject}: a feladó országkódja hiányzik vagy érvénytelen ('{consignment.Seller.CountryCode}').");
|
||||||
|
if (!IsValidCountry(consignment.Buyer.CountryCode))
|
||||||
|
errors.Add($"{subject}: a címzett országkódja hiányzik vagy érvénytelen ('{consignment.Buyer.CountryCode}').");
|
||||||
|
if (errors.Count > 0)
|
||||||
|
return EkaerObligationResult.DataError(errors);
|
||||||
|
|
||||||
|
// (3) Küszöb alatt, érvényes országkódok: külföld (eltér) → kötelező, belföld (egyezik) → nem kötelező (üzenet nélkül).
|
||||||
|
return CountryEquals(consignment.Seller.CountryCode, consignment.Buyer.CountryCode)
|
||||||
|
? EkaerObligationResult.NotRequired
|
||||||
|
: EkaerObligationResult.Required;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Érvényes EKÁER országkód: pontosan 2 ASCII betű (ISO-2). Üres / más hosszúságú → érvénytelen.</summary>
|
||||||
|
private static bool IsValidCountry(string? code)
|
||||||
|
{
|
||||||
|
var c = code?.Trim();
|
||||||
|
return c is { Length: 2 } && c.All(char.IsAsciiLetter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CountryEquals(string? a, string? b) => string.Equals(a?.Trim(), b?.Trim(), StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private static string Subject(EkaerConsignment c)
|
||||||
|
=> c.IsOutgoing ? $"Rendelés #{c.ForeignKey}" : string.IsNullOrWhiteSpace(c.Seller.Name) ? $"Szállítólevél #{c.ForeignKey}" : c.Seller.Name!;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
using AyCode.Services.Nav.Ekaer;
|
||||||
|
|
||||||
|
namespace FruitBank.Common.Services.Ekaer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A teljes EKÁER konfiguráció egy helyen (configból, <c>appsettings.json</c> „Ekaer" szekció): a bejelentő
|
||||||
|
/// cégadata + a küszöbök + az árfolyam. A küszöbök és az árfolyam évente / jogszabály szerint változhatnak,
|
||||||
|
/// ezért configban élnek — nem a kódban beégetve.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class EkaerSettings : IEkaerSettings
|
||||||
|
{
|
||||||
|
/// <summary>A bejelentő saját cégadatai (címzett a bejövő relációban) + a lerakodási hely.</summary>
|
||||||
|
public EkaerCompanyInfo Company { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>EUR→HUF átváltási árfolyam a tétel-érték HUF-ra számításához. Forrás: MNB napi középárfolyam
|
||||||
|
/// (a NAV nem közöl külön EKÁER-árfolyamot). SZÁNDÉKOSAN nincs default: ha a config nem töltődik be, 0 marad,
|
||||||
|
/// és a külföldi érték-átváltás hibát dob (<see cref="EkaerValueCalculator.ResolveRateToHuf"/>) — így elavult /
|
||||||
|
/// beégetett árfolyammal soha nem számolunk.</summary>
|
||||||
|
public double EurHufRate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Tömeg-küszöb kg-ban: e felett (vagy az érték-küszöb felett) kell EKÁER. Kockázatos élelmiszer: 200 kg.
|
||||||
|
/// Default nélkül: be nem töltött config → 0 → minden szállítmány „átlépi" (mindent jelentünk, a biztonság felé).</summary>
|
||||||
|
public double ThresholdWeightKg { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Érték-küszöb HUF-ban (nettó): e felett (vagy a tömeg-küszöb felett) kell EKÁER. Kockázatos élelmiszer: 250 000 Ft.
|
||||||
|
/// Default nélkül: be nem töltött config → 0 → minden szállítmány „átlépi" (mindent jelentünk, a biztonság felé).
|
||||||
|
/// <c>int</c>: a legmagasabb küszöb 5 millió Ft, bőven belefér.</summary>
|
||||||
|
public int ThresholdValueHuf { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
using FruitBank.Common.Dtos;
|
||||||
|
using FruitBank.Common.Entities;
|
||||||
|
|
||||||
|
namespace FruitBank.Common.Services.Ekaer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A tétel-érték HUF-ra számítása — közös a leképezés (tradeCardItem.value) és a küszöb-kapu
|
||||||
|
/// (<c>CreateMissingEkaerHistories</c>) között, hogy a kétféle számítás ne csúszhasson el egymástól.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A forrás-pénznem a <c>Partner.Currency</c> (ISO 4217). Jelenleg csak HUF és EUR van használatban
|
||||||
|
/// (100%-ban a partnerből következik): HUF → nincs átváltás, minden más → EUR-ként, az árfolyammal.
|
||||||
|
/// Ha valaha más deviza is megjelenik, ezt a feltevést bővíteni kell (akkor per-deviza árfolyam kell).
|
||||||
|
/// </remarks>
|
||||||
|
public static class EkaerValueCalculator
|
||||||
|
{
|
||||||
|
public static bool IsHuf(string? currency) => string.Equals(currency?.Trim(), "HUF", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
/// <summary>A forrás-pénznem → HUF szorzó. HUF → 1 (árfolyam nem kell). Minden más (EUR) → a megadott
|
||||||
|
/// EUR-HUF árfolyam; ha az hiányzik/0, <see cref="InvalidOperationException"/> — soha nem számolunk elavult /
|
||||||
|
/// üres árfolyammal (a config-default szándékosan nincs).</summary>
|
||||||
|
public static double ResolveRateToHuf(string? currency, double eurHufRate)
|
||||||
|
{
|
||||||
|
if (IsHuf(currency)) return 1d;
|
||||||
|
if (eurHufRate <= 0)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"EKÁER EUR-HUF árfolyam nincs konfigurálva (appsettings Ekaer:ExchangeRate:EurHuf) — külföldi (nem HUF) értéket nem lehet HUF-ra számolni.");
|
||||||
|
return eurHufRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Egy tétel értéke a számla pénznemében (egységár × dokumentum-mennyiség), átváltás előtt.</summary>
|
||||||
|
public static double ItemLineValue(ShippingItem item) => item.UnitPriceOnDocument * item.QuantityOnDocument;
|
||||||
|
|
||||||
|
/// <summary>Egy tétel értéke HUF-ban, egészre kerekítve. A NAV <c>value > 0</c>-t vár — 0/ismeretlen
|
||||||
|
/// esetén <c>null</c> (a mezőt nem töltjük, a validátor jelzi, ha kötelező lenne).</summary>
|
||||||
|
public static long? ItemValueHuf(ShippingItem item, double rateToHuf)
|
||||||
|
{
|
||||||
|
var huf = Math.Round(ItemLineValue(item) * rateToHuf, MidpointRounding.AwayFromZero);
|
||||||
|
return huf > 0 ? (long)huf : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Kimenő (Order) változatok ────────────────────────────────────────────
|
||||||
|
// Az érték a NETTÓ (ÁFA nélküli) egységár × mennyiség — az EKÁER „nettó érték"-et vár.
|
||||||
|
|
||||||
|
/// <summary>Egy rendelés-tétel nettó értéke a számla pénznemében (UnitPriceExclTax × mennyiség), átváltás előtt.</summary>
|
||||||
|
public static double ItemLineValue(OrderItemDto item) => (double)item.UnitPriceExclTax * item.Quantity;
|
||||||
|
|
||||||
|
/// <summary>Egy rendelés-tétel értéke HUF-ban, egészre kerekítve. 0/ismeretlen → <c>null</c> (lásd a bejövő párját).</summary>
|
||||||
|
public static long? ItemValueHuf(OrderItemDto item, double rateToHuf)
|
||||||
|
{
|
||||||
|
var huf = Math.Round(ItemLineValue(item) * rateToHuf, MidpointRounding.AwayFromZero);
|
||||||
|
return huf > 0 ? (long)huf : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using AyCode.Services.Nav.Ekaer;
|
using AyCode.Services.Nav.Ekaer;
|
||||||
using AyCode.Services.Nav.Ekaer.Models;
|
using AyCode.Services.Nav.Ekaer.Models;
|
||||||
|
using FruitBank.Common.Dtos;
|
||||||
using FruitBank.Common.Entities;
|
using FruitBank.Common.Entities;
|
||||||
|
|
||||||
namespace FruitBank.Common.Services.Ekaer;
|
namespace FruitBank.Common.Services.Ekaer;
|
||||||
|
|
@ -22,4 +23,31 @@ public interface IShippingToEkaerMapper
|
||||||
/// <param name="company">A bejelentő saját cégadatai (címzett bejövő relációban) + a lerakodási hely.</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>
|
/// <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);
|
IReadOnlyList<TradeCardOperationType> MapShipping(Shipping shipping, EkaerCompanyInfo company, OperationType operation = OperationType.Create);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EGY szállítólevelet (<see cref="ShippingDocument"/>) képez le egy tradeCard-dá — a dokumentum-szintű
|
||||||
|
/// EKÁER-granularitás egysége (1 dokumentum = 1 tradeCard = 1 TCN). A jármű/fuvarozó adatok a
|
||||||
|
/// <c>document.Shipping</c>-ből jönnek; ha az nincs betöltve, ezek üresen maradnak (a validátor jelzi).
|
||||||
|
/// </summary>
|
||||||
|
TradeCardType MapDocument(ShippingDocument document, EkaerCompanyInfo company);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EGY kimenő rendelést (<see cref="FruitBank.Common.Dtos.OrderDto"/>) képez le egy tradeCard-dá: a bejelentő
|
||||||
|
/// (mi) az ELADÓ, a vevő a CÍMZETT, az irány belföldi értékesítés (<c>D</c>/<c>S</c>). A vonó jármű forrása
|
||||||
|
/// a kimenő fuvar-adat (OrderDto) — amíg az nincs bekötve, üresen marad (a validátor jelzi).
|
||||||
|
/// </summary>
|
||||||
|
TradeCardType MapOrder(OrderDto order, EkaerCompanyInfo company);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bejövő forrás → normalizált <see cref="EkaerConsignment"/>. Egy vagy több <see cref="ShippingDocument"/>-tel
|
||||||
|
/// hívható: EGY dokumentummal a tradeCard-generáláshoz, a (Shipping, Partner) CSOPORTtal a kötelezettség-summához
|
||||||
|
/// (a tételek összevonva). A hívó gondoskodik róla, hogy a csoport azonos partneré és azonos Shippingé legyen.
|
||||||
|
/// </summary>
|
||||||
|
EkaerConsignment ToConsignment(IReadOnlyCollection<ShippingDocument> documents, EkaerCompanyInfo company);
|
||||||
|
|
||||||
|
/// <summary>Kimenő forrás → normalizált <see cref="EkaerConsignment"/> (egy rendelés = egy szállítmány).</summary>
|
||||||
|
EkaerConsignment ToConsignment(OrderDto order, EkaerCompanyInfo company);
|
||||||
|
|
||||||
|
/// <summary>Normalizált szállítmány → NAV tradeCard. KÖZÖS a két irányra; a <c>MapDocument</c>/<c>MapOrder</c> ezt hívja.</summary>
|
||||||
|
TradeCardType BuildTradeCard(EkaerConsignment consignment);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ using AyCode.Core.Interfaces;
|
||||||
using AyCode.Entities;
|
using AyCode.Entities;
|
||||||
using AyCode.Services.Nav.Ekaer;
|
using AyCode.Services.Nav.Ekaer;
|
||||||
using AyCode.Services.Nav.Ekaer.Models;
|
using AyCode.Services.Nav.Ekaer.Models;
|
||||||
|
using FruitBank.Common.Dtos;
|
||||||
using FruitBank.Common.Entities;
|
using FruitBank.Common.Entities;
|
||||||
|
using Nop.Core.Domain.Customers;
|
||||||
using TradeReasonType = AyCode.Services.Nav.Ekaer.Models.Common.TradeReasonType;
|
using TradeReasonType = AyCode.Services.Nav.Ekaer.Models.Common.TradeReasonType;
|
||||||
using TradeType = AyCode.Services.Nav.Ekaer.Models.Common.TradeType;
|
using TradeType = AyCode.Services.Nav.Ekaer.Models.Common.TradeType;
|
||||||
|
|
||||||
|
|
@ -10,15 +12,28 @@ namespace FruitBank.Common.Services.Ekaer;
|
||||||
|
|
||||||
/// <inheritdoc cref="IShippingToEkaerMapper"/>
|
/// <inheritdoc cref="IShippingToEkaerMapper"/>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Tiszta (állapotmentes) leképező. A feladót és a saját céget egységesen <see cref="ICompanyInfoBase"/>-ként kezeli.
|
/// Tiszta (állapotmentes) leképező. A bejövő (<c>ShippingDocument</c>-csoport) és a kimenő (<c>OrderDto</c>) forrás
|
||||||
/// A <c>TradeType</c>/<c>TradeReasonType</c> enumokat aliasszal hozzuk be a <c>Models.TradeCardType</c> osztály
|
/// előbb egy irány-független <see cref="EkaerConsignment"/>-re képződik (<see cref="ToConsignment(IReadOnlyCollection{ShippingDocument}, EkaerCompanyInfo)"/>
|
||||||
/// és a <c>Models.Common.TradeCardType</c> enum névütközése miatt.
|
/// / <see cref="ToConsignment(OrderDto, EkaerCompanyInfo)"/>), majd EBBŐL épül a NAV tradeCard (<see cref="BuildTradeCard"/>).
|
||||||
|
/// Így az irányfüggő tudás a két adapterre szorul, a NAV-build közös. A fel-/lerakodási hely és a jármű már a NAV-típus
|
||||||
|
/// (a saját telephely kész <see cref="LocationType"/>, mezővesztés nélkül átengedve); a feladó/címzett normalizálása a
|
||||||
|
/// build-ben történik. A <c>TradeType</c>/<c>TradeReasonType</c> enumokat aliasszal hozzuk be a névütközés miatt.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
|
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>
|
/// <summary>A NAV EKÁER magyar rendszer — a „belföld" alapértéke HU (a vevő-ország feloldásáig).</summary>
|
||||||
private const string HomeCountry = "HU";
|
private const string HomeCountry = "HU";
|
||||||
|
|
||||||
|
/// <summary>Kimenő pénznem — jelenleg minden HUF (a vevő/rendelés devizája az OrderDto-ban még nincs leképezve).</summary>
|
||||||
|
private const string OutboundCurrency = "HUF";
|
||||||
|
|
||||||
|
private readonly IEkaerSettings _settings;
|
||||||
|
|
||||||
|
public ShippingToEkaerMapper(IEkaerSettings settings)
|
||||||
|
=> _settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||||
|
|
||||||
|
// ── Belépési pontok (vékony wrapperek a köztes modell + a közös build köré) ───────────────────
|
||||||
|
|
||||||
public IReadOnlyList<TradeCardOperationType> MapShipping(Shipping shipping, EkaerCompanyInfo company, OperationType operation = OperationType.Create)
|
public IReadOnlyList<TradeCardOperationType> MapShipping(Shipping shipping, EkaerCompanyInfo company, OperationType operation = OperationType.Create)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(shipping);
|
ArgumentNullException.ThrowIfNull(shipping);
|
||||||
|
|
@ -30,86 +45,202 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
|
||||||
// Granularitás: egy ShippingDocument → egy tradeCard (lásd EKAER_TODO #5).
|
// Granularitás: egy ShippingDocument → egy tradeCard (lásd EKAER_TODO #5).
|
||||||
foreach (var document in shipping.ShippingDocuments ?? [])
|
foreach (var document in shipping.ShippingDocuments ?? [])
|
||||||
{
|
{
|
||||||
|
document.Shipping ??= shipping; // a vontató/fuvarozó a Shippingről jön
|
||||||
index++;
|
index++;
|
||||||
operations.Add(new TradeCardOperationType
|
operations.Add(new TradeCardOperationType
|
||||||
{
|
{
|
||||||
Index = index,
|
Index = index,
|
||||||
Operation = operation,
|
Operation = operation,
|
||||||
TradeCard = BuildTradeCard(shipping, document, company),
|
TradeCard = BuildTradeCard(ToConsignment([document], company)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return operations;
|
return operations;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TradeCardType BuildTradeCard(Shipping shipping, ShippingDocument document, EkaerCompanyInfo company)
|
public TradeCardType MapDocument(ShippingDocument document, EkaerCompanyInfo company)
|
||||||
{
|
{
|
||||||
var seller = document.Partner; // a beszállító (feladó) — ICompanyInfoBase
|
ArgumentNullException.ThrowIfNull(document);
|
||||||
|
ArgumentNullException.ThrowIfNull(company);
|
||||||
|
return BuildTradeCard(ToConsignment([document], company));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TradeCardType MapOrder(OrderDto order, EkaerCompanyInfo company)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(order);
|
||||||
|
ArgumentNullException.ThrowIfNull(company);
|
||||||
|
return BuildTradeCard(ToConsignment(order, company));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Adapterek: forrás → normalizált EkaerConsignment (az EGYETLEN irányfüggő rész) ────────────
|
||||||
|
|
||||||
|
/// <summary>Bejövő: egy vagy több <c>ShippingDocument</c> (a hívó (Shipping, Partner)-re csoportosít) → egy
|
||||||
|
/// szállítmány a csoport ÖSSZES tételével. Egy dokumentummal a generáláshoz, a csoporttal a kötelezettség-summához.</summary>
|
||||||
|
public EkaerConsignment ToConsignment(IReadOnlyCollection<ShippingDocument> documents, EkaerCompanyInfo company)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(documents);
|
||||||
|
ArgumentNullException.ThrowIfNull(company);
|
||||||
|
if (documents.Count == 0) throw new ArgumentException("Legalább egy ShippingDocument szükséges.", nameof(documents));
|
||||||
|
|
||||||
|
var first = documents.First();
|
||||||
|
var partner = first.Partner; // a csoport azonos partneré (a hívó (Shipping, Partner)-re csoportosít)
|
||||||
|
var shipping = first.Shipping; // azonos Shipping; a kapunál lehet null (a jármű ott nem kell)
|
||||||
|
var rateToHuf = SafeRateToHuf(partner?.Currency);
|
||||||
|
|
||||||
|
var lines = documents
|
||||||
|
.SelectMany(d => d.ShippingItems ?? [])
|
||||||
|
.Select(item => new EkaerLine
|
||||||
|
{
|
||||||
|
ExternalId = item.Id.ToString(),
|
||||||
|
TradeReason = TradeReasonType.A, // bejövő áru = beszerzés
|
||||||
|
Vtsz = NormalizeVtsz(item.ProductDto?.Gtin),
|
||||||
|
Name = item.ProductName,
|
||||||
|
WeightKg = item.MeasuredGrossWeight,
|
||||||
|
ValueHuf = EkaerValueCalculator.ItemValueHuf(item, rateToHuf),
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return new EkaerConsignment
|
||||||
|
{
|
||||||
|
ForeignKey = first.Id,
|
||||||
|
IsOutgoing = false,
|
||||||
|
Currency = partner?.Currency,
|
||||||
|
Seller = PartnerEndpoint(partner),
|
||||||
|
Buyer = CompanyEndpoint(company),
|
||||||
|
LoadLocation = BuildLocation(partner), // a beszállító telephelye (a PartnerDepot-bekötés külön feladat)
|
||||||
|
UnloadLocation = company.Site, // a saját telephely (kész LocationType — mezővesztés nélkül átengedve)
|
||||||
|
Lines = lines,
|
||||||
|
Vehicle = BuildVehicle(shipping?.CargoTruck),
|
||||||
|
Trailer = BuildVehicle(shipping?.CargoTrailer),
|
||||||
|
CarrierName = shipping?.CargoPartner?.Name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Kimenő: egy rendelés → egy szállítmány. Nincs Shipping; a kötelezettséget a rendelésre nézzük.</summary>
|
||||||
|
public EkaerConsignment ToConsignment(OrderDto order, EkaerCompanyInfo company)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(order);
|
||||||
|
ArgumentNullException.ThrowIfNull(company);
|
||||||
|
|
||||||
|
var customer = order.Customer;
|
||||||
|
var rateToHuf = SafeRateToHuf(OutboundCurrency); // jelenleg minden HUF
|
||||||
|
|
||||||
|
var lines = (order.OrderItemDtos ?? [])
|
||||||
|
.Select(item => new EkaerLine
|
||||||
|
{
|
||||||
|
ExternalId = item.Id.ToString(),
|
||||||
|
TradeReason = TradeReasonType.S, // kimenő áru = értékesítés
|
||||||
|
Vtsz = NormalizeVtsz(item.ProductDto?.Gtin),
|
||||||
|
Name = item.ProductDto?.Name,
|
||||||
|
WeightKg = item.GrossWeight,
|
||||||
|
ValueHuf = EkaerValueCalculator.ItemValueHuf(item, rateToHuf),
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return new EkaerConsignment
|
||||||
|
{
|
||||||
|
ForeignKey = order.Id,
|
||||||
|
IsOutgoing = true,
|
||||||
|
Currency = OutboundCurrency,
|
||||||
|
Seller = CompanyEndpoint(company),
|
||||||
|
Buyer = CustomerEndpoint(customer),
|
||||||
|
LoadLocation = company.Site,
|
||||||
|
UnloadLocation = BuildCustomerLocation(customer),
|
||||||
|
Lines = lines,
|
||||||
|
// Kimenőnél NEM töltjük a szállítmányozót: a vevő maga viszi el, és a vevő MÁR a címzett (destinationName) —
|
||||||
|
// külön fuvarozót nem tartunk nyilván; a carrierText opcionális (a NAV nem követeli) → üresen hagyjuk.
|
||||||
|
CarrierName = null,
|
||||||
|
// A vonó jármű (rendszám) a customer-hez még nincs bekötve → üresen marad, a felrakodás megkezdéséig pótolandó
|
||||||
|
// (a validátor warningolja). Amint bekötik, a Vehicle is innen jön.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Közös build: normalizált szállítmány → NAV tradeCard (a korábbi két ág helyett egy) ───────
|
||||||
|
|
||||||
|
public TradeCardType BuildTradeCard(EkaerConsignment consignment)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(consignment);
|
||||||
|
|
||||||
var tradeCard = new TradeCardType
|
var tradeCard = new TradeCardType
|
||||||
{
|
{
|
||||||
TradeType = ResolveTradeType(seller),
|
TradeType = ResolveTradeType(consignment),
|
||||||
ModByCarrierEnabled = false, // mi jelentünk; a fuvarozó alapból nem módosíthat
|
ModByCarrierEnabled = false, // mi jelentünk; a fuvarozó alapból nem módosíthat
|
||||||
|
|
||||||
// Feladó / eladó = a beszállító
|
SellerName = consignment.Seller.Name,
|
||||||
SellerName = seller?.Name,
|
SellerVatNumber = NormalizeVatNumber(consignment.Seller.VatNumber),
|
||||||
SellerVatNumber = NormalizeVatNumber(seller?.TaxId),
|
SellerCountry = NormalizeCountryCode(consignment.Seller.CountryCode, 2),
|
||||||
SellerCountry = NormalizeCountryCode(seller?.CountryCode, 2),
|
SellerAddress = Truncate(consignment.Seller.FullAddress, 200),
|
||||||
SellerAddress = Truncate(seller?.FullAddress, 200),
|
|
||||||
|
|
||||||
// Címzett = a bejelentő saját cége (bejövő relációban)
|
DestinationName = consignment.Buyer.Name,
|
||||||
DestinationName = company.Name,
|
DestinationVatNumber = NormalizeVatNumber(consignment.Buyer.VatNumber),
|
||||||
DestinationVatNumber = NormalizeVatNumber(company.TaxId),
|
DestinationCountry = NormalizeCountryCode(consignment.Buyer.CountryCode, 2),
|
||||||
DestinationCountry = NormalizeCountryCode(company.CountryCode, 2),
|
DestinationAddress = Truncate(consignment.Buyer.FullAddress, 200),
|
||||||
DestinationAddress = Truncate(company.FullAddress, 200),
|
|
||||||
|
|
||||||
// Fuvarozó (Shipping.CargoPartner). Regisztrált EKAER-azonosító nincs, csak szöveges név.
|
CarrierText = consignment.CarrierName,
|
||||||
CarrierText = shipping.CargoPartner?.Name,
|
|
||||||
|
|
||||||
// Lerakodás = saját telephely (a cégadatból); felrakodás = a beszállító telephelye.
|
// A helyek kész NAV LocationType-ok (a saját telephely érintetlenül) — átengedve.
|
||||||
UnloadLocation = company.UnloadLocation,
|
LoadLocation = consignment.LoadLocation,
|
||||||
LoadLocation = BuildLoadLocation(seller),
|
UnloadLocation = consignment.UnloadLocation,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Vonó jármű + vontatmány: az EKÁER külön bejegyzésként kéri (vehicle / vehicle2).
|
if (consignment.Vehicle != null) tradeCard.Vehicle = consignment.Vehicle;
|
||||||
if (shipping.CargoTruck != null) tradeCard.Vehicle = BuildVehicle(shipping.CargoTruck);
|
if (consignment.Trailer != null) tradeCard.Vehicle2 = consignment.Trailer;
|
||||||
if (shipping.CargoTrailer != null) tradeCard.Vehicle2 = BuildVehicle(shipping.CargoTrailer);
|
|
||||||
|
|
||||||
foreach (var item in document.ShippingItems ?? []) tradeCard.Items.Add(BuildItem(item));
|
foreach (var line in consignment.Lines) tradeCard.Items.Add(BuildItem(line));
|
||||||
return tradeCard;
|
return tradeCard;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Belföld (a feladó és a címzett országkódja megegyezik) → <c>D</c>; eltérő országok → kimenőnél
|
||||||
/// Belföldi feladó (HU) → <c>D</c> (belföld-belföld), egyébként → <c>I</c> (import). A NAV EKÁER magyar,
|
/// export (<c>E</c>), bejövőnél import (<c>I</c>).</summary>
|
||||||
/// így a belföld mindig HU; az export (<c>E</c>) jelenleg nincs leképezve (lásd EKAER_TODO #1, #7).
|
private static TradeType ResolveTradeType(EkaerConsignment c)
|
||||||
/// </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(),
|
if (string.Equals(c.Seller.CountryCode?.Trim(), c.Buyer.CountryCode?.Trim(), StringComparison.OrdinalIgnoreCase))
|
||||||
// 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.
|
return TradeType.D;
|
||||||
TradeReason = TradeReasonType.A,
|
return c.IsOutgoing ? TradeType.E : TradeType.I;
|
||||||
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)
|
private static TradeCardItemType BuildItem(EkaerLine line) => new()
|
||||||
// Value (HUF): a deviza/FX tisztázásáig NEM töltjük (a mező opcionális). Lásd EKAER_TODO #3, #10.
|
{
|
||||||
|
ItemExternalId = line.ExternalId,
|
||||||
|
TradeReason = line.TradeReason,
|
||||||
|
ProductVtsz = line.Vtsz, // már normalizált (az adapterben)
|
||||||
|
ProductName = line.Name,
|
||||||
|
Weight = (decimal)line.WeightKg,
|
||||||
|
Value = line.ValueHuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
private static BasicVehicleDetailType BuildVehicle(CargoTruck truck) => new()
|
// ── Forrás → normalizálatlan végpont (feladó/címzett: név/adószám/ország/egysoros cím) ─────────
|
||||||
|
|
||||||
|
private static EkaerEndpoint PartnerEndpoint(ICompanyInfoBase? partner) => new()
|
||||||
{
|
{
|
||||||
PlateNumber = NormalizePlateNumber(truck.LicencePlate),
|
Name = partner?.Name,
|
||||||
Country = NormalizeCountryCode(truck.CountryCode, 3),
|
VatNumber = partner?.TaxId,
|
||||||
|
CountryCode = partner?.CountryCode,
|
||||||
|
FullAddress = partner?.FullAddress,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
private static EkaerEndpoint CompanyEndpoint(EkaerCompanyInfo company) => new()
|
||||||
/// 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.)
|
Name = company.Name,
|
||||||
/// </summary>
|
VatNumber = company.TaxId,
|
||||||
private static LocationType? BuildLoadLocation(ICompanyInfoBase? seller)
|
CountryCode = company.CountryCode,
|
||||||
|
FullAddress = company.FullAddress,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>A vevő mint fél. Ország jelenleg HU — a Customer.CountryId→ISO feloldás (export E) külön feladat.</summary>
|
||||||
|
private static EkaerEndpoint CustomerEndpoint(Customer? customer) => new()
|
||||||
|
{
|
||||||
|
Name = customer?.Company,
|
||||||
|
VatNumber = customer?.VatNumber,
|
||||||
|
CountryCode = HomeCountry,
|
||||||
|
FullAddress = ComposeCustomerAddress(customer),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Forrás → NAV LocationType / jármű (a saját telephelyet az adapter közvetlenül adja) ────────
|
||||||
|
|
||||||
|
/// <summary>Felrakodási hely a beszállító adataiból (bejövő). A NAV magyar feladónál a Phone/Email-t is kéri, ami
|
||||||
|
/// az entitásban nincs — lásd EKAER_TODO #6.</summary>
|
||||||
|
private static LocationType? BuildLocation(ICompanyInfoBase? seller)
|
||||||
{
|
{
|
||||||
if (seller is null) return null;
|
if (seller is null) return null;
|
||||||
return new LocationType
|
return new LocationType
|
||||||
|
|
@ -123,6 +254,48 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Lerakodási hely a vevő adataiból (kimenő). Ország jelenleg HU (belföld).</summary>
|
||||||
|
private static LocationType? BuildCustomerLocation(Customer? customer)
|
||||||
|
{
|
||||||
|
if (customer is null) return null;
|
||||||
|
return new LocationType
|
||||||
|
{
|
||||||
|
Name = customer.Company,
|
||||||
|
VatNumber = NormalizeVatNumber(customer.VatNumber),
|
||||||
|
Country = HomeCountry,
|
||||||
|
ZipCode = NormalizeZipCode(customer.ZipPostalCode),
|
||||||
|
City = customer.City,
|
||||||
|
Street = customer.StreetAddress,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BasicVehicleDetailType? BuildVehicle(CargoTruck? truck)
|
||||||
|
{
|
||||||
|
if (truck is null) return null;
|
||||||
|
return new BasicVehicleDetailType
|
||||||
|
{
|
||||||
|
PlateNumber = NormalizePlateNumber(truck.LicencePlate),
|
||||||
|
Country = NormalizeCountryCode(truck.CountryCode, 3),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>A számla-pénznem → HUF szorzó, NEM dobó változat (a kapu külföldinél küszöb nélkül jelent, ott az érték
|
||||||
|
/// nem kell): HUF → 1; külföldi + érvényes árfolyam → árfolyam; külföldi + hiányzó árfolyam → 0 (a tétel-érték null lesz).
|
||||||
|
/// Generáláskor a service config-kapuja (TryConfigError) előbb elvágja a hiányzó-árfolyamos külföldi esetet.</summary>
|
||||||
|
private double SafeRateToHuf(string? currency)
|
||||||
|
=> EkaerValueCalculator.IsHuf(currency) ? 1d : (_settings.EurHufRate > 0 ? _settings.EurHufRate : 0d);
|
||||||
|
|
||||||
|
/// <summary>A vevő egysoros címe (irsz + város + utca) a NAV <c>destinationAddress</c>-hez.</summary>
|
||||||
|
private static string? ComposeCustomerAddress(Customer? customer)
|
||||||
|
{
|
||||||
|
if (customer is null) return null;
|
||||||
|
var parts = new[] { customer.ZipPostalCode, customer.City, customer.StreetAddress, customer.StreetAddress2 }
|
||||||
|
.Where(p => !string.IsNullOrWhiteSpace(p));
|
||||||
|
return EmptyToNull(string.Join(" ", parts).Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Normalizálók (NAV pattern-ek) ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// <summary>Adószám normalizálása. Pattern: <c>[0-9A-Z-]{1,15}</c>.</summary>
|
/// <summary>Adószám normalizálása. Pattern: <c>[0-9A-Z-]{1,15}</c>.</summary>
|
||||||
private static string? NormalizeVatNumber(string? value)
|
private static string? NormalizeVatNumber(string? value)
|
||||||
{
|
{
|
||||||
|
|
@ -155,6 +328,15 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
|
||||||
return Truncate(EmptyToNull(cleaned), 10);
|
return Truncate(EmptyToNull(cleaned), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>VTSZ normalizálása. Pattern: <c>[0-9]{4,8}</c> — a forrásban tagolt formátum is lehet
|
||||||
|
/// („0805 10 00", „0805.10.00"), ezért csak a számjegyek maradnak; a 4-nél rövidebbet a validátor jelzi.</summary>
|
||||||
|
private static string? NormalizeVtsz(string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value)) return null;
|
||||||
|
var cleaned = new string([.. value.Where(char.IsAsciiDigit)]);
|
||||||
|
return Truncate(EmptyToNull(cleaned), 8);
|
||||||
|
}
|
||||||
|
|
||||||
private static string? EmptyToNull(string? value) => string.IsNullOrEmpty(value) ? null : value;
|
private static string? EmptyToNull(string? value) => string.IsNullOrEmpty(value) ? null : value;
|
||||||
|
|
||||||
private static string? Truncate(string? value, int maxLen)
|
private static string? Truncate(string? value, int maxLen)
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,21 @@ public class SignalRTags : AcSignalRTags
|
||||||
public const int GetStockTakingItemsByStockTakingId = 178;
|
public const int GetStockTakingItemsByStockTakingId = 178;
|
||||||
public const int AddOrUpdateMeasuredStockTakingItemPallet = 179;
|
public const int AddOrUpdateMeasuredStockTakingItemPallet = 179;
|
||||||
|
|
||||||
|
public const int GetPartnerDepots = 180;
|
||||||
|
public const int GetPartnerDepotById = 181;
|
||||||
|
public const int GetPartnerDepotsByPartnerId = 182;
|
||||||
|
public const int AddPartnerDepot = 183;
|
||||||
|
public const int UpdatePartnerDepot = 184;
|
||||||
|
|
||||||
|
public const int GetEkaerHistories = 185;
|
||||||
|
public const int GetEkaerHistoryById = 186;
|
||||||
|
public const int GetEkaerHistoriesByForeignKey = 187;
|
||||||
|
public const int AddEkaerHistory = 188;
|
||||||
|
public const int UpdateEkaerHistory = 189;
|
||||||
|
public const int GenerateEkaerXmlDocument = 190;
|
||||||
|
public const int CreateEkaerHistory = 191;
|
||||||
|
public const int CreateMissingEkaerHistories = 192;
|
||||||
|
public const int GetEkaerHistoryCount = 193;
|
||||||
|
|
||||||
public const int AuthenticateUser = 195;
|
public const int AuthenticateUser = 195;
|
||||||
public const int RefreshToken = 200;
|
public const int RefreshToken = 200;
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,10 @@ A `PartnerBase` (és így `Partner` / `CargoPartner`) + a `CargoTruck` a nopComm
|
||||||
|
|
||||||
## FBANKAPP-DMODEL-I-P6X4: A `Product.Gtin` átmenetileg a VTSZ-t tárolja — szétválasztandó
|
## 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)
|
**Status:** Closed (2026-06-15) — superseded by `MGFBANKPLUG-EKAER-I-T3X8` · **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.
|
### Resolution
|
||||||
|
**Visszahelyezve** az EKÁER-topicba: **`MGFBANKPLUG-EKAER-I-T3X8`** (`docs/EKAER/EKAER_ISSUES.md`, újra **Open**). A 2026-06-02-i ide-helyezés **visszavonva** — a VTSZ-forrás (`Product.Gtin`) szétválasztását a konkrét EKÁER-leképezéshez kötve, ott követjük (egy tracker). Az alábbi leírás referencia.
|
||||||
|
|
||||||
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ő**:
|
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).
|
- **GTIN** — globális kereskedelmi cikkszám (vonalkód-azonosító, EAN/UPC).
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
using FruitBank.Common.Services.Ekaer;
|
||||||
|
using TradeReasonType = AyCode.Services.Nav.Ekaer.Models.Common.TradeReasonType;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Shared.Tests.Ekaer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tesztek a <see cref="EkaerReportability"/> bejelentés-kötelezettség-eldöntésére. Tisztán memóriában felépített
|
||||||
|
/// <see cref="EkaerConsignment"/>-eken fut (nincs hálózat/DB), determinisztikus. Küszöb: 200 kg / 250 000 Ft.
|
||||||
|
/// </summary>
|
||||||
|
[TestClass]
|
||||||
|
public sealed class EkaerReportabilityTests
|
||||||
|
{
|
||||||
|
private static readonly EkaerSettings Settings = new()
|
||||||
|
{
|
||||||
|
EurHufRate = 356,
|
||||||
|
ThresholdWeightKg = 200,
|
||||||
|
ThresholdValueHuf = 250_000,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static EkaerConsignment Consignment(string? sellerCountry, string? buyerCountry, params EkaerLine[] lines) => new()
|
||||||
|
{
|
||||||
|
Seller = new EkaerEndpoint { Name = "Feladó", CountryCode = sellerCountry },
|
||||||
|
Buyer = new EkaerEndpoint { Name = "Címzett", CountryCode = buyerCountry },
|
||||||
|
Lines = lines,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static EkaerLine Line(double weightKg, long? valueHuf) =>
|
||||||
|
new() { ExternalId = "1", WeightKg = weightKg, ValueHuf = valueHuf, TradeReason = TradeReasonType.A };
|
||||||
|
|
||||||
|
// ---- Belföld + küszöb ----------------------------------------------------
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Domestic_BelowBothThresholds_NotRequired()
|
||||||
|
{
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("HU", "HU", Line(100, 100_000)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.NotRequired, result.Obligation);
|
||||||
|
Assert.AreEqual(0, result.Errors.Count, "küszöb alatt NINCS üzenet (nem hiba)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Domestic_WeightAtOrOverThreshold_Required()
|
||||||
|
{
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("HU", "HU", Line(200, 0)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.Required, result.Obligation, "a tömeg eléri a küszöböt → kötelező (VAGY-logika)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Domestic_ValueOverThreshold_Required()
|
||||||
|
{
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("HU", "HU", Line(10, 300_000)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.Required, result.Obligation, "az érték átlépi a küszöböt → kötelező");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Domestic_AggregatedOverThreshold_Required()
|
||||||
|
{
|
||||||
|
// Egyenként 200 kg ALATT, EGYÜTT fölötte → kötelező. Ez a (Shipping, Partner)-aggregálás lényege.
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("HU", "HU", Line(120, 0), Line(120, 0)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.Required, result.Obligation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Külföld (a két országkód eltér) → küszöb nélkül kötelező ------------
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CrossBorder_BelowThreshold_StillRequired()
|
||||||
|
{
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("DE", "HU", Line(1, 1)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.Required, result.Obligation, "eltérő országkód → mindig kötelező, küszöb nélkül");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SameForeignCountryBothEnds_TreatedAsDomesticThreshold()
|
||||||
|
{
|
||||||
|
// Mindkét vég azonos (nem HU) ország → NEM határátlépő → a küszöb dönt.
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("DE", "DE", Line(1, 1)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.NotRequired, result.Obligation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Adathiba: érvénytelen/hiányzó országkód → DataError CSAK küszöb alatt ----
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void BelowThreshold_MissingSellerCountry_DataError()
|
||||||
|
{
|
||||||
|
// Küszöb alatt MÁR számít az ország (foreign-vs-belföld) — hiányzó országkód → nem dönthető el → DataError.
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment(null, "HU", Line(10, 1000)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.DataError, result.Obligation);
|
||||||
|
Assert.IsTrue(result.Errors.Count > 0, "a hiányzó országkódot jelezni kell a felhasználónak");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void BelowThreshold_InvalidSellerCountry_NotIso2_DataError()
|
||||||
|
{
|
||||||
|
// Teljes név (nem ISO-2) → érvénytelen → küszöb alatt nem dönthető el → DataError.
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("Magyarország", "HU", Line(10, 1000)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.DataError, result.Obligation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void BelowThreshold_MissingBuyerCountry_DataError()
|
||||||
|
{
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("HU", "", Line(10, 1000)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.DataError, result.Obligation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void OverThreshold_InvalidCountry_Required_NotDataError()
|
||||||
|
{
|
||||||
|
// Küszöb FELETT (tömeg 500 ≥ 200) a kötelezettség az országkódtól FÜGGETLEN → a sor létrejön (a hibát a
|
||||||
|
// generate-validálás jelzi), NEM DataError.
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment(null, "Magyarország", Line(500, 0)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.Required, result.Obligation);
|
||||||
|
Assert.AreEqual(0, result.Errors.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Hibás / üres adatok (robusztusság) ----------------------------------
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NoLines_Domestic_NotRequired()
|
||||||
|
{
|
||||||
|
// Üres szállítmány (nincs tétel) → tömeg/érték 0 → küszöb alatt → belföld → nem kötelező (NEM dob).
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("HU", "HU"), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.NotRequired, result.Obligation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void BothCountriesInvalid_BelowThreshold_DataError_WithTwoMessages()
|
||||||
|
{
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment(null, "", Line(10, 1000)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.DataError, result.Obligation);
|
||||||
|
Assert.AreEqual(2, result.Errors.Count, "mindkét hibás országkódot külön jelezni kell");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NullLineValue_HandledAsZero_WeightDecides()
|
||||||
|
{
|
||||||
|
// Hiányzó (null) tétel-érték (pl. külföldi deviza árfolyam nélkül) → 0-ként számít, NEM dob; a tömeg dönt.
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("HU", "HU", Line(500, null)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.Required, result.Obligation, "a tömeg átlépi a küszöböt — a null érték nem akadály");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,9 @@ namespace FruitBankHybrid.Shared.Tests.Ekaer;
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class ShippingToEkaerMapperTests
|
public sealed class ShippingToEkaerMapperTests
|
||||||
{
|
{
|
||||||
private static readonly ShippingToEkaerMapper Mapper = new();
|
// EurHufRate > 0 kell: a teszt-partnernek nincs Currency-je (null → külföldiként kezelt), és árfolyam nélkül
|
||||||
|
// a value-számítás ResolveRateToHuf-ja dobna. A value-t a tesztek nem ellenőrzik; ez csak az átváltáshoz kell.
|
||||||
|
private static readonly ShippingToEkaerMapper Mapper = new(new EkaerSettings { EurHufRate = 356 });
|
||||||
|
|
||||||
// ---- Helpers ------------------------------------------------------------
|
// ---- Helpers ------------------------------------------------------------
|
||||||
|
|
||||||
|
|
@ -67,18 +69,42 @@ public sealed class ShippingToEkaerMapperTests
|
||||||
PostalCode = "1102",
|
PostalCode = "1102",
|
||||||
City = "Budapest",
|
City = "Budapest",
|
||||||
Street = "Raktar utca 5",
|
Street = "Raktar utca 5",
|
||||||
UnloadLocation = new LocationType
|
Site = new LocationType
|
||||||
{
|
{
|
||||||
Name = "FruitBank Raktár",
|
Name = "FruitBank Raktár",
|
||||||
VatNumber = "98765432-2-41",
|
VatNumber = "98765432-2-41",
|
||||||
|
Phone = "+36301234567", // NAV-formátum: + / 06 prefix kötelező
|
||||||
|
Email = "raktar@fruitbank.hu",
|
||||||
Country = "HU",
|
Country = "HU",
|
||||||
ZipCode = "1102",
|
ZipCode = "1239",
|
||||||
City = "Budapest",
|
City = "Budapest",
|
||||||
Street = "Raktar utca",
|
Street = "Nagykőrösi út",
|
||||||
StreetNumber = "5",
|
StreetNumber = "353",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static ShippingDocument CreateInboundDocument(int itemId, double weight, string sellerCountry = "HU") => new()
|
||||||
|
{
|
||||||
|
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 = [new ShippingItem
|
||||||
|
{
|
||||||
|
Id = itemId,
|
||||||
|
Name = "Alma",
|
||||||
|
ProductDto = new ProductDto { Gtin = "08081010", Name = "Alma" },
|
||||||
|
MeasuredGrossWeight = weight,
|
||||||
|
UnitPriceOnDocument = 5.0,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
// ---- Granularitás / index ----------------------------------------------
|
// ---- Granularitás / index ----------------------------------------------
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
@ -166,7 +192,7 @@ public sealed class ShippingToEkaerMapperTests
|
||||||
Assert.AreEqual("FruitBank Kft", tradeCard.DestinationName);
|
Assert.AreEqual("FruitBank Kft", tradeCard.DestinationName);
|
||||||
Assert.AreEqual("HU", tradeCard.DestinationCountry);
|
Assert.AreEqual("HU", tradeCard.DestinationCountry);
|
||||||
StringAssert.Contains(tradeCard.DestinationAddress, "Budapest", "a destinationAddress a company FullAddress-éből jön");
|
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");
|
Assert.AreSame(company.Site, tradeCard.UnloadLocation, "a lerakodási hely a cég telephelyéből jön");
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
@ -212,4 +238,55 @@ public sealed class ShippingToEkaerMapperTests
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MapShipping_NullCompany_Throws()
|
public void MapShipping_NullCompany_Throws()
|
||||||
=> Assert.ThrowsExactly<ArgumentNullException>(() => Mapper.MapShipping(CreateShipping(), null!));
|
=> Assert.ThrowsExactly<ArgumentNullException>(() => Mapper.MapShipping(CreateShipping(), null!));
|
||||||
|
|
||||||
|
// ---- Köztes modell: ToConsignment (bejövő) ------------------------------
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ToConsignment_Inbound_MultipleDocuments_CombinesLinesAndAggregatesWeight()
|
||||||
|
{
|
||||||
|
// Két szállítólevél (azonos partner, azonos Shipping) → egy szállítmány, MINDKÉT tétellel — a küszöb-aggregáláshoz.
|
||||||
|
var consignment = Mapper.ToConsignment(
|
||||||
|
[CreateInboundDocument(itemId: 1, weight: 100), CreateInboundDocument(itemId: 2, weight: 150)],
|
||||||
|
CreateCompany());
|
||||||
|
|
||||||
|
Assert.AreEqual(2, consignment.Lines.Count, "a csoport ÖSSZES tétele egy szállítmányban");
|
||||||
|
Assert.AreEqual(250d, consignment.Lines.Sum(l => l.WeightKg), 0.001, "a tömeg a dokumentumokból összegződik");
|
||||||
|
Assert.IsFalse(consignment.IsOutgoing);
|
||||||
|
Assert.AreEqual("HU", consignment.Seller.CountryCode, "feladó = a beszállító partner");
|
||||||
|
Assert.AreEqual("HU", consignment.Buyer.CountryCode, "címzett = a saját cég");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ToConsignment_Inbound_BuildTradeCard_PreservesCompanySiteAsUnloadLocation()
|
||||||
|
{
|
||||||
|
var company = CreateCompany();
|
||||||
|
var tradeCard = Mapper.BuildTradeCard(Mapper.ToConsignment([CreateInboundDocument(1, 100)], company));
|
||||||
|
Assert.AreSame(company.Site, tradeCard.UnloadLocation, "a saját telephely érintetlenül (mezővesztés nélkül) megy át");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ToConsignment_Inbound_EmptyDocuments_Throws()
|
||||||
|
=> Assert.ThrowsExactly<ArgumentException>(() => Mapper.ToConsignment([], CreateCompany()));
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ToConsignment_Inbound_NullPartner_DoesNotThrow_SellerCountryNull()
|
||||||
|
{
|
||||||
|
// Hibás adat: nincs Partner a szállítólevélen → az adapter NEM dob; a feladó-ország null (ezt a kötelezettség-
|
||||||
|
// értékelő / generate-validálás kezeli, nem a leképező).
|
||||||
|
var doc = new ShippingDocument { Partner = null, ShippingItems = [new ShippingItem { Id = 1, MeasuredGrossWeight = 50 }] };
|
||||||
|
var consignment = Mapper.ToConsignment([doc], CreateCompany());
|
||||||
|
|
||||||
|
Assert.IsNull(consignment.Seller.CountryCode, "nincs partner → nincs feladó-ország");
|
||||||
|
Assert.AreEqual(1, consignment.Lines.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ToConsignment_Inbound_NullShippingItems_EmptyLines()
|
||||||
|
{
|
||||||
|
// Hibás/hiányos adat: nincs tétel a szállítólevélen → üres Lines, NEM dob.
|
||||||
|
var doc = new ShippingDocument { Partner = new Partner { CountryCode = "HU" }, ShippingItems = null };
|
||||||
|
var consignment = Mapper.ToConsignment([doc], CreateCompany());
|
||||||
|
|
||||||
|
Assert.AreEqual(0, consignment.Lines.Count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,149 +34,6 @@ namespace FruitBankHybrid.Shared.Tests
|
||||||
_signalRClient = TestSignalRClientFactory.Create(nameof(FruitBankClientTests));
|
_signalRClient = TestSignalRClientFactory.Create(nameof(FruitBankClientTests));
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Partner
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task GetPartnersTest()
|
|
||||||
{
|
|
||||||
var partners = await _signalRClient.GetPartners();
|
|
||||||
|
|
||||||
Assert.IsNotNull(partners);
|
|
||||||
Assert.IsNotEmpty(partners);
|
|
||||||
}
|
|
||||||
|
|
||||||
//[TestMethod]
|
|
||||||
//[DataRow(1)]
|
|
||||||
public async Task<Partner> GetPartnerByIdTest(int partnerId)
|
|
||||||
{
|
|
||||||
var partner = await _signalRClient.GetPartnerById(partnerId);
|
|
||||||
|
|
||||||
Assert.IsNotNull(partner);
|
|
||||||
Assert.AreEqual(partnerId, partner.Id);
|
|
||||||
|
|
||||||
return partner;
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
[DataRow(2)]
|
|
||||||
public async Task UpdatePartnerTest(int partnerId)
|
|
||||||
{
|
|
||||||
var partner = await GetPartnerByIdTest(partnerId);
|
|
||||||
|
|
||||||
var newName = GetFixtureName(partner.Name);
|
|
||||||
|
|
||||||
partner.Name = newName;
|
|
||||||
partner = await _signalRClient.UpdatePartner(partner);
|
|
||||||
|
|
||||||
Assert.IsNotNull(partner);
|
|
||||||
Assert.IsTrue(partner.Name == newName);
|
|
||||||
|
|
||||||
partner.Name = GetOriginalName(partner.Name);
|
|
||||||
partner = await _signalRClient.UpdatePartner(partner);
|
|
||||||
|
|
||||||
Assert.IsNotNull(partner);
|
|
||||||
Assert.AreEqual(partnerId, partner.Id);
|
|
||||||
}
|
|
||||||
#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()
|
||||||
|
|
@ -299,7 +156,7 @@ namespace FruitBankHybrid.Shared.Tests
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Assert.IsNotNull(shippingItem.ShippingDocument);
|
Assert.IsNotNull(shippingItem.ShippingDocument);
|
||||||
Assert.IsTrue(shippingItem.ShippingDocument.ShippingDocumentToFiles?.Count == 0);
|
Assert.AreEqual(0, shippingItem.ShippingDocument.ShippingDocumentToFiles?.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,11 +170,11 @@ namespace FruitBankHybrid.Shared.Tests
|
||||||
if (shippingItem.IsMeasurable) Assert.IsNotNull(shippingItem.Pallet, $"shippingItem.Pallet == null; shippingItem.PalletId: {shippingItem.PalletId}");
|
if (shippingItem.IsMeasurable) Assert.IsNotNull(shippingItem.Pallet, $"shippingItem.Pallet == null; shippingItem.PalletId: {shippingItem.PalletId}");
|
||||||
|
|
||||||
Assert.IsNotNull(shippingItem.ProductDto, $"shippingItem.Product == null; shippingItem.ProductId: {shippingItem.ProductId}");
|
Assert.IsNotNull(shippingItem.ProductDto, $"shippingItem.Product == null; shippingItem.ProductId: {shippingItem.ProductId}");
|
||||||
Assert.IsTrue(shippingItem.Id == shippingItemId);
|
Assert.AreEqual(shippingItemId, shippingItem.Id);
|
||||||
|
|
||||||
Assert.IsTrue(shippingItem.QuantityOnDocument > 0, "QuantityOnDocument == 0");
|
Assert.IsGreaterThan(0, shippingItem.QuantityOnDocument, "QuantityOnDocument == 0");
|
||||||
Assert.IsTrue(shippingItem.NetWeightOnDocument > 0, "NetWeightOnDocument == 0");
|
Assert.IsGreaterThan(0, shippingItem.NetWeightOnDocument, "NetWeightOnDocument == 0");
|
||||||
Assert.IsTrue(shippingItem.GrossWeightOnDocument > 0, "GrossWeightOnDocument == 0");
|
Assert.IsGreaterThan(0, shippingItem.GrossWeightOnDocument, "GrossWeightOnDocument == 0");
|
||||||
|
|
||||||
return shippingItem;
|
return shippingItem;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
using AyCode.Services.Nav;
|
||||||
|
using AyCode.Services.Nav.Ekaer.Models;
|
||||||
|
using FruitBank.Common;
|
||||||
|
using FruitBank.Common.Entities;
|
||||||
|
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Shared.Tests
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class FruitBankEkaerTests
|
||||||
|
{
|
||||||
|
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(FruitBankEkaerTests));
|
||||||
|
}
|
||||||
|
|
||||||
|
#region EkaerHistory / Create
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Backfill: minden meglévő szállítólevélre létrehozza az EkaerHistory rekordot (Pending, XML nélkül).
|
||||||
|
/// A generálás a grid Generate gombjával, kézzel történik. Idempotens: újrafuttatva nem duplikál.
|
||||||
|
/// </summary>
|
||||||
|
[TestMethod]
|
||||||
|
public async Task CreateEkaerHistoryForAllShippingDocumentsTest()
|
||||||
|
{
|
||||||
|
var shippingDocuments = await _signalRClient.GetShippingDocuments();
|
||||||
|
|
||||||
|
Assert.IsNotNull(shippingDocuments);
|
||||||
|
Assert.IsNotEmpty(shippingDocuments);
|
||||||
|
|
||||||
|
foreach (var shippingDocument in shippingDocuments)
|
||||||
|
{
|
||||||
|
var ekaerHistory = await _signalRClient.CreateEkaerHistory(shippingDocument.Id, false);
|
||||||
|
|
||||||
|
Console.WriteLine($"doc#{shippingDocument.Id}: EkaerHistory Id: {(ekaerHistory == null ? "NULL VÁLASZ!" : ekaerHistory.Id.ToString())}; Status: {ekaerHistory?.Status}");
|
||||||
|
|
||||||
|
Assert.IsNotNull(ekaerHistory, $"shippingDocument.Id: {shippingDocument.Id}");
|
||||||
|
Assert.IsGreaterThan(0, ekaerHistory.Id, $"shippingDocument.Id: {shippingDocument.Id}");
|
||||||
|
Assert.AreEqual(shippingDocument.Id, ekaerHistory.ForeignKey);
|
||||||
|
Assert.IsFalse(ekaerHistory.IsOutgoing);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Idempotencia: a második hívás a meglévőt adja vissza, nem duplikál.
|
||||||
|
var firstDocumentId = shippingDocuments[0].Id;
|
||||||
|
var again = await _signalRClient.CreateEkaerHistory(firstDocumentId, false);
|
||||||
|
var histories = await _signalRClient.GetEkaerHistoriesByForeignKey(firstDocumentId);
|
||||||
|
|
||||||
|
Assert.IsNotNull(again);
|
||||||
|
Assert.IsNotNull(histories);
|
||||||
|
Assert.AreEqual(1, histories.Count(h => !h.IsOutgoing), $"Duplikált bejövő EkaerHistory; shippingDocumentId: {firstDocumentId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion EkaerHistory / Create
|
||||||
|
|
||||||
|
#region EkaerHistory / Generate
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Backfill + teljes Generate-út teszt: minden meglévő szállítólevélre legenerálja az EKÁER XML-t
|
||||||
|
/// (rekord upsert a szerveren), így a grid valós adatot kap és a Generate gomb útvonala tesztelt.
|
||||||
|
/// </summary>
|
||||||
|
//[TestMethod] //Kikommentezve: a generálás a grid Generate gombjával, kézzel történik — a teszt később még kelleni fog.
|
||||||
|
public async Task GenerateEkaerXmlDocumentForAllShippingDocumentsTest()
|
||||||
|
{
|
||||||
|
var shippingDocuments = await _signalRClient.GetShippingDocuments();
|
||||||
|
|
||||||
|
Assert.IsNotNull(shippingDocuments);
|
||||||
|
Assert.IsNotEmpty(shippingDocuments);
|
||||||
|
|
||||||
|
foreach (var shippingDocument in shippingDocuments)
|
||||||
|
{
|
||||||
|
var ekaerHistory = await _signalRClient.GenerateEkaerXmlDocument(shippingDocument.Id, false);
|
||||||
|
|
||||||
|
// A szerver által visszaadott állapot/hibalista logolása — az assertek ELŐTT, hogy hibánál is látsszon.
|
||||||
|
Console.WriteLine($"doc#{shippingDocument.Id}: Status: {(ekaerHistory == null ? "NULL VÁLASZ!" : ekaerHistory.Status.ToString())}");
|
||||||
|
if (!string.IsNullOrWhiteSpace(ekaerHistory?.ErrorText)) Console.WriteLine($" ErrorText: {ekaerHistory.ErrorText}");
|
||||||
|
|
||||||
|
Assert.IsNotNull(ekaerHistory, $"shippingDocument.Id: {shippingDocument.Id}");
|
||||||
|
Assert.AreEqual(shippingDocument.Id, ekaerHistory.ForeignKey);
|
||||||
|
Assert.IsFalse(ekaerHistory.IsOutgoing);
|
||||||
|
Assert.IsFalse(string.IsNullOrWhiteSpace(ekaerHistory.XmlDoc), $"XmlDoc üres; shippingDocument.Id: {shippingDocument.Id}");
|
||||||
|
Assert.IsTrue(ekaerHistory.Status is EkaerStatus.Generated or EkaerStatus.ValidationError,
|
||||||
|
$"Status: {ekaerHistory.Status}; shippingDocument.Id: {shippingDocument.Id}; ErrorText: {ekaerHistory.ErrorText}");
|
||||||
|
|
||||||
|
// A grid útvonala: az XmlDoc visszaolvasható tradeCard-dá.
|
||||||
|
var tradeCard = NavXmlHelper.Deserialize<TradeCardType>(ekaerHistory.XmlDoc!);
|
||||||
|
|
||||||
|
Assert.IsNotNull(tradeCard);
|
||||||
|
Console.WriteLine($" items: {tradeCard.Items.Count}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Idempotencia: az újragenerálás NEM duplikál — dokumentumonként egy bejövő rekord marad.</summary>
|
||||||
|
//[TestMethod] //Kikommentezve: a generálás a grid Generate gombjával, kézzel történik — a teszt később még kelleni fog.
|
||||||
|
public async Task GenerateEkaerXmlDocumentIsIdempotentTest()
|
||||||
|
{
|
||||||
|
var shippingDocuments = await _signalRClient.GetShippingDocuments();
|
||||||
|
|
||||||
|
Assert.IsNotNull(shippingDocuments);
|
||||||
|
Assert.IsNotEmpty(shippingDocuments);
|
||||||
|
|
||||||
|
var shippingDocumentId = shippingDocuments[0].Id;
|
||||||
|
|
||||||
|
var first = await _signalRClient.GenerateEkaerXmlDocument(shippingDocumentId, false);
|
||||||
|
var second = await _signalRClient.GenerateEkaerXmlDocument(shippingDocumentId, false);
|
||||||
|
|
||||||
|
Assert.IsNotNull(first);
|
||||||
|
Assert.IsNotNull(second);
|
||||||
|
Assert.AreEqual(first.Id, second.Id, "Az újragenerálás új rekordot hozott létre frissítés helyett!");
|
||||||
|
|
||||||
|
var histories = await _signalRClient.GetEkaerHistoriesByForeignKey(shippingDocumentId);
|
||||||
|
|
||||||
|
Assert.IsNotNull(histories);
|
||||||
|
Assert.AreEqual(1, histories.Count(h => !h.IsOutgoing), $"Duplikált bejövő EkaerHistory; shippingDocumentId: {shippingDocumentId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion EkaerHistory / Generate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
using FruitBank.Common;
|
||||||
|
using FruitBank.Common.Entities;
|
||||||
|
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||||
|
|
||||||
|
// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Shared.Tests
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class FruitBankPartnerTests
|
||||||
|
{
|
||||||
|
private const int CustomerIdAasdDsserverCom = 6;//aasd@dsserver.com
|
||||||
|
private const string Fixture = "_test.temp";
|
||||||
|
|
||||||
|
private FruitBankSignalRClient _signalRClient = null!;
|
||||||
|
|
||||||
|
private static string GetFixtureName(string name) => $"{GetOriginalName(name)}{Fixture}";
|
||||||
|
private static string GetOriginalName(string name) => name.Replace(Fixture, string.Empty);
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void TestInit()
|
||||||
|
{
|
||||||
|
if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
||||||
|
|
||||||
|
_signalRClient = TestSignalRClientFactory.Create(nameof(FruitBankClientTests));
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Partner
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task GetPartnersTest()
|
||||||
|
{
|
||||||
|
var partners = await _signalRClient.GetPartners();
|
||||||
|
|
||||||
|
Assert.IsNotNull(partners);
|
||||||
|
Assert.IsNotEmpty(partners);
|
||||||
|
}
|
||||||
|
|
||||||
|
//[TestMethod]
|
||||||
|
//[DataRow(1)]
|
||||||
|
public async Task<Partner> GetPartnerByIdTest(int partnerId)
|
||||||
|
{
|
||||||
|
var partner = await _signalRClient.GetPartnerById(partnerId);
|
||||||
|
|
||||||
|
Assert.IsNotNull(partner);
|
||||||
|
Assert.AreEqual(partnerId, partner.Id);
|
||||||
|
|
||||||
|
return partner;
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[DataRow(2)]
|
||||||
|
public async Task UpdatePartnerTest(int partnerId)
|
||||||
|
{
|
||||||
|
var partner = await GetPartnerByIdTest(partnerId);
|
||||||
|
|
||||||
|
var newName = GetFixtureName(partner.Name);
|
||||||
|
|
||||||
|
partner.Name = newName;
|
||||||
|
partner = await _signalRClient.UpdatePartner(partner);
|
||||||
|
|
||||||
|
Assert.IsNotNull(partner);
|
||||||
|
Assert.AreEqual(newName, partner.Name);
|
||||||
|
|
||||||
|
partner.Name = GetOriginalName(partner.Name);
|
||||||
|
partner = await _signalRClient.UpdatePartner(partner);
|
||||||
|
|
||||||
|
Assert.IsNotNull(partner);
|
||||||
|
Assert.AreEqual(partnerId, partner.Id);
|
||||||
|
}
|
||||||
|
#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.AreEqual(newName, cargoPartner.Name);
|
||||||
|
|
||||||
|
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.AreEqual(newLicencePlate, cargoTruck.LicencePlate);
|
||||||
|
|
||||||
|
cargoTruck.LicencePlate = GetOriginalName(cargoTruck.LicencePlate);
|
||||||
|
cargoTruck = await _signalRClient.UpdateCargoTruck(cargoTruck);
|
||||||
|
|
||||||
|
Assert.IsNotNull(cargoTruck);
|
||||||
|
Assert.AreEqual(cargoTruckId, cargoTruck.Id);
|
||||||
|
}
|
||||||
|
#endregion CargoTruck
|
||||||
|
|
||||||
|
#region PartnerDepot
|
||||||
|
[TestMethod]
|
||||||
|
public async Task GetPartnerDepotsTest()
|
||||||
|
{
|
||||||
|
var partnerDepots = await _signalRClient.GetPartnerDepots();
|
||||||
|
|
||||||
|
Assert.IsNotNull(partnerDepots);
|
||||||
|
Assert.IsNotEmpty(partnerDepots);
|
||||||
|
}
|
||||||
|
|
||||||
|
//[TestMethod]
|
||||||
|
//[DataRow(1)]
|
||||||
|
public async Task<PartnerDepot> GetPartnerDepotByIdTest(int partnerDepotId)
|
||||||
|
{
|
||||||
|
var partnerDepot = await _signalRClient.GetPartnerDepotById(partnerDepotId);
|
||||||
|
|
||||||
|
Assert.IsNotNull(partnerDepot);
|
||||||
|
Assert.AreEqual(partnerDepotId, partnerDepot.Id);
|
||||||
|
|
||||||
|
return partnerDepot;
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[DataRow(1)]
|
||||||
|
public async Task GetPartnerDepotsByPartnerIdTest(int partnerId)
|
||||||
|
{
|
||||||
|
var partnerDepots = await _signalRClient.GetPartnerDepotsByPartnerId(partnerId);
|
||||||
|
|
||||||
|
Assert.IsNotNull(partnerDepots);
|
||||||
|
Assert.IsNotEmpty(partnerDepots);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[DataRow(1)]
|
||||||
|
public async Task UpdatePartnerDepotTest(int partnerDepotId)
|
||||||
|
{
|
||||||
|
var partnerDepot = await GetPartnerDepotByIdTest(partnerDepotId);
|
||||||
|
|
||||||
|
var newName = GetFixtureName(partnerDepot.Name);
|
||||||
|
|
||||||
|
partnerDepot.Name = newName;
|
||||||
|
partnerDepot = await _signalRClient.UpdatePartnerDepot(partnerDepot);
|
||||||
|
|
||||||
|
Assert.IsNotNull(partnerDepot);
|
||||||
|
Assert.AreEqual(newName, partnerDepot.Name);
|
||||||
|
|
||||||
|
partnerDepot.Name = GetOriginalName(partnerDepot.Name);
|
||||||
|
partnerDepot = await _signalRClient.UpdatePartnerDepot(partnerDepot);
|
||||||
|
|
||||||
|
Assert.IsNotNull(partnerDepot);
|
||||||
|
Assert.AreEqual(partnerDepotId, partnerDepot.Id);
|
||||||
|
}
|
||||||
|
#endregion PartnerDepot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using FruitBank.Common.SignalRs;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Shared.Tests
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class SignalRTagsTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="SignalRTags"/> értékei a drót-protokoll azonosítói: két azonos értékű
|
||||||
|
/// konstans (jellemzően copy-paste után) némán másik endpointra irányítaná a hívást.
|
||||||
|
/// A kézi számkiosztás szándékos (stabil szerződés), ez a teszt csak alá feszít egy hálót:
|
||||||
|
/// kibukik a duplikátumon, és megnevezi a vétkes konstansokat.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Csak a <see cref="SignalRTags"/>-ben DEKLARÁLT konstansokat vizsgálja (<c>DeclaredOnly</c>) —
|
||||||
|
/// ide ír a fejlesztő kézzel. A framework-ős <c>AcSignalRTags</c>-szal való átfedés
|
||||||
|
/// ellenőrzéséhez <c>BindingFlags.FlattenHierarchy</c> kellene.
|
||||||
|
/// </remarks>
|
||||||
|
[TestMethod]
|
||||||
|
public void SignalRTags_HasNoDuplicateValues()
|
||||||
|
{
|
||||||
|
var tags = typeof(SignalRTags)
|
||||||
|
.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
|
||||||
|
.Where(f => f.IsLiteral && f.FieldType == typeof(int))
|
||||||
|
.Select(f => (f.Name, Value: (int)f.GetRawConstantValue()!))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var duplicates = tags
|
||||||
|
.GroupBy(t => t.Value)
|
||||||
|
.Where(g => g.Count() > 1)
|
||||||
|
.Select(g => $" {g.Key} = {string.Join(", ", g.Select(t => t.Name))}")
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Assert.AreEqual(0, duplicates.Count, $"Több SignalRTag ugyanazt az értéket kapta:{Environment.NewLine}{string.Join(Environment.NewLine, duplicates)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
@using AyCode.Blazor.Components.Components.Grids
|
@using AyCode.Blazor.Components.Components.Grids
|
||||||
@using AyCode.Utils.Extensions
|
@using AyCode.Utils.Extensions
|
||||||
@using FruitBank.Common.Dtos
|
@using FruitBank.Common.Dtos
|
||||||
@using FruitBank.Common.Entities
|
@using FruitBank.Common.Entities
|
||||||
|
|
@ -26,24 +26,65 @@
|
||||||
<Columns>
|
<Columns>
|
||||||
<MgGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true"/>
|
<MgGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true"/>
|
||||||
<DxGridDataColumn FieldName="PartnerId" Caption="Partner" Visible="@(!ParentDataItemIsPartner)" ReadOnly="@ParentDataItemIsPartner">
|
<DxGridDataColumn FieldName="PartnerId" Caption="Partner" Visible="@(!ParentDataItemIsPartner)" ReadOnly="@ParentDataItemIsPartner">
|
||||||
<EditSettings>
|
<CellDisplayTemplate>
|
||||||
<DxComboBoxSettings Data="Partners"
|
@{
|
||||||
ValueFieldName="Id"
|
var sdPd = (ShippingDocument)context.DataItem;
|
||||||
TextFieldName="Name"
|
<text>@(PartnersDictById.GetValueOrDefault(sdPd.PartnerId)?.Name)</text>
|
||||||
DropDownBodyCssClass="dd-body-class"
|
}
|
||||||
ListRenderMode="ListRenderMode.Entire"
|
</CellDisplayTemplate>
|
||||||
SearchMode="ListSearchMode.AutoSearch"
|
<CellEditTemplate>
|
||||||
SearchFilterCondition="ListSearchFilterCondition.Contains"
|
@{
|
||||||
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto">
|
var sdPe = (ShippingDocument)context.EditModel;
|
||||||
<Columns>
|
}
|
||||||
<DxListEditorColumn FieldName="@nameof(Partner.Id)" />
|
<DxComboBox TData="Partner" TValue="int"
|
||||||
<DxListEditorColumn FieldName="@nameof(Partner.Name)" />
|
Data="@Partners"
|
||||||
<DxListEditorColumn FieldName="@nameof(Partner.TaxId)" />
|
Value="@sdPe.PartnerId"
|
||||||
</Columns>
|
ValueChanged="@((int v) => OnPartnerChanged(sdPe, v))"
|
||||||
</DxComboBoxSettings>
|
ValueExpression="@(() => sdPe.PartnerId)"
|
||||||
</EditSettings>
|
ValueFieldName="@nameof(Partner.Id)"
|
||||||
|
TextFieldName="@nameof(Partner.Name)"
|
||||||
|
DropDownBodyCssClass="dd-body-class"
|
||||||
|
ListRenderMode="ListRenderMode.Entire"
|
||||||
|
SearchMode="ListSearchMode.AutoSearch"
|
||||||
|
SearchFilterCondition="ListSearchFilterCondition.Contains"
|
||||||
|
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto">
|
||||||
|
<Columns>
|
||||||
|
<DxListEditorColumn FieldName="@nameof(Partner.Id)" />
|
||||||
|
<DxListEditorColumn FieldName="@nameof(Partner.Name)" />
|
||||||
|
<DxListEditorColumn FieldName="@nameof(Partner.TaxId)" />
|
||||||
|
</Columns>
|
||||||
|
</DxComboBox>
|
||||||
|
</CellEditTemplate>
|
||||||
</DxGridDataColumn>
|
</DxGridDataColumn>
|
||||||
<DxGridDataColumn FieldName="Shipping.ShippingDate" Caption="Beérkezés" ReadOnly="true" />
|
<DxGridDataColumn FieldName="PartnerDepotId" Caption="Telephely">
|
||||||
|
<CellDisplayTemplate>
|
||||||
|
@{
|
||||||
|
var sdD = (ShippingDocument)context.DataItem;
|
||||||
|
var depotD = PartnersDictById.GetValueOrDefault(sdD.PartnerId)?.PartnerDepots?.FirstOrDefault(d => d.Id == sdD.PartnerDepotId);
|
||||||
|
|
||||||
|
<div style="text-align:left">@depotD?.Name</div>
|
||||||
|
}
|
||||||
|
</CellDisplayTemplate>
|
||||||
|
<CellEditTemplate>
|
||||||
|
@{
|
||||||
|
var sdE = (ShippingDocument)context.EditModel;
|
||||||
|
var depotsE = PartnersDictById.GetValueOrDefault(sdE.PartnerId)?.PartnerDepots ?? new List<PartnerDepot>();
|
||||||
|
}
|
||||||
|
<DxComboBox TData="PartnerDepot" TValue="int?"
|
||||||
|
Data="@depotsE"
|
||||||
|
@bind-Value="sdE.PartnerDepotId"
|
||||||
|
ValueFieldName="@nameof(PartnerDepot.Id)"
|
||||||
|
TextFieldName="@nameof(PartnerDepot.Name)"
|
||||||
|
NullText="(telephely…)"
|
||||||
|
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto">
|
||||||
|
<Columns>
|
||||||
|
<DxListEditorColumn FieldName="@nameof(PartnerDepot.Name)" Caption="Név" />
|
||||||
|
<DxListEditorColumn FieldName="@nameof(PartnerDepot.FullAddress)" Caption="Cím" />
|
||||||
|
</Columns>
|
||||||
|
</DxComboBox>
|
||||||
|
</CellEditTemplate>
|
||||||
|
</DxGridDataColumn>
|
||||||
|
<DxGridDataColumn FieldName="Shipping.ShippingDate" Caption="Beérkezés" ReadOnly="true" />
|
||||||
<DxGridDataColumn FieldName="ShippingId" Caption="Shipping" Visible="@(!ParentDataItemIsShipping)" ReadOnly="@ParentDataItemIsShipping">
|
<DxGridDataColumn FieldName="ShippingId" Caption="Shipping" Visible="@(!ParentDataItemIsShipping)" ReadOnly="@ParentDataItemIsShipping">
|
||||||
<EditSettings>
|
<EditSettings>
|
||||||
<DxComboBoxSettings Data="Shippings"
|
<DxComboBoxSettings Data="Shippings"
|
||||||
|
|
@ -185,7 +226,10 @@
|
||||||
private LoggerClient<GridShippingDocument> _logger = null!;
|
private LoggerClient<GridShippingDocument> _logger = null!;
|
||||||
private int _activeTabIndex;
|
private int _activeTabIndex;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
// Partner-lookup ID szerint a cella-templatekhez (a ReloadDataFromDb tölti) — renderenkénti lineáris keresés helyett.
|
||||||
|
private Dictionary<int, Partner> PartnersDictById = new();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_logger = new LoggerClient<GridShippingDocument>(LogWriters.ToArray());
|
_logger = new LoggerClient<GridShippingDocument>(LogWriters.ToArray());
|
||||||
|
|
||||||
|
|
@ -223,9 +267,13 @@
|
||||||
{
|
{
|
||||||
if (Shippings == null && ParentDataItem is Shipping shippingParent) Shippings = [shippingParent];
|
if (Shippings == null && ParentDataItem is Shipping shippingParent) Shippings = [shippingParent];
|
||||||
if (Partners == null && ParentDataItem is Partner partnerParent) Partners = [partnerParent];
|
if (Partners == null && ParentDataItem is Partner partnerParent) Partners = [partnerParent];
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Partner-lookup ID szerint a cella-templatekhez (O(1) a renderenkénti FirstOrDefault helyett), a Partners véglegesülése után.
|
||||||
|
PartnersDictById = Partners?.ToDictionary(p => p.Id) ?? new();
|
||||||
|
|
||||||
|
if (!IsMasterGrid) return;
|
||||||
|
|
||||||
if (Grid == null) return;
|
if (Grid == null) return;
|
||||||
|
|
||||||
using (await ObjectLock.GetSemaphore<ShippingDocument>().UseWaitAsync())
|
using (await ObjectLock.GetSemaphore<ShippingDocument>().UseWaitAsync())
|
||||||
|
|
@ -269,6 +317,16 @@
|
||||||
EditItemsEnabled = true;
|
EditItemsEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cascade: a Partner-combo ValueChanged-je. Beírjuk az új partnert; ha a partnernek PONTOSAN EGY telephelye van,
|
||||||
|
// arra auto-állunk, különben nullázzuk (a régi nem az új partneré); majd újrarenderelünk → a Telephely-combo frissül.
|
||||||
|
void OnPartnerChanged(ShippingDocument sd, int partnerId)
|
||||||
|
{
|
||||||
|
sd.PartnerId = partnerId;
|
||||||
|
var depots = PartnersDictById.GetValueOrDefault(partnerId)?.PartnerDepots;
|
||||||
|
sd.PartnerDepotId = depots?.Count == 1 ? depots[0].Id : null;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
protected async Task OnActiveTabChanged(int activeTabIndex)
|
protected async Task OnActiveTabChanged(int activeTabIndex)
|
||||||
{
|
{
|
||||||
_activeTabIndex = activeTabIndex;
|
_activeTabIndex = activeTabIndex;
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@
|
||||||
<DxGridDataColumn FieldName="City" />
|
<DxGridDataColumn FieldName="City" />
|
||||||
<DxGridDataColumn FieldName="Street" />
|
<DxGridDataColumn FieldName="Street" />
|
||||||
|
|
||||||
<DxGridDataColumn FieldName="Created" ReadOnly="true" />
|
<DxGridDataColumn FieldName="Created" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
|
||||||
<DxGridDataColumn FieldName="Modified" ReadOnly="true" />
|
<DxGridDataColumn FieldName="Modified" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
|
||||||
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
|
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
|
||||||
</Columns>
|
</Columns>
|
||||||
<DetailRowTemplate>
|
<DetailRowTemplate>
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@
|
||||||
<DxGridDataColumn FieldName="@nameof(CargoTruck.CountryCode)" />
|
<DxGridDataColumn FieldName="@nameof(CargoTruck.CountryCode)" />
|
||||||
<DxGridDataColumn FieldName="@nameof(CargoTruck.IsTrailer)" />
|
<DxGridDataColumn FieldName="@nameof(CargoTruck.IsTrailer)" />
|
||||||
|
|
||||||
<DxGridDataColumn FieldName="Created" ReadOnly="true" />
|
<DxGridDataColumn FieldName="Created" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
|
||||||
<DxGridDataColumn FieldName="Modified" ReadOnly="true" />
|
<DxGridDataColumn FieldName="Modified" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
|
||||||
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
|
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
|
||||||
</Columns>
|
</Columns>
|
||||||
<ToolbarTemplate>
|
<ToolbarTemplate>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
@using AyCode.Blazor.Components.Components.Grids
|
||||||
|
@using AyCode.Core.Helpers
|
||||||
|
@using AyCode.Core.Interfaces
|
||||||
|
@using AyCode.Core.Loggers
|
||||||
|
@using AyCode.Services.Nav.Ekaer.Models
|
||||||
|
@using FruitBank.Common.Entities
|
||||||
|
@using FruitBankHybrid.Shared.Services.Loggers
|
||||||
|
@using FruitBankHybrid.Shared.Services.SignalRs
|
||||||
|
|
||||||
|
@inject IEnumerable<IAcLogWriterClientBase> LogWriters
|
||||||
|
@inject FruitBankSignalRClient FruitBankSignalRClient
|
||||||
|
|
||||||
|
@if (TradeCard != null)
|
||||||
|
{
|
||||||
|
@* Fejléc: csak a user-nek fontos, a mapper által ténylegesen töltött mezők. *@
|
||||||
|
<div style="display:grid; grid-template-columns:auto 1fr auto 1fr; gap:2px 12px; padding:8px 12px; font-size:0.85rem;">
|
||||||
|
<b>Irány:</b><span>@TradeCard.TradeType</span>
|
||||||
|
<b>Fuvarozó:</b><span>@TradeCard.CarrierText</span>
|
||||||
|
|
||||||
|
<b>Eladó:</b><span>@FormatParty(TradeCard.SellerName, TradeCard.SellerVatNumber, TradeCard.SellerAddress)</span>
|
||||||
|
<b>Címzett:</b><span>@FormatParty(TradeCard.DestinationName, TradeCard.DestinationVatNumber, TradeCard.DestinationAddress)</span>
|
||||||
|
|
||||||
|
<b>Felrakodás:</b><span>@FormatLocation(TradeCard.LoadLocation)</span>
|
||||||
|
<b>Lerakodás:</b><span>@FormatLocation(TradeCard.UnloadLocation)</span>
|
||||||
|
|
||||||
|
<b>Vontató:</b><span>@TradeCard.Vehicle?.PlateNumber</span>
|
||||||
|
<b>Pótkocsi:</b><span>@TradeCard.Vehicle2?.PlateNumber</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(ErrorText))
|
||||||
|
{
|
||||||
|
@* A generálás/küldés üzenetei SORONKÉNT, a saját súlyosságukkal színezve: error → piros, warning → arany/sárga. *@
|
||||||
|
<div style="padding:0 12px 8px 12px; font-size:0.85rem;">
|
||||||
|
@foreach (var line in ErrorMessageLines)
|
||||||
|
{
|
||||||
|
<div style="color:@(line.IsError ? "#dc3545" : "#caa000");">@line.Text</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<MgGridWithInfoPanel ShowInfoPanel="false">
|
||||||
|
<GridContent>
|
||||||
|
<GridEkaerDetailBase @ref="Grid"
|
||||||
|
DataSource="TradeCardItems"
|
||||||
|
ParentDataItem="ParentDataItem"
|
||||||
|
SignalRClient="FruitBankSignalRClient"
|
||||||
|
AutoSaveLayoutName="GridEkaerDetail"
|
||||||
|
Logger="_logger"
|
||||||
|
CssClass="@GridCss"
|
||||||
|
ValidationEnabled="false">
|
||||||
|
<Columns>
|
||||||
|
<DxGridDataColumn FieldName="@nameof(TradeCardItemType.ItemExternalId)" Caption="Tétel az." ReadOnly="true" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(TradeCardItemType.TradeReason)" Caption="Jogcím" ReadOnly="true" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(TradeCardItemType.ProductVtsz)" Caption="VTSZ" ReadOnly="true" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(TradeCardItemType.ProductName)" Caption="Megnevezés" ReadOnly="true" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(TradeCardItemType.Weight)" Caption="Bruttó tömeg (kg)" ReadOnly="true" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(TradeCardItemType.Value)" Caption="Érték (HUF)" ReadOnly="true" />
|
||||||
|
</Columns>
|
||||||
|
</GridEkaerDetailBase>
|
||||||
|
</GridContent>
|
||||||
|
</MgGridWithInfoPanel>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public TradeCardType? TradeCard { get; set; }
|
||||||
|
[Parameter] public IId<int>? ParentDataItem { get; set; }
|
||||||
|
|
||||||
|
public GridEkaerDetailBase Grid { get; set; }
|
||||||
|
|
||||||
|
public bool IsMasterGrid => ParentDataItem == null;
|
||||||
|
string GridCss => !IsMasterGrid ? "hide-toolbar" : string.Empty;
|
||||||
|
|
||||||
|
private string? ErrorText => (ParentDataItem as EkaerHistory)?.ErrorText;
|
||||||
|
|
||||||
|
// Soronkénti üzenet + súlyosság az [Error]/[Warning] prefixből (a service így fűzi). Prefix nélküli (pl. config-hiba) → error.
|
||||||
|
private IEnumerable<(bool IsError, string Text)> ErrorMessageLines
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(ErrorText)) yield break;
|
||||||
|
|
||||||
|
foreach (var raw in ErrorText.Split('\n'))
|
||||||
|
{
|
||||||
|
var line = raw.Trim();
|
||||||
|
if (line.Length == 0) continue;
|
||||||
|
|
||||||
|
if (line.StartsWith("[Warning]")) yield return (false, line["[Warning]".Length..].TrimStart());
|
||||||
|
else if (line.StartsWith("[Error]")) yield return (true, line["[Error]".Length..].TrimStart());
|
||||||
|
else yield return (true, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AcObservableCollection<TradeCardItemType> TradeCardItems = [];
|
||||||
|
|
||||||
|
private LoggerClient<GridEkaerDetail> _logger;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_logger = new LoggerClient<GridEkaerDetail>(LogWriters.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
TradeCardItems = TradeCard?.Items is { } items ? new AcObservableCollection<TradeCardItemType>(items) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatParty(string? name, string? vatNumber, string? address)
|
||||||
|
{
|
||||||
|
var party = string.IsNullOrWhiteSpace(vatNumber) ? name : $"{name} ({vatNumber})";
|
||||||
|
return string.IsNullOrWhiteSpace(address) ? party ?? string.Empty : $"{party} — {address}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatLocation(LocationType? location)
|
||||||
|
{
|
||||||
|
if (location == null) return string.Empty;
|
||||||
|
return string.Join(" ", new[] { location.ZipCode, location.City, location.Street }.Where(p => !string.IsNullOrWhiteSpace(p)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
using AyCode.Services.Nav.Ekaer.Models;
|
||||||
|
using DevExpress.Blazor;
|
||||||
|
using FruitBankHybrid.Shared.Pages;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Shared.Components.Grids.Ekaers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Readonly tétel-grid az EKÁER detail row-hoz: a generált tradeCard tételeit (<see cref="TradeCardItemType"/>)
|
||||||
|
/// mutatja, az <c>EkaerHistory.XmlDoc</c>-ból lokálisan deszerializálva. SZÁNDÉKOSAN nincs Get/Add/Update/Remove
|
||||||
|
/// tag — tag nélkül a grid readonly, a DataSource pedig lokális (nem SignalR-ből töltött).
|
||||||
|
/// A <see cref="TradeCardItemType"/> az IId<int>-et explicit partial-lal teljesíti (Id = ItemExternalId-ból számítva).
|
||||||
|
/// </summary>
|
||||||
|
public class GridEkaerDetailBase : FruitBankGridBase<TradeCardItemType>, IGrid
|
||||||
|
{
|
||||||
|
public GridEkaerDetailBase() : base()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,294 @@
|
||||||
|
@using AyCode.Blazor.Components.Components.Grids
|
||||||
|
@using AyCode.Core.Helpers
|
||||||
|
@using AyCode.Core.Interfaces
|
||||||
|
@using AyCode.Core.Loggers
|
||||||
|
@using AyCode.Services.Nav
|
||||||
|
@using AyCode.Services.Nav.Ekaer.Models
|
||||||
|
@using AyCode.Utils.Extensions
|
||||||
|
@using FruitBank.Common.Dtos
|
||||||
|
@using FruitBank.Common.Entities
|
||||||
|
@using FruitBankHybrid.Shared.Databases
|
||||||
|
@using FruitBankHybrid.Shared.Extensions
|
||||||
|
@using FruitBankHybrid.Shared.Services.Loggers
|
||||||
|
@using FruitBankHybrid.Shared.Services.SignalRs
|
||||||
|
|
||||||
|
@inject IEnumerable<IAcLogWriterClientBase> LogWriters
|
||||||
|
@inject FruitBankSignalRClient FruitBankSignalRClient
|
||||||
|
@inject IJSRuntime JS
|
||||||
|
@inject IDialogService DialogService
|
||||||
|
|
||||||
|
<MgGridWithInfoPanel ShowInfoPanel="@IsMasterGrid">
|
||||||
|
<GridContent>
|
||||||
|
<GridEkaerHistoryBase @ref="Grid"
|
||||||
|
DataSource="EkaerHistories"
|
||||||
|
ParentDataItem="ParentDataItem"
|
||||||
|
Filter="Filter"
|
||||||
|
AutoSaveLayoutName="GridEkaerHistory"
|
||||||
|
SignalRClient="FruitBankSignalRClient"
|
||||||
|
Logger="_logger"
|
||||||
|
CssClass="@GridCss"
|
||||||
|
ValidationEnabled="false"
|
||||||
|
OnGridFocusedRowChanged="Grid_FocusedRowChanged">
|
||||||
|
<Columns>
|
||||||
|
<DxGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(EkaerHistory.ForeignKey)" ReadOnly="true" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(EkaerHistory.IsOutgoing)" ReadOnly="true" />
|
||||||
|
@* A kézi NAV-beadás fázisában a Status / EKÁER szám / SentDate kézzel szerkeszthető. *@
|
||||||
|
<DxGridDataColumn FieldName="@nameof(EkaerHistory.Status)" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(EkaerHistory.EkaerNumber)" Caption="EKÁER szám" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(EkaerHistory.SentDate)" DisplayFormat="yyyy.MM.dd HH:mm" />
|
||||||
|
@* Audit: a value-számításhoz alkalmazott árfolyam (HUF feladónál 1). ReadOnly — generáláskor töltődik. *@
|
||||||
|
<DxGridDataColumn FieldName="@nameof(EkaerHistory.ConversionRate)" Caption="Árfolyam" ReadOnly="true" DisplayFormat="0.00##" />
|
||||||
|
@* Üzenet-oszlop: warningot ÉS errort is mutat. Ikon a súlyosság szerint: ⛔ blokkoló hiba, ⚠️ warning (pótlandó). *@
|
||||||
|
<DxGridDataColumn FieldName="@nameof(EkaerHistory.ErrorText)" Caption="Üzenet" ReadOnly="true">
|
||||||
|
<CellDisplayTemplate>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(context.DisplayText))
|
||||||
|
{
|
||||||
|
var isError = ((EkaerHistory)context.DataItem).Status.IsError();
|
||||||
|
<span title="@context.DisplayText">
|
||||||
|
<span aria-hidden="true" style="font-size:1.2em; margin-right:3px;">@(isError ? "⛔" : "⚠️")</span>@context.DisplayText
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</CellDisplayTemplate>
|
||||||
|
</DxGridDataColumn>
|
||||||
|
|
||||||
|
<DxGridDataColumn FieldName="Created" ReadOnly="true" DisplayFormat="yyyy.MM.dd HH:mm" />
|
||||||
|
<DxGridDataColumn FieldName="Modified" ReadOnly="true" DisplayFormat="yyyy.MM.dd HH:mm" />
|
||||||
|
<DxGridDataColumn Caption="Műveletek" Width="200" AllowSort="false" AllowGroup="false">
|
||||||
|
<CellDisplayTemplate>
|
||||||
|
@{
|
||||||
|
var row = (EkaerHistory)context.DataItem;
|
||||||
|
<DxButton Text="Generate"
|
||||||
|
SizeMode="SizeMode.Small"
|
||||||
|
RenderStyle="ButtonRenderStyle.Primary"
|
||||||
|
Enabled="@(CanGenerate(row))"
|
||||||
|
Attributes="@(new Dictionary<string, object> { ["title"] = row.ErrorText ?? "EKÁER XML generálása és validálása" })"
|
||||||
|
Click="async () => await OnGenerateClick(row)" />
|
||||||
|
<DxButton Text="Copy"
|
||||||
|
SizeMode="SizeMode.Small"
|
||||||
|
RenderStyle="ButtonRenderStyle.Secondary"
|
||||||
|
Enabled="@(CanCopy(row))"
|
||||||
|
Attributes="@(new Dictionary<string, object> { ["title"] = CanCopy(row) ? "A generált XML vágólapra másolása (kézi NAV-beadáshoz)" : "Hibás bejelentés nem másolható — javítsd az adatokat és generáld újra." })"
|
||||||
|
Click="async () => await OnCopyClick(row)" />
|
||||||
|
}
|
||||||
|
</CellDisplayTemplate>
|
||||||
|
</DxGridDataColumn>
|
||||||
|
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
|
||||||
|
</Columns>
|
||||||
|
<DetailRowTemplate>
|
||||||
|
@{
|
||||||
|
var ekaerHistory = (EkaerHistory)context.DataItem;
|
||||||
|
TradeCardType? tradeCard = null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(ekaerHistory.XmlDoc))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tradeCard = NavXmlHelper.Deserialize<TradeCardType>(ekaerHistory.XmlDoc);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error($"EkaerHistory XmlDoc deserialize error; id: {ekaerHistory.Id}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<GridEkaerDetail TradeCard="@tradeCard" ParentDataItem="@ekaerHistory"></GridEkaerDetail>
|
||||||
|
}
|
||||||
|
</DetailRowTemplate>
|
||||||
|
<ToolbarTemplate>
|
||||||
|
@if (IsMasterGrid)
|
||||||
|
{
|
||||||
|
@* EKÁER-rekord a toolbar "sorok létrehozása" gombbal készül (new/delete tiltva — nincs automata
|
||||||
|
érzékelés, a létrehozás explicit és idempotens). Az Edit a kézi NAV-beadás fázisában
|
||||||
|
engedélyezett: Status / EKÁER szám / SentDate kézi rögzítéséhez. *@
|
||||||
|
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" EnableNew="false" EnableEdit="true" EnableDelete="false">
|
||||||
|
<ToolbarItemsExtended>
|
||||||
|
@* A dátumválasztó editor csak Template-ben mehet toolbarba; a gomb natív DxToolbarItem,
|
||||||
|
hogy a toolbar stílusát/igazítását kapja (lásd: "Ai process..." minta a GridShippingDocument-ben). *@
|
||||||
|
@* dxbl-toolbar-edit: a DevExpress téma saját opt-in osztálya editor-t hordozó toolbar-itemre —
|
||||||
|
ettől kapja a beépített középre igazítást és a toolbarba simuló editor-stílust. *@
|
||||||
|
<DxToolbarItem BeginGroup="true" CssClass="dxbl-toolbar-edit ekaer-create-from-date-item">
|
||||||
|
<Template Context="toolbarItemContext">
|
||||||
|
<DxDateEdit @bind-Date="CreateFromDate"
|
||||||
|
Format="yyyy.MM.dd"
|
||||||
|
MinDate="@DateTime.Today.AddDays(-7)"
|
||||||
|
MaxDate="@DateTime.Today"
|
||||||
|
CssClass="ekaer-create-from-date" />
|
||||||
|
</Template>
|
||||||
|
</DxToolbarItem>
|
||||||
|
<DxToolbarItem Text="EKÁER sorok létrehozása"
|
||||||
|
Enabled="@(!_creatingMissing)"
|
||||||
|
Tooltip="A választott dátumtól kezdődő, rekord nélküli szállítólevelekre/rendelésekre Pending sort hoz létre (idempotens)."
|
||||||
|
Click="OnCreateMissingClick">
|
||||||
|
</DxToolbarItem>
|
||||||
|
</ToolbarItemsExtended>
|
||||||
|
</MgGridToolbarTemplate>
|
||||||
|
}
|
||||||
|
</ToolbarTemplate>
|
||||||
|
</GridEkaerHistoryBase>
|
||||||
|
</GridContent>
|
||||||
|
</MgGridWithInfoPanel>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Inject] public required DatabaseClient Database { get; set; }
|
||||||
|
|
||||||
|
[Parameter] public AcObservableCollection<EkaerHistory>? EkaerHistories { get; set; }
|
||||||
|
|
||||||
|
const string ExportFileName = "ExportResult";
|
||||||
|
string GridSearchText = "";
|
||||||
|
bool EditItemsEnabled { get; set; }
|
||||||
|
int FocusedRowVisibleIndex { get; set; }
|
||||||
|
public GridEkaerHistoryBase Grid { get; set; }
|
||||||
|
string GridCss => !IsMasterGrid ? "hide-toolbar" : string.Empty;
|
||||||
|
|
||||||
|
[Parameter] public IId<int>? ParentDataItem { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Szerver-oldali szűrő (a tabok adják): ToSubmit / Sent / NeedsCompletion. Default All = minden.</summary>
|
||||||
|
[Parameter] public EkaerHistoryFilter Filter { get; set; } = EkaerHistoryFilter.All;
|
||||||
|
|
||||||
|
/// <summary>A szülő (oldal) értesítése, ha egy művelet státuszt válthatott (generálás / kézi mentés / jövőbeni beküldés) —
|
||||||
|
/// hogy a fül-számlálók azonnal frissüljenek, mert a sor átkerülhet másik tabra.</summary>
|
||||||
|
[Parameter] public EventCallback OnDataChanged { get; set; }
|
||||||
|
|
||||||
|
/// <summary>A szülő (oldal) értesítése a hosszú művelet (sorok létrehozása) elejéről (true) / végéről (false) —
|
||||||
|
/// hogy az oldal Loading panelje megjelenjen/eltűnjön.</summary>
|
||||||
|
[Parameter] public EventCallback<bool> OnBusyChanged { get; set; }
|
||||||
|
|
||||||
|
public bool IsMasterGrid => ParentDataItem == null;
|
||||||
|
|
||||||
|
private LoggerClient<GridEkaerHistory> _logger;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_logger = new LoggerClient<GridEkaerHistory>(LogWriters.ToArray());
|
||||||
|
await ReloadDataFromDb(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReloadDataFromDb(bool forceReload = false)
|
||||||
|
{
|
||||||
|
if (!IsMasterGrid) return;
|
||||||
|
|
||||||
|
if (Grid == null) return;
|
||||||
|
|
||||||
|
using (await ObjectLock.GetSemaphore<EkaerHistory>().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();
|
||||||
|
await NotifyDataChanged(); // kézi szerkesztés (pl. Status → SentWithMissingData) → fül-szám frissítése
|
||||||
|
}
|
||||||
|
|
||||||
|
FocusedRowVisibleIndex = args.VisibleIndex;
|
||||||
|
EditItemsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly HashSet<int> _generatingIds = [];
|
||||||
|
|
||||||
|
// Elküldött (NAV-nál lévő) bejelentést nem generálunk újra némán; minden más állapot újragenerálható.
|
||||||
|
private bool CanGenerate(EkaerHistory ekaerHistory)
|
||||||
|
=> !ekaerHistory.Status.IsSent() && !_generatingIds.Contains(ekaerHistory.Id);
|
||||||
|
|
||||||
|
// Csak BLOKKOLÓ hiba (ValidationError) esetén NEM másolható; a warning (pl. hiányzó rendszám) küldhető → másolható.
|
||||||
|
private static bool CanCopy(EkaerHistory ekaerHistory)
|
||||||
|
=> ekaerHistory.Status.IsSubmittable() && !string.IsNullOrWhiteSpace(ekaerHistory.XmlDoc);
|
||||||
|
|
||||||
|
// A szülő-oldal (fül-számlálók) értesítése egy státuszt érintő művelet után. Üres delegáltnál no-op.
|
||||||
|
private Task NotifyDataChanged() => OnDataChanged.HasDelegate ? OnDataChanged.InvokeAsync() : Task.CompletedTask;
|
||||||
|
|
||||||
|
private async Task OnGenerateClick(EkaerHistory ekaerHistory)
|
||||||
|
{
|
||||||
|
if (!_generatingIds.Add(ekaerHistory.Id)) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var updated = await FruitBankSignalRClient.GenerateEkaerXmlDocument(ekaerHistory.ForeignKey, ekaerHistory.IsOutgoing);
|
||||||
|
|
||||||
|
if (updated == null)
|
||||||
|
{
|
||||||
|
_logger.Error($"GenerateEkaerXmlDocument null választ adott; ForeignKey: {ekaerHistory.ForeignKey}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A sor frissítése helyben — a grid ugyanazt a példányt mutatja.
|
||||||
|
ekaerHistory.Status = updated.Status;
|
||||||
|
ekaerHistory.XmlDoc = updated.XmlDoc;
|
||||||
|
ekaerHistory.ConversionRate = updated.ConversionRate;
|
||||||
|
ekaerHistory.ErrorText = updated.ErrorText;
|
||||||
|
ekaerHistory.EkaerNumber = updated.EkaerNumber;
|
||||||
|
ekaerHistory.SentDate = updated.SentDate;
|
||||||
|
ekaerHistory.Modified = updated.Modified;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error($"GenerateEkaerXmlDocument hiba; ForeignKey: {ekaerHistory.ForeignKey}", ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_generatingIds.Remove(ekaerHistory.Id);
|
||||||
|
Grid?.Reload();
|
||||||
|
await NotifyDataChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime CreateFromDate { get; set; } = DateTime.Today.AddDays(-3);
|
||||||
|
private bool _creatingMissing;
|
||||||
|
|
||||||
|
private async Task OnCreateMissingClick()
|
||||||
|
{
|
||||||
|
if (_creatingMissing) return;
|
||||||
|
_creatingMissing = true;
|
||||||
|
await OnBusyChanged.InvokeAsync(true); // Loading panel BE — a művelet lassú lehet (kimenő ág)
|
||||||
|
|
||||||
|
EkaerCreateResult? result = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = await FruitBankSignalRClient.CreateMissingEkaerHistories(CreateFromDate);
|
||||||
|
var createdCount = result?.CreatedCount ?? 0;
|
||||||
|
_logger.Info($"CreateMissingEkaerHistories; created: {createdCount}; messages: {result?.Messages.Count ?? 0}; fromDate: {CreateFromDate:yyyy.MM.dd}");
|
||||||
|
|
||||||
|
foreach (var message in result?.Messages ?? [])
|
||||||
|
_logger.Warning($"CreateMissingEkaerHistories; skipped: {message}");
|
||||||
|
|
||||||
|
if (createdCount > 0)
|
||||||
|
{
|
||||||
|
await ReloadDataFromDb(true);
|
||||||
|
await NotifyDataChanged(); // a fül-számlálók frissítése (új sorok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error($"CreateMissingEkaerHistories hiba; fromDate: {CreateFromDate:yyyy.MM.dd}", ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_creatingMissing = false;
|
||||||
|
await OnBusyChanged.InvokeAsync(false); // Loading panel KI
|
||||||
|
}
|
||||||
|
|
||||||
|
// Az adathiány/formátumhiba miatt KIHAGYOTT jelöltek üzenetei popupban — ezeket javítani kell, mielőtt bekerülhetnek.
|
||||||
|
if (result?.Messages is { Count: > 0 } skipped)
|
||||||
|
await DialogService.ShowMessageBoxAsync("EKÁER — kihagyott tételek", string.Join(Environment.NewLine, skipped), MessageBoxRenderStyle.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnCopyClick(EkaerHistory ekaerHistory)
|
||||||
|
{
|
||||||
|
if (!CanCopy(ekaerHistory)) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await JS.InvokeVoidAsync("navigator.clipboard.writeText", ekaerHistory.XmlDoc);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error($"XmlDoc vágólapra másolása sikertelen; EkaerHistory.Id: {ekaerHistory.Id}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
using AyCode.Utils.Extensions;
|
||||||
|
using DevExpress.Blazor;
|
||||||
|
using FruitBank.Common.Entities;
|
||||||
|
using FruitBank.Common.SignalRs;
|
||||||
|
using FruitBankHybrid.Shared.Pages;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Shared.Components.Grids.Ekaers;
|
||||||
|
|
||||||
|
public class GridEkaerHistoryBase : FruitBankGridBase<EkaerHistory>, IGrid
|
||||||
|
{
|
||||||
|
private bool _isFirstInitializeParameterCore;
|
||||||
|
private bool _isFirstInitializeParameters;
|
||||||
|
|
||||||
|
/// <summary>Szerver-oldali szűrő a master-grid lekérdezéséhez (a tabok adják át). All = minden.</summary>
|
||||||
|
[Parameter] public EkaerHistoryFilter Filter { get; set; } = EkaerHistoryFilter.All;
|
||||||
|
|
||||||
|
public GridEkaerHistoryBase() : base()
|
||||||
|
{
|
||||||
|
//GetAllMessageTag = SignalRTags.GetEkaerHistories;
|
||||||
|
AddMessageTag = SignalRTags.AddEkaerHistory;
|
||||||
|
UpdateMessageTag = SignalRTags.UpdateEkaerHistory;
|
||||||
|
|
||||||
|
//RemoveMessageTag = SignalRTags.;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (GetAllMessageTag > 0) return;
|
||||||
|
|
||||||
|
if (IsMasterGrid)
|
||||||
|
{
|
||||||
|
GetAllMessageTag = SignalRTags.GetEkaerHistories;
|
||||||
|
ContextIds = [Filter]; // szerver-oldali szűrő a tabokhoz (All = minden) — a GetEkaerHistories(filter) param-ja
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ContextIds == null || ContextIds.Length == 0) ContextIds = [ParentDataItem!.Id];
|
||||||
|
|
||||||
|
// A ForeignKey általános (Shipping/Order stb.) — bármely szülőnél a ForeignKey-re szűrünk.
|
||||||
|
GetAllMessageTag = SignalRTags.GetEkaerHistoriesByForeignKey;
|
||||||
|
if (KeyFieldNameToParentId.IsNullOrWhiteSpace()) KeyFieldNameToParentId = nameof(EkaerHistory.ForeignKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
await base.OnInitializedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
base.OnParametersSet();
|
||||||
|
|
||||||
|
if (!_isFirstInitializeParameters)
|
||||||
|
{
|
||||||
|
_isFirstInitializeParameters = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task SetParametersAsyncCore(ParameterView parameters)
|
||||||
|
{
|
||||||
|
await base.SetParametersAsyncCore(parameters);
|
||||||
|
|
||||||
|
if (!_isFirstInitializeParameterCore)
|
||||||
|
{
|
||||||
|
//ShowFilterRow = true;
|
||||||
|
//ShowGroupPanel = true;
|
||||||
|
//AllowSort = false;
|
||||||
|
|
||||||
|
//etc...
|
||||||
|
|
||||||
|
_isFirstInitializeParameterCore = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
<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.Currency)" />
|
||||||
<DxGridDataColumn FieldName="@nameof(Partner.CountryCode)" />
|
<DxGridDataColumn FieldName="@nameof(Partner.CountryCode)" />
|
||||||
<DxGridDataColumn FieldName="PostalCode" />
|
<DxGridDataColumn FieldName="PostalCode" />
|
||||||
<DxGridDataColumn FieldName="@nameof(Partner.Country)" />
|
<DxGridDataColumn FieldName="@nameof(Partner.Country)" />
|
||||||
|
|
@ -38,14 +38,15 @@
|
||||||
<DxGridDataColumn FieldName="City" />
|
<DxGridDataColumn FieldName="City" />
|
||||||
<DxGridDataColumn FieldName="Street" />
|
<DxGridDataColumn FieldName="Street" />
|
||||||
|
|
||||||
<DxGridDataColumn FieldName="Created" ReadOnly="true" />
|
<DxGridDataColumn FieldName="Created" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
|
||||||
<DxGridDataColumn FieldName="Modified" ReadOnly="true" />
|
<DxGridDataColumn FieldName="Modified" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
|
||||||
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
|
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
|
||||||
</Columns>
|
</Columns>
|
||||||
<DetailRowTemplate>
|
<DetailRowTemplate>
|
||||||
@if (IsMasterGrid)
|
@if (IsMasterGrid)
|
||||||
{
|
{
|
||||||
var partner = ((Partner)context.DataItem);
|
var partner = ((Partner)context.DataItem);
|
||||||
|
var partnerDepots = partner?.PartnerDepots ?? [];
|
||||||
var shippingDocuments = partner?.ShippingDocuments ?? [];
|
var shippingDocuments = partner?.ShippingDocuments ?? [];
|
||||||
|
|
||||||
<DxTabs>
|
<DxTabs>
|
||||||
|
|
@ -62,6 +63,13 @@
|
||||||
<GridShippingItemTemplate ShippingItems="@observableShippingItems" ParentDataItem="@partner" />
|
<GridShippingItemTemplate ShippingItems="@observableShippingItems" ParentDataItem="@partner" />
|
||||||
}
|
}
|
||||||
</DxTabPage>
|
</DxTabPage>
|
||||||
|
|
||||||
|
<DxTabPage Text="Telephelyek">
|
||||||
|
@{
|
||||||
|
var observablePartnerDepots = new AcObservableCollection<PartnerDepot>(partnerDepots);
|
||||||
|
<GridPartnerDepot PartnerDepots="observablePartnerDepots" ParentDataItem="@partner"></GridPartnerDepot>
|
||||||
|
}
|
||||||
|
</DxTabPage>
|
||||||
</DxTabs>
|
</DxTabs>
|
||||||
}
|
}
|
||||||
</DetailRowTemplate>
|
</DetailRowTemplate>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
@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>
|
||||||
|
<GridPartnerDepotBase @ref="Grid"
|
||||||
|
DataSource="PartnerDepots"
|
||||||
|
ParentDataItem="ParentDataItem"
|
||||||
|
AutoSaveLayoutName="GridPartnerDepot"
|
||||||
|
SignalRClient="FruitBankSignalRClient"
|
||||||
|
Logger="_logger"
|
||||||
|
CssClass="@GridCss"
|
||||||
|
ValidationEnabled="false"
|
||||||
|
OnGridFocusedRowChanged="Grid_FocusedRowChanged">
|
||||||
|
<Columns>
|
||||||
|
<DxGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(PartnerDepot.PartnerId)" />
|
||||||
|
|
||||||
|
<DxGridDataColumn FieldName="Name" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(PartnerDepot.CountryCode)" />
|
||||||
|
<DxGridDataColumn FieldName="PostalCode" />
|
||||||
|
<DxGridDataColumn FieldName="City" />
|
||||||
|
<DxGridDataColumn FieldName="Street" />
|
||||||
|
|
||||||
|
<DxGridDataColumn FieldName="Created" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
|
||||||
|
<DxGridDataColumn FieldName="Modified" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
|
||||||
|
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
|
||||||
|
</Columns>
|
||||||
|
@* <DetailRowTemplate>
|
||||||
|
@if (IsMasterGrid)
|
||||||
|
{
|
||||||
|
var partnerDepot = ((PartnerDepot)context.DataItem);
|
||||||
|
|
||||||
|
<DxTabs>
|
||||||
|
<DxTabPage Text="Szállítólevelek">
|
||||||
|
@{
|
||||||
|
var observableShippingDocuments = new AcObservableCollection<ShippingDocument>(shippingDocuments);
|
||||||
|
<GridShippingDocument ShippingDocuments="@observableShippingDocuments" ParentDataItem="@partner" Partners="@Partners"></GridShippingDocument>
|
||||||
|
}
|
||||||
|
</DxTabPage>
|
||||||
|
|
||||||
|
<DxTabPage Text="Szállítmány tételek">
|
||||||
|
@{
|
||||||
|
var observableShippingItems = new AcObservableCollection<ShippingItem>(shippingDocuments.SelectMany(sd => sd.ShippingItems ?? []));
|
||||||
|
<GridShippingItemTemplate ShippingItems="@observableShippingItems" ParentDataItem="@partner" />
|
||||||
|
}
|
||||||
|
</DxTabPage>
|
||||||
|
</DxTabs>
|
||||||
|
}
|
||||||
|
</DetailRowTemplate>
|
||||||
|
*@ <ToolbarTemplate>
|
||||||
|
@if (IsMasterGrid)
|
||||||
|
{
|
||||||
|
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
|
||||||
|
}
|
||||||
|
</ToolbarTemplate>
|
||||||
|
</GridPartnerDepotBase>
|
||||||
|
</GridContent>
|
||||||
|
</MgGridWithInfoPanel>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
//[Inject] public required ObjectLock ObjectLock { get; set; }
|
||||||
|
[Inject] public required DatabaseClient Database { get; set; }
|
||||||
|
|
||||||
|
[Parameter] public AcObservableCollection<PartnerDepot>? PartnerDepots { get; set; }
|
||||||
|
|
||||||
|
const string ExportFileName = "ExportResult";
|
||||||
|
string GridSearchText = "";
|
||||||
|
bool EditItemsEnabled { get; set; }
|
||||||
|
int FocusedRowVisibleIndex { get; set; }
|
||||||
|
public GridPartnerDepotBase Grid { get; set; }
|
||||||
|
string GridCss => !IsMasterGrid ? "hide-toolbar" : string.Empty;
|
||||||
|
|
||||||
|
[Parameter] public IId<int>? ParentDataItem { get; set; }
|
||||||
|
|
||||||
|
public bool IsMasterGrid => ParentDataItem == null;
|
||||||
|
public bool ParentDataItemIsPartner => (ParentDataItem is Partner);
|
||||||
|
|
||||||
|
private int _activeTabIndex;
|
||||||
|
private LoggerClient<GridPartnerDepot> _logger;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_logger = new LoggerClient<GridPartnerDepot>(LogWriters.ToArray());
|
||||||
|
await ReloadDataFromDb(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReloadDataFromDb(bool forceReload = false)
|
||||||
|
{
|
||||||
|
if (!IsMasterGrid) return;
|
||||||
|
|
||||||
|
if (Grid == null) return;
|
||||||
|
|
||||||
|
using (await ObjectLock.GetSemaphore<PartnerDepot>().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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
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.Partners;
|
||||||
|
|
||||||
|
public class GridPartnerDepotBase: FruitBankGridBase<PartnerDepot>, IGrid
|
||||||
|
{
|
||||||
|
private bool _isFirstInitializeParameterCore;
|
||||||
|
private bool _isFirstInitializeParameters;
|
||||||
|
|
||||||
|
public GridPartnerDepotBase() : base()
|
||||||
|
{
|
||||||
|
//GetAllMessageTag = SignalRTags.GetPartnerDepots;
|
||||||
|
AddMessageTag = SignalRTags.AddPartnerDepot;
|
||||||
|
UpdateMessageTag = SignalRTags.UpdatePartnerDepot;
|
||||||
|
|
||||||
|
//RemoveMessageTag = SignalRTags.;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (GetAllMessageTag > 0) return;
|
||||||
|
|
||||||
|
if (IsMasterGrid) GetAllMessageTag = SignalRTags.GetPartnerDepots;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ContextIds == null || ContextIds.Length == 0) ContextIds = [ParentDataItem!.Id];
|
||||||
|
|
||||||
|
switch (ParentDataItem)
|
||||||
|
{
|
||||||
|
case IPartner:
|
||||||
|
GetAllMessageTag = SignalRTags.GetPartnerDepotsByPartnerId;
|
||||||
|
if (KeyFieldNameToParentId.IsNullOrWhiteSpace()) KeyFieldNameToParentId = nameof(PartnerDepot.PartnerId);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -86,8 +86,8 @@
|
||||||
<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" />
|
||||||
<DxGridDataColumn FieldName="IsAllMeasured" Caption="Mérések kész" ReadOnly="true" />
|
<DxGridDataColumn FieldName="IsAllMeasured" Caption="Mérések kész" ReadOnly="true" />
|
||||||
<DxGridDataColumn FieldName="Created" Caption="Létrehozva" ReadOnly="true" />
|
<DxGridDataColumn FieldName="Created" Caption="Létrehozva" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
|
||||||
<DxGridDataColumn FieldName="Modified" Caption="Módosítva" ReadOnly="true" />
|
<DxGridDataColumn FieldName="Modified" Caption="Módosítva" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
|
||||||
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
|
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
|
||||||
</Columns>
|
</Columns>
|
||||||
<DetailRowTemplate>
|
<DetailRowTemplate>
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,8 @@ public class ProductDtoTable(FruitBankSignalRClient fruitBankSignalRClient) : Ac
|
||||||
|
|
||||||
//Clear();
|
//Clear();
|
||||||
this.Replace(items);
|
this.Replace(items);
|
||||||
|
//this.Replace(items.Cast<ProductDtoTableItem>());
|
||||||
|
|
||||||
//foreach (var productDto in items)
|
//foreach (var productDto in items)
|
||||||
//{
|
//{
|
||||||
// this.UpdateCollection(productDto, false);
|
// this.UpdateCollection(productDto, false);
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,6 @@
|
||||||
<Reference Include="AyCode.Services">
|
<Reference Include="AyCode.Services">
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Services.dll</HintPath>
|
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Services.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="AyCode.Services.Server">
|
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Services.Server.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="AyCode.Utils">
|
<Reference Include="AyCode.Utils">
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Utils.dll</HintPath>
|
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Utils.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,12 @@
|
||||||
<span class="icon shippings-icon" aria-hidden="true"></span> Szállítmányok
|
<span class="icon shippings-icon" aria-hidden="true"></span> Szállítmányok
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link" href="Ekaer" title="EKÁER">
|
||||||
|
<span class="icon ekaer-icon" aria-hidden="true"></span> EKÁER
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,10 @@
|
||||||
--icon-mask-image: var(--icon-login-mask-image);
|
--icon-mask-image: var(--icon-login-mask-image);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::deep .ekaer-icon {
|
||||||
|
--icon-mask-image: var(--icon-ekaer-mask-image);
|
||||||
|
}
|
||||||
|
|
||||||
.nav-item {
|
.nav-item {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
@page "/Ekaer"
|
||||||
|
@using FruitBank.Common.Entities
|
||||||
|
@using FruitBankHybrid.Shared.Components
|
||||||
|
@using FruitBankHybrid.Shared.Components.Grids.Ekaers
|
||||||
|
@using FruitBankHybrid.Shared.Databases
|
||||||
|
<h3>EKÁER - Adminisztrátor</h3>
|
||||||
|
|
||||||
|
<DxDialogProvider />
|
||||||
|
|
||||||
|
<div style="margin-top: 30px;">
|
||||||
|
|
||||||
|
<DxLoadingPanel @bind-Visible="LoadingPanelVisibility.Visible"
|
||||||
|
IsContentBlocked="true"
|
||||||
|
ApplyBackgroundShading="true"
|
||||||
|
IndicatorAreaVisible="false"
|
||||||
|
Text="Adatok szinkronizálása folyamatban...">
|
||||||
|
|
||||||
|
<DxTabs ActiveTabIndexChanged="(i) => OnActiveTabChanged(i)" RenderMode="TabsRenderMode.OnDemand" AllowTabReorder="false">
|
||||||
|
<DxTabPage Text="Beküldésre váró">
|
||||||
|
<GridEkaerHistory @ref="gridEkaerHistoryPending" Filter="EkaerHistoryFilter.ToSubmit" OnDataChanged="RefreshNeedsCompletionCountAsync" OnBusyChanged="OnGridBusyChanged"></GridEkaerHistory>
|
||||||
|
</DxTabPage>
|
||||||
|
<DxTabPage Text="@NeedsCompletionTabText">
|
||||||
|
<GridEkaerHistory @ref="gridEkaerHistoryNeedsCompletion" Filter="EkaerHistoryFilter.NeedsCompletion" OnDataChanged="RefreshNeedsCompletionCountAsync" OnBusyChanged="OnGridBusyChanged"></GridEkaerHistory>
|
||||||
|
</DxTabPage>
|
||||||
|
<DxTabPage Text="Elküldött">
|
||||||
|
<GridEkaerHistory @ref="gridEkaerHistorySent" Filter="EkaerHistoryFilter.Sent" OnDataChanged="RefreshNeedsCompletionCountAsync" OnBusyChanged="OnGridBusyChanged"></GridEkaerHistory>
|
||||||
|
</DxTabPage>
|
||||||
|
</DxTabs>
|
||||||
|
</DxLoadingPanel>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
using AyCode.Core.Loggers;
|
||||||
|
using FruitBank.Common.Entities;
|
||||||
|
using FruitBank.Common.Models;
|
||||||
|
using FruitBankHybrid.Shared.Components.Grids.Ekaers;
|
||||||
|
using FruitBankHybrid.Shared.Databases;
|
||||||
|
using FruitBankHybrid.Shared.Services.Loggers;
|
||||||
|
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||||
|
using Mango.Nop.Core.Loggers;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Shared.Pages;
|
||||||
|
|
||||||
|
public partial class Ekaer : ComponentBase
|
||||||
|
{
|
||||||
|
[Inject] public required IEnumerable<IAcLogWriterClientBase> LogWriters { get; set; }
|
||||||
|
[Inject] public required NavigationManager NavManager { get; set; }
|
||||||
|
[Inject] public required LoggedInModel LoggedInModel { get; set; }
|
||||||
|
[Inject] public required FruitBankSignalRClient FruitBankSignalRClient { get; set; }
|
||||||
|
|
||||||
|
private GridEkaerHistory gridEkaerHistoryPending;
|
||||||
|
private GridEkaerHistory gridEkaerHistorySent;
|
||||||
|
private GridEkaerHistory gridEkaerHistoryNeedsCompletion;
|
||||||
|
|
||||||
|
private ILogger _logger = null!;
|
||||||
|
public int ActiveTabIndex;
|
||||||
|
|
||||||
|
/// <summary>Pótlásra váró (SentWithMissingData) rekordok száma a fül feliratához — hogy ránézésre látsszon, ha van pótolnivaló.</summary>
|
||||||
|
private int _needsCompletionCount;
|
||||||
|
public string NeedsCompletionTabText => _needsCompletionCount > 0 ? $"Pótlásra váró ({_needsCompletionCount})" : "Pótlásra váró";
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (!LoggedInModel.IsRevisor) NavManager.NavigateTo("/Login");
|
||||||
|
|
||||||
|
_logger = new LoggerClient<Ekaer>(LogWriters.ToArray());
|
||||||
|
_logger.Info("OnInitializedAsync");
|
||||||
|
|
||||||
|
await base.OnInitializedAsync();
|
||||||
|
await RefreshNeedsCompletionCountAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task OnActiveTabChanged(int activeTabIndex)
|
||||||
|
{
|
||||||
|
ActiveTabIndex = activeTabIndex;
|
||||||
|
// Fülváltáskor frissítjük a számlálót — a gridben végzett művelet (generálás/státusz-állítás) megváltoztathatta.
|
||||||
|
await RefreshNeedsCompletionCountAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>A grid jelzi a hosszú művelet (sorok létrehozása) elejét (true) / végét (false) → az oldal Loading panelje meg/elrejtve.</summary>
|
||||||
|
private Task OnGridBusyChanged(bool busy)
|
||||||
|
{
|
||||||
|
LoadingPanelVisibility.Visible = busy;
|
||||||
|
return InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>A „Pótlásra váró" rekordok számának frissítése a fül feliratához (a count endpointról, flag-szűrővel).</summary>
|
||||||
|
private async Task RefreshNeedsCompletionCountAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_needsCompletionCount = await FruitBankSignalRClient.GetEkaerHistoryCount(EkaerHistoryFilter.NeedsCompletion);
|
||||||
|
//_needsCompletionCount = await FruitBankSignalRClient.GetEkaerHistoryCount(EkaerHistoryFilter.ToSubmit);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error($"GetEkaerHistoryCount hiba: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using AyCode.Core.Interfaces;
|
using AyCode.Core.Interfaces;
|
||||||
using AyCode.Core.Loggers;
|
using AyCode.Core.Loggers;
|
||||||
using AyCode.Services.Server.SignalRs;
|
|
||||||
using AyCode.Services.SignalRs;
|
using AyCode.Services.SignalRs;
|
||||||
using AyCode.Utils.Extensions;
|
using AyCode.Utils.Extensions;
|
||||||
using DevExpress.Blazor;
|
using DevExpress.Blazor;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using AyCode.Core.Extensions;
|
using AyCode.Core.Extensions;
|
||||||
using AyCode.Core.Helpers;
|
using AyCode.Core.Helpers;
|
||||||
using AyCode.Core.Loggers;
|
using AyCode.Core.Loggers;
|
||||||
using AyCode.Services.Server.SignalRs;
|
|
||||||
using AyCode.Services.SignalRs;
|
using AyCode.Services.SignalRs;
|
||||||
using DevExpress.Utils.About;
|
using DevExpress.Utils.About;
|
||||||
using FruitBank.Common;
|
using FruitBank.Common;
|
||||||
|
|
@ -66,6 +65,26 @@ namespace FruitBankHybrid.Shared.Services.SignalRs
|
||||||
public Task<Partner?> UpdatePartner(Partner partner) => PostDataAsync(SignalRTags.UpdatePartner, partner);
|
public Task<Partner?> UpdatePartner(Partner partner) => PostDataAsync(SignalRTags.UpdatePartner, partner);
|
||||||
#endregion Partner
|
#endregion Partner
|
||||||
|
|
||||||
|
#region PartnerDepot
|
||||||
|
public Task<List<PartnerDepot>?> GetPartnerDepots() => GetAllAsync<List<PartnerDepot>>(SignalRTags.GetPartnerDepots);
|
||||||
|
public Task<PartnerDepot?> GetPartnerDepotById(int id) => GetByIdAsync<PartnerDepot?>(SignalRTags.GetPartnerDepotById, id);
|
||||||
|
public Task<List<PartnerDepot>?> GetPartnerDepotsByPartnerId(int partnerId) => GetAllAsync<List<PartnerDepot>>(SignalRTags.GetPartnerDepotsByPartnerId, [partnerId]);
|
||||||
|
public Task<PartnerDepot?> AddPartnerDepot(PartnerDepot partnerDepot) => PostDataAsync(SignalRTags.AddPartnerDepot, partnerDepot);
|
||||||
|
public Task<PartnerDepot?> UpdatePartnerDepot(PartnerDepot partnerDepot) => PostDataAsync(SignalRTags.UpdatePartnerDepot, partnerDepot);
|
||||||
|
#endregion PartnerDepot
|
||||||
|
|
||||||
|
#region EkaerHistory
|
||||||
|
public Task<List<EkaerHistory>?> GetEkaerHistories(EkaerHistoryFilter filter) => GetAllAsync<List<EkaerHistory>>(SignalRTags.GetEkaerHistories, [filter]);
|
||||||
|
public Task<EkaerHistory?> GetEkaerHistoryById(int id) => GetByIdAsync<EkaerHistory?>(SignalRTags.GetEkaerHistoryById, id);
|
||||||
|
public Task<List<EkaerHistory>?> GetEkaerHistoriesByForeignKey(int foreignKey) => GetAllAsync<List<EkaerHistory>>(SignalRTags.GetEkaerHistoriesByForeignKey, [foreignKey]);
|
||||||
|
public Task<EkaerHistory?> AddEkaerHistory(EkaerHistory ekaerHistory) => PostDataAsync(SignalRTags.AddEkaerHistory, ekaerHistory);
|
||||||
|
public Task<EkaerHistory?> UpdateEkaerHistory(EkaerHistory ekaerHistory) => PostDataAsync(SignalRTags.UpdateEkaerHistory, ekaerHistory);
|
||||||
|
public Task<EkaerHistory?> GenerateEkaerXmlDocument(int foreignKey, bool isOutgoing) => GetByIdAsync<EkaerHistory?>(SignalRTags.GenerateEkaerXmlDocument, [foreignKey, isOutgoing]);
|
||||||
|
public Task<EkaerHistory?> CreateEkaerHistory(int foreignKey, bool isOutgoing) => GetByIdAsync<EkaerHistory?>(SignalRTags.CreateEkaerHistory, [foreignKey, isOutgoing]);
|
||||||
|
public Task<EkaerCreateResult?> CreateMissingEkaerHistories(DateTime fromDate) => GetByIdAsync<EkaerCreateResult?>(SignalRTags.CreateMissingEkaerHistories, fromDate);
|
||||||
|
public Task<int> GetEkaerHistoryCount(EkaerHistoryFilter filter) => GetByIdAsync<int>(SignalRTags.GetEkaerHistoryCount, filter);
|
||||||
|
#endregion EkaerHistory
|
||||||
|
|
||||||
#region CargoPartner
|
#region CargoPartner
|
||||||
public Task<List<CargoPartner>?> GetCargoPartners() => GetAllAsync<List<CargoPartner>>(SignalRTags.GetCargoPartners);
|
public Task<List<CargoPartner>?> GetCargoPartners() => GetAllAsync<List<CargoPartner>>(SignalRTags.GetCargoPartners);
|
||||||
public Task<CargoPartner?> GetCargoPartnerById(int id) => GetByIdAsync<CargoPartner?>(SignalRTags.GetCargoPartnerById, id);
|
public Task<CargoPartner?> GetCargoPartnerById(int id) => GetByIdAsync<CargoPartner?>(SignalRTags.GetCargoPartnerById, id);
|
||||||
|
|
@ -163,10 +182,10 @@ namespace FruitBankHybrid.Shared.Services.SignalRs
|
||||||
=> GetAllAsync<List<ShippingDocument>>(SignalRTags.GetShippingDocumentsByShippingId, [shippingId]);
|
=> GetAllAsync<List<ShippingDocument>>(SignalRTags.GetShippingDocumentsByShippingId, [shippingId]);
|
||||||
|
|
||||||
public Task<List<ShippingDocument>?> GetShippingDocumentsByProductId(int productId)
|
public Task<List<ShippingDocument>?> GetShippingDocumentsByProductId(int productId)
|
||||||
=> GetAllAsync<List<ShippingDocument>>(SignalRTags.GetShippingDocumentsByShippingId, [productId]);
|
=> GetAllAsync<List<ShippingDocument>>(SignalRTags.GetShippingDocumentsByProductId, [productId]);
|
||||||
|
|
||||||
public Task<List<ShippingDocument>?> GetShippingDocumentsByPartnerId(int partnerId)
|
public Task<List<ShippingDocument>?> GetShippingDocumentsByPartnerId(int partnerId)
|
||||||
=> GetAllAsync<List<ShippingDocument>>(SignalRTags.GetShippingDocumentsByShippingId, [partnerId]);
|
=> GetAllAsync<List<ShippingDocument>>(SignalRTags.GetShippingDocumentsByPartnerId, [partnerId]);
|
||||||
|
|
||||||
public Task<ShippingDocument?> AddShippingDocument(ShippingDocument shippingDocument)
|
public Task<ShippingDocument?> AddShippingDocument(ShippingDocument shippingDocument)
|
||||||
=> PostDataAsync(SignalRTags.AddShippingDocument, shippingDocument);
|
=> PostDataAsync(SignalRTags.AddShippingDocument, shippingDocument);
|
||||||
|
|
@ -185,18 +204,29 @@ namespace FruitBankHybrid.Shared.Services.SignalRs
|
||||||
#endregion Customer
|
#endregion Customer
|
||||||
|
|
||||||
#region Product
|
#region Product
|
||||||
|
//TODO: itt ProductDtoTableItem kéne, csak az AycodeHubProtocol ProductDto-ra deserialize-ol! - J.
|
||||||
|
//public Task<List<ProductDto>?> GetProductDtoTableItems() => GetAllAsync<List<ProductDto>>(SignalRTags.GetProductDtos);
|
||||||
|
// IDEIGLENES gyors fix: a GetProductDtos tagon a szerver List<ProductDto>-t küld, a binár
|
||||||
|
// protokoll viszont a wire-típusba (ProductDto) deszerializál — így a közvetlen
|
||||||
|
// List<ProductDtoTableItem> kérés üres lett (a ProductDtoTableItem a ProductDto kliens-oldali
|
||||||
|
// leszármazottja, amit a szerver nem ismer). A működő GetProductDtos()-t hívjuk, és bináris
|
||||||
|
// round-trippel a kért leszármazottra alakítjuk (azonos mezők). VÉGLEGES megoldás: requestId →
|
||||||
|
// kért-típus a protokollban (AQN-leváltás) — lásd AyCode.Services/docs/SIGNALR_BINARY_PROTOCOL.
|
||||||
|
public async Task<List<ProductDtoTableItem>?> GetProductDtoTableItems()
|
||||||
|
{
|
||||||
|
var dtos = await GetProductDtos();
|
||||||
|
if (dtos == null) return null;
|
||||||
|
|
||||||
public Task<List<ProductDtoTableItem>?> GetProductDtoTableItems()
|
var bytes = SignalRSerializationHelper.SerializeToBinary(dtos);
|
||||||
=> GetAllAsync<List<ProductDtoTableItem>>(SignalRTags.GetProductDtos);
|
return SignalRSerializationHelper.DeserializeFromBinary<List<ProductDtoTableItem>>(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
public Task<List<ProductDto>?> GetProductDtos()
|
public Task<List<ProductDto>?> GetProductDtos() => GetAllAsync<List<ProductDto>>(SignalRTags.GetProductDtos);
|
||||||
=> GetAllAsync<List<ProductDto>>(SignalRTags.GetProductDtos);
|
|
||||||
|
|
||||||
//public Task<List<MeasuringProductDto>?> GetAllMeasuringProductDtos()
|
//public Task<List<MeasuringProductDto>?> GetAllMeasuringProductDtos()
|
||||||
// => GetAllAsync<List<MeasuringProductDto>>(SignalRTags.GetAllMeasuringProductDtos);
|
// => GetAllAsync<List<MeasuringProductDto>>(SignalRTags.GetAllMeasuringProductDtos);
|
||||||
|
|
||||||
public Task<ProductDto?> GetProductDtoById(int productId)
|
public Task<ProductDto?> GetProductDtoById(int productId) => GetByIdAsync<ProductDto?>(SignalRTags.GetMeasuringProductDtoById, productId);
|
||||||
=> GetByIdAsync<ProductDto?>(SignalRTags.GetMeasuringProductDtoById, productId);
|
|
||||||
|
|
||||||
#endregion Product
|
#endregion Product
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using AyCode.Core.Helpers;
|
using AyCode.Core.Helpers;
|
||||||
using AyCode.Core.Interfaces;
|
using AyCode.Core.Interfaces;
|
||||||
using AyCode.Core.Loggers;
|
using AyCode.Core.Loggers;
|
||||||
using AyCode.Services.Server.SignalRs;
|
|
||||||
using AyCode.Services.SignalRs;
|
using AyCode.Services.SignalRs;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,9 @@
|
||||||
"UseStatefulReconnect": true
|
"UseStatefulReconnect": true
|
||||||
},
|
},
|
||||||
"AcBinaryHubProtocol": {
|
"AcBinaryHubProtocol": {
|
||||||
"ProtocolMode": "AsyncSegment",
|
"ProtocolMode": "Bytes",
|
||||||
"BufferSize": 4096,
|
"BufferSize": 4096,
|
||||||
"FlushPolicy": "Coalesced",
|
"FlushPolicy": "Coalesced",
|
||||||
"FlushTimeout": "00:00:10"
|
"FlushTimeout": "00:00:15"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,22 @@ h1:focus {
|
||||||
--icon-orders-mask-image: url("images/orders-fluent.svg");
|
--icon-orders-mask-image: url("images/orders-fluent.svg");
|
||||||
--icon-shippings-mask-image: url("images/shippings-fluent.svg");
|
--icon-shippings-mask-image: url("images/shippings-fluent.svg");
|
||||||
--icon-login-mask-image: url("images/login-fluent.svg");
|
--icon-login-mask-image: url("images/login-fluent.svg");
|
||||||
|
--icon-ekaer-mask-image: url("images/ekaer-fluent.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* EKÁER toolbar dátumválasztó (GridEkaerHistory) — kompakt szélesség, hogy illeszkedjen a toolbar-elemek közé. */
|
||||||
|
.ekaer-create-from-date {
|
||||||
|
width: 9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A Template-es toolbar-item tartalma alapból felülre csúszik. Az elsődleges igazítást a DevExpress saját
|
||||||
|
dxbl-toolbar-edit osztálya adja (a téma-CSS szállítja, a CssClass-ban kapja meg az item) — ez itt a
|
||||||
|
fallback, ami a markup-szerkezettől függetlenül középre igazít. */
|
||||||
|
.ekaer-create-from-date-item,
|
||||||
|
.ekaer-create-from-date-item > div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 5.4 10 3l6 2.4V10c0 3.5-2.5 6.4-6 7.4-3.5-1-6-3.9-6-7.4V5.4Z" stroke="black" stroke-width="1.4" stroke-linejoin="round"/>
|
||||||
|
<path d="M7.2 10l2 2 3.8-4.4" stroke="black" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 354 B |
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
|
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
|
||||||
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||||
|
|
||||||
<RunAOTCompilation>false</RunAOTCompilation>
|
<RunAOTCompilation>true</RunAOTCompilation>
|
||||||
<WasmStripILAfterAOT>true</WasmStripILAfterAOT>
|
<WasmStripILAfterAOT>true</WasmStripILAfterAOT>
|
||||||
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
@ -51,33 +51,18 @@
|
||||||
<Reference Include="AyCode.Core">
|
<Reference Include="AyCode.Core">
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.dll</HintPath>
|
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="AyCode.Core.Server">
|
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.Server.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="AyCode.Entities">
|
<Reference Include="AyCode.Entities">
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.dll</HintPath>
|
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="AyCode.Entities.Server">
|
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.Server.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="AyCode.Interfaces">
|
<Reference Include="AyCode.Interfaces">
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.dll</HintPath>
|
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="AyCode.Interfaces.Server">
|
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.Server.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="AyCode.Models">
|
<Reference Include="AyCode.Models">
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Models.dll</HintPath>
|
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Models.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="AyCode.Models.Server">
|
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Models.Server.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="AyCode.Services">
|
<Reference Include="AyCode.Services">
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Services.dll</HintPath>
|
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Services.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="AyCode.Services.Server">
|
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Services.Server.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="AyCode.Utils">
|
<Reference Include="AyCode.Utils">
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Utils.dll</HintPath>
|
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Utils.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
|
@ -87,7 +72,7 @@
|
||||||
<Reference Include="DevExpress.Data.v25.1"></Reference>
|
<Reference Include="DevExpress.Data.v25.1"></Reference>
|
||||||
<Reference Include="DevExpress.Utils.v25.1"></Reference>
|
<Reference Include="DevExpress.Utils.v25.1"></Reference>
|
||||||
<Reference Include="Mango.Nop.Core">
|
<Reference Include="Mango.Nop.Core">
|
||||||
<HintPath>..\..\NopCommerce.Common\4.70\Libraries\Mango.Nop.Core\bin\FruitBank\$(Configuration)\net9.0\Mango.Nop.Core.dll</HintPath>
|
<HintPath>..\..\NopCommerce.Common\4.70\Libraries\Mango.Nop.Core\bin\FruitBank\$(Configuration)\net9.0\Mango.Nop.Core.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,9 @@
|
||||||
"UseStatefulReconnect": true
|
"UseStatefulReconnect": true
|
||||||
},
|
},
|
||||||
"AcBinaryHubProtocol": {
|
"AcBinaryHubProtocol": {
|
||||||
"ProtocolMode": "AsyncSegment",
|
"ProtocolMode": "Bytes",
|
||||||
"BufferSize": 4096,
|
"BufferSize": 4096,
|
||||||
"FlushPolicy": "Coalesced",
|
"FlushPolicy": "Coalesced",
|
||||||
"FlushTimeout": "00:00:10"
|
"FlushTimeout": "00:00:15"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,9 @@
|
||||||
"UseStatefulReconnect": true
|
"UseStatefulReconnect": true
|
||||||
},
|
},
|
||||||
"AcBinaryHubProtocol": {
|
"AcBinaryHubProtocol": {
|
||||||
"ProtocolMode": "AsyncSegment",
|
"ProtocolMode": "Bytes",
|
||||||
"BufferSize": 4096,
|
"BufferSize": 4096,
|
||||||
"FlushPolicy": "Coalesced",
|
"FlushPolicy": "Coalesced",
|
||||||
"FlushTimeout": "00:00:10"
|
"FlushTimeout": "00:00:15"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<ApplicationVersion>1</ApplicationVersion>
|
<ApplicationVersion>1</ApplicationVersion>
|
||||||
<PackageVersion>$(ApplicationDisplayVersion)</PackageVersion>
|
<PackageVersion>$(ApplicationDisplayVersion)</PackageVersion>
|
||||||
|
|
||||||
<RunAOTCompilation>false</RunAOTCompilation>
|
<RunAOTCompilation>true</RunAOTCompilation>
|
||||||
<WasmStripILAfterAOT>true</WasmStripILAfterAOT>
|
<WasmStripILAfterAOT>true</WasmStripILAfterAOT>
|
||||||
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
||||||
|
|
||||||
|
|
@ -47,9 +47,9 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
<PropertyGroup Condition="$(TargetFramework.Contains('-android'))">
|
<!--<PropertyGroup Condition="$(TargetFramework.Contains('-android'))">
|
||||||
<RunAOTCompilation>false</RunAOTCompilation>
|
<RunAOTCompilation>false</RunAOTCompilation>
|
||||||
</PropertyGroup>
|
</PropertyGroup>-->
|
||||||
|
|
||||||
<PropertyGroup Condition="$(TargetFramework.Contains('-ios')) and '$(Configuration)' == 'Release'">
|
<PropertyGroup Condition="$(TargetFramework.Contains('-ios')) and '$(Configuration)' == 'Release'">
|
||||||
<UseInterpreter>true</UseInterpreter>
|
<UseInterpreter>true</UseInterpreter>
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@
|
||||||
<Version>10</Version>
|
<Version>10</Version>
|
||||||
<SourceModelProvider>
|
<SourceModelProvider>
|
||||||
<ConnectionBasedModelProvider>
|
<ConnectionBasedModelProvider>
|
||||||
<ConnectionString>Data Source=195.26.231.218;Initial Catalog=FruitBank_DEV;Integrated Security=False;Persist Security Info=False;User ID=sa;Pooling=False;Multiple Active Result Sets=False;Trust Server Certificate=True</ConnectionString>
|
<ConnectionString>Data Source=100.73.220.50;Initial Catalog=FruitBank_DEV;Integrated Security=False;Persist Security Info=False;User ID=sa;Pooling=False;Trust Server Certificate=True</ConnectionString>
|
||||||
</ConnectionBasedModelProvider>
|
</ConnectionBasedModelProvider>
|
||||||
</SourceModelProvider>
|
</SourceModelProvider>
|
||||||
<TargetModelProvider>
|
<TargetModelProvider>
|
||||||
<ConnectionBasedModelProvider>
|
<ConnectionBasedModelProvider>
|
||||||
<ConnectionString>Data Source=195.26.231.218;Initial Catalog=FruitBank_PROD;Integrated Security=False;Persist Security Info=False;User ID=sa;Pooling=False;Multiple Active Result Sets=False;Trust Server Certificate=True</ConnectionString>
|
<ConnectionString>Data Source=100.73.220.50;Initial Catalog=FruitBank_PROD;Integrated Security=False;Persist Security Info=False;User ID=sa;Pooling=False;Trust Server Certificate=True</ConnectionString>
|
||||||
</ConnectionBasedModelProvider>
|
</ConnectionBasedModelProvider>
|
||||||
</TargetModelProvider>
|
</TargetModelProvider>
|
||||||
<SchemaCompareSettingsService>
|
<SchemaCompareSettingsService>
|
||||||
|
|
@ -357,7 +357,7 @@
|
||||||
</PropertyElementName>
|
</PropertyElementName>
|
||||||
<PropertyElementName>
|
<PropertyElementName>
|
||||||
<Name>TargetConnectionString</Name>
|
<Name>TargetConnectionString</Name>
|
||||||
<Value>Data Source=195.26.231.218;Initial Catalog=FruitBank_PROD;Integrated Security=False;Persist Security Info=False;User ID=sa;Pooling=False;Multiple Active Result Sets=False;Trust Server Certificate=True;Application Name="Microsoft SQL Server Data Tools, Schema Compare"</Value>
|
<Value>Data Source=100.73.220.50;Initial Catalog=FruitBank_PROD;Integrated Security=False;Persist Security Info=False;User ID=sa;Pooling=False;Multiple Active Result Sets=False;Trust Server Certificate=True;Application Name="Microsoft SQL Server Data Tools, Schema Compare"</Value>
|
||||||
</PropertyElementName>
|
</PropertyElementName>
|
||||||
<PropertyElementName>
|
<PropertyElementName>
|
||||||
<Name>TreatVerificationErrorsAsWarnings</Name>
|
<Name>TreatVerificationErrorsAsWarnings</Name>
|
||||||
|
|
@ -1002,17 +1002,13 @@
|
||||||
<Name>dbo</Name>
|
<Name>dbo</Name>
|
||||||
<Name>PK_AvalaraItemClassification</Name>
|
<Name>PK_AvalaraItemClassification</Name>
|
||||||
</SelectedItem>
|
</SelectedItem>
|
||||||
<SelectedItem Type="Microsoft.Data.Tools.Schema.Sql.SchemaModel.SqlTable, Microsoft.Data.Tools.Schema.Sql, Version=170.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91">
|
|
||||||
<Name>dbo</Name>
|
|
||||||
<Name>FacebookPixelConfiguration</Name>
|
|
||||||
</SelectedItem>
|
|
||||||
<SelectedItem Type="Microsoft.Data.Tools.Schema.Sql.SchemaModel.SqlPrimaryKeyConstraint, Microsoft.Data.Tools.Schema.Sql, Version=170.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91">
|
<SelectedItem Type="Microsoft.Data.Tools.Schema.Sql.SchemaModel.SqlPrimaryKeyConstraint, Microsoft.Data.Tools.Schema.Sql, Version=170.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91">
|
||||||
<Name>dbo</Name>
|
<Name>dbo</Name>
|
||||||
<Name>PK_FacebookPixelConfiguration</Name>
|
<Name>PK_FacebookPixelConfiguration</Name>
|
||||||
</SelectedItem>
|
</SelectedItem>
|
||||||
<SelectedItem Type="Microsoft.Data.Tools.Schema.Sql.SchemaModel.SqlTable, Microsoft.Data.Tools.Schema.Sql, Version=170.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91">
|
<SelectedItem Type="Microsoft.Data.Tools.Schema.Sql.SchemaModel.SqlTable, Microsoft.Data.Tools.Schema.Sql, Version=170.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91">
|
||||||
<Name>dbo</Name>
|
<Name>dbo</Name>
|
||||||
<Name>GoogleAuthenticatorRecord</Name>
|
<Name>FacebookPixelConfiguration</Name>
|
||||||
</SelectedItem>
|
</SelectedItem>
|
||||||
<SelectedItem Type="Microsoft.Data.Tools.Schema.Sql.SchemaModel.SqlPrimaryKeyConstraint, Microsoft.Data.Tools.Schema.Sql, Version=170.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91">
|
<SelectedItem Type="Microsoft.Data.Tools.Schema.Sql.SchemaModel.SqlPrimaryKeyConstraint, Microsoft.Data.Tools.Schema.Sql, Version=170.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91">
|
||||||
<Name>dbo</Name>
|
<Name>dbo</Name>
|
||||||
|
|
@ -1020,12 +1016,16 @@
|
||||||
</SelectedItem>
|
</SelectedItem>
|
||||||
<SelectedItem Type="Microsoft.Data.Tools.Schema.Sql.SchemaModel.SqlTable, Microsoft.Data.Tools.Schema.Sql, Version=170.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91">
|
<SelectedItem Type="Microsoft.Data.Tools.Schema.Sql.SchemaModel.SqlTable, Microsoft.Data.Tools.Schema.Sql, Version=170.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91">
|
||||||
<Name>dbo</Name>
|
<Name>dbo</Name>
|
||||||
<Name>TaxTransactionLog</Name>
|
<Name>GoogleAuthenticatorRecord</Name>
|
||||||
</SelectedItem>
|
</SelectedItem>
|
||||||
<SelectedItem Type="Microsoft.Data.Tools.Schema.Sql.SchemaModel.SqlPrimaryKeyConstraint, Microsoft.Data.Tools.Schema.Sql, Version=170.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91">
|
<SelectedItem Type="Microsoft.Data.Tools.Schema.Sql.SchemaModel.SqlPrimaryKeyConstraint, Microsoft.Data.Tools.Schema.Sql, Version=170.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91">
|
||||||
<Name>dbo</Name>
|
<Name>dbo</Name>
|
||||||
<Name>PK_TaxTransactionLog</Name>
|
<Name>PK_TaxTransactionLog</Name>
|
||||||
</SelectedItem>
|
</SelectedItem>
|
||||||
|
<SelectedItem Type="Microsoft.Data.Tools.Schema.Sql.SchemaModel.SqlTable, Microsoft.Data.Tools.Schema.Sql, Version=170.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91">
|
||||||
|
<Name>dbo</Name>
|
||||||
|
<Name>TaxTransactionLog</Name>
|
||||||
|
</SelectedItem>
|
||||||
<SelectedItem Type="Microsoft.Data.Tools.Schema.Sql.SchemaModel.SqlView, Microsoft.Data.Tools.Schema.Sql, Version=170.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91">
|
<SelectedItem Type="Microsoft.Data.Tools.Schema.Sql.SchemaModel.SqlView, Microsoft.Data.Tools.Schema.Sql, Version=170.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91">
|
||||||
<Name>dbo</Name>
|
<Name>dbo</Name>
|
||||||
<Name>vOrder</Name>
|
<Name>vOrder</Name>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue