using AyCode.Core.Extensions; using AyCode.Core.Helpers; using AyCode.Core.Loggers; using AyCode.Services.SignalRs; using DevExpress.Utils.About; using FruitBank.Common; using FruitBank.Common.Dtos; using FruitBank.Common.Entities; using FruitBank.Common.Interfaces; using FruitBank.Common.Models; using FruitBank.Common.SignalRs; using FruitBankHybrid.Shared.Databases; using FruitBankHybrid.Shared.Services.Loggers; using Mango.Nop.Core.Dtos; using Mango.Nop.Core.Models; using MessagePack.Resolvers; using Microsoft.AspNetCore.SignalR.Client; using Nop.Core.Domain.Customers; using System.Collections.ObjectModel; using System.ServiceModel.Channels; using AyCode.Core.Serializers; using Mango.Nop.Core.Entities; namespace FruitBankHybrid.Shared.Services.SignalRs { public class FruitBankSignalRClient : AcSignalRClientBase, IFruitBankDataControllerClient, ICustomOrderSignalREndpointClient, IStockSignalREndpointClient { public FruitBankSignalRClient(IHubConnectionBuilder hubBuilder, Func loggerFactory) : base(hubBuilder, loggerFactory(nameof(FruitBankSignalRClient))) { EnableBinaryDiagnostics = FruitBankConstClient.SignalRSerializerDiagnosticLog; ConstHelper.NameByValue(0); } /// /// MessageTag, JSON /// public event Func OnMessageReceived = null!; protected override async Task MessageReceived(int messageTag, SignalParams signalParams, object data) { var responseDataMessage = new SignalResponseDataMessage { Status = signalParams.Status, DataSerializerType = AcSerializerType.Binary, RawResponseData = data }; // Szerver ENV-announce (push-on-connect) — kliens-belső kezelés, nem megy tovább app-eseményként. if (messageTag == SignalRTags.ServerEnvironment) { HandleServerEnvironmentAnnounce(responseDataMessage); return; } await OnMessageReceived(messageTag, responseDataMessage); } private void SendMessageToAllClients(int messageTag, string message) { if (messageTag == SignalRTags.NotificationReceived) { Logger.Info(message); } } public Task?> GetMeasuringModels() => GetAllAsync>(SignalRTags.GetMeasuringModels); /// /// FAIL CLOSED: a nyers SignalResponseDataMessage úton kérdez — timeout/hiba esetén EXCEPTION, nem default(false). /// (A sima GetAllAsync<bool> timeoutnál default-ot adna vissza, ami "DEV"-nek hazudná a nem-elérhető szervert.) /// public async Task IsProdEnvironment() { var response = await GetAllAsync(SignalRTags.IsProdEnvironment) ?? throw new InvalidOperationException( "IsProdEnvironment: nincs szerver-válasz (timeout/kapcsolat-hiba) — a környezet nem igazolható (fail closed)."); return response.GetResponseData(); } #region Server environment (IsProd) hidratálás — az EGYETLEN értékadási pont a FruitBankConstClient.IsProd-hoz private Task? _environmentHydration; // ELSŐ felépülés: HARD GATE — a StartConnection bevárja a hidratálást, addig SEMMILYEN más művelet // nem megy ki ezen a kapcsolaton. Hiba → a kiváltó kérés is elhasal (fail closed: env-tudás nélkül // nem beszélünk a szerverrel). Reconnect: NINCS újra-lekérdezés ("csak az elején 1x") — az env-váltást // (szerver-restart MÁSIK DB-vel) a szerver push-on-connect announce-a jelzi (ServerEnvironment tag). protected override async Task OnConnectionEstablished() { if (FruitBankConstClient.IsProdHydrated) return; _environmentHydration = HydrateEnvironmentAsync(); await _environmentHydration; } /// /// A szerver-környezet (FruitBankConstClient.IsProd) hidratálásának bevárása — teszt-guardok / UI ezt await-eli. /// Ha az érték már megvan (gate vagy announce töltötte), azonnal kész; hibás korábbi kísérletnél újrapróbál, /// és ha az is bukik, a kivétel a hívóra megy (fail closed). /// public Task EnsureEnvironmentAsync() { if (FruitBankConstClient.IsProdHydrated) return Task.CompletedTask; return _environmentHydration is { IsFaulted: false, IsCanceled: false } running ? running : _environmentHydration = HydrateEnvironmentAsync(); } private async Task HydrateEnvironmentAsync() { try { var isProd = await IsProdEnvironment(); FruitBankConstClient.SetIsProd(isProd); Logger.Info($"Server environment: {(isProd ? "PROD" : "DEV")}"); } catch (Exception ex) { Logger.Error($"Server environment hydration FAILED — FruitBankConstClient.IsProd hidratálatlan marad (fail closed); {ex.Message}", ex); throw; } } // A szerver MINDEN kapcsolat-felépüléskor bemondja a környezetét (push-on-connect announce). // Első alkalommal ez is hidratálhat (verseny a gate-lekérdezéssel — azonos értéket írnak, ártalmatlan); // KÉSŐBB az összevetés a lényeg: eltérés = a szerver restart közben MÁSIK DB-re állt → hangos riasztás. private void HandleServerEnvironmentAnnounce(SignalResponseDataMessage responseDataMessage) { var isProd = responseDataMessage.GetResponseData(); if (FruitBankConstClient.IsProdHydrated && FruitBankConstClient.IsProd != isProd) Logger.Error($"SERVER ENVIRONMENT CHANGED: {(FruitBankConstClient.IsProd ? "PROD" : "DEV")} → {(isProd ? "PROD" : "DEV")} — a szerver újraindult MÁSIK adatbázissal!"); else if (!FruitBankConstClient.IsProdHydrated) Logger.Info($"Server environment (announce): {(isProd ? "PROD" : "DEV")}"); FruitBankConstClient.SetIsProd(isProd); } #endregion Server environment #region Partner public Task?> GetPartners() => GetAllAsync>(SignalRTags.GetPartners); public Task GetPartnerById(int id) => GetByIdAsync(SignalRTags.GetPartnerById, id); public Task AddPartner(Partner partner) => PostDataAsync(SignalRTags.AddPartner, partner); public Task UpdatePartner(Partner partner) => PostDataAsync(SignalRTags.UpdatePartner, partner); #endregion Partner #region PartnerDepot public Task?> GetPartnerDepots() => GetAllAsync>(SignalRTags.GetPartnerDepots); public Task GetPartnerDepotById(int id) => GetByIdAsync(SignalRTags.GetPartnerDepotById, id); public Task?> GetPartnerDepotsByPartnerId(int partnerId) => GetAllAsync>(SignalRTags.GetPartnerDepotsByPartnerId, [partnerId]); public Task AddPartnerDepot(PartnerDepot partnerDepot) => PostDataAsync(SignalRTags.AddPartnerDepot, partnerDepot); public Task UpdatePartnerDepot(PartnerDepot partnerDepot) => PostDataAsync(SignalRTags.UpdatePartnerDepot, partnerDepot); #endregion PartnerDepot #region EkaerHistory public Task?> GetEkaerHistories(EkaerHistoryFilter filter) => GetAllAsync>(SignalRTags.GetEkaerHistories, [filter]); public Task GetEkaerHistoryById(int id) => GetByIdAsync(SignalRTags.GetEkaerHistoryById, id); public Task?> GetEkaerHistoriesByForeignKey(int foreignKey) => GetAllAsync>(SignalRTags.GetEkaerHistoriesByForeignKey, [foreignKey]); public Task AddEkaerHistory(EkaerHistory ekaerHistory) => PostDataAsync(SignalRTags.AddEkaerHistory, ekaerHistory); public Task UpdateEkaerHistory(EkaerHistory ekaerHistory) => PostDataAsync(SignalRTags.UpdateEkaerHistory, ekaerHistory); public Task GenerateEkaerXmlDocument(int ekaerHistoryId) => GetByIdAsync(SignalRTags.GenerateEkaerXmlDocument, ekaerHistoryId); public Task SendEkaerToNav(int ekaerHistoryId) => GetByIdAsync(SignalRTags.SendEkaerToNav, ekaerHistoryId); public Task CreateEkaerHistory(int foreignKey, bool isOutgoing) => GetByIdAsync(SignalRTags.CreateEkaerHistory, [foreignKey, isOutgoing]); public Task CreateMissingEkaerHistories(DateTime fromDate) => GetByIdAsync(SignalRTags.CreateMissingEkaerHistories, fromDate); public Task GetEkaerHistoryCount(EkaerHistoryFilter filter) => GetByIdAsync(SignalRTags.GetEkaerHistoryCount, filter); #endregion EkaerHistory #region CargoPartner public Task?> GetCargoPartners() => GetAllAsync>(SignalRTags.GetCargoPartners); public Task GetCargoPartnerById(int id) => GetByIdAsync(SignalRTags.GetCargoPartnerById, id); public Task AddCargoPartner(CargoPartner cargoPartner) => PostDataAsync(SignalRTags.AddCargoPartner, cargoPartner); public Task UpdateCargoPartner(CargoPartner cargoPartner) => PostDataAsync(SignalRTags.UpdateCargoPartner, cargoPartner); #endregion CargoPartner #region CargoTruck public Task?> GetCargoTrucks() => GetAllAsync>(SignalRTags.GetCargoTrucks); public Task GetCargoTruckById(int id) => GetByIdAsync(SignalRTags.GetCargoTruckById, id); public Task?> GetCargoTrucksByCargoPartnerId(int cargoPartnerId) => GetAllAsync>(SignalRTags.GetCargoTrucksByCargoPartnerId, [cargoPartnerId]); public Task AddCargoTruck(CargoTruck cargoTruck) => PostDataAsync(SignalRTags.AddCargoTruck, cargoTruck); public Task UpdateCargoTruck(CargoTruck cargoTruck) => PostDataAsync(SignalRTags.UpdateCargoTruck, cargoTruck); #endregion CargoTruck #region Shipping public Task?> GetShippings() => GetAllAsync>(SignalRTags.GetShippings); public Task?> GetNotMeasuredShippings() => GetAllAsync>(SignalRTags.GetNotMeasuredShippings); public Task GetShippingById(int id) => GetByIdAsync(SignalRTags.GetShippingById, id); public Task AddShipping(Shipping shipping) => PostDataAsync(SignalRTags.AddShipping, shipping); public Task UpdateShipping(Shipping shipping) => PostDataAsync(SignalRTags.UpdateShipping, shipping); public Task?> ProcessAndSaveFullShippingJson(string fullShippingJson, int customerId) => GetAllAsync>(SignalRTags.ProcessAndSaveFullShippingJson, [fullShippingJson, customerId]); #endregion Shipping #region ShippingItem public Task?> GetShippingItems() => GetAllAsync>(SignalRTags.GetShippingItems); public Task?> GetShippingItemsByDocumentId(int shippingDocumentId) => GetAllAsync>(SignalRTags.GetShippingItemsByDocumentId, [shippingDocumentId]); public Task?> GetShippingItemsByShippingId(int shippingId) => GetAllAsync>(SignalRTags.GetShippingItemsByShippingId, [shippingId]); public Task?> GetShippingItemsByPartnerId(int partnerId) => GetAllAsync>(SignalRTags.GetShippingItemsByPartnerId, [partnerId]); public Task GetShippingItemById(int id) => GetByIdAsync(SignalRTags.GetShippingItemById, id); public Task AddShippingItem(ShippingItem shippingItem) => PostDataAsync(SignalRTags.AddShippingItem, shippingItem); public Task UpdateShippingItem(ShippingItem shippingItem) => PostDataAsync(SignalRTags.UpdateShippingItem, shippingItem); public Task UpdateMeasuredShippingItem(ShippingItem shippingItem) => PostDataAsync(SignalRTags.UpdateMeasuredShippingItem, shippingItem); #endregion ShippingItem #region ShippingItemPallet public Task?> GetShippingItemPallets() => GetAllAsync>(SignalRTags.GetShippingItemPallets); public Task AddShippingItemPallet(ShippingItemPallet shippingItemPallet) => PostDataAsync(SignalRTags.AddShippingItemPallet, shippingItemPallet); public Task UpdateShippingItemPallet(ShippingItemPallet shippingItemPallet) => PostDataAsync(SignalRTags.UpdateShippingItemPallet, shippingItemPallet); public Task AddOrUpdateMeasuredShippingItemPallet(ShippingItemPallet shippingItemPallet) => PostDataAsync(SignalRTags.AddOrUpdateMeasuredShippingItemPallet, shippingItemPallet); public Task AddOrUpdateMeasuredShippingItemPallets(List shippingItemPallets) => PostDataAsync, ShippingItem>(SignalRTags.AddOrUpdateMeasuredShippingItemPallets, shippingItemPallets); #endregion ShippingItemPallet #region ShippingDocument public Task?> GetShippingDocuments() => GetAllAsync>(SignalRTags.GetShippingDocuments); public Task GetShippingDocumentById(int id) => GetByIdAsync(SignalRTags.GetShippingDocumentById, id); public Task?> GetShippingDocumentsByShippingId(int shippingId) => GetAllAsync>(SignalRTags.GetShippingDocumentsByShippingId, [shippingId]); public Task?> GetShippingDocumentsByProductId(int productId) => GetAllAsync>(SignalRTags.GetShippingDocumentsByProductId, [productId]); public Task?> GetShippingDocumentsByPartnerId(int partnerId) => GetAllAsync>(SignalRTags.GetShippingDocumentsByPartnerId, [partnerId]); public Task AddShippingDocument(ShippingDocument shippingDocument) => PostDataAsync(SignalRTags.AddShippingDocument, shippingDocument); public Task UpdateShippingDocument(ShippingDocument shippingDocument) => PostDataAsync(SignalRTags.UpdateShippingDocument, shippingDocument); #endregion ShippingDocument #region Customer public Task?> GetMeasuringUsers() => GetAllAsync>(SignalRTags.GetMeasuringUsers); public Task?> GetCustomerRolesByCustomerId(int customerId) => GetAllAsync>(SignalRTags.GetCustomerRolesByCustomerId, [customerId]); #endregion Customer #region Product //TODO: itt ProductDtoTableItem kéne, csak az AycodeHubProtocol ProductDto-ra deserialize-ol! - J. //public Task?> GetProductDtoTableItems() => GetAllAsync>(SignalRTags.GetProductDtos); // IDEIGLENES gyors fix: a GetProductDtos tagon a szerver List-t küld, a binár // protokoll viszont a wire-típusba (ProductDto) deszerializál — így a közvetlen // List kérés üres lett (a ProductDtoTableItem a ProductDto kliens-oldali // leszármazottja, amit a szerver nem ismer). A működő GetProductDtos()-t hívjuk, és bináris // round-trippel a kért leszármazottra alakítjuk (azonos mezők). VÉGLEGES megoldás: requestId → // kért-típus a protokollban (AQN-leváltás) — lásd AyCode.Services/docs/SIGNALR_BINARY_PROTOCOL. public async Task?> GetProductDtoTableItems() { var dtos = await GetProductDtos(); if (dtos == null) return null; var bytes = SignalRSerializationHelper.SerializeToBinary(dtos); return SignalRSerializationHelper.DeserializeFromBinary>(bytes); } public Task?> GetProductDtos() => GetAllAsync>(SignalRTags.GetProductDtos); //public Task?> GetAllMeasuringProductDtos() // => GetAllAsync>(SignalRTags.GetAllMeasuringProductDtos); public Task GetProductDtoById(int productId) => GetByIdAsync(SignalRTags.GetMeasuringProductDtoById, productId); #endregion Product #region Authenticate public Task LoginMeasuringUser(string customerEmail, string customerPassword) => LoginMeasuringUser(new MgLoginModelRequest(customerEmail, customerPassword)); public Task LoginMeasuringUser(MgLoginModelRequest loginModelRequest) => PostDataAsync(SignalRTags.AuthenticateUser, loginModelRequest); #endregion Authenticate #region GridLayout public Task GetGridLayout(string key, int userId) => GetByIdAsync(SignalRTags.GetGridLayoutTag, [key, userId]); public Task SaveGridLayout(string key, string layoutJson, int userId) => PostAsync(SignalRTags.SaveGridLayoutTag, [key, layoutJson, userId]); public Task DeleteGridLayout(string key, int userId) => PostAsync(SignalRTags.DeleteGridLayoutTag, [key, userId]); #endregion GridLayout #region Orders public Task?> GetAllOrderDtoTableItems() => GetAllAsync>(SignalRTags.GetAllOrderDtos); public Task?> GetAllOrderDtos() => GetAllAsync>(SignalRTags.GetAllOrderDtos); public Task GetOrderDtoById(int orderId) => GetByIdAsync(SignalRTags.GetOrderDtoById, orderId); public Task?> GetPendingOrderDtos() => GetAllAsync>(SignalRTags.GetPendingOrderDtos); public Task?> GetPendingOrderDtosForMeasuring(int lastDaysCount) => GetAllAsync>(SignalRTags.GetPendingOrderDtosForMeasuring, [lastDaysCount]); public Task?> GetAllOrderDtoByIds(int[] orderIds) => GetAllAsync>(SignalRTags.GetAllOrderDtoByIds, [orderIds]); public Task?> GetAllOrderItemDtos() => GetAllAsync>(SignalRTags.GetAllOrderItemDtos); public Task AddOrUpdateMeasuredOrderItemPallet(OrderItemPallet orderItemPallet) => PostDataAsync(SignalRTags.AddOrUpdateMeasuredOrderItemPallet, orderItemPallet); public Task StartMeasuring(int orderId, int userId) => GetByIdAsync(SignalRTags.StartMeasuring, [orderId, userId]); public Task SetOrderStatusToComplete(int orderId, int revisorId) => GetByIdAsync(SignalRTags.SetOrderStatusToComplete, [orderId, revisorId]); public Task?> GetAllOrderDtoByProductId(int productId) => GetAllAsync>(SignalRTags.GetAllOrderDtoByProductId, [productId]); public Task GetOrderItemDtoById(int orderItemId) => GetByIdAsync(SignalRTags.GetOrderItemDtoById, [orderItemId]); public Task?> GetAllOrderItemDtoByOrderId(int orderId) => GetAllAsync>(SignalRTags.GetAllOrderItemDtoByOrderId, [orderId]); public Task?> GetAllOrderItemDtoByProductId(int productId) => GetAllAsync>(SignalRTags.GetAllOrderItemDtoByProductId, [productId]); public Task?> GetAllOrderItemPallets() => GetAllAsync>(SignalRTags.GetAllOrderItemPallets); public Task GetOrderItemPalletById(int orderItemPalletId) => GetByIdAsync(SignalRTags.GetOrderItemPalletById, [orderItemPalletId]); public Task?> GetAllOrderItemPalletByOrderItemId(int orderItemId) => GetAllAsync>(SignalRTags.GetAllOrderItemPalletByOrderItemId, [orderItemId]); public Task?> GetAllOrderItemPalletByOrderId(int orderId) => GetAllAsync>(SignalRTags.GetAllOrderItemPalletByOrderId, [orderId]); public Task?> GetAllOrderItemPalletByProductId(int productId) => GetAllAsync>(SignalRTags.GetAllOrderItemPalletByProductId, [productId]); public Task?> GetStockQuantityHistoryDtos() => GetAllAsync>(SignalRTags.GetStockQuantityHistoryDtos); public Task?> GetStockQuantityHistoryDtosByProductId(int productId) => GetAllAsync>(SignalRTags.GetStockQuantityHistoryDtosByProductId, [productId]); #endregion Orders public async Task?> GetGenericAttributeDtosByEntityIdAndKeyGroup(int productId, string keyGroup, int storeId) { throw new NotImplementedException(); } public async Task AddGenericAttributeDto(GenericAttributeDto genericAttributeDto) { throw new NotImplementedException(); } public async Task UpdateGenericAttributeDto(GenericAttributeDto genericAttributeDto) { throw new NotImplementedException(); } public Task?> GetStockTakings(bool loadRelations) => GetAllAsync>(SignalRTags.GetStockTakings, [loadRelations]); public async Task?> GetStockTakingsByProductId(int productId) { throw new NotImplementedException(); } public Task AddStockTaking(StockTaking stockTaking) => PostDataAsync(SignalRTags.AddStockTaking, stockTaking); public Task RefreshStockTakingItem(int stockTakingItemId) => GetByIdAsync(SignalRTags.RefreshStockTakingItem, stockTakingItemId); public Task UpdateStockTaking(StockTaking stockTaking) => PostDataAsync(SignalRTags.UpdateStockTaking, stockTaking); public Task?> GetStockTakingItems() => GetAllAsync>(SignalRTags.GetStockTakingItems); public Task GetStockTakingItemsById(int stockTakingItemId) => GetByIdAsync(SignalRTags.GetStockTakingItemsById, [stockTakingItemId]); public Task?> GetStockTakingItemsByProductId(int productId) => GetAllAsync>(SignalRTags.GetStockTakingItemsByProductId, [productId]); public Task?> GetStockTakingItemsByStockTakingId(int stockTakingId) => GetAllAsync>(SignalRTags.GetStockTakingItemsByStockTakingId, [stockTakingId]); public async Task AddStockTakingItem(StockTakingItem stockTakingItem) { throw new NotImplementedException(); } public async Task UpdateStockTakingItem(StockTakingItem stockTakingItem) { throw new NotImplementedException(); } public async Task?> GetStockTakingItemPallets() { throw new NotImplementedException(); } public async Task?> GetStockTakingItemPalletsByProductId(int productId) { throw new NotImplementedException(); } public Task CloseStockTaking(int stockTakingId) => PostDataAsync(SignalRTags.CloseStockTaking, stockTakingId); public async Task AddStockTakingItemPallet(StockTakingItemPallet stockTakingItemPallet) { throw new NotImplementedException(); } public async Task UpdateStockTakingItemPallet(StockTakingItemPallet stockTakingItemPallet) { throw new NotImplementedException(); } } }