EKÁER: add mapping table, group logic, atomic ops
Refactored EKÁER logic to use EkaerHistoryMapping junction table, decoupling EkaerHistory from direct foreign keys. Grouped inbound declarations by (ShippingId, PartnerId, PartnerDepotId) and enabled atomic creation of histories with mappings. Updated controller endpoints, DI registration, and documentation. Improved error handling, logging, and adjusted method signatures for the new mapping-based approach.
This commit is contained in:
parent
1d56eba8ec
commit
c71bf2fcd8
|
|
@ -275,7 +275,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
||||||
public async Task<List<EkaerHistory>> GetEkaerHistories(EkaerHistoryFilter ekaerHistoryFilter)
|
public async Task<List<EkaerHistory>> GetEkaerHistories(EkaerHistoryFilter ekaerHistoryFilter)
|
||||||
{
|
{
|
||||||
_logger.Detail($"GetEkaerHistories invoked; ekaerHistoryFilter: {ekaerHistoryFilter}");
|
_logger.Detail($"GetEkaerHistories invoked; ekaerHistoryFilter: {ekaerHistoryFilter}");
|
||||||
return await ctx.EkaerHistories.GetByFilter(ekaerHistoryFilter).ToListAsync();
|
return await ctx.EkaerHistories.GetByFilter(ekaerHistoryFilter, true).ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[SignalR(SignalRTags.GetEkaerHistoryCount)]
|
[SignalR(SignalRTags.GetEkaerHistoryCount)]
|
||||||
|
|
@ -289,14 +289,19 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
||||||
public async Task<EkaerHistory> GetEkaerHistoryById(int id)
|
public async Task<EkaerHistory> GetEkaerHistoryById(int id)
|
||||||
{
|
{
|
||||||
_logger.Detail($"GetEkaerHistoryById invoked; id: {id}");
|
_logger.Detail($"GetEkaerHistoryById invoked; id: {id}");
|
||||||
return await ctx.EkaerHistories.GetByIdAsync(id);
|
return await ctx.EkaerHistories.GetByIdAsync(id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SignalR(SignalRTags.GetEkaerHistoriesByForeignKey)]
|
[SignalR(SignalRTags.GetEkaerHistoriesByForeignKey)]
|
||||||
public async Task<List<EkaerHistory>> GetEkaerHistoriesByForeignKey(int foreignKey)
|
public async Task<List<EkaerHistory>> GetEkaerHistoriesByForeignKey(int foreignKey)
|
||||||
{
|
{
|
||||||
_logger.Detail($"GetEkaerHistoriesByForeignKey invoked; foreignKey: {foreignKey}");
|
_logger.Detail($"GetEkaerHistoriesByForeignKey invoked; foreignKey: {foreignKey}");
|
||||||
return await ctx.EkaerHistories.GetByForeignKey(foreignKey).ToListAsync();
|
|
||||||
|
// A forrás-rekordot (ShippingDocument/Order Id) lefedő EKÁER-deklaráció(k), a Mappings-szel betöltve — EGY query
|
||||||
|
// (EXISTS a mapping-táblára), nem kettő.
|
||||||
|
return await ctx.EkaerHistories.GetAll(true)
|
||||||
|
.Where(eh => ctx.EkaerHistoryMappings.Table.Any(m => m.EkaerHistoryId == eh.Id && m.ForeignKey == foreignKey))
|
||||||
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[SignalR(SignalRTags.AddEkaerHistory)]
|
[SignalR(SignalRTags.AddEkaerHistory)]
|
||||||
|
|
@ -307,7 +312,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
||||||
_logger.Detail($"AddEkaerHistory invoked; id: {ekaerHistory.Id}");
|
_logger.Detail($"AddEkaerHistory invoked; id: {ekaerHistory.Id}");
|
||||||
|
|
||||||
await ctx.EkaerHistories.InsertAsync(ekaerHistory);
|
await ctx.EkaerHistories.InsertAsync(ekaerHistory);
|
||||||
return await ctx.EkaerHistories.GetByIdAsync(ekaerHistory.Id);
|
return await ctx.EkaerHistories.GetByIdAsync(ekaerHistory.Id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SignalR(SignalRTags.UpdateEkaerHistory)]
|
[SignalR(SignalRTags.UpdateEkaerHistory)]
|
||||||
|
|
@ -318,48 +323,57 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
||||||
_logger.Detail($"UpdateEkaerHistory invoked; id: {ekaerHistory.Id}");
|
_logger.Detail($"UpdateEkaerHistory invoked; id: {ekaerHistory.Id}");
|
||||||
|
|
||||||
await ctx.EkaerHistories.UpdateAsync(ekaerHistory);
|
await ctx.EkaerHistories.UpdateAsync(ekaerHistory);
|
||||||
return await ctx.EkaerHistories.GetByIdAsync(ekaerHistory.Id);
|
return await ctx.EkaerHistories.GetByIdAsync(ekaerHistory.Id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SignalR(SignalRTags.GenerateEkaerXmlDocument)]
|
[SignalR(SignalRTags.GenerateEkaerXmlDocument)]
|
||||||
public async Task<EkaerHistory> GenerateEkaerXmlDocument(int foreignKey, bool isOutgoing)
|
public async Task<EkaerHistory> GenerateEkaerXmlDocument(int ekaerHistoryId)
|
||||||
{
|
{
|
||||||
_logger.Detail($"GenerateEkaerXmlDocument invoked; foreignKey: {foreignKey}; isOutgoing: {isOutgoing}");
|
_logger.Detail($"GenerateEkaerXmlDocument invoked; ekaerHistoryId: {ekaerHistoryId}");
|
||||||
|
|
||||||
// Upsert: (ForeignKey + IsOutgoing) párra EGY rekord — az újragenerálás nem duplikál.
|
var ekaerHistory = await ctx.EkaerHistories.GetByIdAsync(ekaerHistoryId, true)
|
||||||
var ekaerHistory = await ctx.EkaerHistories.GetByForeignKey(foreignKey).FirstOrDefaultAsync(eh => eh.IsOutgoing == isOutgoing);
|
?? throw new ArgumentException($"EkaerHistory not found; id: {ekaerHistoryId}", nameof(ekaerHistoryId));
|
||||||
|
|
||||||
if (!isOutgoing)
|
// A deklarációhoz tartozó forrás-rekordok a BETÖLTÖTT mappingből (bejövő: N szállítólevél; kimenő: 1 rendelés).
|
||||||
|
var foreignKeys = ekaerHistory.Mappings?.Select(m => m.ForeignKey).ToList() ?? [];
|
||||||
|
if (foreignKeys.Count == 0) throw new InvalidOperationException($"EkaerHistory #{ekaerHistoryId} has no mapped source records.");
|
||||||
|
|
||||||
|
if (!ekaerHistory.IsOutgoing)
|
||||||
{
|
{
|
||||||
// Bejövő: a GetAll(true) a mapperhez kellő teljes gráfot tölti (Partner, Items+ProductDto, Shipping→járművek).
|
// Bejövő: a csoport ÖSSZES szállítólevele EGY tradeCard-dá (összevont tömeg/érték); GetAll(true) = teljes gráf.
|
||||||
var shippingDocument = await ctx.ShippingDocuments.GetByIdAsync(foreignKey, true)
|
var documents = await ctx.ShippingDocuments.GetAll(true).Where(sd => foreignKeys.Contains(sd.Id)).ToListAsync();
|
||||||
?? throw new ArgumentException($"ShippingDocument not found; id: {foreignKey}", nameof(foreignKey));
|
if (documents.Count == 0) throw new InvalidOperationException($"EkaerHistory #{ekaerHistoryId}: no ShippingDocuments found for the mapped ids.");
|
||||||
ekaerHistory = fruitBankEkaerService.GenerateEkaerXmlDocument(shippingDocument, ekaerHistory);
|
ekaerHistory = fruitBankEkaerService.GenerateEkaerXmlDocument(documents, ekaerHistory);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Kimenő: a GetByIdAsync(true) betölti a Customer-t, tételeket, palettákat (GrossWeight) és a ProductDto-t.
|
// Kimenő: egy rendelés (a GetByIdAsync(true) tölti a Customer-t, tételeket, palettákat (GrossWeight), ProductDto-t).
|
||||||
var order = await ctx.OrderDtos.GetByIdAsync(foreignKey, true)
|
var order = await ctx.OrderDtos.GetByIdAsync(foreignKeys[0], true)
|
||||||
?? throw new ArgumentException($"Order not found; id: {foreignKey}", nameof(foreignKey));
|
?? throw new ArgumentException($"Order not found; id: {foreignKeys[0]}");
|
||||||
ekaerHistory = fruitBankEkaerService.GenerateEkaerXmlDocument(order, ekaerHistory);
|
ekaerHistory = fruitBankEkaerService.GenerateEkaerXmlDocument(order, ekaerHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ekaerHistory.Id > 0) await ctx.EkaerHistories.UpdateAsync(ekaerHistory);
|
await ctx.EkaerHistories.UpdateAsync(ekaerHistory);
|
||||||
else await ctx.EkaerHistories.InsertAsync(ekaerHistory);
|
// A friss sort adjuk vissza (frissített Modified + a betöltött Mappings); a sor biztosan létezik (épp updateltük).
|
||||||
|
return await ctx.EkaerHistories.GetByIdAsync(ekaerHistory.Id, true);
|
||||||
return await ctx.EkaerHistories.GetByIdAsync(ekaerHistory.Id) ?? ekaerHistory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Idempotens rekord-létrehozás: ha a (foreignKey, isOutgoing) párra már van EkaerHistory, azt adja vissza
|
/// Idempotens rekord-létrehozás: ha a forrás-rekordra (foreignKey + isOutgoing) már van mapping → a hozzá tartozó
|
||||||
/// érintetlenül; különben új Pending rekordot hoz létre. A generálás (XmlDoc) külön lépés: GenerateEkaerXmlDocument.
|
/// EkaerHistory-t adja vissza érintetlenül; különben új Pending rekordot + EGY mapping sort hoz létre (atomikusan).
|
||||||
|
/// A generálás (XmlDoc) külön lépés: GenerateEkaerXmlDocument(ekaerHistoryId).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SignalR(SignalRTags.CreateEkaerHistory)]
|
[SignalR(SignalRTags.CreateEkaerHistory)]
|
||||||
public async Task<EkaerHistory> CreateEkaerHistory(int foreignKey, bool isOutgoing)
|
public async Task<EkaerHistory> CreateEkaerHistory(int foreignKey, bool isOutgoing)
|
||||||
{
|
{
|
||||||
_logger.Detail($"CreateEkaerHistory invoked; foreignKey: {foreignKey}; isOutgoing: {isOutgoing}");
|
_logger.Detail($"CreateEkaerHistory invoked; foreignKey: {foreignKey}; isOutgoing: {isOutgoing}");
|
||||||
|
|
||||||
var existing = await ctx.EkaerHistories.GetByForeignKey(foreignKey).FirstOrDefaultAsync(eh => eh.IsOutgoing == isOutgoing);
|
// Idempotencia: a forrás-rekordra (azonos irányban) már létező mapping → a hozzá tartozó EkaerHistory,
|
||||||
|
// a Mappings-szel betöltve (GetAll(true)) — EGY query (EXISTS a mappingre), nincs külön újraolvasás.
|
||||||
|
var existing = await LinqToDB.AsyncExtensions.FirstOrDefaultAsync(
|
||||||
|
ctx.EkaerHistories.GetAll(true).Where(eh => eh.IsOutgoing == isOutgoing
|
||||||
|
&& ctx.EkaerHistoryMappings.Table.Any(m => m.EkaerHistoryId == eh.Id && m.ForeignKey == foreignKey)));
|
||||||
|
|
||||||
if (existing != null) return existing;
|
if (existing != null) return existing;
|
||||||
|
|
||||||
// A forrás-entitás léte irányfüggő: bejövő → ShippingDocument, kimenő → Order.
|
// A forrás-entitás léte irányfüggő: bejövő → ShippingDocument, kimenő → Order.
|
||||||
|
|
@ -372,10 +386,11 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
||||||
_ = await ctx.OrderDtos.GetByIdAsync(foreignKey, false) ?? throw new ArgumentException($"Order not found; id: {foreignKey}", nameof(foreignKey));
|
_ = await ctx.OrderDtos.GetByIdAsync(foreignKey, false) ?? throw new ArgumentException($"Order not found; id: {foreignKey}", nameof(foreignKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
var ekaerHistory = new EkaerHistory { ForeignKey = foreignKey, IsOutgoing = isOutgoing, Status = EkaerStatus.Pending };
|
// EGY EkaerHistory + EGY mapping sor (egyelemű csoport), atomikusan.
|
||||||
await ctx.EkaerHistories.InsertAsync(ekaerHistory);
|
var ekaerHistory = await ctx.AddEkaerHistoryWithMappingsAsync(new EkaerHistory { IsOutgoing = isOutgoing, Status = EkaerStatus.Pending }, [foreignKey]);
|
||||||
|
|
||||||
return await ctx.EkaerHistories.GetByIdAsync(ekaerHistory.Id) ?? ekaerHistory;
|
// A friss sort adjuk vissza, a betöltött Mappings-szel; a sor biztosan létezik (épp beszúrtuk).
|
||||||
|
return await ctx.EkaerHistories.GetByIdAsync(ekaerHistory.Id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -388,21 +403,21 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
||||||
{
|
{
|
||||||
_logger.Detail($"CreateMissingEkaerHistories invoked; fromDate: {fromDate:yyyy-MM-dd}");
|
_logger.Detail($"CreateMissingEkaerHistories invoked; fromDate: {fromDate:yyyy-MM-dd}");
|
||||||
|
|
||||||
var toCreate = new List<EkaerHistory>();
|
var toCreate = new List<(EkaerHistory History, List<int> ForeignKeys)>();
|
||||||
var messages = new HashSet<string>(); // dedup: ugyanaz a partner (pl. hiányzó országkód) több csoportban is előjöhet
|
var messages = new HashSet<string>(); // dedup: ugyanaz a partner (pl. hiányzó országkód) több csoportban is előjöhet
|
||||||
|
|
||||||
// ── Bejövő ── Rekord nélküli szállítólevelek a dátumtól, Partnerrel + tételekkel betöltve a kötelezettség-kapuhoz.
|
// ── Bejövő ── Rekord nélküli szállítólevelek a dátumtól, Partnerrel + tételekkel betöltve a kötelezettség-kapuhoz.
|
||||||
// A bejelentés-egység (Shipping, Partner) = feladó→címzett→jármű: a küszöb a csoport ÖSSZES dokumentumára aggregál.
|
// A bejelentés-egység (Shipping, Partner, PartnerDepot) = feladó(+telephely)→címzett→jármű: EGY EkaerHistory/csoport, a küszöb a csoport ÖSSZES dokumentumára aggregál.
|
||||||
var candidates = await ctx.ShippingDocuments.GetAll()
|
var candidates = await ctx.ShippingDocuments.GetAll()
|
||||||
.Where(sd => sd.ShippingDate >= fromDate)
|
.Where(sd => sd.ShippingDate >= fromDate)
|
||||||
.Where(sd => !ctx.EkaerHistories.Table.Any(eh => eh.ForeignKey == sd.Id && !eh.IsOutgoing))
|
.Where(sd => !ctx.EkaerHistoryMappings.Table.Any(m => m.ForeignKey == sd.Id && ctx.EkaerHistories.Table.Any(eh => eh.Id == m.EkaerHistoryId && !eh.IsOutgoing)))
|
||||||
.LoadWith(sd => sd.Partner)
|
.LoadWith(sd => sd.Partner)
|
||||||
.LoadWith(sd => sd.ShippingItems)
|
.LoadWith(sd => sd.ShippingItems)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
// Partner nélküli (még nincs rendesen felvett) szállítólevél CSENDBEN kihagyva — nincs feladó, nem kész;
|
// Partner nélküli (még nincs rendesen felvett) szállítólevél CSENDBEN kihagyva — nincs feladó, nem kész;
|
||||||
// a mentes partner (IsEkaer=false) is. A maradék (Shipping, Partner) csoportban (a dokumentum mindig kap Shippinget).
|
// a mentes partner (IsEkaer=false) is. A maradék (Shipping, Partner, PartnerDepot) csoportban (a dokumentum mindig kap Shippinget).
|
||||||
foreach (var group in candidates.Where(sd => sd.Partner != null && sd.Partner.IsEkaer != false).GroupBy(sd => (sd.ShippingId, sd.PartnerId)))
|
foreach (var group in candidates.Where(sd => sd.Partner != null && sd.Partner.IsEkaer != false).GroupBy(sd => (sd.ShippingId, sd.PartnerId, sd.PartnerDepotId)))
|
||||||
{
|
{
|
||||||
var docs = group.ToList();
|
var docs = group.ToList();
|
||||||
try
|
try
|
||||||
|
|
@ -411,14 +426,13 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
||||||
if (result.Obligation == EkaerObligation.DataError) { messages.UnionWith(result.Errors); continue; }
|
if (result.Obligation == EkaerObligation.DataError) { messages.UnionWith(result.Errors); continue; }
|
||||||
if (result.Obligation == EkaerObligation.NotRequired) continue;
|
if (result.Obligation == EkaerObligation.NotRequired) continue;
|
||||||
|
|
||||||
// Kötelező → a csoport minden dokumentumára egy-egy Pending sor (a rekord-szerkezet per-dokumentum marad).
|
// Kötelező → EGY EkaerHistory a csoportra + a csoport dokumentumai mapping-foreignKey-ként.
|
||||||
foreach (var doc in docs)
|
toCreate.Add((new EkaerHistory { IsOutgoing = false, StatusId = (int)EkaerStatus.Pending }, docs.Select(d => d.Id).ToList()));
|
||||||
toCreate.Add(new EkaerHistory { ForeignKey = doc.Id, IsOutgoing = false, StatusId = (int)EkaerStatus.Pending });
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error($"CreateMissingEkaerHistories; inbound evaluate failed; ShippingId: {group.Key.ShippingId}; PartnerId: {group.Key.PartnerId}", ex);
|
_logger.Error($"CreateMissingEkaerHistories; inbound evaluate failed; ShippingId: {group.Key.ShippingId}; PartnerId: {group.Key.PartnerId}; PartnerDepotId: {group.Key.PartnerDepotId}", ex);
|
||||||
messages.Add($"Szállítólevél-csoport (Shipping #{group.Key.ShippingId}, Partner #{group.Key.PartnerId}): a kötelezettség nem dönthető el — {ex.Message}");
|
messages.Add($"Szállítólevél-csoport (Shipping #{group.Key.ShippingId}, Partner #{group.Key.PartnerId}, Telephely #{group.Key.PartnerDepotId?.ToString() ?? "—"}): a kötelezettség nem dönthető el — {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -426,7 +440,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
||||||
// Előbb csak az azonosítók (könnyű szűrés), majd EGY batch-betöltés a teljes gráffal (GetAllByIds) — nincs N+1.
|
// Előbb csak az azonosítók (könnyű szűrés), majd EGY batch-betöltés a teljes gráffal (GetAllByIds) — nincs N+1.
|
||||||
var missingOrderIds = await ctx.OrderDtos.GetAllByOrderStatus(OrderStatus.Complete, false)
|
var missingOrderIds = await ctx.OrderDtos.GetAllByOrderStatus(OrderStatus.Complete, false)
|
||||||
.Where(o => o.GenericAttributes.Any(ga => ga.Key == nameof(OrderDto.DateOfReceipt) && DateTime.Parse(ga.Value) >= fromDate.Date))
|
.Where(o => o.GenericAttributes.Any(ga => ga.Key == nameof(OrderDto.DateOfReceipt) && DateTime.Parse(ga.Value) >= fromDate.Date))
|
||||||
.Where(o => !ctx.EkaerHistories.Table.Any(eh => eh.ForeignKey == o.Id && eh.IsOutgoing))
|
.Where(o => !ctx.EkaerHistoryMappings.Table.Any(m => m.ForeignKey == o.Id && ctx.EkaerHistories.Table.Any(eh => eh.Id == m.EkaerHistoryId && eh.IsOutgoing)))
|
||||||
.Select(o => o.Id)
|
.Select(o => o.Id)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
|
@ -440,7 +454,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
||||||
if (result.Obligation == EkaerObligation.DataError) { messages.UnionWith(result.Errors); continue; }
|
if (result.Obligation == EkaerObligation.DataError) { messages.UnionWith(result.Errors); continue; }
|
||||||
if (result.Obligation == EkaerObligation.NotRequired) continue;
|
if (result.Obligation == EkaerObligation.NotRequired) continue;
|
||||||
|
|
||||||
toCreate.Add(new EkaerHistory { ForeignKey = order.Id, IsOutgoing = true, StatusId = (int)EkaerStatus.Pending });
|
toCreate.Add((new EkaerHistory { IsOutgoing = true, StatusId = (int)EkaerStatus.Pending }, [order.Id]));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -451,16 +465,17 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
||||||
|
|
||||||
// ── Beszúrás ──
|
// ── Beszúrás ──
|
||||||
var createdCount = 0;
|
var createdCount = 0;
|
||||||
foreach (var ekaerHistory in toCreate)
|
foreach (var (history, foreignKeys) in toCreate)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ctx.EkaerHistories.InsertAsync(ekaerHistory);
|
// EkaerHistory + a forrás-mapping sorok atomikusan (a junction tartja a kapcsolatot, nem a rendszer-tábla).
|
||||||
|
await ctx.AddEkaerHistoryWithMappingsAsync(history, foreignKeys);
|
||||||
createdCount++;
|
createdCount++;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error($"CreateMissingEkaerHistories; insert failed; ForeignKey: {ekaerHistory.ForeignKey}; IsOutgoing: {ekaerHistory.IsOutgoing}", ex);
|
_logger.Error($"CreateMissingEkaerHistories; insert failed; IsOutgoing: {history.IsOutgoing}; foreignKeys: [{string.Join(",", foreignKeys)}]", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,16 @@ public class EkaerHistoryDbTable : MgDbTableBase<EkaerHistory>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
// History tábla: legújabb elöl. Nincs asszociáció → nincs LoadWith / loadRelations.
|
// History tábla: legújabb elöl.
|
||||||
public override IOrderedQueryable<EkaerHistory> GetAll() => base.GetAll().OrderByDescending(p => p.Id);
|
public override IOrderedQueryable<EkaerHistory> GetAll() => base.GetAll().OrderByDescending(p => p.Id);
|
||||||
|
|
||||||
public Task<EkaerHistory> GetByIdAsync(int id) => GetAll().FirstOrDefaultAsync(p => p.Id == id);
|
// loadRelations → a deklarációt lefedő forrás-mapping sorok (Mappings) is töltődnek (a kliensnek kellenek).
|
||||||
public IQueryable<EkaerHistory> GetByForeignKey(int foreignKey) => GetAll().Where(p => p.ForeignKey == foreignKey);
|
public IQueryable<EkaerHistory> GetAll(bool loadRelations)
|
||||||
|
=> loadRelations ? GetAll().LoadWith(eh => eh.Mappings) : GetAll();
|
||||||
|
|
||||||
|
// FirstOrDefaultAsync kétértelmű (System.Linq.Async + LinqToDB) az IQueryable-ön (a GetAll(loadRelations) LoadWith
|
||||||
|
// miatt már nem IOrderedQueryable) → explicit a LinqToDB-é, ugyanúgy mint a CountByFilterAsync-nél.
|
||||||
|
public Task<EkaerHistory> GetByIdAsync(int id, bool loadRelations) => LinqToDB.AsyncExtensions.FirstOrDefaultAsync(GetAll(loadRelations), p => p.Id == id);
|
||||||
|
|
||||||
// A NAV-nál lévő partíciók fix StatusId-k; minden más (a jövőbeni új státuszok is) „beküldésre váró".
|
// A NAV-nál lévő partíciók fix StatusId-k; minden más (a jövőbeni új státuszok is) „beküldésre váró".
|
||||||
private static readonly int[] ToSubmitStatusIds = [.. Enum.GetValues<EkaerStatus>().Select(s => (int)s).Except([(int)EkaerStatus.Sent, (int)EkaerStatus.SentWithMissingData])];
|
private static readonly int[] ToSubmitStatusIds = [.. Enum.GetValues<EkaerStatus>().Select(s => (int)s).Except([(int)EkaerStatus.Sent, (int)EkaerStatus.SentWithMissingData])];
|
||||||
|
|
@ -27,20 +32,20 @@ public class EkaerHistoryDbTable : MgDbTableBase<EkaerHistory>
|
||||||
/// <summary>Flag-alapú szűrés a tabokhoz/count-hoz. <see cref="EkaerHistoryFilter.All"/> (= 0) → minden;
|
/// <summary>Flag-alapú szűrés a tabokhoz/count-hoz. <see cref="EkaerHistoryFilter.All"/> (= 0) → minden;
|
||||||
/// egyébként a beállított flag-ek diszjunkt StatusId-partícióinak UNIÓJA (IN). A státusz-helperek (IsSent stb.)
|
/// egyébként a beállított flag-ek diszjunkt StatusId-partícióinak UNIÓJA (IN). A státusz-helperek (IsSent stb.)
|
||||||
/// nem fordulnak SQL-re, ezért közvetlen StatusId-halmazzal szűrünk.</summary>
|
/// nem fordulnak SQL-re, ezért közvetlen StatusId-halmazzal szűrünk.</summary>
|
||||||
public IQueryable<EkaerHistory> GetByFilter(EkaerHistoryFilter filter)
|
public IQueryable<EkaerHistory> GetByFilter(EkaerHistoryFilter filter, bool loadRelations)
|
||||||
{
|
{
|
||||||
// HasFlag(All) mindig true (0 bit), ezért előbb az All-ágat zárjuk rövidre.
|
// HasFlag(All) mindig true (0 bit), ezért előbb az All-ágat zárjuk rövidre.
|
||||||
if (filter == EkaerHistoryFilter.All) return GetAll();
|
if (filter == EkaerHistoryFilter.All) return GetAll(loadRelations);
|
||||||
|
|
||||||
var statusIds = new List<int>();
|
var statusIds = new List<int>();
|
||||||
if (filter.HasFlag(EkaerHistoryFilter.ToSubmit)) statusIds.AddRange(ToSubmitStatusIds);
|
if (filter.HasFlag(EkaerHistoryFilter.ToSubmit)) statusIds.AddRange(ToSubmitStatusIds);
|
||||||
if (filter.HasFlag(EkaerHistoryFilter.Sent)) statusIds.Add((int)EkaerStatus.Sent);
|
if (filter.HasFlag(EkaerHistoryFilter.Sent)) statusIds.Add((int)EkaerStatus.Sent);
|
||||||
if (filter.HasFlag(EkaerHistoryFilter.NeedsCompletion)) statusIds.Add((int)EkaerStatus.SentWithMissingData);
|
if (filter.HasFlag(EkaerHistoryFilter.NeedsCompletion)) statusIds.Add((int)EkaerStatus.SentWithMissingData);
|
||||||
|
|
||||||
return GetAll().Where(eh => statusIds.Contains(eh.StatusId));
|
return GetAll(loadRelations).Where(eh => statusIds.Contains(eh.StatusId));
|
||||||
}
|
}
|
||||||
|
|
||||||
// A CountAsync kétértelmű (a System.Linq.Async ÉS a LinqToDB is hozza ugyanazt a nevet, no-arg hívásnál mindkettő illik)
|
// A CountAsync kétértelmű (a System.Linq.Async ÉS a LinqToDB is hozza ugyanazt a nevet, no-arg hívásnál mindkettő illik)
|
||||||
// → explicit a LinqToDB-é. (A ToListAsync nem ütközik, azt a hívó közvetlenül használhatja.)
|
// → explicit a LinqToDB-é. (A ToListAsync nem ütközik, azt a hívó közvetlenül használhatja.)
|
||||||
public Task<int> CountByFilterAsync(EkaerHistoryFilter filter) => LinqToDB.AsyncExtensions.CountAsync(GetByFilter(filter));
|
public Task<int> CountByFilterAsync(EkaerHistoryFilter filter) => LinqToDB.AsyncExtensions.CountAsync(GetByFilter(filter, false));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
using FruitBank.Common.Entities;
|
||||||
|
using LinqToDB;
|
||||||
|
using Mango.Nop.Data.Repositories;
|
||||||
|
using Nop.Core.Caching;
|
||||||
|
using Nop.Core.Configuration;
|
||||||
|
using Nop.Core.Events;
|
||||||
|
using Nop.Data;
|
||||||
|
|
||||||
|
namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
|
||||||
|
|
||||||
|
public class EkaerHistoryMappingDbTable : MgDbTableBase<EkaerHistoryMapping>
|
||||||
|
{
|
||||||
|
public EkaerHistoryMappingDbTable(IEventPublisher eventPublisher, INopDataProvider dataProvider, IShortTermCacheManager shortTermCacheManager, IStaticCacheManager staticCacheManager, AppSettings appSettings)
|
||||||
|
: base(eventPublisher, dataProvider, shortTermCacheManager, staticCacheManager, appSettings)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadRelations → a szülő EkaerHistory is töltődik (a junction → deklaráció navigáció).
|
||||||
|
public IQueryable<EkaerHistoryMapping> GetAll(bool loadRelations) => loadRelations ? base.GetAll().LoadWith(m => m.EkaerHistory) : base.GetAll();
|
||||||
|
|
||||||
|
// Egy EKÁER-deklaráció forrás-rekordjai (bejövő: N ShippingDocument; kimenő: 1 Order).
|
||||||
|
public IQueryable<EkaerHistoryMapping> GetByEkaerHistoryId(int ekaerHistoryId, bool loadRelations) => GetAll(loadRelations).Where(m => m.EkaerHistoryId == ekaerHistoryId);
|
||||||
|
|
||||||
|
// Egy forrás-rekordhoz (ShippingDocument/Order Id) tartozó mapping sor(ok) — a dedup-hoz / a sorhoz tartozó EKÁER kereséséhez.
|
||||||
|
public IQueryable<EkaerHistoryMapping> GetByForeignKey(int foreignKey, bool loadRelations) => GetAll(loadRelations).Where(m => m.ForeignKey == foreignKey);
|
||||||
|
}
|
||||||
|
|
@ -57,6 +57,7 @@ public class FruitBankDbContext : MgDbContextBase,
|
||||||
public PartnerDbTable Partners { get; set; }
|
public PartnerDbTable Partners { get; set; }
|
||||||
public PartnerDepotDbTable PartnerDepots { get; set; }
|
public PartnerDepotDbTable PartnerDepots { get; set; }
|
||||||
public EkaerHistoryDbTable EkaerHistories { get; set; }
|
public EkaerHistoryDbTable EkaerHistories { get; set; }
|
||||||
|
public EkaerHistoryMappingDbTable EkaerHistoryMappings { get; set; }
|
||||||
public CargoPartnerDbTable CargoPartners { get; set; }
|
public CargoPartnerDbTable CargoPartners { get; set; }
|
||||||
public CargoTruckDbTable CargoTrucks{ get; set; }
|
public CargoTruckDbTable CargoTrucks{ get; set; }
|
||||||
|
|
||||||
|
|
@ -83,7 +84,7 @@ public class FruitBankDbContext : MgDbContextBase,
|
||||||
public IRepository<StockQuantityHistoryExt> StockQuantityHistoriesExt { get; set; }
|
public IRepository<StockQuantityHistoryExt> StockQuantityHistoriesExt { get; set; }
|
||||||
|
|
||||||
public FruitBankDbContext(INopDataProvider dataProvider, ILockService lockService, FruitBankAttributeService fruitBankAttributeService, IStoreContext storeContext,
|
public FruitBankDbContext(INopDataProvider dataProvider, ILockService lockService, FruitBankAttributeService fruitBankAttributeService, IStoreContext storeContext,
|
||||||
CargoPartnerDbTable cargoPartnerDbTable, CargoTruckDbTable cargoTruckDbTable, PartnerDbTable partnerDbTable, PartnerDepotDbTable partnerDepotDbTable, EkaerHistoryDbTable ekaerHistoryDbTable, ShippingDbTable shippingDbTable, ShippingDocumentDbTable shippingDocumentDbTable, ShippingItemDbTable shippingItemDbTable,
|
CargoPartnerDbTable cargoPartnerDbTable, CargoTruckDbTable cargoTruckDbTable, PartnerDbTable partnerDbTable, PartnerDepotDbTable partnerDepotDbTable, EkaerHistoryDbTable ekaerHistoryDbTable, EkaerHistoryMappingDbTable ekaerHistoryMappingDbTable, ShippingDbTable shippingDbTable, ShippingDocumentDbTable shippingDocumentDbTable, ShippingItemDbTable shippingItemDbTable,
|
||||||
ShippingItemPalletDbTable shippingItemPalletDbTable, FilesDbTable filesDbTable, ShippingDocumentToFilesDbTable shippingDocumentToFilesDbTable,
|
ShippingItemPalletDbTable shippingItemPalletDbTable, FilesDbTable filesDbTable, ShippingDocumentToFilesDbTable shippingDocumentToFilesDbTable,
|
||||||
ProductDtoDbTable productDtoDbTable, OrderDtoDbTable orderDtoDbTable, OrderItemDtoDbTable orderItemDtoDbTable, OrderItemPalletDbTable orderItemPalletDbTable,
|
ProductDtoDbTable productDtoDbTable, OrderDtoDbTable orderDtoDbTable, OrderItemDtoDbTable orderItemDtoDbTable, OrderItemPalletDbTable orderItemPalletDbTable,
|
||||||
StockQuantityHistoryDtoDbTable stockQuantityHistoryDtos, CustomerCreditDbTable customerCreditDbTable,
|
StockQuantityHistoryDtoDbTable stockQuantityHistoryDtos, CustomerCreditDbTable customerCreditDbTable,
|
||||||
|
|
@ -112,6 +113,7 @@ public class FruitBankDbContext : MgDbContextBase,
|
||||||
Partners = partnerDbTable;
|
Partners = partnerDbTable;
|
||||||
PartnerDepots = partnerDepotDbTable;
|
PartnerDepots = partnerDepotDbTable;
|
||||||
EkaerHistories = ekaerHistoryDbTable;
|
EkaerHistories = ekaerHistoryDbTable;
|
||||||
|
EkaerHistoryMappings = ekaerHistoryMappingDbTable;
|
||||||
CargoPartners = cargoPartnerDbTable;
|
CargoPartners = cargoPartnerDbTable;
|
||||||
CargoTrucks = cargoTruckDbTable;
|
CargoTrucks = cargoTruckDbTable;
|
||||||
|
|
||||||
|
|
@ -767,6 +769,25 @@ public class FruitBankDbContext : MgDbContextBase,
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>EKÁER-deklaráció (EkaerHistory) + a hozzá tartozó forrás-mapping sorok ATOMIKUS létrehozása.
|
||||||
|
/// A mapping tartja a kapcsolatot a forrás-rekordokhoz (bejövő: N ShippingDocument egy (Shipping, Partner,
|
||||||
|
/// PartnerDepot) csoportból; kimenő: 1 Order) — így a deklaráció a rendszer-tábláktól FÜGGETLENÜL törölhető
|
||||||
|
/// és újragenerálható. Az InsertAsync az identity Id-t visszaírja, így a mapping sorok már a friss Id-ra mutatnak.</summary>
|
||||||
|
public async Task<EkaerHistory> AddEkaerHistoryWithMappingsAsync(EkaerHistory ekaerHistory, IEnumerable<int> foreignKeys)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(ekaerHistory);
|
||||||
|
|
||||||
|
await TransactionSafeAsync(async _ =>
|
||||||
|
{
|
||||||
|
await EkaerHistories.InsertAsync(ekaerHistory);
|
||||||
|
foreach (var foreignKey in foreignKeys)
|
||||||
|
await EkaerHistoryMappings.InsertAsync(new EkaerHistoryMapping { EkaerHistoryId = ekaerHistory.Id, ForeignKey = foreignKey });
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return ekaerHistory;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<CustomerAddressMapping?> AddCustomerAddressMappingAsync(int customerId, int addressId)
|
public async Task<CustomerAddressMapping?> AddCustomerAddressMappingAsync(int customerId, int addressId)
|
||||||
{
|
{
|
||||||
var customerAddressMapping = new CustomerAddressMapping
|
var customerAddressMapping = new CustomerAddressMapping
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ public class PluginNopStartup : INopStartup
|
||||||
services.AddScoped<PartnerDbTable>();
|
services.AddScoped<PartnerDbTable>();
|
||||||
services.AddScoped<PartnerDepotDbTable>();
|
services.AddScoped<PartnerDepotDbTable>();
|
||||||
services.AddScoped<EkaerHistoryDbTable>();
|
services.AddScoped<EkaerHistoryDbTable>();
|
||||||
|
services.AddScoped<EkaerHistoryMappingDbTable>();
|
||||||
services.AddScoped<CargoPartnerDbTable>();
|
services.AddScoped<CargoPartnerDbTable>();
|
||||||
services.AddScoped<CargoTruckDbTable>();
|
services.AddScoped<CargoTruckDbTable>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ public partial class NameCompatibility : INameCompatibility
|
||||||
|
|
||||||
{ typeof(PartnerDepot), FruitBankConstClient.PartnerDepotDbTableName},
|
{ typeof(PartnerDepot), FruitBankConstClient.PartnerDepotDbTableName},
|
||||||
{ typeof(EkaerHistory), FruitBankConstClient.EkaerHistoryDbTableName},
|
{ typeof(EkaerHistory), FruitBankConstClient.EkaerHistoryDbTableName},
|
||||||
|
{ typeof(EkaerHistoryMapping), FruitBankConstClient.EkaerHistoryMappingDbTableName},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,3 +79,20 @@ A NAV felé **egy VTSZ-re egy `tradeCardItem`** kell — több azonos-VTSZ-ű te
|
||||||
- `FruitBank.Common.Server/Services/Ekaer/{IFruitBankEkaerService,FruitBankEkaerService}.cs` — a `vtszNames` átadása a generate-en
|
- `FruitBank.Common.Server/Services/Ekaer/{IFruitBankEkaerService,FruitBankEkaerService}.cs` — a `vtszNames` átadása a generate-en
|
||||||
- `FruitBankDataController.GenerateEkaerXmlDocument` (plugin) — `(productId, categoryId)` tuple + batch `Category`-load → `Dictionary<vtsz, name>`
|
- `FruitBankDataController.GenerateEkaerXmlDocument` (plugin) — `(productId, categoryId)` tuple + batch `Category`-load → `Dictionary<vtsz, name>`
|
||||||
- nopCommerce `Category` (MetaKeywords = VTSZ, Name = productName) — adat/üzemeltetés
|
- nopCommerce `Category` (MetaKeywords = VTSZ, Name = productName) — adat/üzemeltetés
|
||||||
|
|
||||||
|
## MGFBANKPLUG-EKAER-T-D8R4: Kapu-csoportosítás (Shipping, PartnerDepot)-re; a PartnerDepotId kötelező
|
||||||
|
|
||||||
|
**Status:** Open · **Priority:** P2 · **Type:** T (kapu) · **2026-06**
|
||||||
|
|
||||||
|
A `CreateMissingEkaerHistories` bejövő kapuja jelenleg `(ShippingId, PartnerId, PartnerDepotId)`-re csoportosít. Mivel a **`PartnerDepot` egyértelműen egy `Partner`-hez tartozik** (a depó implikálja a feladót), a `PartnerId` a kulcsban **redundáns** → elég a `(ShippingId, PartnerDepotId)` csoportosítás.
|
||||||
|
|
||||||
|
**Cél:**
|
||||||
|
- A csoportosítási kulcs `(ShippingId, PartnerDepotId)` (a `PartnerId` kivehető).
|
||||||
|
- A **`PartnerDepotId` kötelező** az EKÁER-hez: ha egy szállítólevélen **nincs** `PartnerDepotId` → **hiba**, és **NE generálódjon** rá deklaráció. A kapu a hiányzó-telephelyű dokumentumokat kihagyja + üzenettel jelzi (mint a `DataError`-nál); a generate `ValidationError`-t ad (nem küldhető), nem üres/téves tradeCard-ot.
|
||||||
|
|
||||||
|
**Megj.:** szigorítás a jelenlegihez képest, ahol a `null` `PartnerDepotId` még külön (null-telephelyű) csoportként kezelődik. Bevezetés előtt a meglévő szállítólevelek `PartnerDepotId`-ját pótolni kell, különben a kapu kihagyja őket.
|
||||||
|
|
||||||
|
**Affected:**
|
||||||
|
- `FruitBankDataController.CreateMissingEkaerHistories` (plugin) — a `GroupBy` kulcs `(ShippingId, PartnerDepotId)`-re + a hiányzó-`PartnerDepotId` hiba-ág (kihagyás + üzenet)
|
||||||
|
- `FruitBankDataController.GenerateEkaerXmlDocument` / `FruitBankEkaerService` — hiányzó telephelynél `ValidationError`, nincs generálás
|
||||||
|
- `ShippingToEkaerMapper` / `EkaerReportability` (`FruitBank.Common/Services/Ekaer/`) — ha a feladó-cím a `PartnerDepot`-ból jön, a hiányzó telephely validációs hiba
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue