using Microsoft.AspNetCore.SignalR; namespace foodmarket.Api.Realtime; /// Тонкий обёрточный сервис: контроллеры/сервисы вызывают /// , а доставка идёт через /// в группу org:{Id}. /// /// Метод JSON-сериализуется в SignalR'е автоматически (System.Text.Json). /// Имя метода = ключ для подписки на клиенте. public interface INotificationsPublisher { /// Шлём всем подключённым сессиям одной org-и событие. Task PublishAsync(Guid orgId, string method, object payload); } public sealed class NotificationsPublisher : INotificationsPublisher { private readonly IHubContext _hub; public NotificationsPublisher(IHubContext hub) => _hub = hub; public Task PublishAsync(Guid orgId, string method, object payload) => _hub.Clients.Group(NotificationsHub.GroupName(orgId)).SendAsync(method, payload); } /// DTO событий — стабильные имена для контракта с фронтом. public static class NotificationEvents { /// Сразу после успешного post-a розничной продажи. public const string SalePosted = "SalePosted"; /// Сразу после успешного post-a приёмки. public const string SupplyPosted = "SupplyPosted"; /// Создано персистентное уведомление (центр уведомлений). Фронт по /// событию перечитывает счётчик непрочитанных и список. Шлётся фоновым /// джобом уведомлений об отрицательных остатках. public const string NotificationCreated = "NotificationCreated"; /// Когда после движения остаток продукта стал ниже MinStock. /// Один event на каждый продукт-склад, в котором факт пересечения /// порога. Шлётся только когда было «выше» и стало «ниже» — иначе на /// каждой продаже сыпались бы тосты. public const string LowStock = "LowStock"; } public sealed record SalePostedPayload( Guid SaleId, string Number, decimal Total, Guid StoreId, string? CashierName, Guid? RetailPointId, DateTime PostedAt); public sealed record SupplyPostedPayload( Guid SupplyId, string Number, decimal Total, Guid SupplierId, string? SupplierName, DateTime PostedAt); public sealed record LowStockPayload( Guid ProductId, string ProductName, Guid StoreId, string? StoreName, decimal Quantity, decimal MinStock); public sealed record NotificationCreatedPayload( string Type, string Title, Guid StoreId, int Count, bool Escalation);