A mérési értesítők eljutnak a vevőhöz: kiváltás, tokenek, egyszeri küldés
- Az Order.OrderTotal token a saját, mérés utáni végösszeget adja - a nopnak nincs ilyen tokenje, a sablonban eddig szó szerint ment ki - Az admin tokenlistája feltöltve a két FruitBank sablonra (GetTokenGroups, GetListOfAllowedTokensAsync) - A Started értesítőt a StartMeasuring endpoint váltja ki, a tranzakción kívül - a mérésindítás nem ír Order-sort, így esemény sem keletkezik - Az egyszeri küldés a FruitBankNotificationService-be került: a jelölés csak sikeres sorbaállítás után íródik, így egy bukott küldés nem némítja el véglegesen a rendelést - ADR 0003-H7C4, NOTIF topic és a docs/NOTIFICATION/ leírás
This commit is contained in:
@@ -16,3 +16,4 @@ Per-repo topic registry for this plugin, per the **per-repo extension convention
|
||||
| `ORDD` | ORDER-DRAFT | AI-assisted intake channel: inbound free-text messages (Viber/email/dictation) parsed into order drafts, admin review/approval, then PreOrder creation that feeds the existing PRE-ORDER conversion. Covers parsing, product/partner resolution, learning mappings, the draft lifecycle and the approval entry point. | `docs/ORDERDRAFT/` |
|
||||
| `MARGIN`| ÁRRÉS | Beszerzési önköltség és árrés: a rendelés-tételek ↔ beszerzések (költség-rétegek) hozzárendelése, a fuvar- és egyéb költségek tárolása és szétosztása, deviza- és áfakezelés az árrés szempontjából, valamint a kimutatás dokumentum / kamion / hónap bontásban. | `docs/MARGIN/` |
|
||||
| `PMDATA`| TERMÉK-TÖRZSADAT | A termék mérési/logisztikai törzsadata (`IsMeasurable`, `Tare`, `AverageWeight`, `AverageWeightTreshold`, `CratesPerPallet`): tárolása, írási útjai, validálása és tömeges szerkesztése. A készlet-vezérelt értékek (`NetWeight`, `IncomingQuantity`) NEM ide tartoznak — azok a `STOCK` és a `PREO` topiké. | `docs/PMDATA/` |
|
||||
| `NOTIF` | ÉRTESÍTÉS | Vevői e-mail értesítők: a plugin saját `MessageTemplate`-jei, a nop token-provider kiváltása és a saját tokenek, a `MeasuringStatus`-átmenetekhez kötött kiváltás, valamint az ismétlés elleni jelölés. A nop natív értesítői NEM ide tartoznak. | `docs/NOTIFICATION/` |
|
||||
|
||||
@@ -19,7 +19,7 @@ using Nop.Services.Catalog;
|
||||
|
||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers;
|
||||
|
||||
public class CustomOrderSignalREndpoint(FruitBankDbContext ctx, SignalRSendToClientService sendToClient, IPriceCalculationService customPriceCalculationService,IEventPublisher eventPublisher, IWorkContext workContext, IEnumerable<IAcLogWriterBase> logWriters)
|
||||
public class CustomOrderSignalREndpoint(FruitBankDbContext ctx, SignalRSendToClientService sendToClient, IPriceCalculationService customPriceCalculationService,IEventPublisher eventPublisher, IWorkContext workContext, FruitBankNotificationService fruitBankNotificationService, IEnumerable<IAcLogWriterBase> logWriters)
|
||||
: ICustomOrderSignalREndpointServer
|
||||
{
|
||||
// PROD-on 15 napos adat-ablak; DEV-en 30 nap (a PROD-ból másolt adaton már bőven van mit listázni).
|
||||
@@ -146,7 +146,29 @@ public class CustomOrderSignalREndpoint(FruitBankDbContext ctx, SignalRSendToCli
|
||||
_logger.Detail($"StartMeasuring invoked; orderId: {orderId}; userId: {userId}");
|
||||
|
||||
if (!await ctx.StartMeasuringSafeAsync(orderId, userId)) return null;
|
||||
return await ctx.OrderDtos.GetByIdAsync(orderId, true);
|
||||
|
||||
var orderDto = await ctx.OrderDtos.GetByIdAsync(orderId, true);
|
||||
|
||||
// A mérésindítás csak a MeasurementOwnerId GenericAttribute-ot írja, Order-sort nem — ezért nem keletkezik
|
||||
// `EntityUpdatedEvent<Order>`, amire a Services/EventConsumer figyel, és a „Started" értesítőnek nincs
|
||||
// kiváltója. A hívás a tranzakción KÍVÜL áll: ha a mérésindítás visszagördül, ne menjen ki levél.
|
||||
await SendOrderStartedNotificationAsync(orderDto);
|
||||
|
||||
return orderDto;
|
||||
}
|
||||
|
||||
/// <summary>Sends the "order started" customer notification for a just-started measurement.</summary>
|
||||
private async Task SendOrderStartedNotificationAsync(OrderDto orderDto)
|
||||
{
|
||||
if (orderDto == null) return;
|
||||
|
||||
var order = ctx.Orders.GetById(orderDto.Id);
|
||||
if (order == null) return;
|
||||
|
||||
// A StartMeasuringSafeAsync akkor is sikert jelez, ha a mérést már korábban elindították (a
|
||||
// StartMeasuringAsync ilyenkor változatlanul visszaadja a rendelést) — az ismétlés ellen a szolgáltatás
|
||||
// jelölése véd, nem a hívás eredménye.
|
||||
await fruitBankNotificationService.SendOrderStartedCustomerNotificationOnceAsync(order, orderDto.IsMeasurable);
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.SetOrderStatusToComplete, SignalRTags.SendOrderChanged, SendToClientType.Others)]
|
||||
|
||||
@@ -13,6 +13,7 @@ using Nop.Core.Domain.Tax;
|
||||
using Nop.Core.Domain.Vendors;
|
||||
using Nop.Core.Events;
|
||||
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
|
||||
using Nop.Plugin.Misc.FruitBankPlugin.Services;
|
||||
using Nop.Services.Attributes;
|
||||
using Nop.Services.Blogs;
|
||||
using Nop.Services.Catalog;
|
||||
@@ -39,6 +40,12 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Infrastructure
|
||||
{
|
||||
public class FruitBankMessageTokenProvider : MessageTokenProvider
|
||||
{
|
||||
/// <summary>The order total token — the FruitBank-calculated amount stored on the order.</summary>
|
||||
public const string ORDER_TOTAL_TOKEN = "Order.OrderTotal";
|
||||
|
||||
/// <summary>The measurable-order note token — its value is HTML, supplied by the notification service.</summary>
|
||||
public const string ORDER_MEASURABLE_NOTE_TOKEN = "Order.MeasurableNote";
|
||||
|
||||
private readonly IOrderService _orderService;
|
||||
private readonly IPriceFormatter _priceFormatter;
|
||||
private readonly ICurrencyService _currencyService;
|
||||
@@ -145,12 +152,55 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Infrastructure
|
||||
tokens.Remove(existing);
|
||||
|
||||
tokens.Add(new Token("Order.Product(s)", await BuildCustomProductTableAsync(order, languageId), true));
|
||||
|
||||
// A nopnak NINCS `Order.OrderTotal` tokenje — a végösszeget csak a termék-tábla lábában jeleníti meg,
|
||||
// és ott is az árfolyammal átszámított értéket. Nekünk a saját, mérés utáni szám kell: a
|
||||
// CustomPriceCalculationService.CheckAndUpdateOrderTotalPrice írja az `Order.OrderTotal` oszlopba a
|
||||
// tételek bruttó (kedvezménnyel csökkentett) árainak összegét — a token ezt az eltárolt értéket adja.
|
||||
tokens.Add(new Token(ORDER_TOTAL_TOKEN, await FormatOrderTotalAsync(order, languageId)));
|
||||
}
|
||||
|
||||
/// <summary>Maps the FruitBank message templates to their token groups.</summary>
|
||||
public override IEnumerable<string> GetTokenGroups(MessageTemplate messageTemplate)
|
||||
{
|
||||
// A bázis a nop saját `MessageTemplateSystemNames` konstansaira switch-el, és ismeretlen sablonnévre
|
||||
// ÜRES csoportlistát ad. A `GetListOfAllowedTokensAsync` szűrője az üres listát nem `null`-ként kezeli,
|
||||
// ezért maradt üresen az admin „Engedélyezett tokenek" doboza a két FruitBank-sablonnál.
|
||||
if (messageTemplate.Name == FruitBankNotificationService.ORDER_STARTED_TEMPLATE_NAME ||
|
||||
messageTemplate.Name == FruitBankNotificationService.ORDER_AUDITED_TEMPLATE_NAME)
|
||||
return [TokenGroupNames.StoreTokens, TokenGroupNames.OrderTokens, TokenGroupNames.CustomerTokens];
|
||||
|
||||
return base.GetTokenGroups(messageTemplate);
|
||||
}
|
||||
|
||||
/// <summary>Adds the FruitBank-specific order tokens to the admin's allowed-token list.</summary>
|
||||
public override async Task<IEnumerable<string>> GetListOfAllowedTokensAsync(IEnumerable<string> tokenGroups = null)
|
||||
{
|
||||
var allowedTokens = (await base.GetListOfAllowedTokensAsync(tokenGroups)).ToList();
|
||||
|
||||
// A bázis a privát `AllowedTokens` szótárból dolgozik — a saját tokenjeink csak itt kerülhetnek a listába.
|
||||
if (tokenGroups == null || tokenGroups.Contains(TokenGroupNames.OrderTokens))
|
||||
{
|
||||
allowedTokens.Add($"%{ORDER_TOTAL_TOKEN}%");
|
||||
allowedTokens.Add($"%{ORDER_MEASURABLE_NOTE_TOKEN}%");
|
||||
}
|
||||
|
||||
return allowedTokens.Distinct();
|
||||
}
|
||||
|
||||
/// <summary>The order's display currency — the customer's, falling back to the primary store currency.</summary>
|
||||
private async Task<Currency> GetOrderCurrencyAsync(Order order)
|
||||
=> await _currencyService.GetCurrencyByCodeAsync(order.CustomerCurrencyCode)
|
||||
?? await _currencyService.GetCurrencyByIdAsync(_currencySettings.PrimaryStoreCurrencyId);
|
||||
|
||||
/// <summary>The stored order total, formatted — the single source for both the token and the table footer.</summary>
|
||||
private async Task<string> FormatOrderTotalAsync(Order order, int languageId)
|
||||
=> await _priceFormatter.FormatPriceAsync(
|
||||
order.OrderTotal, true, await GetOrderCurrencyAsync(order), languageId, true);
|
||||
|
||||
private async Task<string> BuildCustomProductTableAsync(Order order, int languageId)
|
||||
{
|
||||
var currency = await _currencyService.GetCurrencyByCodeAsync(order.CustomerCurrencyCode)
|
||||
?? await _currencyService.GetCurrencyByIdAsync(_currencySettings.PrimaryStoreCurrencyId);
|
||||
var currency = await GetOrderCurrencyAsync(order);
|
||||
|
||||
var items = await _orderService.GetOrderItemsAsync(order.Id);
|
||||
var itemDtos = await _dbContext.OrderItemDtos.GetAllByOrderId(order.Id).ToListAsync();
|
||||
@@ -206,8 +256,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Infrastructure
|
||||
}
|
||||
}
|
||||
|
||||
var orderTotal = await _priceFormatter.FormatPriceAsync(
|
||||
order.OrderTotal, true, currency, languageId, true);
|
||||
var orderTotal = await FormatOrderTotalAsync(order, languageId);
|
||||
|
||||
if(itemDtos.Any(i => i.IsMeasurable))
|
||||
{
|
||||
|
||||
@@ -138,38 +138,16 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||
var orderDto = await _dbContext.OrderDtos.GetByIdAsync(eventMessage.Entity.Id, true);
|
||||
if (orderDto == null) return;
|
||||
|
||||
// Az egyszeri küldés (jelölés-ellenőrzés → küldés → jelölés CSAK sikeres sorbaállítás után) a
|
||||
// FruitBankNotificationService-ben lakik: a `Started` ágat a CustomOrderSignalREndpoint is kiváltja,
|
||||
// és a két hívónak ugyanazt az idempotenciát kell látnia.
|
||||
if (orderDto.MeasuringStatus == MeasuringStatus.Audited)
|
||||
{
|
||||
var alreadySent = await _fruitBankAttributeService
|
||||
.GetGenericAttributeValueAsync<Order, bool>(eventMessage.Entity.Id, "OrderAuditedNotificationSent");
|
||||
await _fruitBankNotificationService
|
||||
.SendOrderAuditedCustomerNotificationOnceAsync(eventMessage.Entity, orderDto.IsMeasurable);
|
||||
|
||||
if (!alreadySent)
|
||||
{
|
||||
await _fruitBankNotificationService
|
||||
.SendOrderAuditedCustomerNotificationAsync(eventMessage.Entity, orderDto.IsMeasurable);
|
||||
|
||||
await _fruitBankAttributeService
|
||||
.InsertOrUpdateGenericAttributeAsync<Order, bool>(
|
||||
eventMessage.Entity.Id, "OrderAuditedNotificationSent", true);
|
||||
}
|
||||
}
|
||||
else if (orderDto.MeasuringStatus == MeasuringStatus.Started)
|
||||
{
|
||||
var alreadySent = await _fruitBankAttributeService
|
||||
.GetGenericAttributeValueAsync<Order, bool>(eventMessage.Entity.Id, "OrderStartedNotificationSent");
|
||||
|
||||
if (!alreadySent)
|
||||
{
|
||||
await _fruitBankNotificationService
|
||||
.SendOrderStartedCustomerNotificationAsync(eventMessage.Entity, orderDto.IsMeasurable);
|
||||
|
||||
await _fruitBankAttributeService
|
||||
.InsertOrUpdateGenericAttributeAsync<Order, bool>(
|
||||
eventMessage.Entity.Id, "OrderStartedNotificationSent", true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await _fruitBankNotificationService
|
||||
.SendOrderStartedCustomerNotificationOnceAsync(eventMessage.Entity, orderDto.IsMeasurable);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using Nop.Core.Domain.Common;
|
||||
using Nop.Core.Domain.Messages;
|
||||
using Nop.Core.Domain.Orders;
|
||||
using Nop.Core.Events;
|
||||
using Nop.Plugin.Misc.FruitBankPlugin.Infrastructure;
|
||||
using Nop.Services.Affiliates;
|
||||
using Nop.Services.Catalog;
|
||||
using Nop.Services.Common;
|
||||
@@ -37,6 +38,7 @@ public class FruitBankNotificationService(
|
||||
// A nop natív naplózója: a bejegyzések az Admin → Rendszer → Napló alatt megjelennek. Enélkül egy elmaradt
|
||||
// vevői értesítés nyomtalan maradna — élesben pontosan ez rejtett el egy üres törzsű sablont.
|
||||
Nop.Services.Logging.ILogger logger,
|
||||
FruitBankAttributeService fruitBankAttributeService,
|
||||
MessagesSettings messagesSettings) : WorkflowMessageService(commonSettings,
|
||||
emailAccountSettings,
|
||||
addressService,
|
||||
@@ -59,6 +61,12 @@ public class FruitBankNotificationService(
|
||||
public const string ORDER_AUDITED_TEMPLATE_NAME = "FruitBank.OrderAudited.CustomerNotification";
|
||||
public const string ORDER_STARTED_TEMPLATE_NAME = "FruitBank.OrderStarted.CustomerNotification";
|
||||
|
||||
/// <summary>GenericAttribute key guarding the "order started" notification against repeats.</summary>
|
||||
public const string ORDER_STARTED_SENT_KEY = "OrderStartedNotificationSent";
|
||||
|
||||
/// <summary>GenericAttribute key guarding the "order audited" notification against repeats.</summary>
|
||||
public const string ORDER_AUDITED_SENT_KEY = "OrderAuditedNotificationSent";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -162,6 +170,32 @@ public class FruitBankNotificationService(
|
||||
return await SendNotificationAsync(ORDER_AUDITED_TEMPLATE_NAME, order, measurableNote);
|
||||
}
|
||||
|
||||
/// <summary>Sends the "order started" notification unless it already went out for this order.</summary>
|
||||
public Task<bool> SendOrderStartedCustomerNotificationOnceAsync(Order order, bool isMeasurable)
|
||||
=> SendOnceAsync(order, ORDER_STARTED_SENT_KEY, () => SendOrderStartedCustomerNotificationAsync(order, isMeasurable));
|
||||
|
||||
/// <summary>Sends the "order audited" notification unless it already went out for this order.</summary>
|
||||
public Task<bool> SendOrderAuditedCustomerNotificationOnceAsync(Order order, bool isMeasurable)
|
||||
=> SendOnceAsync(order, ORDER_AUDITED_SENT_KEY, () => SendOrderAuditedCustomerNotificationAsync(order, isMeasurable));
|
||||
|
||||
// ── idempotencia ────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Runs the send once per order, keyed by a GenericAttribute flag.</summary>
|
||||
private async Task<bool> SendOnceAsync(Order order, string sentKey, Func<Task<int>> sendAsync)
|
||||
{
|
||||
if (await fruitBankAttributeService.GetGenericAttributeValueAsync<Order, bool>(order.Id, sentKey)) return false;
|
||||
|
||||
var queuedEmailId = await sendAsync();
|
||||
|
||||
// A jelölés CSAK sikeres sorbaállítás után íródik. A küldés minden hibaágon 0-t ad vissza (hiányzó vagy
|
||||
// inaktív sablon, nincs használható e-mail fiók, nincs vevő), és egy ilyenkor is kiírt jelölés VÉGLEGESEN
|
||||
// elnémítaná a rendelést: a két értesítőnek nincs második kiváltója, amire újrapróbálkozhatna.
|
||||
if (queuedEmailId <= 0) return false;
|
||||
|
||||
await fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Order, bool>(order.Id, sentKey, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── shared core ─────────────────────────────────────────────────────────
|
||||
|
||||
private async Task<int> SendNotificationAsync(string templateName, Order order, string measurableNote)
|
||||
@@ -215,7 +249,7 @@ public class FruitBankNotificationService(
|
||||
|
||||
await messageTokenProvider.AddCustomerTokensAsync(tokens, customer);
|
||||
|
||||
tokens.Add(new Token("Order.MeasurableNote", measurableNote, true));
|
||||
tokens.Add(new Token(FruitBankMessageTokenProvider.ORDER_MEASURABLE_NOTE_TOKEN, measurableNote, true));
|
||||
|
||||
int addressId = 0;
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# NOTIF — ISSUES
|
||||
|
||||
> ID format, Status vocabulary, type codes and archival are **not restated here** — see [`TOPIC_CODES.md`](../../.github/TOPIC_CODES.md) (→ framework registry).
|
||||
|
||||
Kontextus: [`README.md`](README.md).
|
||||
|
||||
---
|
||||
|
||||
## MGFBANKPLUG-NOTIF-I-Q3M8: A mérési értesítők kiváltása és idempotencia-jelölése
|
||||
|
||||
**Status:** Closed (2026-08-18) · **Priority:** P1 · **Type:** I · **2026-08-18**
|
||||
|
||||
Az [ADR 0003-H7C4](../adr/0003-H7C4-dto-event-bridge-wiring.md) horgony-bejegyzése. A rekord a DTO→fő-entitás
|
||||
esemény-híd bekötését rögzíti; ez a bejegyzés viszi az implementáció állapotát, a rekord *Follow-ups required*
|
||||
szakasza szerint.
|
||||
|
||||
**Mérés (2026-06-01 … 2026-08-19, a javítás előtt):** 3017 rendelés, 2916 lezárt, mind auditált raklappal —
|
||||
és mindössze 91 „értesítés megkísérelve" jelölés, 13 ténylegesen sorba tett levél.
|
||||
|
||||
### Lépések
|
||||
|
||||
- [x] **A híd bekötése** — `MgDtoDbTableBase` deklarálja a három `IConsumer<>` interfészt; `Mango.Nop.Data` →
|
||||
`Nop.Services` projektreferencia. Ezzel az `Audited` ág kiváltója helyreállt.
|
||||
- [x] **A `Started` ág triggere** — a mérés indítása (`FruitBankDbContext.StartMeasuringAsync`) csak
|
||||
`GenericAttribute`-ot ír, `OrderItemDtos`-ra pedig egyetlen írás sincs a pluginban, így a `Started`
|
||||
ablakban nem keletkezik `EntityUpdatedEvent<Order>`. A `CustomOrderSignalREndpoint.StartMeasuring` hívja
|
||||
közvetlenül, a mérésindítási tranzakció sikeres lezárulta után, a tranzakción kívül.
|
||||
- [x] **A jelölés akkor is ráíródott, ha a küldés nem sikerült** — a jelölés így azt jelentette, hogy
|
||||
„megpróbáltuk", nem azt, hogy „kiment", és mivel ott volt, a rendszer soha nem próbálta újra. Az
|
||||
idempotencia mostantól egyetlen helyen, a `FruitBankNotificationService.SendOnceAsync`-ben lakik, és
|
||||
mindkét hívó (a consumer `Audited` ága, valamint a `Started` triggert adó endpoint) azon át megy; a jelölés
|
||||
csak `queuedEmailId > 0` esetén íródik ki. Következmény: sikertelen küldés után a következő kiváltó
|
||||
újrapróbálkozik — tartósan hibás sablon- vagy fiók-konfigurációnál ez rendelés-frissítésenként egy újabb
|
||||
kísérletet jelent, amit a napló rendeléshez kötve jelez.
|
||||
- [x] **Visszamenőleg elégett jelölések — elfogadott jelenlegi állapot (2026-08-18)** — a sablonok
|
||||
2026-08-10-i helyreállítása előtt megkísérelt értesítések jelölése ott maradt a rendeléseken. **Nem
|
||||
takarítjuk:** a jelölés törlése azt eredményezné, hogy a következő Order-frissítésnél kimenne egy
|
||||
„elkészült, átvehető" levél hetekkel korábban átvett rendelésekre. A hiányzó levelek pótlása ezeknél
|
||||
nem cél.
|
||||
|
||||
### Resolution
|
||||
|
||||
Mind a négy lépés lezárva. A kiváltás két oldalról helyreállt — a keretbeli esemény-híd bekötésével
|
||||
([ADR 0003-H7C4](../adr/0003-H7C4-dto-event-bridge-wiring.md)) az `Audited`, a `StartMeasuring` endpoint
|
||||
közvetlen hívásával a `Started` ág —, az idempotencia pedig egyetlen helyre került, ahol a jelölés csak
|
||||
sikeres sorbaállítás után íródik ki. A visszamenőleg elégett jelöléseket szándékosan nem takarítjuk.
|
||||
|
||||
A mai állapot leírása: [`README.md`](README.md). **Éles ellenőrzés még hátravan:** a lezárás–jelölés–levél
|
||||
hármas újramérése néhány nap üzem után mondja meg, hogy a három szám együtt mozog-e.
|
||||
|
||||
### Érintett
|
||||
|
||||
- `Mango.Nop.Data/Repositories/MgDtoDbTableBase.cs`
|
||||
- `Services/EventConsumer.cs` — a `MeasuringStatus`-elágazás és a jelölés írása
|
||||
- `Domains/DataLayer/FruitBankDbContext.cs` — `StartMeasuringAsync`, `SetOrderStatusToCompleteAsync`
|
||||
@@ -0,0 +1,63 @@
|
||||
# ÉRTESÍTÉS — vevői e-mail értesítők
|
||||
|
||||
> Topic `NOTIF` (prefix `MGFBANKPLUG`) → entry ID-k `MGFBANKPLUG-NOTIF-<I|T|B>-<RAND>`. ID-formátum/Status-szótár → [`TOPIC_CODES.md`](../../.github/TOPIC_CODES.md) (→ keret-registry).
|
||||
> Companions: [`NOTIFICATION_ISSUES.md`](NOTIFICATION_ISSUES.md) — ismert defektek.
|
||||
|
||||
A plugin két saját vevői értesítőt küld a rendelés mérési állapotához kötve, a nop `MessageTemplate` /
|
||||
`QueuedEmail` gépezetén keresztül. A nop natív értesítői (OrderPlaced stb.) változatlanul működnek; ez a
|
||||
dokumentum azt írja le, amit a plugin tesz hozzá.
|
||||
|
||||
## A két sablon
|
||||
|
||||
| Sablon neve | Mikor | Törzs |
|
||||
|---|---|---|
|
||||
| `FruitBank.OrderStarted.CustomerNotification` | a rendelés mérése elindult | „elkezdtük feldolgozni" + mérendő tételnél árelőrejelzés |
|
||||
| `FruitBank.OrderAudited.CustomerNotification` | a rendelés lezárult (`OrderStatus.Complete`) | „elkészült, átvehető" + végösszeg |
|
||||
|
||||
A sablonok kanonikus alapértékét a `FruitBankPlugin.InstallAsync` írja be. **Már telepített pluginon ez nem fut
|
||||
le újra** — ezért éles adatbázisban a sablon kézzel is létrejöhet, eltérő tartalommal. A helyreállító runbook:
|
||||
[`../sql/fix_order_notification_templates.sql`](../sql/fix_order_notification_templates.sql).
|
||||
|
||||
## Kiváltás
|
||||
|
||||
A két értesítőt két különböző mechanizmus váltja ki.
|
||||
|
||||
A **`Started`** ág kiváltója nem esemény: a mérésindítás csak a `MeasurementOwnerId` GenericAttribute-ot írja,
|
||||
Order-sort nem, ezért a `CustomOrderSignalREndpoint.StartMeasuring` hívja közvetlenül, a mérésindítási
|
||||
tranzakció sikeres lezárulta után. A hívás a tranzakción kívül áll: visszagördülő mérésindításnál ne menjen ki
|
||||
levél.
|
||||
|
||||
Az **`Audited`** ág a `Services/EventConsumer`-en át jön, ami az `EntityUpdatedEvent<Order>`-re fut, és a
|
||||
rendelés **származtatott** `MeasuringStatus`-a alapján ágazik el. Order szinten ez az állapot az `OrderStatus`-ra
|
||||
kulcsol (`IsComplete` → `Audited`), nem a raklapok `RevisorId`-jára. A kiváltó maga a lezárás: a
|
||||
`SetOrderStatusToCompleteAsync` a DTO-táblán ír, ami a DTO→fő-entitás esemény-hídon át
|
||||
`EntityUpdatedEvent<Order>`-t szül (ADR 0003-H7C4).
|
||||
|
||||
Ismétlés ellen rendelésenként egy-egy `GenericAttribute` jelölés véd (`ORDER_STARTED_SENT_KEY`,
|
||||
`ORDER_AUDITED_SENT_KEY`). A jelölés kezelése egyetlen helyen, a `FruitBankNotificationService.SendOnceAsync`-ben
|
||||
van, és mindkét hívó azon át megy — a `Started` ágat ugyanis két kiváltó is elérheti.
|
||||
|
||||
A jelölés **csak sikeres sorbaállítás után** íródik ki. A küldés minden hibaágon `0`-t ad vissza (hiányzó vagy
|
||||
inaktív sablon, nincs használható e-mail fiók, nincs vevő); az ilyenkor is kiírt jelölés véglegesen elnémítaná a
|
||||
rendelést. Sikertelen küldés után a következő kiváltó újrapróbálkozik.
|
||||
|
||||
## Tokenek
|
||||
|
||||
A `FruitBankMessageTokenProvider` a nop `MessageTokenProvider`-ét váltja ki (`services.Replace`), és három
|
||||
ponton tér el tőle:
|
||||
|
||||
- `Order.Product(s)` — saját HTML-tábla; mérendő tételnél ár helyett „kalkuláció alatt".
|
||||
- `Order.OrderTotal` — a nopnak **nincs** ilyen tokenje; a végösszeget csak a termék-tábla lábában jeleníti meg.
|
||||
A token értéke a `CustomPriceCalculationService` által az `Order.OrderTotal` oszlopba írt szám: bruttó,
|
||||
tételszinten már érvényesített kedvezménnyel, árfolyam-átszámítás nélkül.
|
||||
- `Order.MeasurableNote` — HTML-t hordozó token, amit a `FruitBankNotificationService` ad hozzá küldéskor.
|
||||
Az admin **teszt-küldésében** ezért üres marad.
|
||||
|
||||
A nop az admin „Engedélyezett tokenek" listáját a sablon nevéhez rendelt token-csoportokból állítja elő, és
|
||||
ismeretlen sablonnévre üres listát ad. A provider ezért felülírja a `GetTokenGroups`-ot a két FruitBank
|
||||
sablonnévre, és a `GetListOfAllowedTokensAsync`-ben fűzi a listához a két saját tokent.
|
||||
|
||||
## Naplózás
|
||||
|
||||
A küldés minden kimaradását a nop natív naplójába írjuk (Admin → Rendszer → Napló): hiányzó vagy inaktív
|
||||
sablon, hiányzó e-mail fiók, nem található vevő. Egy elmaradt vevői levél enélkül nyomtalan maradna.
|
||||
@@ -23,6 +23,7 @@ Topic documentation for the FruitBank-specific NopCommerce plugin (Layer 3 — c
|
||||
- [`EKAER/`](EKAER/README.md) — NAV EKÁER reporting, server side: obligation gate, `Shipping`/`Order` → `tradeCard` mapping, submission (+ `EKAER_ISSUES.md` / `EKAER_TODO.md`)
|
||||
- [`STOCK/`](STOCK/README.md) — stock movement: quantity + weight running balance, history and its consistency check (+ `STOCK_ISSUES.md`)
|
||||
- [`MARGIN/`](MARGIN/README.md) — purchase cost and margin: cost layers, FIFO attribution at order completion, freight/extra distribution, and the report (+ `MARGIN_TODO.md` / `MARGIN_ISSUES.md`, and `MARGIN_schema.sql`)
|
||||
- [`NOTIFICATION/`](NOTIFICATION/README.md) — customer e-mail notifications: the plugin's own message templates, the token-provider replacement, the `MeasuringStatus`-driven trigger and the repeat-guard flags (+ `NOTIFICATION_ISSUES.md`)
|
||||
- [`PMDATA/`](PMDATA/README.md) — product measurement/logistics master data (`IsMeasurable`, `Tare`, `AverageWeight`, `AverageWeightTreshold`, `CratesPerPallet`): storage, write paths, validation (+ `PMDATA_TODO.md`; the SQL runbook is the flat [`PRODUCT_WEIGHT_DATA_DB_VALIDATION.md`](PRODUCT_WEIGHT_DATA_DB_VALIDATION.md))
|
||||
|
||||
## Navigation
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -21,6 +21,7 @@ Az implementáció állapotát ADR-enként **pontosan egy** `_TODO` / `_ISSUES`
|
||||
|---|---|---|---|
|
||||
| [0001-V4T7](0001-V4T7-product-master-data-storage.md) | A termék mérési/logisztikai törzsadata elsőrendű, plugin-tulajdonú táblába kerül a `GenericAttribute`-ok helyett | Proposed (2026-08-11) | `MGFBANKPLUG-PMDATA-T-H3N8` ([PMDATA_TODO](../PMDATA/PMDATA_TODO.md)) — Open |
|
||||
| [0002-R7Q2](0002-R7Q2-document-cost-layers-and-margin.md) | A nyereség-kimutatás a szállítólevél szintjén számol, FIFO-val fogyasztott költség-rétegekből, és minden pénzügyi értéket lekérdezéssel állít elő | Proposed (2026-08-13) | `MGFBANKPLUG-MARGIN-T-K5V9` ([MARGIN_TODO](../MARGIN/MARGIN_TODO.md)) — InProgress |
|
||||
| [0003-H7C4](0003-H7C4-dto-event-bridge-wiring.md) | A DTO-repók esemény-hídja bekötésre kerül a keretben, a `Nop.Services` referencia árán | Accepted (2026-08-18) | `MGFBANKPLUG-NOTIF-I-Q3M8` ([NOTIFICATION_ISSUES](../NOTIFICATION/NOTIFICATION_ISSUES.md)) — Closed |
|
||||
|
||||
## Ezt a repót érintő döntések más rétegben
|
||||
|
||||
|
||||
Reference in New Issue
Block a user