| | | 1 | | using System.Collections.ObjectModel; |
| | | 2 | | using System.Globalization; |
| | | 3 | | using Services.ApiServices.InterpartsPriceApi.Models; |
| | | 4 | | using Services.ApiServices.TradesoftApi.Models.Response; |
| | | 5 | | |
| | | 6 | | namespace Services.ApiServices.InterpartsPriceApi; |
| | | 7 | | |
| | | 8 | | public static class InterpartsPriceOfferConverter |
| | | 9 | | { |
| | | 10 | | /// <summary> |
| | | 11 | | /// Id валюты справочника Interparts, по курсу которой пересчитываются цены ответа API: «RUR (б/н)». |
| | | 12 | | /// Совпадает с валютой по умолчанию в окне поиска Interparts, поэтому итоги сходятся один в один. |
| | | 13 | | /// </summary> |
| | | 14 | | private const int AppliedRateCurrencyId = 1; |
| | | 15 | | |
| | | 16 | | /// <summary>Валюта цен в ответе price-api — доллары.</summary> |
| | | 17 | | private const string SourceCurrencyCode = "USD"; |
| | | 18 | | |
| | 1 | 19 | | private static readonly IReadOnlyDictionary<int, decimal> EmptyRates = |
| | 1 | 20 | | new ReadOnlyDictionary<int, decimal>(new Dictionary<int, decimal>()); |
| | | 21 | | |
| | 1 | 22 | | private static readonly IReadOnlyDictionary<int, string> EmptyCurrencyNames = |
| | 1 | 23 | | new ReadOnlyDictionary<int, string>(new Dictionary<int, string>()); |
| | | 24 | | |
| | | 25 | | public static List<PreOrderItem> ConvertToPreOrderItems( |
| | | 26 | | IEnumerable<InterpartsPriceOffer>? sourceOffers, |
| | | 27 | | string? requestLogin, |
| | | 28 | | IReadOnlyDictionary<int, decimal>? ratesToRub, |
| | | 29 | | IReadOnlyDictionary<int, string>? currencyNames = null) |
| | | 30 | | { |
| | 4 | 31 | | if (sourceOffers == null) |
| | 1 | 32 | | return new List<PreOrderItem>(); |
| | | 33 | | |
| | 3 | 34 | | var rates = ratesToRub ?? EmptyRates; |
| | | 35 | | |
| | 3 | 36 | | return sourceOffers |
| | 3 | 37 | | .Where(o => o != null) |
| | 2 | 38 | | .Select(o => ConvertSingleItem(o, requestLogin, rates, currencyNames)) |
| | 2 | 39 | | .Where(i => i != null) |
| | 3 | 40 | | .ToList()!; |
| | | 41 | | } |
| | | 42 | | |
| | | 43 | | public static PreOrderItem? ConvertSingleItem( |
| | | 44 | | InterpartsPriceOffer? offer, |
| | | 45 | | string? requestLogin, |
| | | 46 | | IReadOnlyDictionary<int, decimal>? ratesToRub, |
| | | 47 | | IReadOnlyDictionary<int, string>? currencyNames = null) |
| | | 48 | | { |
| | 25 | 49 | | var rates = ratesToRub ?? EmptyRates; |
| | | 50 | | |
| | 25 | 51 | | if (offer == null) |
| | 1 | 52 | | return null; |
| | | 53 | | |
| | 24 | 54 | | if (offer.IsPriceActual != true) |
| | 2 | 55 | | return null; |
| | | 56 | | |
| | 22 | 57 | | if (offer.Amount <= 0) |
| | 3 | 58 | | return null; |
| | | 59 | | |
| | 19 | 60 | | var supplierPriceString = BuildSupplierPurchasePriceString(offer, rates); |
| | 19 | 61 | | var originalCurrencyCode = SourceCurrencyCode; |
| | 19 | 62 | | var originalPriceString = offer.OurPrice2Decimal.HasValue |
| | 19 | 63 | | ? offer.OurPrice2Decimal.Value.ToString("0.##", CultureInfo.InvariantCulture) |
| | 19 | 64 | | : "0"; |
| | | 65 | | |
| | 19 | 66 | | var currencyCode = "RUB"; |
| | 19 | 67 | | var itemId = string.IsNullOrWhiteSpace(offer.PartCodeShort) ? offer.PartCode : offer.PartCodeShort; |
| | 19 | 68 | | var effectiveLogin = string.IsNullOrWhiteSpace(requestLogin) ? "admin" : requestLogin.Trim(); |
| | | 69 | | |
| | 19 | 70 | | var supplierName = offer.SupplierName ?? string.Empty; |
| | 19 | 71 | | var partCodeShort = offer.PartCodeShort ?? offer.PartCode ?? string.Empty; |
| | 19 | 72 | | var direction = offer.SupplierExtId ?? string.Empty; |
| | | 73 | | |
| | 19 | 74 | | if (direction.Contains('-')) |
| | 1 | 75 | | return null; |
| | | 76 | | |
| | 18 | 77 | | direction += "_" + offer.SupplyTypeExtId ?? string.Empty; |
| | | 78 | | |
| | 18 | 79 | | var costPriceString = TryBuildCostPriceStringFromInputPrice(offer, rates); |
| | 18 | 80 | | var priceDescription = BuildPriceDescription(offer, rates, currencyNames); |
| | | 81 | | |
| | 18 | 82 | | return new PreOrderItem |
| | 18 | 83 | | { |
| | 18 | 84 | | Caption = offer.PartDescription, |
| | 18 | 85 | | Rest = (offer.Amount ?? 0).ToString(CultureInfo.InvariantCulture), |
| | 18 | 86 | | DeliveryDays = (offer.Term ?? 0).ToString(CultureInfo.InvariantCulture), |
| | 18 | 87 | | DeliveryDaysMin = offer.Term ?? 0, |
| | 18 | 88 | | DeliveryDaysMax = offer.Term ?? 0, |
| | 18 | 89 | | CurrencyCode = currencyCode, |
| | 18 | 90 | | Producer = offer.ProducerName ?? string.Empty, |
| | 18 | 91 | | Code = partCodeShort, |
| | 18 | 92 | | Price = costPriceString, |
| | 18 | 93 | | CostPrice = supplierPriceString, |
| | 18 | 94 | | Direction = direction, |
| | 18 | 95 | | ItemHash = $"{partCodeShort}_{supplierName}".GetHashCode().ToString("X"), |
| | 18 | 96 | | ItemId = itemId ?? string.Empty, |
| | 18 | 97 | | MinQuantity = offer.Multiplicity ?? 1, |
| | 18 | 98 | | SiteUrl = "http://studio-api.interparts.ru/", |
| | 18 | 99 | | Title = supplierName, |
| | 18 | 100 | | PriceDescription = priceDescription, |
| | 18 | 101 | | Description = |
| | 18 | 102 | | $"Поставщик: {supplierName}\n" + |
| | 18 | 103 | | $"Артикул: {partCodeShort}\n" + |
| | 18 | 104 | | $"Цена поставщика: {supplierPriceString} {currencyCode}\n" + |
| | 18 | 105 | | (costPriceString != null ? $"Себестоимость (вход): {costPriceString} {currencyCode}\n" : string.Empty) + |
| | 18 | 106 | | $"Цена в оригинальной валюте (поставщик): {originalPriceString} {originalCurrencyCode}\n" + |
| | 18 | 107 | | $"SupplierCurrencyId: {offer.SupplierCurrencyId}\n" + |
| | 18 | 108 | | $"Логин запроса: {effectiveLogin}\n" + |
| | 18 | 109 | | $"Время обновления: {offer.UpdatedTime}\n" |
| | 18 | 110 | | }; |
| | | 111 | | } |
| | | 112 | | |
| | | 113 | | private static string BuildSupplierPurchasePriceString( |
| | | 114 | | InterpartsPriceOffer offer, |
| | | 115 | | IReadOnlyDictionary<int, decimal> rates) |
| | | 116 | | { |
| | 19 | 117 | | var rub = ConvertSupplierPriceToRub(offer.OurPrice2Decimal, AppliedRateCurrencyId, rates).RubPrice; |
| | 19 | 118 | | return FormatRoundedRubString(rub); |
| | | 119 | | } |
| | | 120 | | |
| | | 121 | | private static string? TryBuildCostPriceStringFromInputPrice( |
| | | 122 | | InterpartsPriceOffer offer, |
| | | 123 | | IReadOnlyDictionary<int, decimal> rates) |
| | | 124 | | { |
| | 18 | 125 | | if (!offer.InputPriceDecimal.HasValue) |
| | 16 | 126 | | return null; |
| | | 127 | | |
| | 2 | 128 | | var rub = ConvertSupplierPriceToRub(offer.InputPriceDecimal, AppliedRateCurrencyId, rates).RubPrice; |
| | 2 | 129 | | return FormatRoundedRubString(rub); |
| | | 130 | | } |
| | | 131 | | |
| | | 132 | | private static string FormatRoundedRubString(double? rubPrice) |
| | | 133 | | { |
| | 41 | 134 | | if (!rubPrice.HasValue) |
| | 0 | 135 | | return "0"; |
| | | 136 | | |
| | 41 | 137 | | return Math.Round(rubPrice.Value, MidpointRounding.AwayFromZero) |
| | 41 | 138 | | .ToString("0", CultureInfo.InvariantCulture); |
| | | 139 | | } |
| | | 140 | | |
| | | 141 | | private static (double? RubPrice, bool UsedFallbackRate) ConvertSupplierPriceToRub( |
| | | 142 | | double? sourcePrice, |
| | | 143 | | int? supplierCurrencyId, |
| | | 144 | | IReadOnlyDictionary<int, decimal> rates) |
| | | 145 | | { |
| | 21 | 146 | | if (!sourcePrice.HasValue) |
| | 0 | 147 | | return (0, false); |
| | | 148 | | |
| | 21 | 149 | | var rate = InterpartsCurrencyRateService.GetRateToRub(AppliedRateCurrencyId, rates, out var usedFallback); |
| | | 150 | | |
| | 21 | 151 | | return (sourcePrice.Value * (double)rate, usedFallback); |
| | | 152 | | } |
| | | 153 | | |
| | | 154 | | /// <summary> |
| | | 155 | | /// Расшифровка пересчёта для подсказки в проценке: цена ответа API, применённый курс и результат в рублях. |
| | | 156 | | /// Точка с запятой — разделитель строк подсказки на фронте. |
| | | 157 | | /// </summary> |
| | | 158 | | private static string BuildPriceDescription( |
| | | 159 | | InterpartsPriceOffer offer, |
| | | 160 | | IReadOnlyDictionary<int, decimal> rates, |
| | | 161 | | IReadOnlyDictionary<int, string>? currencyNames) |
| | | 162 | | { |
| | 18 | 163 | | var rate = InterpartsCurrencyRateService.GetRateToRub(AppliedRateCurrencyId, rates, out var usedFallback); |
| | 18 | 164 | | var names = currencyNames ?? EmptyCurrencyNames; |
| | 18 | 165 | | var rateName = names.TryGetValue(AppliedRateCurrencyId, out var name) && !string.IsNullOrWhiteSpace(name) |
| | 18 | 166 | | ? name |
| | 18 | 167 | | : $"id {AppliedRateCurrencyId}"; |
| | | 168 | | |
| | 18 | 169 | | var lines = new List<string>(); |
| | | 170 | | |
| | 18 | 171 | | if (offer.InputPriceDecimal.HasValue) |
| | | 172 | | { |
| | 2 | 173 | | lines.Add( |
| | 2 | 174 | | $"Цена: {FormatSourceValue(offer.InputPriceDecimal.Value)} {SourceCurrencyCode} × {FormatRate(rate)} = " |
| | 2 | 175 | | $"{FormatRoundedRubString(offer.InputPriceDecimal.Value * (double)rate)} RUB"); |
| | | 176 | | } |
| | | 177 | | |
| | 18 | 178 | | if (offer.OurPrice2Decimal.HasValue) |
| | | 179 | | { |
| | 18 | 180 | | lines.Add( |
| | 18 | 181 | | $"Себест.: {FormatSourceValue(offer.OurPrice2Decimal.Value)} {SourceCurrencyCode} × {FormatRate(rate)} = |
| | 18 | 182 | | $"{FormatRoundedRubString(offer.OurPrice2Decimal.Value * (double)rate)} RUB"); |
| | | 183 | | } |
| | | 184 | | |
| | 18 | 185 | | lines.Add(usedFallback |
| | 18 | 186 | | ? $"Курс не найден в справочнике Interparts (id {AppliedRateCurrencyId}), применён 1" |
| | 18 | 187 | | : $"Курс: справочник Interparts «{rateName}» (id {AppliedRateCurrencyId}) = {FormatRate(rate)}"); |
| | | 188 | | |
| | 18 | 189 | | return string.Join("; ", lines); |
| | | 190 | | } |
| | | 191 | | |
| | | 192 | | private static string FormatSourceValue(double value) => |
| | 20 | 193 | | value.ToString("0.####", CultureInfo.InvariantCulture); |
| | | 194 | | |
| | | 195 | | private static string FormatRate(decimal rate) => |
| | 25 | 196 | | rate.ToString("0.####", CultureInfo.InvariantCulture); |
| | | 197 | | |
| | | 198 | | private static string MapSupplierCurrencyIdToCode(int? supplierCurrencyId) |
| | | 199 | | { |
| | 0 | 200 | | return supplierCurrencyId switch |
| | 0 | 201 | | { |
| | 0 | 202 | | 1 or 14 or 15 => "RUB", |
| | 0 | 203 | | 3 or 7 => "USD", |
| | 0 | 204 | | 5 or 13 => "EUR", |
| | 0 | 205 | | 2 or 20 or 21 => "BYN", |
| | 0 | 206 | | _ => "UNKNOWN" |
| | 0 | 207 | | }; |
| | | 208 | | } |
| | | 209 | | } |