| | | 1 | | using Microsoft.AspNetCore.Authorization; |
| | | 2 | | using Microsoft.AspNetCore.Mvc; |
| | | 3 | | using Microsoft.EntityFrameworkCore; |
| | | 4 | | using Microsoft.Extensions.Configuration; |
| | | 5 | | using OzonRepositories.Context; |
| | | 6 | | using PartsInfo.HttpUtils; |
| | | 7 | | using Services.ApiServices.OzonApi; |
| | | 8 | | using Services.CacheServices.CommercialCategoryLookup; |
| | | 9 | | using Services.DataServices; |
| | | 10 | | using Services.SearchServices.OrderSearch; |
| | | 11 | | |
| | | 12 | | namespace OzonOrdersWeb.Areas.Studio2.Controllers |
| | | 13 | | { |
| | | 14 | | [Authorize(Roles = "User,Admin")] |
| | | 15 | | [Area("Studio2")] |
| | | 16 | | [Route("Studio2/Orders")] |
| | | 17 | | public class OrderLookupController : Controller |
| | | 18 | | { |
| | | 19 | | private readonly ApplicationDbContext _context; |
| | | 20 | | private readonly OzonClientServices _ozonClientServices; |
| | | 21 | | private readonly ClientStatusDataServices _clientStatusServices; |
| | | 22 | | private readonly EtProducerDataServices _etProducerDataServices; |
| | | 23 | | private readonly ProxyHttpClientService _proxyHttpClientService; |
| | | 24 | | private readonly ICommercialCategoryLookupService _commercialCategoryLookup; |
| | | 25 | | private readonly IOrderSearchService _orderSearchService; |
| | | 26 | | private readonly IConfiguration _configuration; |
| | | 27 | | |
| | 0 | 28 | | public OrderLookupController( |
| | 0 | 29 | | ApplicationDbContext context, |
| | 0 | 30 | | OzonClientServices ozonClientServices, |
| | 0 | 31 | | ClientStatusDataServices clientStatusServices, |
| | 0 | 32 | | EtProducerDataServices etProducerDataServices, |
| | 0 | 33 | | ProxyHttpClientService proxyHttpClientService, |
| | 0 | 34 | | ICommercialCategoryLookupService commercialCategoryLookup, |
| | 0 | 35 | | IOrderSearchService orderSearchService, |
| | 0 | 36 | | IConfiguration configuration) |
| | | 37 | | { |
| | 0 | 38 | | _context = context; |
| | 0 | 39 | | _ozonClientServices = ozonClientServices; |
| | 0 | 40 | | _clientStatusServices = clientStatusServices; |
| | 0 | 41 | | _etProducerDataServices = etProducerDataServices; |
| | 0 | 42 | | _proxyHttpClientService = proxyHttpClientService; |
| | 0 | 43 | | _commercialCategoryLookup = commercialCategoryLookup; |
| | 0 | 44 | | _orderSearchService = orderSearchService; |
| | 0 | 45 | | _configuration = configuration; |
| | 0 | 46 | | } |
| | | 47 | | |
| | | 48 | | /// <summary>Lazy autocomplete категорий товаров (DISTINCT Products.CommercialCategory, кэш 24 ч).</summary> |
| | | 49 | | [HttpGet] |
| | | 50 | | [Route("SearchCommercialCategories")] |
| | | 51 | | public async Task<IActionResult> SearchCommercialCategories( |
| | | 52 | | [FromQuery] string? q, |
| | | 53 | | [FromQuery] int limit = 50, |
| | | 54 | | CancellationToken cancellationToken = default) |
| | | 55 | | { |
| | 0 | 56 | | var items = await _commercialCategoryLookup.SearchAsync(q, limit, cancellationToken); |
| | 0 | 57 | | return Ok(new { items }); |
| | 0 | 58 | | } |
| | | 59 | | |
| | | 60 | | /// <summary> |
| | | 61 | | /// Автодополнение по артикулу/ключу товара из ES-индекса заказов (не только текущая страница |
| | | 62 | | /// таблицы) — подсказки при вводе в поле "Артикул"/"Key" на Studio2/Orders. |
| | | 63 | | /// </summary> |
| | | 64 | | [HttpGet] |
| | | 65 | | [Route("SearchArticleSuggestions")] |
| | | 66 | | public async Task<IActionResult> SearchArticleSuggestions( |
| | | 67 | | [FromQuery] string? q, |
| | | 68 | | [FromQuery] int limit = 10, |
| | | 69 | | CancellationToken cancellationToken = default) |
| | | 70 | | { |
| | 0 | 71 | | if (string.IsNullOrWhiteSpace(q) || q.Trim().Length < 2 || |
| | 0 | 72 | | !_configuration.GetValue<bool>("OrdersSearch:UseElasticsearch")) |
| | | 73 | | { |
| | 0 | 74 | | return Ok(new { items = Array.Empty<string>() }); |
| | | 75 | | } |
| | | 76 | | |
| | 0 | 77 | | var items = await _orderSearchService.SuggestArticlesAsync(q.Trim(), limit, cancellationToken); |
| | 0 | 78 | | return Ok(new { items }); |
| | 0 | 79 | | } |
| | | 80 | | |
| | | 81 | | [HttpGet] |
| | | 82 | | [Route("SearchArticleByEAN")] |
| | | 83 | | public async Task<IActionResult> SearchArticleByEAN([FromQuery] string ean) |
| | | 84 | | { |
| | 0 | 85 | | if (string.IsNullOrWhiteSpace(ean)) |
| | 0 | 86 | | return BadRequest(new { error = "EAN код не указан" }); |
| | | 87 | | |
| | 0 | 88 | | if (!System.Text.RegularExpressions.Regex.IsMatch(ean, @"^\d{8}$|^\d{12}$|^\d{13}$")) |
| | 0 | 89 | | return BadRequest(new { error = "Неверный формат EAN кода" }); |
| | | 90 | | |
| | | 91 | | try |
| | | 92 | | { |
| | 0 | 93 | | var productInDb = await _context.Products |
| | 0 | 94 | | .AsNoTracking() |
| | 0 | 95 | | .FirstOrDefaultAsync(p => !string.IsNullOrEmpty(p.Barcode) && p.Barcode.Trim() == ean.Trim()); |
| | | 96 | | |
| | 0 | 97 | | if (productInDb != null) |
| | | 98 | | { |
| | 0 | 99 | | return Ok(new |
| | 0 | 100 | | { |
| | 0 | 101 | | found = true, |
| | 0 | 102 | | source = "database", |
| | 0 | 103 | | article = productInDb.Article, |
| | 0 | 104 | | barcode = productInDb.Barcode, |
| | 0 | 105 | | message = $"Артикул найден в базе: {productInDb.Article}" |
| | 0 | 106 | | }); |
| | | 107 | | } |
| | | 108 | | |
| | | 109 | | try |
| | | 110 | | { |
| | 0 | 111 | | var tecdocApiUrl = $"https://tecdoc.interparts.ru/api/v1/articles/search/ean/{ean}"; |
| | 0 | 112 | | var tecdocResponse = await _proxyHttpClientService.GetJsonAsync(tecdocApiUrl); |
| | | 113 | | |
| | 0 | 114 | | if (tecdocResponse != null && |
| | 0 | 115 | | tecdocResponse.RootElement.TryGetProperty("count", out var countElement) && |
| | 0 | 116 | | countElement.GetInt32() > 0 && |
| | 0 | 117 | | tecdocResponse.RootElement.TryGetProperty("results", out var resultsElement) && |
| | 0 | 118 | | resultsElement.ValueKind == System.Text.Json.JsonValueKind.Array && |
| | 0 | 119 | | resultsElement.GetArrayLength() > 0) |
| | | 120 | | { |
| | 0 | 121 | | var firstResult = resultsElement[0]; |
| | 0 | 122 | | if (firstResult.TryGetProperty("article", out var articleElement)) |
| | | 123 | | { |
| | 0 | 124 | | string? articleNumber = null; |
| | 0 | 125 | | string? normalizedDescription = null; |
| | | 126 | | |
| | 0 | 127 | | if (articleElement.TryGetProperty("dataSupplierArticleNumber", out var articleNumberElement) |
| | 0 | 128 | | articleNumber = articleNumberElement.GetString(); |
| | | 129 | | |
| | 0 | 130 | | if (articleElement.TryGetProperty("normalizedDescription", out var descElement)) |
| | 0 | 131 | | normalizedDescription = descElement.GetString(); |
| | | 132 | | |
| | 0 | 133 | | if (!string.IsNullOrWhiteSpace(articleNumber)) |
| | | 134 | | { |
| | 0 | 135 | | return Ok(new |
| | 0 | 136 | | { |
| | 0 | 137 | | found = true, |
| | 0 | 138 | | source = "tecdoc", |
| | 0 | 139 | | article = articleNumber.Trim(), |
| | 0 | 140 | | barcode = ean, |
| | 0 | 141 | | description = normalizedDescription ?? "", |
| | 0 | 142 | | message = $"Артикул найден в TecDoc: {articleNumber.Trim()}" |
| | 0 | 143 | | }); |
| | | 144 | | } |
| | | 145 | | } |
| | | 146 | | } |
| | 0 | 147 | | } |
| | 0 | 148 | | catch (Exception ex) |
| | | 149 | | { |
| | | 150 | | System.Diagnostics.Debug.WriteLine($"Ошибка поиска через TecDoc: {ex.Message}"); |
| | 0 | 151 | | } |
| | | 152 | | |
| | 0 | 153 | | var searchResults = new List<object>(); |
| | 0 | 154 | | string? foundModel = null; |
| | | 155 | | |
| | | 156 | | try |
| | | 157 | | { |
| | 0 | 158 | | var eanApiUrl = $"https://api.upcitemdb.com/prod/trial/lookup?upc={ean}"; |
| | 0 | 159 | | var eanApiResponse = await _proxyHttpClientService.GetJsonAsync(eanApiUrl); |
| | | 160 | | |
| | 0 | 161 | | if (eanApiResponse != null && |
| | 0 | 162 | | eanApiResponse.RootElement.TryGetProperty("code", out var codeElement) && |
| | 0 | 163 | | codeElement.GetString() == "OK" && |
| | 0 | 164 | | eanApiResponse.RootElement.TryGetProperty("items", out var itemsElement) && |
| | 0 | 165 | | itemsElement.ValueKind == System.Text.Json.JsonValueKind.Array) |
| | | 166 | | { |
| | 0 | 167 | | foreach (var item in itemsElement.EnumerateArray()) |
| | | 168 | | { |
| | 0 | 169 | | string? title = null; |
| | 0 | 170 | | string? brand = null; |
| | 0 | 171 | | string? model = null; |
| | 0 | 172 | | string? description = null; |
| | 0 | 173 | | string? upc = null; |
| | | 174 | | |
| | 0 | 175 | | if (item.TryGetProperty("title", out var titleElement)) title = titleElement.GetString(); |
| | 0 | 176 | | if (item.TryGetProperty("brand", out var brandElement)) brand = brandElement.GetString(); |
| | 0 | 177 | | if (item.TryGetProperty("model", out var modelElement)) model = modelElement.GetString(); |
| | 0 | 178 | | if (item.TryGetProperty("description", out var descElement2)) description = descElement2.Get |
| | 0 | 179 | | if (item.TryGetProperty("upc", out var upcElement)) upc = upcElement.GetString(); |
| | | 180 | | |
| | 0 | 181 | | if (!string.IsNullOrEmpty(model) && string.IsNullOrEmpty(foundModel)) |
| | 0 | 182 | | foundModel = model; |
| | | 183 | | |
| | 0 | 184 | | if (!string.IsNullOrEmpty(title)) |
| | | 185 | | { |
| | 0 | 186 | | searchResults.Add(new |
| | 0 | 187 | | { |
| | 0 | 188 | | source = "upcitemdb", |
| | 0 | 189 | | title = title, |
| | 0 | 190 | | brand = brand ?? "", |
| | 0 | 191 | | model = model ?? "", |
| | 0 | 192 | | ean = ean, |
| | 0 | 193 | | description = description ?? "", |
| | 0 | 194 | | upc = upc ?? "" |
| | 0 | 195 | | }); |
| | | 196 | | } |
| | | 197 | | } |
| | | 198 | | } |
| | 0 | 199 | | } |
| | 0 | 200 | | catch (Exception ex) |
| | | 201 | | { |
| | | 202 | | System.Diagnostics.Debug.WriteLine($"Ошибка поиска через UPCItemDB: {ex.Message}"); |
| | 0 | 203 | | } |
| | | 204 | | |
| | | 205 | | try |
| | | 206 | | { |
| | 0 | 207 | | var eanSearchUrl = $"https://api.ean-search.org/api?token=demo&op=barcode-lookup&ean={ean}"; |
| | 0 | 208 | | var eanSearchResponse = await _proxyHttpClientService.GetJsonAsync(eanSearchUrl); |
| | | 209 | | |
| | 0 | 210 | | if (eanSearchResponse != null && |
| | 0 | 211 | | eanSearchResponse.RootElement.TryGetProperty("name", out var nameElement)) |
| | | 212 | | { |
| | 0 | 213 | | string? name = nameElement.GetString(); |
| | 0 | 214 | | string? categoryName = null; |
| | 0 | 215 | | string? vendor = null; |
| | | 216 | | |
| | 0 | 217 | | if (eanSearchResponse.RootElement.TryGetProperty("categoryName", out var categoryElement)) |
| | 0 | 218 | | categoryName = categoryElement.GetString(); |
| | | 219 | | |
| | 0 | 220 | | if (eanSearchResponse.RootElement.TryGetProperty("vendor", out var vendorElement)) |
| | 0 | 221 | | vendor = vendorElement.GetString(); |
| | | 222 | | |
| | 0 | 223 | | if (!string.IsNullOrEmpty(name)) |
| | | 224 | | { |
| | 0 | 225 | | searchResults.Add(new |
| | 0 | 226 | | { |
| | 0 | 227 | | source = "ean-search", |
| | 0 | 228 | | name = name, |
| | 0 | 229 | | category = categoryName ?? "", |
| | 0 | 230 | | vendor = vendor ?? "", |
| | 0 | 231 | | ean = ean |
| | 0 | 232 | | }); |
| | | 233 | | } |
| | | 234 | | } |
| | 0 | 235 | | } |
| | 0 | 236 | | catch (Exception ex) |
| | | 237 | | { |
| | | 238 | | System.Diagnostics.Debug.WriteLine($"Ошибка поиска через EAN-Search: {ex.Message}"); |
| | 0 | 239 | | } |
| | | 240 | | |
| | 0 | 241 | | if (searchResults.Any()) |
| | | 242 | | { |
| | 0 | 243 | | return Ok(new |
| | 0 | 244 | | { |
| | 0 | 245 | | found = true, |
| | 0 | 246 | | source = "external", |
| | 0 | 247 | | ean = ean, |
| | 0 | 248 | | model = foundModel, |
| | 0 | 249 | | results = searchResults, |
| | 0 | 250 | | message = $"Найдено {searchResults.Count} результат(ов) во внешних базах данных" |
| | 0 | 251 | | }); |
| | | 252 | | } |
| | | 253 | | |
| | 0 | 254 | | return Ok(new |
| | 0 | 255 | | { |
| | 0 | 256 | | found = false, |
| | 0 | 257 | | source = "external", |
| | 0 | 258 | | ean = ean, |
| | 0 | 259 | | model = (string?)null, |
| | 0 | 260 | | message = "Артикул не найден ни в базе данных, ни во внешних источниках" |
| | 0 | 261 | | }); |
| | | 262 | | } |
| | 0 | 263 | | catch (Exception ex) |
| | | 264 | | { |
| | 0 | 265 | | return StatusCode(500, new { error = $"Ошибка при поиске: {ex.Message}" }); |
| | | 266 | | } |
| | 0 | 267 | | } |
| | | 268 | | |
| | | 269 | | [HttpGet] |
| | | 270 | | [Route("GetClientStatusesByClient")] |
| | | 271 | | public async Task<IActionResult> GetClientStatusesByClient(int clientId) |
| | | 272 | | { |
| | | 273 | | try |
| | | 274 | | { |
| | 0 | 275 | | var client = await _ozonClientServices.GetOzonClientAsync(clientId); |
| | 0 | 276 | | if (client == null) |
| | 0 | 277 | | return Json(new { success = false, message = "Клиент не найден" }); |
| | | 278 | | |
| | 0 | 279 | | var allStatuses = await _clientStatusServices.GetClientStatuses(); |
| | 0 | 280 | | var clientStatuses = allStatuses |
| | 0 | 281 | | .Where(s => s.ClientTypeId == null || s.ClientTypeId == (int)client.ClientType) |
| | 0 | 282 | | .OrderBy(s => s.Name) |
| | 0 | 283 | | .ToList(); |
| | | 284 | | |
| | 0 | 285 | | var statusList = clientStatuses.Select(s => new { id = s.Id, name = s.Name }).ToList(); |
| | 0 | 286 | | return Json(new { success = true, statuses = statusList }); |
| | | 287 | | } |
| | 0 | 288 | | catch (Exception ex) |
| | | 289 | | { |
| | 0 | 290 | | return Json(new { success = false, message = ex.Message }); |
| | | 291 | | } |
| | 0 | 292 | | } |
| | | 293 | | |
| | | 294 | | [HttpGet] |
| | | 295 | | [Route("GetClientStatusesByClientType")] |
| | | 296 | | public async Task<IActionResult> GetClientStatusesByClientType(int? clientTypeId) |
| | | 297 | | { |
| | | 298 | | try |
| | | 299 | | { |
| | 0 | 300 | | var allStatuses = await _clientStatusServices.GetClientStatuses(); |
| | 0 | 301 | | var clientStatuses = allStatuses |
| | 0 | 302 | | .Where(s => s.ClientTypeId == null || |
| | 0 | 303 | | (clientTypeId.HasValue && s.ClientTypeId == clientTypeId.Value)) |
| | 0 | 304 | | .OrderBy(s => s.Name) |
| | 0 | 305 | | .ToList(); |
| | | 306 | | |
| | 0 | 307 | | var statusList = clientStatuses.Select(s => new { id = s.Id, name = s.Name }).ToList(); |
| | 0 | 308 | | return Json(new { success = true, statuses = statusList }); |
| | | 309 | | } |
| | 0 | 310 | | catch (Exception ex) |
| | | 311 | | { |
| | 0 | 312 | | return Json(new { success = false, message = ex.Message }); |
| | | 313 | | } |
| | 0 | 314 | | } |
| | | 315 | | |
| | | 316 | | [HttpGet] |
| | | 317 | | [Route("GetCurrencyByCode")] |
| | | 318 | | public async Task<IActionResult> GetCurrencyByCode(string currencyCode) |
| | | 319 | | { |
| | | 320 | | try |
| | | 321 | | { |
| | 0 | 322 | | var currency = await _context.Currencys.FirstOrDefaultAsync(c => c.Name == currencyCode); |
| | 0 | 323 | | if (currency == null) |
| | 0 | 324 | | return Json(new { success = false, message = "Валюта не найдена" }); |
| | | 325 | | |
| | 0 | 326 | | return Json(new { success = true, currencyId = currency.Id }); |
| | | 327 | | } |
| | 0 | 328 | | catch (Exception ex) |
| | | 329 | | { |
| | 0 | 330 | | return Json(new { success = false, message = ex.Message }); |
| | | 331 | | } |
| | 0 | 332 | | } |
| | | 333 | | |
| | | 334 | | [HttpGet] |
| | | 335 | | [Route("SearchEtProducers")] |
| | | 336 | | public async Task<IActionResult> SearchEtProducers(string query) |
| | | 337 | | { |
| | | 338 | | try |
| | | 339 | | { |
| | 0 | 340 | | if (string.IsNullOrWhiteSpace(query)) |
| | 0 | 341 | | return Json(new { success = true, producers = new List<object>() }); |
| | | 342 | | |
| | 0 | 343 | | var allEtProducers = await _etProducerDataServices.GetEtProducers(); |
| | 0 | 344 | | var filteredProducers = allEtProducers |
| | 0 | 345 | | .Where(e => e.Id == e.RealId && !string.IsNullOrEmpty(e.MarketPrefix)) |
| | 0 | 346 | | .ToList(); |
| | | 347 | | |
| | 0 | 348 | | var searchQuery = query.Trim().ToLower(); |
| | 0 | 349 | | var results = filteredProducers |
| | 0 | 350 | | .Where(e => |
| | 0 | 351 | | (!string.IsNullOrEmpty(e.Name) && e.Name.ToLower().Contains(searchQuery)) || |
| | 0 | 352 | | (!string.IsNullOrEmpty(e.MarketPrefix) && e.MarketPrefix.ToLower().Contains(searchQuery))) |
| | 0 | 353 | | .OrderBy(e => e.Name) |
| | 0 | 354 | | .Take(20) |
| | 0 | 355 | | .Select(e => new { id = e.Id, name = e.Name, marketPrefix = e.MarketPrefix }) |
| | 0 | 356 | | .ToList(); |
| | | 357 | | |
| | 0 | 358 | | return Json(new { success = true, producers = results }); |
| | | 359 | | } |
| | 0 | 360 | | catch (Exception ex) |
| | | 361 | | { |
| | 0 | 362 | | return Json(new { success = false, message = ex.Message }); |
| | | 363 | | } |
| | 0 | 364 | | } |
| | | 365 | | |
| | | 366 | | [HttpGet] |
| | | 367 | | [Route("GetEtProducerMarketPrefix")] |
| | | 368 | | public async Task<IActionResult> GetEtProducerMarketPrefix(string producerName) |
| | | 369 | | { |
| | | 370 | | try |
| | | 371 | | { |
| | 0 | 372 | | if (string.IsNullOrWhiteSpace(producerName)) |
| | 0 | 373 | | return Json(new { success = true, marketPrefix = "" }); |
| | | 374 | | |
| | 0 | 375 | | var allEtProducers = await _etProducerDataServices.GetEtProducers(); |
| | 0 | 376 | | var foundProducer = allEtProducers |
| | 0 | 377 | | .FirstOrDefault(e => e.Id == e.RealId && |
| | 0 | 378 | | !string.IsNullOrEmpty(e.MarketPrefix) && |
| | 0 | 379 | | e.Name.Equals(producerName.Trim(), StringComparison.OrdinalIgnoreCase)); |
| | | 380 | | |
| | 0 | 381 | | return Json(foundProducer != null |
| | 0 | 382 | | ? new { success = true, marketPrefix = foundProducer.MarketPrefix } |
| | 0 | 383 | | : new { success = true, marketPrefix = "" }); |
| | | 384 | | } |
| | 0 | 385 | | catch (Exception ex) |
| | | 386 | | { |
| | 0 | 387 | | return Json(new { success = false, message = ex.Message }); |
| | | 388 | | } |
| | 0 | 389 | | } |
| | | 390 | | |
| | | 391 | | [HttpGet] |
| | | 392 | | [Route("GetEtProducersByArticle")] |
| | | 393 | | public async Task<IActionResult> GetEtProducersByArticle(string article) |
| | | 394 | | { |
| | | 395 | | try |
| | | 396 | | { |
| | 0 | 397 | | if (string.IsNullOrWhiteSpace(article)) |
| | 0 | 398 | | return Json(new { success = true, producers = new List<object>() }); |
| | | 399 | | |
| | 0 | 400 | | var normalizedArticle = System.Text.RegularExpressions.Regex |
| | 0 | 401 | | .Replace(article, @"[^a-zA-Zа-яА-Я0-9]", "").ToLower(); |
| | | 402 | | |
| | 0 | 403 | | if (string.IsNullOrEmpty(normalizedArticle)) |
| | 0 | 404 | | return Json(new { success = true, producers = new List<object>() }); |
| | | 405 | | |
| | | 406 | | // Pre-filter in DB using the first 4 chars, then do exact normalized match in memory |
| | 0 | 407 | | var prefix = normalizedArticle.Length >= 4 |
| | 0 | 408 | | ? normalizedArticle[..4] |
| | 0 | 409 | | : normalizedArticle; |
| | | 410 | | |
| | 0 | 411 | | var orders = await _context.Orders |
| | 0 | 412 | | .Where(o => o.Article != null && |
| | 0 | 413 | | o.EtProducerId != null && |
| | 0 | 414 | | EF.Functions.Like(o.Article, $"%{prefix}%")) |
| | 0 | 415 | | .Include(o => o.EtProducer) |
| | 0 | 416 | | .AsNoTracking() |
| | 0 | 417 | | .ToListAsync(); |
| | | 418 | | |
| | 0 | 419 | | var matchingOrders = orders |
| | 0 | 420 | | .Where(o => !string.IsNullOrEmpty(o.Article) && |
| | 0 | 421 | | System.Text.RegularExpressions.Regex |
| | 0 | 422 | | .Replace(o.Article, @"[^a-zA-Zа-яА-Я0-9]", "").ToLower() == normalizedArticle) |
| | 0 | 423 | | .ToList(); |
| | | 424 | | |
| | 0 | 425 | | var producersByCount = matchingOrders |
| | 0 | 426 | | .Where(o => o.EtProducer != null && |
| | 0 | 427 | | o.EtProducer.Id == o.EtProducer.RealId && |
| | 0 | 428 | | !string.IsNullOrEmpty(o.EtProducer.MarketPrefix)) |
| | 0 | 429 | | .GroupBy(o => o.EtProducerId) |
| | 0 | 430 | | .Select(g => |
| | 0 | 431 | | { |
| | 0 | 432 | | var producer = g.First().EtProducer!; |
| | 0 | 433 | | return new |
| | 0 | 434 | | { |
| | 0 | 435 | | producer, |
| | 0 | 436 | | count = g.Count(), |
| | 0 | 437 | | productName = g |
| | 0 | 438 | | .Where(o => !string.IsNullOrEmpty(o.ProductName)) |
| | 0 | 439 | | .GroupBy(o => o.ProductName) |
| | 0 | 440 | | .OrderByDescending(pg => pg.Count()) |
| | 0 | 441 | | .Select(pg => pg.Key) |
| | 0 | 442 | | .FirstOrDefault() ?? string.Empty |
| | 0 | 443 | | }; |
| | 0 | 444 | | }) |
| | 0 | 445 | | .OrderByDescending(x => x.count) |
| | 0 | 446 | | .ThenBy(x => x.producer.Name) |
| | 0 | 447 | | .Take(10) |
| | 0 | 448 | | .Select(x => new |
| | 0 | 449 | | { |
| | 0 | 450 | | id = x.producer.Id, |
| | 0 | 451 | | name = x.producer.Name, |
| | 0 | 452 | | marketPrefix = x.producer.MarketPrefix, |
| | 0 | 453 | | orderCount = x.count, |
| | 0 | 454 | | productName = x.productName |
| | 0 | 455 | | }) |
| | 0 | 456 | | .ToList(); |
| | | 457 | | |
| | 0 | 458 | | return Json(new { success = true, producers = producersByCount }); |
| | | 459 | | } |
| | 0 | 460 | | catch (Exception ex) |
| | | 461 | | { |
| | 0 | 462 | | return Json(new { success = false, message = ex.Message }); |
| | | 463 | | } |
| | 0 | 464 | | } |
| | | 465 | | |
| | | 466 | | [HttpGet] |
| | | 467 | | [Route("CheckOrderUnique")] |
| | | 468 | | public async Task<IActionResult> CheckOrderUnique(string shipmentNumber, string productKey) |
| | | 469 | | { |
| | | 470 | | try |
| | | 471 | | { |
| | 0 | 472 | | if (string.IsNullOrWhiteSpace(shipmentNumber) || string.IsNullOrWhiteSpace(productKey)) |
| | 0 | 473 | | return Json(new { success = true, isUnique = true, message = "" }); |
| | | 474 | | |
| | 0 | 475 | | var compositeKey = $"{shipmentNumber.Trim()}{productKey.Trim()}"; |
| | 0 | 476 | | var existingOrder = await _context.Orders |
| | 0 | 477 | | .Where(o => o.ShipmentNumber != null && o.ProductKey != null && |
| | 0 | 478 | | (o.ShipmentNumber.Trim() + o.ProductKey.Trim()) == compositeKey) |
| | 0 | 479 | | .AsNoTracking() |
| | 0 | 480 | | .FirstOrDefaultAsync(); |
| | | 481 | | |
| | 0 | 482 | | if (existingOrder != null) |
| | | 483 | | { |
| | 0 | 484 | | return Json(new |
| | 0 | 485 | | { |
| | 0 | 486 | | success = true, |
| | 0 | 487 | | isUnique = false, |
| | 0 | 488 | | message = $"Заказ с таким номером отправления и ключом продукта уже существует (ID: {existingOrd |
| | 0 | 489 | | }); |
| | | 490 | | } |
| | | 491 | | |
| | 0 | 492 | | return Json(new { success = true, isUnique = true, message = "" }); |
| | | 493 | | } |
| | 0 | 494 | | catch (Exception ex) |
| | | 495 | | { |
| | 0 | 496 | | return Json(new { success = false, message = ex.Message }); |
| | | 497 | | } |
| | 0 | 498 | | } |
| | | 499 | | |
| | | 500 | | [HttpGet] |
| | | 501 | | [Route("GetProductByArticle")] |
| | | 502 | | public async Task<IActionResult> GetProductByArticle(string article) |
| | | 503 | | { |
| | | 504 | | try |
| | | 505 | | { |
| | 0 | 506 | | if (string.IsNullOrWhiteSpace(article)) |
| | 0 | 507 | | return Json(new { success = true, product = (object?)null }); |
| | | 508 | | |
| | 0 | 509 | | var normalizedArticle = System.Text.RegularExpressions.Regex |
| | 0 | 510 | | .Replace(article, @"[^a-zA-Zа-яА-Я0-9]", "").ToLower(); |
| | | 511 | | |
| | 0 | 512 | | if (string.IsNullOrEmpty(normalizedArticle)) |
| | 0 | 513 | | return Json(new { success = true, product = (object?)null }); |
| | | 514 | | |
| | 0 | 515 | | var product = await _context.Products |
| | 0 | 516 | | .AsNoTracking() |
| | 0 | 517 | | .FirstOrDefaultAsync(p => p.Article != null && |
| | 0 | 518 | | System.Text.RegularExpressions.Regex |
| | 0 | 519 | | .Replace(p.Article, @"[^a-zA-Zа-яА-Я0-9]", "").ToLower() == |
| | 0 | 520 | | normalizedArticle); |
| | | 521 | | |
| | 0 | 522 | | if (product != null) |
| | | 523 | | { |
| | 0 | 524 | | return Json(new |
| | 0 | 525 | | { |
| | 0 | 526 | | success = true, |
| | 0 | 527 | | product = new |
| | 0 | 528 | | { |
| | 0 | 529 | | commercialCategory = product.CommercialCategory, |
| | 0 | 530 | | volumetricWeight = product.VolumetricWeight, |
| | 0 | 531 | | currentPriceWithDiscount = product.CurrentPriceWithDiscount, |
| | 0 | 532 | | weight = product.Weight, |
| | 0 | 533 | | volume = product.Volume, |
| | 0 | 534 | | barcode = product.Barcode |
| | 0 | 535 | | } |
| | 0 | 536 | | }); |
| | | 537 | | } |
| | | 538 | | |
| | 0 | 539 | | return Json(new { success = true, product = (object?)null }); |
| | | 540 | | } |
| | 0 | 541 | | catch (Exception ex) |
| | | 542 | | { |
| | 0 | 543 | | return Json(new { success = false, message = ex.Message }); |
| | | 544 | | } |
| | 0 | 545 | | } |
| | | 546 | | } |
| | | 547 | | } |