Merge
This commit is contained in:
commit
ab912a4ab9
|
|
@ -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.Models;
|
||||
using FruitBank.Common.Dtos;
|
||||
using FruitBank.Common.Entities;
|
||||
using FruitBank.Common.Services.Ekaer;
|
||||
|
||||
|
|
@ -8,20 +10,22 @@ namespace FruitBank.Common.Server.Services.Ekaer;
|
|||
/// <inheritdoc cref="IFruitBankEkaerService"/>
|
||||
/// <remarks>
|
||||
/// A teljes lánc: <c>map</c> (<see cref="IShippingToEkaerMapper"/>, FruitBank.Common) →
|
||||
/// <c>validate → send</c> (<see cref="IEkaerSubmitService"/>, AyCode.Services). A NAV-fiók hitelesítő adatait
|
||||
/// (<c>INavCredentials</c>) és a saját cégadatokat (<see cref="EkaerCompanyInfo"/>) a DI szolgáltatja.
|
||||
/// <c>validate → send</c> (<see cref="IEkaerSubmitService"/>, AyCode.Services). A saját cégadatot
|
||||
/// (<see cref="EkaerSettings.Company"/>) és a NAV-fiók hitelesítő adatait a DI szolgáltatja.
|
||||
/// </remarks>
|
||||
public sealed class FruitBankEkaerService : IFruitBankEkaerService
|
||||
{
|
||||
private readonly IShippingToEkaerMapper _mapper;
|
||||
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));
|
||||
_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)
|
||||
|
|
@ -29,7 +33,75 @@ public sealed class FruitBankEkaerService : IFruitBankEkaerService
|
|||
ArgumentNullException.ThrowIfNull(shipping);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
public EkaerHistory GenerateEkaerXmlDocument(IReadOnlyCollection<ShippingDocument> documents, EkaerHistory? ekaerHistory = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(documents);
|
||||
if (documents.Count == 0) throw new ArgumentException("documents is empty", nameof(documents));
|
||||
ekaerHistory ??= new EkaerHistory { IsOutgoing = false };
|
||||
|
||||
// A csoport azonos Partneré (a kapu így csoportosít) → az első dokumentum pénzneme a mérvadó.
|
||||
var currency = documents.First().Partner?.Currency;
|
||||
// A csoport ÖSSZES dokumentuma EGY tradeCard-dá (összevont tömeg/érték): ToConsignment → BuildTradeCard.
|
||||
var tradeCard = _mapper.BuildTradeCard(_mapper.ToConsignment(documents, _settings.Company));
|
||||
return TryConfigError(ekaerHistory, currency) ?? Finalize(ekaerHistory, tradeCard, currency);
|
||||
}
|
||||
|
||||
public EkaerHistory GenerateEkaerXmlDocument(OrderDto order, EkaerHistory? ekaerHistory = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(order);
|
||||
ekaerHistory ??= new EkaerHistory { 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);
|
||||
|
||||
public void SetSummary(EkaerHistory history, DateTime? shippingDate, string? partner)
|
||||
{
|
||||
history.ShippingDate = shippingDate;
|
||||
history.Partner = partner;
|
||||
}
|
||||
|
||||
/// <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.Models;
|
||||
using FruitBank.Common.Dtos;
|
||||
using FruitBank.Common.Entities;
|
||||
using FruitBank.Common.Services.Ekaer;
|
||||
|
||||
namespace FruitBank.Common.Server.Services.Ekaer;
|
||||
|
||||
|
|
@ -16,4 +18,36 @@ public interface IFruitBankEkaerService
|
|||
/// vagy a NAV-válasz — lásd <see cref="EkaerSubmitResult"/>.
|
||||
/// </summary>
|
||||
Task<EkaerSubmitResult> SubmitShippingAsync(Shipping shipping, OperationType operation = OperationType.Create, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Egy szállítólevél-CSOPORTBÓL (egy EKÁER-egység dokumentumai) 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="documents">A deklarációhoz tartozó szállítólevél-CSOPORT (egy (Shipping, Partner, PartnerDepot) egység) a betöltött gráffal (Partner, Items+ProductDto, Shipping→járművek) — összevont tömeggel/értékkel EGY tradeCard.</param>
|
||||
/// <param name="ekaerHistory">A csoport meglévő rekordja (újrageneráláskor); <c>null</c> → új rekord készül.</param>
|
||||
EkaerHistory GenerateEkaerXmlDocument(IReadOnlyCollection<ShippingDocument> documents, 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);
|
||||
|
||||
/// <summary>Beállítja a deklaráció (<see cref="EkaerHistory"/>) összegző mezőit (DB-mentes): <c>ShippingDate</c> + <c>Partner</c>.
|
||||
/// A csoporton belül invariánsak; a forrás-kinyerés (bejövő: doc.ShippingDate + Partner.Name; kimenő: order.DateOfReceipt +
|
||||
/// Customer.Company) a hívóé. Fallback NINCS — null/üres marad, ha hiányzik.</summary>
|
||||
void SetSummary(EkaerHistory history, DateTime? shippingDate, string? partner);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; } = [];
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -2,7 +2,6 @@
|
|||
using AyCode.Core.Serializers.Toons;
|
||||
using FruitBank.Common.Interfaces;
|
||||
using LinqToDB.Mapping;
|
||||
using Mango.Nop.Core.Entities;
|
||||
|
||||
namespace FruitBank.Common.Entities;
|
||||
|
||||
|
|
@ -12,6 +11,12 @@ namespace FruitBank.Common.Entities;
|
|||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.PartnerDbTableName)]
|
||||
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; }
|
||||
}
|
||||
|
|
@ -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? ShippingId { get; set; }
|
||||
public int? PartnerDepotId { get; set; }
|
||||
|
||||
public string DocumentIdNumber { 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)]
|
||||
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)]
|
||||
public List<ShippingItem>? ShippingItems { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ public static class FruitBankConstClient
|
|||
public const string PalletDbTableName = "fbPallet";
|
||||
public const string FilesDbTableName = "fbFiles";
|
||||
public const string PartnerDbTableName = "fbPartner";
|
||||
public const string PartnerDepotDbTableName = "fbPartnerDepot";
|
||||
|
||||
public const string EkaerHistoryDbTableName = "fbEkaerHistory";
|
||||
public const string EkaerHistoryMappingDbTableName = "fbEkaerHistoryMapping";
|
||||
|
||||
public const string OrderItemPalletDbTableName = "fbOrderItemPallet";
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,26 @@ public interface IFruitBankDataControllerCommon
|
|||
public Task<Partner?> UpdatePartner(Partner 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 ekaerHistoryId);
|
||||
public Task<EkaerHistory?> CreateEkaerHistory(int foreignKey, bool isOutgoing);
|
||||
public Task<EkaerCreateResult?> CreateMissingEkaerHistories(DateTime fromDate);
|
||||
public Task<int> GetEkaerHistoryCount(EkaerHistoryFilter filter);
|
||||
#endregion EkaerHistory
|
||||
|
||||
#region CargoPartner
|
||||
public Task<List<CargoPartner>?> GetCargoPartners();
|
||||
public Task<CargoPartner?> GetCargoPartnerById(int id);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ public interface ICargoPartner : IPartnerBase
|
|||
|
||||
public interface IPartner : IPartnerBase
|
||||
{
|
||||
bool IsEkaer { get; set; }
|
||||
List<PartnerDepot>? PartnerDepots { get; set; }
|
||||
List<ShippingDocument>? ShippingDocuments { get; set; }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ public interface IShippingDocument: IEntityInt, ITimeStampInfo//, IMeasured
|
|||
{
|
||||
public int PartnerId { get; set; }
|
||||
public int? ShippingId { get; set; }
|
||||
public int? PartnerDepotId { get; set; }
|
||||
|
||||
public string DocumentIdNumber { 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.Models;
|
||||
using FruitBank.Common.Dtos;
|
||||
using FruitBank.Common.Entities;
|
||||
|
||||
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="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);
|
||||
|
||||
/// <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.Services.Nav.Ekaer;
|
||||
using AyCode.Services.Nav.Ekaer.Models;
|
||||
using FruitBank.Common.Dtos;
|
||||
using FruitBank.Common.Entities;
|
||||
using Nop.Core.Domain.Customers;
|
||||
using TradeReasonType = AyCode.Services.Nav.Ekaer.Models.Common.TradeReasonType;
|
||||
using TradeType = AyCode.Services.Nav.Ekaer.Models.Common.TradeType;
|
||||
|
||||
|
|
@ -10,15 +12,28 @@ namespace FruitBank.Common.Services.Ekaer;
|
|||
|
||||
/// <inheritdoc cref="IShippingToEkaerMapper"/>
|
||||
/// <remarks>
|
||||
/// Tiszta (állapotmentes) leképező. A feladót és a saját céget egységesen <see cref="ICompanyInfoBase"/>-ként kezeli.
|
||||
/// A <c>TradeType</c>/<c>TradeReasonType</c> enumokat aliasszal hozzuk be a <c>Models.TradeCardType</c> osztály
|
||||
/// és a <c>Models.Common.TradeCardType</c> enum névütközése miatt.
|
||||
/// Tiszta (állapotmentes) leképező. A bejövő (<c>ShippingDocument</c>-csoport) és a kimenő (<c>OrderDto</c>) forrás
|
||||
/// előbb egy irány-független <see cref="EkaerConsignment"/>-re képződik (<see cref="ToConsignment(IReadOnlyCollection{ShippingDocument}, EkaerCompanyInfo)"/>
|
||||
/// / <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>
|
||||
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";
|
||||
|
||||
/// <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)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(shipping);
|
||||
|
|
@ -30,86 +45,202 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
|
|||
// Granularitás: egy ShippingDocument → egy tradeCard (lásd EKAER_TODO #5).
|
||||
foreach (var document in shipping.ShippingDocuments ?? [])
|
||||
{
|
||||
document.Shipping ??= shipping; // a vontató/fuvarozó a Shippingről jön
|
||||
index++;
|
||||
operations.Add(new TradeCardOperationType
|
||||
{
|
||||
Index = index,
|
||||
Operation = operation,
|
||||
TradeCard = BuildTradeCard(shipping, document, company),
|
||||
TradeCard = BuildTradeCard(ToConsignment([document], company)),
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
TradeType = ResolveTradeType(seller),
|
||||
TradeType = ResolveTradeType(consignment),
|
||||
ModByCarrierEnabled = false, // mi jelentünk; a fuvarozó alapból nem módosíthat
|
||||
|
||||
// Feladó / eladó = a beszállító
|
||||
SellerName = seller?.Name,
|
||||
SellerVatNumber = NormalizeVatNumber(seller?.TaxId),
|
||||
SellerCountry = NormalizeCountryCode(seller?.CountryCode, 2),
|
||||
SellerAddress = Truncate(seller?.FullAddress, 200),
|
||||
SellerName = consignment.Seller.Name,
|
||||
SellerVatNumber = NormalizeVatNumber(consignment.Seller.VatNumber),
|
||||
SellerCountry = NormalizeCountryCode(consignment.Seller.CountryCode, 2),
|
||||
SellerAddress = Truncate(consignment.Seller.FullAddress, 200),
|
||||
|
||||
// Címzett = a bejelentő saját cége (bejövő relációban)
|
||||
DestinationName = company.Name,
|
||||
DestinationVatNumber = NormalizeVatNumber(company.TaxId),
|
||||
DestinationCountry = NormalizeCountryCode(company.CountryCode, 2),
|
||||
DestinationAddress = Truncate(company.FullAddress, 200),
|
||||
DestinationName = consignment.Buyer.Name,
|
||||
DestinationVatNumber = NormalizeVatNumber(consignment.Buyer.VatNumber),
|
||||
DestinationCountry = NormalizeCountryCode(consignment.Buyer.CountryCode, 2),
|
||||
DestinationAddress = Truncate(consignment.Buyer.FullAddress, 200),
|
||||
|
||||
// Fuvarozó (Shipping.CargoPartner). Regisztrált EKAER-azonosító nincs, csak szöveges név.
|
||||
CarrierText = shipping.CargoPartner?.Name,
|
||||
CarrierText = consignment.CarrierName,
|
||||
|
||||
// Lerakodás = saját telephely (a cégadatból); felrakodás = a beszállító telephelye.
|
||||
UnloadLocation = company.UnloadLocation,
|
||||
LoadLocation = BuildLoadLocation(seller),
|
||||
// A helyek kész NAV LocationType-ok (a saját telephely érintetlenül) — átengedve.
|
||||
LoadLocation = consignment.LoadLocation,
|
||||
UnloadLocation = consignment.UnloadLocation,
|
||||
};
|
||||
|
||||
// Vonó jármű + vontatmány: az EKÁER külön bejegyzésként kéri (vehicle / vehicle2).
|
||||
if (shipping.CargoTruck != null) tradeCard.Vehicle = BuildVehicle(shipping.CargoTruck);
|
||||
if (shipping.CargoTrailer != null) tradeCard.Vehicle2 = BuildVehicle(shipping.CargoTrailer);
|
||||
if (consignment.Vehicle != null) tradeCard.Vehicle = consignment.Vehicle;
|
||||
if (consignment.Trailer != null) tradeCard.Vehicle2 = consignment.Trailer;
|
||||
|
||||
foreach (var item in document.ShippingItems ?? []) tradeCard.Items.Add(BuildItem(item));
|
||||
foreach (var line in consignment.Lines) tradeCard.Items.Add(BuildItem(line));
|
||||
return tradeCard;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Belföldi feladó (HU) → <c>D</c> (belföld-belföld), egyébként → <c>I</c> (import). A NAV EKÁER magyar,
|
||||
/// így a belföld mindig HU; az export (<c>E</c>) jelenleg nincs leképezve (lásd EKAER_TODO #1, #7).
|
||||
/// </summary>
|
||||
private static TradeType ResolveTradeType(ICompanyInfoBase? seller)
|
||||
=> string.Equals(seller?.CountryCode, HomeCountry, StringComparison.OrdinalIgnoreCase)
|
||||
? TradeType.D
|
||||
: TradeType.I;
|
||||
|
||||
private static TradeCardItemType BuildItem(ShippingItem item) => new()
|
||||
/// <summary>Belföld (a feladó és a címzett országkódja megegyezik) → <c>D</c>; eltérő országok → kimenőnél
|
||||
/// export (<c>E</c>), bejövőnél import (<c>I</c>).</summary>
|
||||
private static TradeType ResolveTradeType(EkaerConsignment c)
|
||||
{
|
||||
ItemExternalId = item.Id.ToString(),
|
||||
// Bejövő áru = beszerzés → A. (Enum: S=értékesítés, A=beszerzés, W=bérmunka, O=egyéb.) Lásd EKAER_TODO #9.
|
||||
TradeReason = TradeReasonType.A,
|
||||
ProductVtsz = item.ProductDto?.Gtin, // VTSZ — átmenetileg a Gtin oszlopban (FBANKAPP-DMODEL-I-P6X4)
|
||||
ProductName = item.ProductName,
|
||||
Weight = (decimal)item.MeasuredGrossWeight, // bruttó tömeg kg-ban (lásd EKAER_TODO #4)
|
||||
// Value (HUF): a deviza/FX tisztázásáig NEM töltjük (a mező opcionális). Lásd EKAER_TODO #3, #10.
|
||||
if (string.Equals(c.Seller.CountryCode?.Trim(), c.Buyer.CountryCode?.Trim(), StringComparison.OrdinalIgnoreCase))
|
||||
return TradeType.D;
|
||||
return c.IsOutgoing ? TradeType.E : TradeType.I;
|
||||
}
|
||||
|
||||
private static TradeCardItemType BuildItem(EkaerLine line) => new()
|
||||
{
|
||||
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),
|
||||
Country = NormalizeCountryCode(truck.CountryCode, 3),
|
||||
Name = partner?.Name,
|
||||
VatNumber = partner?.TaxId,
|
||||
CountryCode = partner?.CountryCode,
|
||||
FullAddress = partner?.FullAddress,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Felrakodási hely a beszállító adataiból. (Magyar feladónál a NAV a Phone/Email-t is kéri, ami az
|
||||
/// entitásban nincs — lásd EKAER_TODO #6.)
|
||||
/// </summary>
|
||||
private static LocationType? BuildLoadLocation(ICompanyInfoBase? seller)
|
||||
private static EkaerEndpoint CompanyEndpoint(EkaerCompanyInfo company) => new()
|
||||
{
|
||||
Name = company.Name,
|
||||
VatNumber = company.TaxId,
|
||||
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;
|
||||
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>
|
||||
private static string? NormalizeVatNumber(string? value)
|
||||
{
|
||||
|
|
@ -155,6 +328,15 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
|
|||
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? Truncate(string? value, int maxLen)
|
||||
|
|
|
|||
|
|
@ -112,6 +112,21 @@ public class SignalRTags : AcSignalRTags
|
|||
public const int GetStockTakingItemsByStockTakingId = 178;
|
||||
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 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ó
|
||||
|
||||
**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ő**:
|
||||
- **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]
|
||||
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 ------------------------------------------------------------
|
||||
|
||||
|
|
@ -67,18 +69,42 @@ public sealed class ShippingToEkaerMapperTests
|
|||
PostalCode = "1102",
|
||||
City = "Budapest",
|
||||
Street = "Raktar utca 5",
|
||||
UnloadLocation = new LocationType
|
||||
Site = new LocationType
|
||||
{
|
||||
Name = "FruitBank Raktár",
|
||||
VatNumber = "98765432-2-41",
|
||||
Phone = "+36301234567", // NAV-formátum: + / 06 prefix kötelező
|
||||
Email = "raktar@fruitbank.hu",
|
||||
Country = "HU",
|
||||
ZipCode = "1102",
|
||||
ZipCode = "1239",
|
||||
City = "Budapest",
|
||||
Street = "Raktar utca",
|
||||
StreetNumber = "5",
|
||||
Street = "Nagykőrösi út",
|
||||
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 ----------------------------------------------
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -166,7 +192,7 @@ public sealed class ShippingToEkaerMapperTests
|
|||
Assert.AreEqual("FruitBank Kft", tradeCard.DestinationName);
|
||||
Assert.AreEqual("HU", tradeCard.DestinationCountry);
|
||||
StringAssert.Contains(tradeCard.DestinationAddress, "Budapest", "a destinationAddress a company FullAddress-éből jön");
|
||||
Assert.AreSame(company.UnloadLocation, tradeCard.UnloadLocation, "a lerakodási hely a cégadatból jön");
|
||||
Assert.AreSame(company.Site, tradeCard.UnloadLocation, "a lerakodási hely a cég telephelyéből jön");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -212,4 +238,55 @@ public sealed class ShippingToEkaerMapperTests
|
|||
[TestMethod]
|
||||
public void MapShipping_NullCompany_Throws()
|
||||
=> 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));
|
||||
}
|
||||
|
||||
#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
|
||||
[TestMethod]
|
||||
public async Task GetShippingsTest()
|
||||
|
|
@ -299,7 +156,7 @@ namespace FruitBankHybrid.Shared.Tests
|
|||
else
|
||||
{
|
||||
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}");
|
||||
|
||||
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.IsTrue(shippingItem.NetWeightOnDocument > 0, "NetWeightOnDocument == 0");
|
||||
Assert.IsTrue(shippingItem.GrossWeightOnDocument > 0, "GrossWeightOnDocument == 0");
|
||||
Assert.IsGreaterThan(0, shippingItem.QuantityOnDocument, "QuantityOnDocument == 0");
|
||||
Assert.IsGreaterThan(0, shippingItem.NetWeightOnDocument, "NetWeightOnDocument == 0");
|
||||
Assert.IsGreaterThan(0, shippingItem.GrossWeightOnDocument, "GrossWeightOnDocument == 0");
|
||||
|
||||
return shippingItem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
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.IsFalse(ekaerHistory.IsOutgoing);
|
||||
Assert.IsNotNull(ekaerHistory.Mappings, $"Mappings null (loadRelations?); shippingDocument.Id: {shippingDocument.Id}");
|
||||
Assert.AreEqual(shippingDocument.Id, ekaerHistory.Mappings.Single().ForeignKey, $"A mapping nem a forrás-dokumentumra mutat; shippingDocument.Id: {shippingDocument.Id}");
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// A generálás mostantól EkaerHistory-Id alapú → előbb a (mapping-elt) rekordot hozzuk létre.
|
||||
var created = await _signalRClient.CreateEkaerHistory(shippingDocument.Id, false);
|
||||
Assert.IsNotNull(created, $"CreateEkaerHistory null; shippingDocument.Id: {shippingDocument.Id}");
|
||||
var ekaerHistory = await _signalRClient.GenerateEkaerXmlDocument(created.Id);
|
||||
|
||||
// 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.IsFalse(ekaerHistory.IsOutgoing);
|
||||
Assert.AreEqual(shippingDocument.Id, ekaerHistory.Mappings!.Single().ForeignKey, $"A mapping nem a forrás-dokumentumra mutat; shippingDocument.Id: {shippingDocument.Id}");
|
||||
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;
|
||||
|
||||
// Generálás EkaerHistory-Id alapján → előbb a rekord (mapping-gel), majd kétszeri generálás ugyanarra az Id-ra.
|
||||
var created = await _signalRClient.CreateEkaerHistory(shippingDocumentId, false);
|
||||
Assert.IsNotNull(created);
|
||||
|
||||
var first = await _signalRClient.GenerateEkaerXmlDocument(created.Id);
|
||||
var second = await _signalRClient.GenerateEkaerXmlDocument(created.Id);
|
||||
|
||||
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)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -88,6 +88,7 @@ public class FullProcessModel
|
|||
public List<OrderDto> Orders { get; set; }
|
||||
public List<PreOrder> PreOrders { get; set; }
|
||||
public List<StockTaking> StockTakings { get; set; }
|
||||
public List<EkaerHistory> EkaerHistories { get; set; }
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@using AyCode.Blazor.Components.Components.Grids
|
||||
@using AyCode.Blazor.Components.Components.Grids
|
||||
@using AyCode.Utils.Extensions
|
||||
@using FruitBank.Common.Dtos
|
||||
@using FruitBank.Common.Entities
|
||||
|
|
@ -26,24 +26,65 @@
|
|||
<Columns>
|
||||
<MgGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true"/>
|
||||
<DxGridDataColumn FieldName="PartnerId" Caption="Partner" Visible="@(!ParentDataItemIsPartner)" ReadOnly="@ParentDataItemIsPartner">
|
||||
<EditSettings>
|
||||
<DxComboBoxSettings Data="Partners"
|
||||
ValueFieldName="Id"
|
||||
TextFieldName="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>
|
||||
</DxComboBoxSettings>
|
||||
</EditSettings>
|
||||
<CellDisplayTemplate>
|
||||
@{
|
||||
var sdPd = (ShippingDocument)context.DataItem;
|
||||
<text>@(PartnersDictById.GetValueOrDefault(sdPd.PartnerId)?.Name)</text>
|
||||
}
|
||||
</CellDisplayTemplate>
|
||||
<CellEditTemplate>
|
||||
@{
|
||||
var sdPe = (ShippingDocument)context.EditModel;
|
||||
}
|
||||
<DxComboBox TData="Partner" TValue="int"
|
||||
Data="@Partners"
|
||||
Value="@sdPe.PartnerId"
|
||||
ValueChanged="@((int v) => OnPartnerChanged(sdPe, v))"
|
||||
ValueExpression="@(() => sdPe.PartnerId)"
|
||||
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 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">
|
||||
<EditSettings>
|
||||
<DxComboBoxSettings Data="Shippings"
|
||||
|
|
@ -185,7 +226,10 @@
|
|||
private LoggerClient<GridShippingDocument> _logger = null!;
|
||||
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());
|
||||
|
||||
|
|
@ -223,9 +267,13 @@
|
|||
{
|
||||
if (Shippings == null && ParentDataItem is Shipping shippingParent) Shippings = [shippingParent];
|
||||
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;
|
||||
|
||||
using (await ObjectLock.GetSemaphore<ShippingDocument>().UseWaitAsync())
|
||||
|
|
@ -269,6 +317,16 @@
|
|||
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)
|
||||
{
|
||||
_activeTabIndex = activeTabIndex;
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@
|
|||
<DxGridDataColumn FieldName="City" />
|
||||
<DxGridDataColumn FieldName="Street" />
|
||||
|
||||
<DxGridDataColumn FieldName="Created" ReadOnly="true" />
|
||||
<DxGridDataColumn FieldName="Modified" ReadOnly="true" />
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@
|
|||
<DxGridDataColumn FieldName="@nameof(CargoTruck.CountryCode)" />
|
||||
<DxGridDataColumn FieldName="@nameof(CargoTruck.IsTrailer)" />
|
||||
|
||||
<DxGridDataColumn FieldName="Created" ReadOnly="true" />
|
||||
<DxGridDataColumn FieldName="Modified" ReadOnly="true" />
|
||||
<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>
|
||||
<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,313 @@
|
|||
@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 Caption="ForeignKey(s)" AllowSort="false" AllowGroup="false">
|
||||
@* A lefedett forrás-rekordok (ShippingDocument/Order Id-k) a betöltött Mappings-ből összefűzve —
|
||||
egyből látszik, ha több dokumentum van egy EKÁER-deklaráció alá vonva. *@
|
||||
<CellDisplayTemplate>
|
||||
@{
|
||||
var row = (EkaerHistory)context.DataItem;
|
||||
}
|
||||
@if (row.Mappings is { Count: > 0 } maps)
|
||||
{
|
||||
var noun = row.IsOutgoing ? "rendelés" : "szállítólevél";
|
||||
<span title="@(maps.Count > 1 ? $"{maps.Count} {noun} egy EKÁER alá vonva" : $"1 {noun}")">@string.Join(", ", maps.Select(m => $"#{m.ForeignKey}"))</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>—</span>
|
||||
}
|
||||
</CellDisplayTemplate>
|
||||
</DxGridDataColumn>
|
||||
<DxGridDataColumn FieldName="@nameof(EkaerHistory.ShippingDate)" Caption="Dátum" DisplayFormat="yyyy.MM.dd" ReadOnly="true" />
|
||||
<DxGridDataColumn FieldName="@nameof(EkaerHistory.Partner)" Caption="Partner" ReadOnly="true" />
|
||||
<DxGridDataColumn FieldName="@nameof(EkaerHistory.IsOutgoing)" Caption="Kimenő?" 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.Id);
|
||||
|
||||
if (updated == null)
|
||||
{
|
||||
_logger.Error($"GenerateEkaerXmlDocument null választ adott; EkaerHistory #{ekaerHistory.Id}");
|
||||
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; EkaerHistory #{ekaerHistory.Id}", 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,75 @@
|
|||
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];
|
||||
|
||||
// Detail-mód: a forrás-rekord (Shipping/Order/ShippingDocument) EKÁER-deklarációi a mappingen át.
|
||||
// Megj.: az EkaerHistory már NEM hordoz per-szülő mezőt (a kapcsolat az EkaerHistoryMapping junctionben él),
|
||||
// ezért a kliens-oldali KeyFieldNameToParentId itt nem alkalmazható — a szerver-oldali szűrés
|
||||
// (GetEkaerHistoriesByForeignKey → mapping) adja a szülőhöz tartozó sorokat. (Jelenleg nincs detail-használat.)
|
||||
GetAllMessageTag = SignalRTags.GetEkaerHistoriesByForeignKey;
|
||||
}
|
||||
|
||||
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="TaxId" />
|
||||
<DxGridDataColumn FieldName="CertificationNumber" />
|
||||
<DxGridDataColumn FieldName="@nameof(CargoPartner.Currency)" />
|
||||
<DxGridDataColumn FieldName="@nameof(Partner.Currency)" />
|
||||
<DxGridDataColumn FieldName="@nameof(Partner.CountryCode)" />
|
||||
<DxGridDataColumn FieldName="PostalCode" />
|
||||
<DxGridDataColumn FieldName="@nameof(Partner.Country)" />
|
||||
|
|
@ -38,14 +38,15 @@
|
|||
<DxGridDataColumn FieldName="City" />
|
||||
<DxGridDataColumn FieldName="Street" />
|
||||
|
||||
<DxGridDataColumn FieldName="Created" ReadOnly="true" />
|
||||
<DxGridDataColumn FieldName="Modified" ReadOnly="true" />
|
||||
<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 partner = ((Partner)context.DataItem);
|
||||
var partnerDepots = partner?.PartnerDepots ?? [];
|
||||
var shippingDocuments = partner?.ShippingDocuments ?? [];
|
||||
|
||||
<DxTabs>
|
||||
|
|
@ -62,6 +63,13 @@
|
|||
<GridShippingItemTemplate ShippingItems="@observableShippingItems" ParentDataItem="@partner" />
|
||||
}
|
||||
</DxTabPage>
|
||||
|
||||
<DxTabPage Text="Telephelyek">
|
||||
@{
|
||||
var observablePartnerDepots = new AcObservableCollection<PartnerDepot>(partnerDepots);
|
||||
<GridPartnerDepot PartnerDepots="observablePartnerDepots" ParentDataItem="@partner"></GridPartnerDepot>
|
||||
}
|
||||
</DxTabPage>
|
||||
</DxTabs>
|
||||
}
|
||||
</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="CargoCompany" Caption="Fuvarozó" ReadOnly="false" />
|
||||
<DxGridDataColumn FieldName="IsAllMeasured" Caption="Mérések kész" ReadOnly="true" />
|
||||
<DxGridDataColumn FieldName="Created" Caption="Létrehozva" ReadOnly="true" />
|
||||
<DxGridDataColumn FieldName="Modified" Caption="Módosítva" 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" DisplayFormat="yyyy.MM.dd hh:mm" />
|
||||
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
|
||||
</Columns>
|
||||
<DetailRowTemplate>
|
||||
|
|
|
|||
|
|
@ -70,6 +70,8 @@ public class ProductDtoTable(FruitBankSignalRClient fruitBankSignalRClient) : Ac
|
|||
|
||||
//Clear();
|
||||
this.Replace(items);
|
||||
//this.Replace(items.Cast<ProductDtoTableItem>());
|
||||
|
||||
//foreach (var productDto in items)
|
||||
//{
|
||||
// this.UpdateCollection(productDto, false);
|
||||
|
|
|
|||
|
|
@ -43,9 +43,6 @@
|
|||
<Reference Include="AyCode.Services">
|
||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Services.dll</HintPath>
|
||||
</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">
|
||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Utils.dll</HintPath>
|
||||
</Reference>
|
||||
|
|
|
|||
|
|
@ -63,6 +63,12 @@
|
|||
<span class="icon shippings-icon" aria-hidden="true"></span> Szállítmányok
|
||||
</NavLink>
|
||||
</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
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@
|
|||
--icon-mask-image: var(--icon-login-mask-image);
|
||||
}
|
||||
|
||||
::deep .ekaer-icon {
|
||||
--icon-mask-image: var(--icon-ekaer-mask-image);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: 0.9rem;
|
||||
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.Loggers;
|
||||
using AyCode.Services.Server.SignalRs;
|
||||
using AyCode.Services.SignalRs;
|
||||
using AyCode.Utils.Extensions;
|
||||
using DevExpress.Blazor;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using AyCode.Core.Extensions;
|
||||
using AyCode.Core.Helpers;
|
||||
using AyCode.Core.Loggers;
|
||||
using AyCode.Services.Server.SignalRs;
|
||||
using AyCode.Services.SignalRs;
|
||||
using DevExpress.Utils.About;
|
||||
using FruitBank.Common;
|
||||
|
|
@ -66,6 +65,26 @@ namespace FruitBankHybrid.Shared.Services.SignalRs
|
|||
public Task<Partner?> UpdatePartner(Partner partner) => PostDataAsync(SignalRTags.UpdatePartner, 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 ekaerHistoryId) => GetByIdAsync<EkaerHistory?>(SignalRTags.GenerateEkaerXmlDocument, ekaerHistoryId);
|
||||
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
|
||||
public Task<List<CargoPartner>?> GetCargoPartners() => GetAllAsync<List<CargoPartner>>(SignalRTags.GetCargoPartners);
|
||||
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]);
|
||||
|
||||
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)
|
||||
=> GetAllAsync<List<ShippingDocument>>(SignalRTags.GetShippingDocumentsByShippingId, [partnerId]);
|
||||
=> GetAllAsync<List<ShippingDocument>>(SignalRTags.GetShippingDocumentsByPartnerId, [partnerId]);
|
||||
|
||||
public Task<ShippingDocument?> AddShippingDocument(ShippingDocument shippingDocument)
|
||||
=> PostDataAsync(SignalRTags.AddShippingDocument, shippingDocument);
|
||||
|
|
@ -185,9 +204,15 @@ namespace FruitBankHybrid.Shared.Services.SignalRs
|
|||
#endregion Customer
|
||||
|
||||
#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()
|
||||
//=> GetAllAsync<List<ProductDtoTableItem>>(SignalRTags.GetProductDtos);
|
||||
{
|
||||
var dtos = await GetProductDtos();
|
||||
if (dtos == null) return null;
|
||||
|
|
@ -196,14 +221,12 @@ namespace FruitBankHybrid.Shared.Services.SignalRs
|
|||
return SignalRSerializationHelper.DeserializeFromBinary<List<ProductDtoTableItem>>(bytes);
|
||||
}
|
||||
|
||||
public Task<List<ProductDto>?> GetProductDtos()
|
||||
=> GetAllAsync<List<ProductDto>>(SignalRTags.GetProductDtos);
|
||||
public Task<List<ProductDto>?> GetProductDtos() => GetAllAsync<List<ProductDto>>(SignalRTags.GetProductDtos);
|
||||
|
||||
//public Task<List<MeasuringProductDto>?> GetAllMeasuringProductDtos()
|
||||
// => GetAllAsync<List<MeasuringProductDto>>(SignalRTags.GetAllMeasuringProductDtos);
|
||||
|
||||
public Task<ProductDto?> GetProductDtoById(int productId)
|
||||
=> GetByIdAsync<ProductDto?>(SignalRTags.GetMeasuringProductDtoById, productId);
|
||||
public Task<ProductDto?> GetProductDtoById(int productId) => GetByIdAsync<ProductDto?>(SignalRTags.GetMeasuringProductDtoById, productId);
|
||||
|
||||
#endregion Product
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using AyCode.Core.Helpers;
|
||||
using AyCode.Core.Interfaces;
|
||||
using AyCode.Core.Loggers;
|
||||
using AyCode.Services.Server.SignalRs;
|
||||
using AyCode.Services.SignalRs;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,6 @@
|
|||
"ProtocolMode": "Bytes",
|
||||
"BufferSize": 4096,
|
||||
"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-shippings-mask-image: url("images/shippings-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 {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
|
||||
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||
|
||||
<RunAOTCompilation>false</RunAOTCompilation>
|
||||
<RunAOTCompilation>true</RunAOTCompilation>
|
||||
<WasmStripILAfterAOT>true</WasmStripILAfterAOT>
|
||||
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
||||
|
||||
|
|
@ -58,33 +58,18 @@
|
|||
<Reference Include="AyCode.Core">
|
||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.dll</HintPath>
|
||||
</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">
|
||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.dll</HintPath>
|
||||
</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">
|
||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.dll</HintPath>
|
||||
</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">
|
||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Models.dll</HintPath>
|
||||
</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">
|
||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Services.dll</HintPath>
|
||||
</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">
|
||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Utils.dll</HintPath>
|
||||
</Reference>
|
||||
|
|
@ -94,7 +79,7 @@
|
|||
<Reference Include="DevExpress.Data.v25.1"></Reference>
|
||||
<Reference Include="DevExpress.Utils.v25.1"></Reference>
|
||||
<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>
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,6 @@
|
|||
"ProtocolMode": "Bytes",
|
||||
"BufferSize": 4096,
|
||||
"FlushPolicy": "Coalesced",
|
||||
"FlushTimeout": "00:00:10"
|
||||
"FlushTimeout": "00:00:15"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,6 @@
|
|||
"ProtocolMode": "Bytes",
|
||||
"BufferSize": 4096,
|
||||
"FlushPolicy": "Coalesced",
|
||||
"FlushTimeout": "00:00:10"
|
||||
"FlushTimeout": "00:00:15"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
<ApplicationVersion>1</ApplicationVersion>
|
||||
<PackageVersion>$(ApplicationDisplayVersion)</PackageVersion>
|
||||
|
||||
<RunAOTCompilation>false</RunAOTCompilation>
|
||||
<RunAOTCompilation>true</RunAOTCompilation>
|
||||
<WasmStripILAfterAOT>true</WasmStripILAfterAOT>
|
||||
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
||||
|
||||
|
|
@ -47,9 +47,9 @@
|
|||
</PropertyGroup>
|
||||
|
||||
|
||||
<PropertyGroup Condition="$(TargetFramework.Contains('-android'))">
|
||||
<!--<PropertyGroup Condition="$(TargetFramework.Contains('-android'))">
|
||||
<RunAOTCompilation>false</RunAOTCompilation>
|
||||
</PropertyGroup>
|
||||
</PropertyGroup>-->
|
||||
|
||||
<PropertyGroup Condition="$(TargetFramework.Contains('-ios')) and '$(Configuration)' == 'Release'">
|
||||
<UseInterpreter>true</UseInterpreter>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
<Version>10</Version>
|
||||
<SourceModelProvider>
|
||||
<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>
|
||||
</SourceModelProvider>
|
||||
<TargetModelProvider>
|
||||
<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>
|
||||
</TargetModelProvider>
|
||||
<SchemaCompareSettingsService>
|
||||
|
|
@ -357,7 +357,7 @@
|
|||
</PropertyElementName>
|
||||
<PropertyElementName>
|
||||
<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>
|
||||
<Name>TreatVerificationErrorsAsWarnings</Name>
|
||||
|
|
@ -1002,17 +1002,13 @@
|
|||
<Name>dbo</Name>
|
||||
<Name>PK_AvalaraItemClassification</Name>
|
||||
</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">
|
||||
<Name>dbo</Name>
|
||||
<Name>PK_FacebookPixelConfiguration</Name>
|
||||
</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>GoogleAuthenticatorRecord</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">
|
||||
<Name>dbo</Name>
|
||||
|
|
@ -1020,12 +1016,16 @@
|
|||
</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>
|
||||
<Name>GoogleAuthenticatorRecord</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">
|
||||
<Name>dbo</Name>
|
||||
<Name>PK_TaxTransactionLog</Name>
|
||||
</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">
|
||||
<Name>dbo</Name>
|
||||
<Name>vOrder</Name>
|
||||
|
|
|
|||
Loading…
Reference in New Issue