# SignalR Endpoints and External Integrations > Part of `Nop.Plugin.Misc.FruitBankPlugin`. See `README.md` for project overview. > For measurement workflows see `docs/MEASUREMENT.md`. > For data layer see `docs/DATA_LAYER.md`. The plugin communicates with FruitBankHybridApp (MAUI + Blazor WASM hybrid clients) via SignalR. Methods decorated with `[SignalR]` attributes auto-broadcast results to connected clients. ## SignalR Configuration Configured in `PluginNopStartup`: | Setting | Value | |---|---| | KeepAliveInterval | `FruitBankConstClient.SignalRKeepAliveIntervalSecond` = 60 s | | ClientTimeoutInterval | `FruitBankConstClient.SignarlRTimeoutIntervalSecond` = 180 s | | MaximumReceiveMessageSize | 30 MB | | EnableDetailedErrors | true | | StatefulReconnectBufferSize | 30 MB | | **Hub protocols** | **`json` (default) + `acbinary`** — a kliens a handshake-ben választ | **Wire protocol:** `AcBinaryHubProtocol` (`AyCode.Services.SignalRs`) — custom binary `IHubProtocol` using `AcBinarySerializer`. Eliminates JSON+Base64 overhead. Protocol name: `"acbinary"`. Registered via `.AddSignalR(...).AddAcBinaryProtocol(opts => { ProtocolMode=Bytes; FlushPolicy=Coalesced; Logger=new Logger(nameof(AyCodeBinaryHubProtocol)); })` — a default **JSON MELLÉ** kerül (nem cseréli); a `#if DEBUG` startup-dump (`>>> HubProtocol: …`) kiírja a ténylegesen regisztrált protokollokat. Hubs registered (mindkettő WebSockets-only endpoint): - `DevAdminSignalRHub` (`/fbHub`) — fő adat-hub, acbinary protokollon. - `LoggerSignalRHub` (`/loggerHub`, `FruitBank.Common.Server`; base: `AcLoggerSignalRHub` az `AyCode.Services.Server`-ből) — kliens-log feltöltés fogadása, wire-típus: `LogItemClient` (2026-07 óta acbinary protokollon; korábban JSON). Kapcsolat-életciklust logol (`Logger client connected/disconnected`), így a halott feltöltő-csatorna a konzolból látszik. ## FruitBankHub `Services/FruitBankHub.cs` — Minimal hub inheriting `Hub`. | Method | Purpose | |---|---| | `SendMessage(string user, string message)` | Broadcast message to all connected clients | ## CustomOrderSignalREndpoint `Areas/Admin/Controllers/CustomOrderSignalREndpoint.cs` — Order management via SignalR. Implements `ICustomOrderSignalREndpointServer`. ### Query Methods | Method | Parameters | Returns | Broadcast Tag | |---|---|---|---| | `GetAllOrderDtos()` | — | Orders from last 15 days with relations | — | | `GetOrderDtoById` | orderId | Single order with relations | — | | `GetPendingOrderDtos()` | — | Pending status orders | — | | `GetPendingOrderDtosForMeasuring` | lastDaysCount | Orders for measurement (unpaid, not cancelled, with DateOfReceipt) | — | | `GetAllOrderDtoByIds` | orderIds[] | Batch retrieval | — | | `GetAllOrderDtoByProductId` | productId | Orders containing product | — | ### Mutation Methods | Method | Parameters | Broadcast Tag | Key Logic | |---|---|---|---| | `StartMeasuring` | orderId, userId | `SendOrderChanged` | Sets MeasurementOwnerId, OrderStatus=Processing, PaymentStatus=Pending | | `SetOrderStatusToComplete` | orderId, revisorId | `SendOrderChanged` | Validates pallets, recalculates prices, updates stock, publishes OrderPaidEvent | | `AddOrUpdateMeasuredOrderItemPallet` | OrderItemPallet | `SendOrderItemPalletChanged` | Saves pallet, triggers price recalculation | ### Order Item & Pallet Queries | Method | Purpose | |---|---| | `GetOrderItemDtosByOrderId` | Items for a specific order | | `GetOrderItemPalletsByOrderItemId` | Pallets for a specific item | | `GetOrderItemPalletById` | Single pallet | ## StockSignalREndpointServer `Areas/Admin/Controllers/StockSignalREndpointServer.cs` — Inventory management via SignalR. | Method | Parameters | Key Logic | |---|---|---| | `GetStockTakings` | loadRelations | All stock taking sessions | | `AddStockTaking` | StockTaking | Creates session, auto-populates items from products + pending order items | | `CloseStockTaking` | stockTakingId | Validates all measured, applies stock corrections, marks closed | | `UpdateStockTaking` | StockTaking | Updates session | | `RefreshStockTakingItem` | stockTakingItemId | Refreshes measured values from pallets | | `GetStockTakingItems` | loadRelations | All items | | `GetStockTakingItemsByStockTakingId` | stockTakingId | Filtered items | | `AddOrUpdateMeasuredStockTakingItemPallet` | StockTakingItemPallet | Saves pallet, refreshes item values | ## FruitBankDataController `Controllers/FruitBankDataController.cs` — Main data API implementing `IFruitBankDataControllerServer`. Methods decorated with `[SignalR]` attributes for FruitBankHybridApp integration. ### Shipping Operations | Method | Purpose | |---|---| | `GetShippings()` | Last 15 days with relations | | `GetNotMeasuredShippings()` | Incomplete shipments | | `GetShippingById` / `AddShipping` / `UpdateShipping` | CRUD | | `GetShippingItems` / `AddShippingItem` / `UpdateShippingItem` | Item CRUD | | `GetShippingItemPallets` / `AddShippingItemPallet` / `UpdateShippingItemPallet` | Pallet CRUD | | `AddOrUpdateMeasuredShippingItemPallet` | Measurement + average weight update + cascade | | `GetShippingDocuments` / `AddShippingDocument` | Document operations, updates product costs on add | | `ProcessAndSaveFullShippingJson` | Parses FullProcessModel JSON, saves complete shipping data | ### Partner & Depot Operations | Method | Purpose | |---|---| | `GetPartners()` | All partners, depots included (`LoadWith(PartnerDepots)` — partners are client-cached, enables client-side depot cascading) | | `GetPartnerById` / `AddPartner` / `UpdatePartner` | CRUD | | `GetPartnerDepots()` / `GetPartnerDepotById` / `GetPartnerDepotsByPartnerId` | Depot queries | | `AddPartnerDepot` / `UpdatePartnerDepot` | Depot CRUD | ### Cargo Operations | Method | Purpose | |---|---| | `GetCargoPartners()` / `GetCargoPartnerById` | Cargo partner queries | | `AddCargoPartner` / `UpdateCargoPartner` | Cargo partner CRUD | | `GetCargoTrucks()` / `GetCargoTruckById` / `GetCargoTrucksByCargoPartnerId` | Truck queries | | `AddCargoTruck` / `UpdateCargoTruck` | Truck CRUD | ### EKÁER Operations Domain logic, mapping and validation rules: `docs/EKAER/README.md`. | Method | Purpose | |---|---| | `GetEkaerHistories(filter)` / `GetEkaerHistoryCount(filter)` | `EkaerHistoryFilter`-based queries | | `GetEkaerHistoryById` / `GetEkaerHistoriesByForeignKey` | Lookups | | `AddEkaerHistory` / `UpdateEkaerHistory` | CRUD | | `CreateEkaerHistory(foreignKey, isOutgoing)` | Creates history for a source record; idempotent — an existing mapping (same source, same direction) returns the existing history | | `CreateMissingEkaerHistories(fromDate)` | Batch backfill, returns `EkaerCreateResult` (created histories + deduplicated validation messages) | | `GenerateEkaerXmlDocument(ekaerHistoryId)` | NAV XML generation; throws if the history does not exist | ### Product & Attribute Operations | Method | Purpose | |---|---| | `GetProductDtos()` | All products with GenericAttributes | | `GetProductDtoById` | Single product | | `GetGenericAttributeDtosByEntityIdAndKeyGroup` | Custom attributes by entity + key group | | `AddGenericAttributeDto` / `UpdateGenericAttributeDto` | Attribute CRUD | ### Grid Layout Roaming Server-side storage for MgGrid user layouts (client behaviour: AyCode.Blazor `docs/MGGRID/MGGRID_LAYOUT.md` — Server Roaming). Tags are framework-level (`AcSignalRTags`), not FruitBank-specific. Storage: GenericAttribute rows — `KeyGroup="GridLayout"`, `Key`=composite layout key (already contains the userId), `EntityId`=userId, `StoreId=0`. | Method | Tag | Purpose | |---|---|---| | `GetGridLayout(key, userId)` | `GetGridLayoutTag` (90010) | Returns layout JSON, `null` if none saved | | `SaveGridLayout(key, layoutJson, userId)` | `SaveGridLayoutTag` (90011) | Upsert | | `DeleteGridLayout(key, userId)` | `DeleteGridLayoutTag` (90012) | Returns `false` if no stored layout | ### User & Auth Operations | Method | Purpose | |---|---| | `GetMeasuringUsers()` | Users in "Measuring" role | | `GetCustomerRolesByCustomerId` | Customer roles | | `LoginMeasuringUser` | Authentication with role check | ### Environment | Method | Purpose | |---|---| | `IsProdEnvironment()` | `true` if the server runs against the PROD database. Definite by construction (ambiguous DB catalog fails server startup); consumers must fail closed on any error | ### Stock History | Method | Purpose | |---|---| | `GetStockQuantityHistoryDtos()` | Last 15 days | | `GetStockQuantityHistoryDtosByProductId` | Product-filtered history | ## InnVoice Integration (Billingo) ### InnVoiceOrderService `Services/InnVoiceOrderService.cs` — Order synchronization with Billingo/InnVoice accounting system via XML API. | Method | Purpose | |---|---| | `CreateOrdersAsync(List)` | Batch order creation | | `CreateOrderAsync(OrderCreateRequest)` | Single order creation | | `GetOrdersByUpdateTimeAsync(DateTime)` | Query by last update time | | `GetOrdersByDateAsync(DateTime)` | Query by order date | | `GetOrderByTechIdAsync(int)` | Query by InnVoice tech ID | | `GetOrderByTableIdAsync(int)` | Query by table ID | **Models:** `OrderCreateRequest`, `OrderCreateResponse`, `InnVoiceOrder`, `InnVoiceOrderLineItem` ### InnvoiceApiService `Services/InnvoiceApiService.cs` — Invoice CRUD with Billingo XML API. | Method | Purpose | |---|---| | `GetInvoiceByIdAsync` / `GetInvoiceByNumberAsync` / `GetInvoiceByTechIdAsync` | Invoice queries | | `GetInvoicesByDateRangeAsync` | Date-range filtered invoices | | `CreateInvoiceAsync` / `UpdateInvoiceAsync` | Invoice CRUD | **Models:** `Invoice`, `InvoiceLineItem`, `InvoiceCreateRequest`, `InvoiceCreateResponse` Both services build and parse XML payloads. Called from `InnVoiceOrderController` and `InnVoiceOrderSyncController`. ## FruitBankAttributeService `Services/FruitBankAttributeService.cs` — GenericAttribute CRUD helper for measurement-related attributes. | Method | Purpose | |---|---| | `GetGenericAttributeValueAsync` | Type-safe attribute retrieval | | `GetMeasuringAttributeValuesAsync` | Get IsMeasurable, NetWeight, Tare, etc. as batch | | `InsertOrUpdateMeasuringAttributeValuesAsync` | Upsert measurement attributes | | `IsMeasurableEntityAsync` | Check if entity's product is measurable | Supports cumulative weight updates (adds to existing value rather than replacing).