using Newtonsoft.Json; using SmartB2B.Enova.Contracts; using SmartB2B.Enova.Service.Diagnostics; using SmartB2B.Enova.Service.Rpc; namespace SmartB2B.Enova.Service.Orders; public sealed class PlaceOrderHandler { private readonly IEnovaOrderAdapter _adapter; private readonly EnovaAdapterConfiguration _configuration; private readonly SemaphoreSlim _orderLock = new(1, 1); public PlaceOrderHandler(IEnovaOrderAdapter adapter, EnovaAdapterConfiguration configuration) { _adapter = adapter; _configuration = configuration; } public async Task HandleAsync(RpcInvocation invocation, CancellationToken cancellationToken) { if (!invocation.Contains("currency_iso")) { throw WampErrorFactory.Create("eu.smartb2b.missing_currency", "Pole currency_iso jest wymagane."); } var currency = invocation.Get("currency_iso")?.Trim(); if (!string.Equals(currency, "PLN", StringComparison.OrdinalIgnoreCase)) { throw WampErrorFactory.Create( "eu.smartb2b.erp.currency_not_supported", $"Waluta '{currency}' nie jest obsługiwana. Wymagana waluta: PLN.", new Dictionary { ["currency_iso"] = currency }); } var inputLines = invocation.Get("lines", []) ?? []; var request = new OrderRequest { CustomerCode = invocation.Get("companyErpId") ?? string.Empty, CustomerReference = invocation.Get("purchase_order_number"), Notes = invocation.Get("notes"), DocumentDefinition = _configuration.DocumentDefinition, WarehouseCode = invocation.Get("warehouseErpId") ?? _configuration.DefaultWarehouseCode, SaveMode = _configuration.SaveMode, Items = inputLines.Select(line => new OrderLineRequest { ProductCode = line.Symbol ?? string.Empty, Quantity = line.Quantity, UnitPrice = line.PriceNetto, Discount = line.Discount }).ToArray() }; var errors = OrderValidator.Validate(request); if (errors.Count > 0) { throw WampErrorFactory.Create( "eu.smartb2b.erp.invalid_order_lines", string.Join(" | ", errors), new Dictionary { ["errors"] = errors }); } ConsoleLogger.Verbose( $"ZO: żądanie zweryfikowane; definicja={request.DocumentDefinition}, " + $"magazyn={request.WarehouseCode ?? "(standardowy)"}, tryb={request.SaveMode}."); ConsoleLogger.Verbose("ZO: oczekiwanie na wyłączną sekcję operacji Enovy."); await _orderLock.WaitAsync(cancellationToken).ConfigureAwait(false); try { Console.WriteLine( $"Tworzenie ZO: kontrahent={request.CustomerCode}, pozycje={request.Items.Count}, referencja={request.CustomerReference ?? "(brak)"}."); OrderResult result; try { ConsoleLogger.Verbose("ZO: przekazanie żądania do adaptera Enovy."); result = await Task.Run(() => _adapter.CreateOrder(request), cancellationToken) .ConfigureAwait(false); } catch (EnovaOperationException exception) { ConsoleLogger.Error( $"Błąd tworzenia ZO [{exception.ErrorUri}]: {exception.Message}", exception); await SentryReporter.CaptureAsync(exception, scope => { scope.SetTag("rpc.procedure", "eu.smartb2b.place_order"); scope.SetTag("wamp.error_uri", exception.ErrorUri); }) .ConfigureAwait(false); throw WampErrorFactory.FromEnova(exception); } Console.WriteLine($"Utworzono ZO {result.Number} (ID: {result.Id})."); ConsoleLogger.Verbose( $"ZO: zapis zakończony; stan={result.SaveMode}, magazyn={result.WarehouseCode}, " + $"netto={result.Net}, VAT={result.Vat}, brutto={result.Gross}."); return new KeywordResult(new Dictionary { ["order_erp_id"] = result.Id, ["order_erp_symbol"] = result.Number, ["value_netto"] = result.Net, ["value_brutto"] = result.Gross, ["stocks"] = Array.Empty() }); } finally { _orderLock.Release(); ConsoleLogger.Verbose("ZO: zwolniono wyłączną sekcję operacji Enovy."); } } } public sealed class PlaceOrderLine { [JsonProperty("symbol")] public string? Symbol { get; init; } [JsonProperty("quantity")] public decimal Quantity { get; init; } [JsonProperty("price_netto")] public decimal PriceNetto { get; init; } [JsonProperty("discount")] public decimal Discount { get; init; } }