EKÁER: depot, isEkaer, grid layout, doc updates

- Switch to DEV DB and split company address street/number
- CustomerCreditWidget: isEkaer now nullable, defaults to true
- FruitBankDataController: add grid layout CRUD endpoints
- EKÁER: require depot for inbound, batch isEkaer for outbound
- Update docs: stricter foreign reporting, depot handling, isEkaer logic, mapping rules, and close depot-grouping issue
This commit is contained in:
Loretta 2026-07-10 15:37:05 +02:00
parent 36f22aa2cc
commit 9f6aa1b903
5 changed files with 218 additions and 25 deletions

View File

@ -35,8 +35,10 @@ public class CustomerCreditWidgetViewComponent : NopViewComponent
var credit = await _customerCreditService.GetByCustomerIdAsync(customerId);
var outstanding = await _customerCreditService.GetOutstandingBalanceAsync(customerId);
// Hiányzó attribútum (null) = köteles — a kapu is így dönt (CreateMissingEkaerHistories: csak az explicit
// false mentesít); a checkbox pipáltat mutat, amíg explicit ki nem veszik.
var isEkaer = await _attributeService
.GetGenericAttributeValueAsync<Customer, bool>(customerId, FruitBankPluginConst.IsEkaerAttribute, storeId: 0);
.GetGenericAttributeValueAsync<Customer, bool?>(customerId, FruitBankPluginConst.IsEkaerAttribute, storeId: 0) ?? true;
var model = new CustomerCreditWidgetModel
{

View File

@ -49,6 +49,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
: BasePluginController, IFruitBankDataControllerServer
{
private const int LastShippingDays = 15;
private const string GridLayoutKeyGroup = "GridLayout";
private readonly ILogger _logger = new Logger<FruitBankDataController>(logWriters.ToArray());
[SignalR(SignalRTags.ProcessAndSaveFullShippingJson)]
@ -77,6 +78,53 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
return await ctx.GenericAttributeDtos.GetByIdAsync(genericAttributeDto.Id);
}
private async Task<GenericAttributeDto?> GetGridLayoutAttributeAsync(string key, int userId)
=> (await ctx.GetGenericAttributeDtosByEntityIdAndKeyGroup(userId, GridLayoutKeyGroup, 0))
.FirstOrDefault(x => x.Key == key);
[SignalR(SignalRTags.GetGridLayoutTag)]
public async Task<string?> GetGridLayout(string key, int userId)
{
return (await GetGridLayoutAttributeAsync(key, userId))?.Value;
}
[SignalR(SignalRTags.SaveGridLayoutTag)]
public async Task<bool> SaveGridLayout(string key, string layoutJson, int userId)
{
var attribute = await GetGridLayoutAttributeAsync(key, userId);
if (attribute == null)
{
await ctx.GenericAttributeDtos.InsertAsync(new GenericAttributeDto
{
EntityId = userId,
KeyGroup = GridLayoutKeyGroup,
Key = key,
Value = layoutJson,
StoreId = 0,
CreatedOrUpdatedDateUTC = DateTime.UtcNow
});
}
else
{
attribute.Value = layoutJson;
attribute.CreatedOrUpdatedDateUTC = DateTime.UtcNow;
await ctx.GenericAttributeDtos.UpdateAsync(attribute);
}
return true;
}
[SignalR(SignalRTags.DeleteGridLayoutTag)]
public async Task<bool> DeleteGridLayout(string key, int userId)
{
var attribute = await GetGridLayoutAttributeAsync(key, userId);
if (attribute == null) return false;
await ctx.GenericAttributeDtos.DeleteAsync(attribute);
return true;
}
[SignalR(SignalRTags.GetStockQuantityHistoryDtos)]
public async Task<List<StockQuantityHistoryDto>> GetStockQuantityHistoryDtos()
{
@ -340,7 +388,10 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
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();
// A PartnerDepot szándékosan NINCS a default loadRelations-ben — csak itt kell (felrakodási hely = depot címe).
var documents = await ctx.ShippingDocuments.GetAll(true)
.LoadWith(sd => sd.PartnerDepot)
.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);
@ -433,6 +484,15 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
if (result.Obligation == EkaerObligation.DataError) { messages.UnionWith(result.Errors); continue; }
if (result.Obligation == EkaerObligation.NotRequired) continue;
// Kötelező, de nincs telephely → NEM jön létre sor, üzenet a popupba (T-D8R4: a PartnerDepot az
// EKÁER-hez kötelező — a felrakodási hely a depot címe); pótlás után a következő futás felveszi.
// A depot-check a kötelezettség-döntés UTÁN: küszöb alatti belföldi csoportra nem szólunk feleslegesen.
if (group.Key.PartnerDepotId is null)
{
messages.Add($"{docs[0].Partner?.Name} (Szállítás #{group.Key.ShippingId}): nincs telephely (PartnerDepot) — EKÁER-bejelentéshez kötelező, a sor nem jött létre.");
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);
@ -455,10 +515,22 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
var missingOrders = missingOrderIds.Count == 0 ? [] : await ctx.OrderDtos.GetAllByIds(missingOrderIds, true).ToListAsync();
// Vevő-szintű mentesség (isEkaer GenericAttribute — mint a Partner.IsEkaer) EGY batch-queryvel a ciklus előtt:
// ami nincs a dictionary-ben = nincs beállítva = köteles (fail-safe; a widget-checkbox is pipáltat mutat rá),
// CSAK az explicit false mentesít.
var customerIds = missingOrders.Select(o => o.CustomerId).Distinct().ToList();
Dictionary<int, bool> isEkaerByCustomer = customerIds.Count == 0 ? new() : (await ctx.GenericAttributes.Table
.Where(ga => ga.KeyGroup == nameof(Customer) && ga.Key == FruitBankPluginConst.IsEkaerAttribute
&& ga.StoreId == 0 && customerIds.Contains(ga.EntityId))
.ToListAsync())
.ToDictionary(ga => ga.EntityId, ga => CommonHelper.To<bool>(ga.Value));
foreach (var order in missingOrders)
{
try
{
if (isEkaerByCustomer.TryGetValue(order.CustomerId, out var isEkaer) && !isEkaer) continue;
var result = fruitBankEkaerService.EvaluateObligation(order);
if (result.Obligation == EkaerObligation.DataError) { messages.UnionWith(result.Errors); continue; }
if (result.Obligation == EkaerObligation.NotRequired) continue;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long