bug fix
This commit is contained in:
commit
330aee3e5f
File diff suppressed because one or more lines are too long
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using AyCode.Core.Extensions;
|
||||
using AyCode.Core.Loggers;
|
||||
using AyCode.Services.Nav.Ekaer;
|
||||
using AyCode.Services.SignalRs;
|
||||
using DocumentFormat.OpenXml.Office2010.Excel;
|
||||
using FruitBank.Common.Dtos;
|
||||
|
|
@ -13,6 +14,8 @@ using FruitBank.Common.Loggers;
|
|||
using FruitBank.Common.Models;
|
||||
using FruitBank.Common.Server;
|
||||
using FruitBank.Common.Server.Interfaces;
|
||||
using FruitBank.Common.Server.Services.Ekaer;
|
||||
using FruitBank.Common.Services.Ekaer;
|
||||
using FruitBank.Common.SignalRs;
|
||||
using LinqToDB;
|
||||
using Mango.Nop.Core.Dtos;
|
||||
|
|
@ -20,6 +23,7 @@ using Mango.Nop.Core.Loggers;
|
|||
using Mango.Nop.Core.Models;
|
||||
using Nop.Core;
|
||||
using Nop.Core.Domain.Customers;
|
||||
using Nop.Core.Domain.Orders;
|
||||
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
|
||||
using Nop.Plugin.Misc.FruitBankPlugin.Factories;
|
||||
using Nop.Plugin.Misc.FruitBankPlugin.Services;
|
||||
|
|
@ -40,6 +44,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
|||
ICustomerRegistrationService customerRegistrationService,
|
||||
ILocalizationService localizationService,
|
||||
PreOrderConversionService preorderConversionService,
|
||||
IFruitBankEkaerService fruitBankEkaerService,
|
||||
IEkaerSettings ekaerSettings,
|
||||
IEnumerable<IAcLogWriterBase> logWriters)
|
||||
: BasePluginController, IFruitBankDataControllerServer
|
||||
{
|
||||
|
|
@ -101,7 +107,9 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
|||
{
|
||||
_logger.Detail($"GetPartners invoked");
|
||||
|
||||
return await ctx.Partners.GetAll().ToListAsync();
|
||||
// A telephelyeket is behúzzuk (CSAK a depókat, NEM a teljes GetAll(true) láncot) — a partnerek
|
||||
// úgyis cache-eltek, így a ShippingDocument-grid telephely-legördülője kliens-oldalon kaszkádolhat.
|
||||
return await ctx.Partners.GetAll().LoadWith(p => p.PartnerDepots).ToListAsync();
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.GetPartnerById)]
|
||||
|
|
@ -112,7 +120,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
|||
//var customers = await ctx.GetCustormersBySystemRoleName("Measuring").ToListAsync();
|
||||
//_logger.Error($"COUNT: {customers.Count}");
|
||||
|
||||
return await ctx.Partners.GetByIdAsync(id);
|
||||
return await ctx.Partners.GetByIdAsync(id, false);
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.AddPartner)]
|
||||
|
|
@ -219,6 +227,274 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
|||
return await ctx.CargoTrucks.GetByIdAsync(cargoTruck.Id, true);
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.GetPartnerDepots)]
|
||||
public async Task<List<PartnerDepot>> GetPartnerDepots()
|
||||
{
|
||||
_logger.Detail($"GetPartnerDepots invoked");
|
||||
|
||||
return await ctx.PartnerDepots.GetAll(true).ToListAsync();
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.GetPartnerDepotById)]
|
||||
public async Task<PartnerDepot> GetPartnerDepotById(int id)
|
||||
{
|
||||
_logger.Detail($"GetPartnerDepotById invoked; id: {id}");
|
||||
return await ctx.PartnerDepots.GetByIdAsync(id, true);
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.GetPartnerDepotsByPartnerId)]
|
||||
public async Task<List<PartnerDepot>> GetPartnerDepotsByPartnerId(int partnerId)
|
||||
{
|
||||
_logger.Detail($"GetPartnerDepotsByPartnerId invoked; partnerId: {partnerId}");
|
||||
return await ctx.PartnerDepots.GetByPartnerId(partnerId, true).ToListAsync();
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.AddPartnerDepot)]
|
||||
public async Task<PartnerDepot> AddPartnerDepot(PartnerDepot partnerDepot)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(partnerDepot);
|
||||
|
||||
_logger.Detail($"AddPartnerDepot invoked; id: {partnerDepot.Id}");
|
||||
|
||||
await ctx.PartnerDepots.InsertAsync(partnerDepot);
|
||||
return await ctx.PartnerDepots.GetByIdAsync(partnerDepot.Id, true);
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.UpdatePartnerDepot)]
|
||||
public async Task<PartnerDepot> UpdatePartnerDepot(PartnerDepot partnerDepot)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(partnerDepot);
|
||||
|
||||
_logger.Detail($"UpdatePartnerDepot invoked; id: {partnerDepot.Id}");
|
||||
|
||||
await ctx.PartnerDepots.UpdateAsync(partnerDepot);
|
||||
return await ctx.PartnerDepots.GetByIdAsync(partnerDepot.Id, true);
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.GetEkaerHistories)]
|
||||
public async Task<List<EkaerHistory>> GetEkaerHistories(EkaerHistoryFilter ekaerHistoryFilter)
|
||||
{
|
||||
_logger.Detail($"GetEkaerHistories invoked; ekaerHistoryFilter: {ekaerHistoryFilter}");
|
||||
return await ctx.EkaerHistories.GetByFilter(ekaerHistoryFilter, true).ToListAsync();
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.GetEkaerHistoryCount)]
|
||||
public async Task<int> GetEkaerHistoryCount(EkaerHistoryFilter ekaerHistoryFilter)
|
||||
{
|
||||
_logger.Detail($"GetEkaerHistoryCount invoked; ekaerHistoryFilter: {ekaerHistoryFilter}");
|
||||
return await ctx.EkaerHistories.CountByFilterAsync(ekaerHistoryFilter);
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.GetEkaerHistoryById)]
|
||||
public async Task<EkaerHistory> GetEkaerHistoryById(int id)
|
||||
{
|
||||
_logger.Detail($"GetEkaerHistoryById invoked; id: {id}");
|
||||
return await ctx.EkaerHistories.GetByIdAsync(id, true);
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.GetEkaerHistoriesByForeignKey)]
|
||||
public async Task<List<EkaerHistory>> GetEkaerHistoriesByForeignKey(int foreignKey)
|
||||
{
|
||||
_logger.Detail($"GetEkaerHistoriesByForeignKey invoked; foreignKey: {foreignKey}");
|
||||
|
||||
// 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)]
|
||||
public async Task<EkaerHistory> AddEkaerHistory(EkaerHistory ekaerHistory)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ekaerHistory);
|
||||
|
||||
_logger.Detail($"AddEkaerHistory invoked; id: {ekaerHistory.Id}");
|
||||
|
||||
await ctx.EkaerHistories.InsertAsync(ekaerHistory);
|
||||
return await ctx.EkaerHistories.GetByIdAsync(ekaerHistory.Id, true);
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.UpdateEkaerHistory)]
|
||||
public async Task<EkaerHistory> UpdateEkaerHistory(EkaerHistory ekaerHistory)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ekaerHistory);
|
||||
|
||||
_logger.Detail($"UpdateEkaerHistory invoked; id: {ekaerHistory.Id}");
|
||||
|
||||
await ctx.EkaerHistories.UpdateAsync(ekaerHistory);
|
||||
return await ctx.EkaerHistories.GetByIdAsync(ekaerHistory.Id, true);
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.GenerateEkaerXmlDocument)]
|
||||
public async Task<EkaerHistory> GenerateEkaerXmlDocument(int ekaerHistoryId)
|
||||
{
|
||||
_logger.Detail($"GenerateEkaerXmlDocument invoked; ekaerHistoryId: {ekaerHistoryId}");
|
||||
|
||||
var ekaerHistory = await ctx.EkaerHistories.GetByIdAsync(ekaerHistoryId, true)
|
||||
?? throw new ArgumentException($"EkaerHistory not found; id: {ekaerHistoryId}", nameof(ekaerHistoryId));
|
||||
|
||||
// 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 csoport ÖSSZES szállítólevele EGY tradeCard-dá (összevont tömeg/érték); GetAll(true) = teljes gráf.
|
||||
var documents = await ctx.ShippingDocuments.GetAll(true).Where(sd => foreignKeys.Contains(sd.Id)).ToListAsync();
|
||||
if (documents.Count == 0) throw new InvalidOperationException($"EkaerHistory #{ekaerHistoryId}: no ShippingDocuments found for the mapped ids.");
|
||||
ekaerHistory = fruitBankEkaerService.GenerateEkaerXmlDocument(documents, ekaerHistory);
|
||||
|
||||
// Összegző (ShippingDate + Partner) frissítése — a csoporton belül invariáns, így bármelyik dok jó.
|
||||
fruitBankEkaerService.SetSummary(ekaerHistory, documents[0].ShippingDate, documents[0].Partner?.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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(foreignKeys[0], true)
|
||||
?? throw new ArgumentException($"Order not found; id: {foreignKeys[0]}");
|
||||
ekaerHistory = fruitBankEkaerService.GenerateEkaerXmlDocument(order, ekaerHistory);
|
||||
|
||||
fruitBankEkaerService.SetSummary(ekaerHistory, order.DateOfReceipt, order.Customer?.Company);
|
||||
}
|
||||
|
||||
await ctx.EkaerHistories.UpdateAsync(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Idempotens rekord-létrehozás: ha a forrás-rekordra (foreignKey + isOutgoing) már van mapping → a hozzá tartozó
|
||||
/// 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>
|
||||
[SignalR(SignalRTags.CreateEkaerHistory)]
|
||||
public async Task<EkaerHistory> CreateEkaerHistory(int foreignKey, bool isOutgoing)
|
||||
{
|
||||
_logger.Detail($"CreateEkaerHistory invoked; foreignKey: {foreignKey}; 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;
|
||||
|
||||
// A forrás-entitás létét ellenőrizzük és betöltjük (relations) az összegző mezőkhöz (ShippingDate + Partner).
|
||||
var ekaerHistory = new EkaerHistory { IsOutgoing = isOutgoing, Status = EkaerStatus.Pending };
|
||||
if (!isOutgoing)
|
||||
{
|
||||
var doc = await ctx.ShippingDocuments.GetByIdAsync(foreignKey, true) ?? throw new ArgumentException($"ShippingDocument not found; id: {foreignKey}", nameof(foreignKey));
|
||||
fruitBankEkaerService.SetSummary(ekaerHistory, doc.ShippingDate, doc.Partner?.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
var order = await ctx.OrderDtos.GetByIdAsync(foreignKey, true) ?? throw new ArgumentException($"Order not found; id: {foreignKey}", nameof(foreignKey));
|
||||
fruitBankEkaerService.SetSummary(ekaerHistory, order.DateOfReceipt, order.Customer?.Company);
|
||||
}
|
||||
|
||||
// EGY EkaerHistory + EGY mapping sor (egyelemű csoport), atomikusan.
|
||||
ekaerHistory = await ctx.AddEkaerHistoryWithMappingsAsync(ekaerHistory, [foreignKey]);
|
||||
|
||||
// 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>
|
||||
/// A megadott dátumtól kezdődő, EKÁER-rekord nélküli szállítólevelekre Pending rekordot hoz létre.
|
||||
/// User-vezérelt (toolbar gomb) — szándékosan NINCS automata rekord-érzékelés: a létrehozás explicit,
|
||||
/// idempotens, és bármikor újrafuttatható (maga a gomb a "reconciliation").
|
||||
/// </summary>
|
||||
[SignalR(SignalRTags.CreateMissingEkaerHistories)]
|
||||
public async Task<EkaerCreateResult> CreateMissingEkaerHistories(DateTime fromDate)
|
||||
{
|
||||
_logger.Detail($"CreateMissingEkaerHistories invoked; fromDate: {fromDate:yyyy-MM-dd}");
|
||||
|
||||
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
|
||||
|
||||
// ── 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, 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()
|
||||
.Where(sd => sd.ShippingDate >= fromDate)
|
||||
.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.ShippingItems)
|
||||
.ToListAsync();
|
||||
|
||||
// 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, 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, sd.PartnerDepotId)))
|
||||
{
|
||||
var docs = group.ToList();
|
||||
try
|
||||
{
|
||||
var result = fruitBankEkaerService.EvaluateObligation(docs);
|
||||
if (result.Obligation == EkaerObligation.DataError) { messages.UnionWith(result.Errors); continue; }
|
||||
if (result.Obligation == EkaerObligation.NotRequired) continue;
|
||||
|
||||
// Kötelező → EGY EkaerHistory a csoportra (összegző: ShippingDate + Partner — invariáns) + a dokumentumok mapping-foreignKey-ként.
|
||||
var history = new EkaerHistory { IsOutgoing = false, StatusId = (int)EkaerStatus.Pending };
|
||||
fruitBankEkaerService.SetSummary(history, docs[0].ShippingDate, docs[0].Partner?.Name);
|
||||
toCreate.Add((history, docs.Select(d => d.Id).ToList()));
|
||||
}
|
||||
catch (Exception 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}, Telephely #{group.Key.PartnerDepotId?.ToString() ?? "—"}): a kötelezettség nem dönthető el — {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Kimenő ── Rekord nélküli, lezárt (Complete) rendelések a dátumtól; rendelésenként vizsgáljuk (nincs Shipping).
|
||||
// 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)
|
||||
.Where(o => o.GenericAttributes.Any(ga => ga.Key == nameof(OrderDto.DateOfReceipt) && DateTime.Parse(ga.Value) >= fromDate.Date))
|
||||
.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)
|
||||
.ToListAsync();
|
||||
|
||||
var missingOrders = missingOrderIds.Count == 0 ? [] : await ctx.OrderDtos.GetAllByIds(missingOrderIds, true).ToListAsync();
|
||||
|
||||
foreach (var order in missingOrders)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = fruitBankEkaerService.EvaluateObligation(order);
|
||||
if (result.Obligation == EkaerObligation.DataError) { messages.UnionWith(result.Errors); continue; }
|
||||
if (result.Obligation == EkaerObligation.NotRequired) continue;
|
||||
|
||||
var history = new EkaerHistory { IsOutgoing = true, StatusId = (int)EkaerStatus.Pending };
|
||||
fruitBankEkaerService.SetSummary(history, order.DateOfReceipt, order.Customer?.Company);
|
||||
toCreate.Add((history, [order.Id]));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"CreateMissingEkaerHistories; outbound evaluate failed; OrderId: {order.Id}", ex);
|
||||
messages.Add($"Rendelés #{order.Id}: a kötelezettség nem dönthető el — {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Beszúrás ──
|
||||
var createdCount = 0;
|
||||
foreach (var (history, foreignKeys) in toCreate)
|
||||
{
|
||||
try
|
||||
{
|
||||
// EkaerHistory + a forrás-mapping sorok atomikusan (a junction tartja a kapcsolatot, nem a rendszer-tábla).
|
||||
await ctx.AddEkaerHistoryWithMappingsAsync(history, foreignKeys);
|
||||
createdCount++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"CreateMissingEkaerHistories; insert failed; IsOutgoing: {history.IsOutgoing}; foreignKeys: [{string.Join(",", foreignKeys)}]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Info($"CreateMissingEkaerHistories; created: {createdCount}; messages: {messages.Count}; fromDate: {fromDate:yyyy-MM-dd}");
|
||||
return new EkaerCreateResult { CreatedCount = createdCount, Messages = [.. messages.OrderBy(m => m)] };
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.GetShippings)]
|
||||
public async Task<List<Shipping>> GetShippings()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
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 EkaerHistoryDbTable : MgDbTableBase<EkaerHistory>
|
||||
{
|
||||
public EkaerHistoryDbTable(IEventPublisher eventPublisher, INopDataProvider dataProvider, IShortTermCacheManager shortTermCacheManager, IStaticCacheManager staticCacheManager, AppSettings appSettings)
|
||||
: base(eventPublisher, dataProvider, shortTermCacheManager, staticCacheManager, appSettings)
|
||||
{
|
||||
}
|
||||
|
||||
// History tábla: legújabb elöl.
|
||||
public override IOrderedQueryable<EkaerHistory> GetAll() => base.GetAll().OrderByDescending(p => p.Id);
|
||||
|
||||
// loadRelations → a deklarációt lefedő forrás-mapping sorok (Mappings) is töltődnek (a kliensnek kellenek).
|
||||
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ó".
|
||||
private static readonly int[] ToSubmitStatusIds = [.. Enum.GetValues<EkaerStatus>().Select(s => (int)s).Except([(int)EkaerStatus.Sent, (int)EkaerStatus.SentWithMissingData])];
|
||||
|
||||
/// <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.)
|
||||
/// nem fordulnak SQL-re, ezért közvetlen StatusId-halmazzal szűrünk.</summary>
|
||||
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.
|
||||
if (filter == EkaerHistoryFilter.All) return GetAll(loadRelations);
|
||||
|
||||
var statusIds = new List<int>();
|
||||
if (filter.HasFlag(EkaerHistoryFilter.ToSubmit)) statusIds.AddRange(ToSubmitStatusIds);
|
||||
if (filter.HasFlag(EkaerHistoryFilter.Sent)) statusIds.Add((int)EkaerStatus.Sent);
|
||||
if (filter.HasFlag(EkaerHistoryFilter.NeedsCompletion)) statusIds.Add((int)EkaerStatus.SentWithMissingData);
|
||||
|
||||
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)
|
||||
// → 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, 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);
|
||||
}
|
||||
|
|
@ -55,6 +55,9 @@ public class FruitBankDbContext : MgDbContextBase,
|
|||
public OrderItemDtoDbTable OrderItemDtos { get; set; }
|
||||
|
||||
public PartnerDbTable Partners { get; set; }
|
||||
public PartnerDepotDbTable PartnerDepots { get; set; }
|
||||
public EkaerHistoryDbTable EkaerHistories { get; set; }
|
||||
public EkaerHistoryMappingDbTable EkaerHistoryMappings { get; set; }
|
||||
public CargoPartnerDbTable CargoPartners { get; set; }
|
||||
public CargoTruckDbTable CargoTrucks{ get; set; }
|
||||
|
||||
|
|
@ -86,7 +89,7 @@ public class FruitBankDbContext : MgDbContextBase,
|
|||
public IRepository<StockQuantityHistoryExt> StockQuantityHistoriesExt { get; set; }
|
||||
|
||||
public FruitBankDbContext(INopDataProvider dataProvider, ILockService lockService, FruitBankAttributeService fruitBankAttributeService, IStoreContext storeContext,
|
||||
CargoPartnerDbTable cargoPartnerDbTable, CargoTruckDbTable cargoTruckDbTable, PartnerDbTable partnerDbTable, 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,
|
||||
ProductDtoDbTable productDtoDbTable, OrderDtoDbTable orderDtoDbTable, OrderItemDtoDbTable orderItemDtoDbTable, OrderItemPalletDbTable orderItemPalletDbTable,
|
||||
StockQuantityHistoryDtoDbTable stockQuantityHistoryDtos, CustomerCreditDbTable customerCreditDbTable,
|
||||
|
|
@ -115,6 +118,9 @@ public class FruitBankDbContext : MgDbContextBase,
|
|||
|
||||
Files = filesDbTable;
|
||||
Partners = partnerDbTable;
|
||||
PartnerDepots = partnerDepotDbTable;
|
||||
EkaerHistories = ekaerHistoryDbTable;
|
||||
EkaerHistoryMappings = ekaerHistoryMappingDbTable;
|
||||
CargoPartners = cargoPartnerDbTable;
|
||||
CargoTrucks = cargoTruckDbTable;
|
||||
|
||||
|
|
@ -775,6 +781,25 @@ public class FruitBankDbContext : MgDbContextBase,
|
|||
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)
|
||||
{
|
||||
var customerAddressMapping = new CustomerAddressMapping
|
||||
|
|
|
|||
|
|
@ -22,9 +22,10 @@ public class PartnerDbTable : MgDbTableBase<Partner>
|
|||
{
|
||||
return loadRelations
|
||||
? GetAll()
|
||||
.LoadWith(sd => sd.ShippingDocuments).ThenLoad(s => s.Shipping)
|
||||
.LoadWith(sd => sd.ShippingDocuments).ThenLoad(si => si.ShippingItems).ThenLoad(sip => sip.ShippingItemPallets)
|
||||
.LoadWith(sd => sd.ShippingDocuments).ThenLoad(sdtof => sdtof.ShippingDocumentToFiles).ThenLoad(f => f.ShippingDocumentFile)
|
||||
.LoadWith(p => p.PartnerDepots)
|
||||
.LoadWith(p => p.ShippingDocuments).ThenLoad(sd => sd.Shipping)
|
||||
.LoadWith(p => p.ShippingDocuments).ThenLoad(sd => sd.ShippingItems).ThenLoad(si => si.ShippingItemPallets)
|
||||
.LoadWith(p => p.ShippingDocuments).ThenLoad(sd => sd.ShippingDocumentToFiles).ThenLoad(sdtof => sdtof.ShippingDocumentFile)
|
||||
: GetAll();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
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 PartnerDepotDbTable : MgDbTableBase<PartnerDepot>
|
||||
{
|
||||
public PartnerDepotDbTable(IEventPublisher eventPublisher, INopDataProvider dataProvider, IShortTermCacheManager shortTermCacheManager, IStaticCacheManager staticCacheManager, AppSettings appSettings)
|
||||
: base(eventPublisher, dataProvider, shortTermCacheManager, staticCacheManager, appSettings)
|
||||
{
|
||||
}
|
||||
|
||||
public override IOrderedQueryable<PartnerDepot> GetAll() => base.GetAll().OrderBy(p => p.PartnerId);
|
||||
|
||||
public IQueryable<PartnerDepot> GetAll(bool loadRelations)
|
||||
{
|
||||
return loadRelations ? GetAll().LoadWith(sd => sd.Partner) : GetAll();
|
||||
}
|
||||
|
||||
public Task<PartnerDepot> GetByIdAsync(int id, bool loadRelations) => GetAll(loadRelations).FirstOrDefaultAsync(p => p.Id == id);
|
||||
public IQueryable<PartnerDepot> GetByPartnerId(int partnerId, bool loadRelations) => GetAll(loadRelations).Where(p => p.PartnerId == partnerId);
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Nop.Core;
|
||||
using Nop.Core.Domain.Orders;
|
||||
using Nop.Core.Infrastructure;
|
||||
using Nop.Data;
|
||||
|
|
@ -43,6 +44,7 @@ using System.Net.Http.Headers;
|
|||
using Mango.Nop.Core.Loggers;
|
||||
using AyCode.Services.Nav;
|
||||
using AyCode.Services.Nav.Ekaer;
|
||||
using AyCode.Services.Nav.Ekaer.Models;
|
||||
using FruitBank.Common.Services.Ekaer;
|
||||
using FruitBank.Common.Server.Services.Ekaer;
|
||||
using System.Security.Authentication;
|
||||
|
|
@ -83,6 +85,9 @@ public class PluginNopStartup : INopStartup
|
|||
services.AddScoped<OrderItemPalletDbTable>();
|
||||
|
||||
services.AddScoped<PartnerDbTable>();
|
||||
services.AddScoped<PartnerDepotDbTable>();
|
||||
services.AddScoped<EkaerHistoryDbTable>();
|
||||
services.AddScoped<EkaerHistoryMappingDbTable>();
|
||||
services.AddScoped<CargoPartnerDbTable>();
|
||||
services.AddScoped<CargoTruckDbTable>();
|
||||
|
||||
|
|
@ -198,18 +203,44 @@ public class PluginNopStartup : INopStartup
|
|||
BaseUrl = c["BaseUrl"] ?? "https://import-test-b.ekaer.nav.gov.hu", // TEST; PROD: https://import.ekaer.nav.gov.hu
|
||||
};
|
||||
});
|
||||
services.AddSingleton(sp =>
|
||||
|
||||
// EKÁER konfiguráció (cégadat + telephely + küszöb + árfolyam), configból (appsettings "Ekaer").
|
||||
// IEkaerSettings-ként kötjük be (AyCode-kontraktus) — a fogyasztók az interfészt kérik, mint az INavCredentials-t.
|
||||
services.AddSingleton<IEkaerSettings>(sp =>
|
||||
{
|
||||
var c = sp.GetRequiredService<IConfiguration>().GetSection("Ekaer:Company");
|
||||
return new EkaerCompanyInfo
|
||||
var c = sp.GetRequiredService<IConfiguration>().GetSection("Ekaer");
|
||||
var co = c.GetSection("Company");
|
||||
var site = co.GetSection("Site");
|
||||
return new EkaerSettings
|
||||
{
|
||||
Name = c["Name"],
|
||||
TaxId = c["TaxId"],
|
||||
CountryCode = c["CountryCode"] ?? "HU", // a NAV EKÁER magyar
|
||||
PostalCode = c["PostalCode"],
|
||||
City = c["City"],
|
||||
Street = c["Street"],
|
||||
// UnloadLocation (saját telephely): magyar címnél a NAV Name/VatNumber/Phone/Email-t is kér — TODO configból összeállítani.
|
||||
Company = new EkaerCompanyInfo
|
||||
{
|
||||
Name = co["Name"],
|
||||
TaxId = co["TaxId"],
|
||||
CountryCode = co["CountryCode"] ?? "HU", // a NAV EKÁER magyar
|
||||
PostalCode = co["PostalCode"],
|
||||
City = co["City"],
|
||||
Street = co["Street"],
|
||||
// Telephely = fizikai fel-/lerakodási hely (bejövőnél lerakodás, kimenőnél felrakodás).
|
||||
// Magyar címnél a NAV Name/VatNumber/Phone/Email-t is megköveteli. Ha = székhely, ugyanaz a cím.
|
||||
Site = new LocationType
|
||||
{
|
||||
Name = co["Name"],
|
||||
VatNumber = co["TaxId"],
|
||||
Country = co["CountryCode"] ?? "HU",
|
||||
ZipCode = site["PostalCode"],
|
||||
City = site["City"],
|
||||
Street = site["Street"],
|
||||
Phone = co["Phone"],
|
||||
Email = co["Email"],
|
||||
},
|
||||
},
|
||||
|
||||
// Default ÉRTÉK szándékosan nincs: be nem töltött config → 0. A 0 árfolyam a számításkor hibát dob,
|
||||
// a 0 küszöb „mindent jelentünk" — egyik sem számol elavult, kódba égetett értékkel.
|
||||
EurHufRate = c.GetValue<double>("ExchangeRate:EurHuf"),
|
||||
ThresholdWeightKg = c.GetValue<double>("Thresholds:WeightKg"),
|
||||
ThresholdValueHuf = c.GetValue<int>("Thresholds:ValueHuf"),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -237,8 +268,10 @@ public class PluginNopStartup : INopStartup
|
|||
})
|
||||
.AddAcBinaryProtocol(opts =>
|
||||
{
|
||||
//opts.ProtocolMode = BinaryProtocolMode.AsyncSegment;
|
||||
opts.ProtocolMode = BinaryProtocolMode.Bytes;
|
||||
opts.FlushPolicy = FlushPolicy.Coalesced;
|
||||
opts.FlushTimeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
// Explicit AcLogger instance (name-based category, matches the previous setup).
|
||||
// If omitted, the options extension falls back to ILogger<AcBinaryHubProtocol> from DI.
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ public partial class NameCompatibility : INameCompatibility
|
|||
|
||||
{ typeof(CargoPartner), FruitBankConstClient.CargoPartnerDbTableName},
|
||||
{ typeof(CargoTruck), FruitBankConstClient.CargoTruckDbTableName},
|
||||
|
||||
{ typeof(PartnerDepot), FruitBankConstClient.PartnerDepotDbTableName},
|
||||
{ typeof(EkaerHistory), FruitBankConstClient.EkaerHistoryDbTableName},
|
||||
{ typeof(EkaerHistoryMapping), FruitBankConstClient.EkaerHistoryMappingDbTableName},
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,9 @@ Scope: a FruitBank EKÁER-bejelentés szerver-oldali / nopCommerce-integrációs
|
|||
|
||||
## MGFBANKPLUG-EKAER-I-T3X8: A `Product.Gtin` átmenetileg a VTSZ-t tárolja — szétválasztandó
|
||||
|
||||
**Status:** Closed (2026-06-02) — superseded · **Priority:** P3 · **Type:** I (adatmodell / átmeneti megoldás)
|
||||
**Status:** Open · **Priority:** P3 · **Type:** I (adatmodell / átmeneti megoldás)
|
||||
|
||||
### Resolution
|
||||
**Áthelyezve** az általános adatmodell-topicba: **`FBANKAPP-DMODEL-I-P6X4`** (`FruitBank.Common/docs/DATAMODEL/DATAMODEL_ISSUES.md#fbankapp-dmodel-i-p6x4`). Indok: a GTIN/VTSZ szétválasztás **általános** adatmodell-kérdés, nem EKÁER-specifikus — az EKÁER csak felhasználója. A követés ott folytatódik; az alábbi leírás referencia.
|
||||
> **Újranyitva (2026-06-15):** a probléma **továbbra is fennáll** — a `Gtin` jelenleg is a VTSZ-t tárolja, a szétválasztás nem történt meg. A 2026-06-02-i `FBANKAPP-DMODEL-I-P6X4`-be (DATAMODEL) helyezés visszavonva; a követés ITT (EKÁER) folytatódik. *(A `FBANKAPP-DMODEL-I-P6X4` ennek megfelelően lezárva — DATAMODEL.)*
|
||||
|
||||
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).
|
||||
|
|
@ -27,3 +26,18 @@ Egy termékhez a kettő nem azonos; a `Gtin` oszlop VTSZ-ként való használata
|
|||
**Affected:**
|
||||
- `FruitBank.Common/Dtos/ProductDto.cs` → `Gtin` property (a `[Column(nameof(Product.Gtin))]` jelöléssel, summary-ban megjelölve)
|
||||
- jövőbeli: `ShippingToEkaerMapper` (`FruitBank.Common/Services/Ekaer/`) — a `productVtsz`-t a külön `Vtsz` mezőből olvassa
|
||||
|
||||
## MGFBANKPLUG-EKAER-I-M6Y2: A számla-pénznem a Partnerből jön — jobb lenne a ShippingDocument-en
|
||||
|
||||
**Status:** Open · **Priority:** P3 · **Type:** I (adatmodell)
|
||||
|
||||
Az EKÁER tétel-érték HUF-ra számításához a forrás-pénznem jelenleg a **`Partner.Currency`**-ből jön (a beszállító alap-devizája). **Most 100%-ban megbízható** — a partner egyértelműen meghatározza a devizát.
|
||||
|
||||
**Hatás:** működik; de a pénznem **konceptuálisan a szállítmányhoz / dokumentumhoz tartozik**, nem a partnerhez (egy partner elvileg többféle devizában is számlázhat).
|
||||
|
||||
**Javítási irány:** külön **`Currency` oszlop a `ShippingDocument`-en**, és az `EkaerValueCalculator` / `ShippingToEkaerMapper` onnan olvasson; a `Partner.Currency` maradjon fallback. **Nem prioritás** — a jelenlegi partner-alapú forrás elegendő.
|
||||
|
||||
**Affected:**
|
||||
- `FruitBank.Common/Entities/ShippingDocument.cs` — jövőbeli `Currency` oszlop
|
||||
- `FruitBank.Common/Services/Ekaer/EkaerValueCalculator.cs` + `ShippingToEkaerMapper.cs` — a deviza-forrás
|
||||
- jelenlegi forrás: `PartnerBase.Currency`
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue