< Summary

Information
Class: OzonOrdersWeb.Areas.Studio2.Controllers.OrderLookupController
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Controllers/OrderLookupController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 321
Coverable lines: 321
Total lines: 547
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 140
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Controllers/OrderLookupController.cs

#LineLine coverage
 1using Microsoft.AspNetCore.Authorization;
 2using Microsoft.AspNetCore.Mvc;
 3using Microsoft.EntityFrameworkCore;
 4using Microsoft.Extensions.Configuration;
 5using OzonRepositories.Context;
 6using PartsInfo.HttpUtils;
 7using Services.ApiServices.OzonApi;
 8using Services.CacheServices.CommercialCategoryLookup;
 9using Services.DataServices;
 10using Services.SearchServices.OrderSearch;
 11
 12namespace 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
 028        public OrderLookupController(
 029            ApplicationDbContext context,
 030            OzonClientServices ozonClientServices,
 031            ClientStatusDataServices clientStatusServices,
 032            EtProducerDataServices etProducerDataServices,
 033            ProxyHttpClientService proxyHttpClientService,
 034            ICommercialCategoryLookupService commercialCategoryLookup,
 035            IOrderSearchService orderSearchService,
 036            IConfiguration configuration)
 37        {
 038            _context = context;
 039            _ozonClientServices = ozonClientServices;
 040            _clientStatusServices = clientStatusServices;
 041            _etProducerDataServices = etProducerDataServices;
 042            _proxyHttpClientService = proxyHttpClientService;
 043            _commercialCategoryLookup = commercialCategoryLookup;
 044            _orderSearchService = orderSearchService;
 045            _configuration = configuration;
 046        }
 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        {
 056            var items = await _commercialCategoryLookup.SearchAsync(q, limit, cancellationToken);
 057            return Ok(new { items });
 058        }
 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        {
 071            if (string.IsNullOrWhiteSpace(q) || q.Trim().Length < 2 ||
 072                !_configuration.GetValue<bool>("OrdersSearch:UseElasticsearch"))
 73            {
 074                return Ok(new { items = Array.Empty<string>() });
 75            }
 76
 077            var items = await _orderSearchService.SuggestArticlesAsync(q.Trim(), limit, cancellationToken);
 078            return Ok(new { items });
 079        }
 80
 81        [HttpGet]
 82        [Route("SearchArticleByEAN")]
 83        public async Task<IActionResult> SearchArticleByEAN([FromQuery] string ean)
 84        {
 085            if (string.IsNullOrWhiteSpace(ean))
 086                return BadRequest(new { error = "EAN код не указан" });
 87
 088            if (!System.Text.RegularExpressions.Regex.IsMatch(ean, @"^\d{8}$|^\d{12}$|^\d{13}$"))
 089                return BadRequest(new { error = "Неверный формат EAN кода" });
 90
 91            try
 92            {
 093                var productInDb = await _context.Products
 094                    .AsNoTracking()
 095                    .FirstOrDefaultAsync(p => !string.IsNullOrEmpty(p.Barcode) && p.Barcode.Trim() == ean.Trim());
 96
 097                if (productInDb != null)
 98                {
 099                    return Ok(new
 0100                    {
 0101                        found = true,
 0102                        source = "database",
 0103                        article = productInDb.Article,
 0104                        barcode = productInDb.Barcode,
 0105                        message = $"Артикул найден в базе: {productInDb.Article}"
 0106                    });
 107                }
 108
 109                try
 110                {
 0111                    var tecdocApiUrl = $"https://tecdoc.interparts.ru/api/v1/articles/search/ean/{ean}";
 0112                    var tecdocResponse = await _proxyHttpClientService.GetJsonAsync(tecdocApiUrl);
 113
 0114                    if (tecdocResponse != null &&
 0115                        tecdocResponse.RootElement.TryGetProperty("count", out var countElement) &&
 0116                        countElement.GetInt32() > 0 &&
 0117                        tecdocResponse.RootElement.TryGetProperty("results", out var resultsElement) &&
 0118                        resultsElement.ValueKind == System.Text.Json.JsonValueKind.Array &&
 0119                        resultsElement.GetArrayLength() > 0)
 120                    {
 0121                        var firstResult = resultsElement[0];
 0122                        if (firstResult.TryGetProperty("article", out var articleElement))
 123                        {
 0124                            string? articleNumber = null;
 0125                            string? normalizedDescription = null;
 126
 0127                            if (articleElement.TryGetProperty("dataSupplierArticleNumber", out var articleNumberElement)
 0128                                articleNumber = articleNumberElement.GetString();
 129
 0130                            if (articleElement.TryGetProperty("normalizedDescription", out var descElement))
 0131                                normalizedDescription = descElement.GetString();
 132
 0133                            if (!string.IsNullOrWhiteSpace(articleNumber))
 134                            {
 0135                                return Ok(new
 0136                                {
 0137                                    found = true,
 0138                                    source = "tecdoc",
 0139                                    article = articleNumber.Trim(),
 0140                                    barcode = ean,
 0141                                    description = normalizedDescription ?? "",
 0142                                    message = $"Артикул найден в TecDoc: {articleNumber.Trim()}"
 0143                                });
 144                            }
 145                        }
 146                    }
 0147                }
 0148                catch (Exception ex)
 149                {
 150                    System.Diagnostics.Debug.WriteLine($"Ошибка поиска через TecDoc: {ex.Message}");
 0151                }
 152
 0153                var searchResults = new List<object>();
 0154                string? foundModel = null;
 155
 156                try
 157                {
 0158                    var eanApiUrl = $"https://api.upcitemdb.com/prod/trial/lookup?upc={ean}";
 0159                    var eanApiResponse = await _proxyHttpClientService.GetJsonAsync(eanApiUrl);
 160
 0161                    if (eanApiResponse != null &&
 0162                        eanApiResponse.RootElement.TryGetProperty("code", out var codeElement) &&
 0163                        codeElement.GetString() == "OK" &&
 0164                        eanApiResponse.RootElement.TryGetProperty("items", out var itemsElement) &&
 0165                        itemsElement.ValueKind == System.Text.Json.JsonValueKind.Array)
 166                    {
 0167                        foreach (var item in itemsElement.EnumerateArray())
 168                        {
 0169                            string? title = null;
 0170                            string? brand = null;
 0171                            string? model = null;
 0172                            string? description = null;
 0173                            string? upc = null;
 174
 0175                            if (item.TryGetProperty("title", out var titleElement)) title = titleElement.GetString();
 0176                            if (item.TryGetProperty("brand", out var brandElement)) brand = brandElement.GetString();
 0177                            if (item.TryGetProperty("model", out var modelElement)) model = modelElement.GetString();
 0178                            if (item.TryGetProperty("description", out var descElement2)) description = descElement2.Get
 0179                            if (item.TryGetProperty("upc", out var upcElement)) upc = upcElement.GetString();
 180
 0181                            if (!string.IsNullOrEmpty(model) && string.IsNullOrEmpty(foundModel))
 0182                                foundModel = model;
 183
 0184                            if (!string.IsNullOrEmpty(title))
 185                            {
 0186                                searchResults.Add(new
 0187                                {
 0188                                    source = "upcitemdb",
 0189                                    title = title,
 0190                                    brand = brand ?? "",
 0191                                    model = model ?? "",
 0192                                    ean = ean,
 0193                                    description = description ?? "",
 0194                                    upc = upc ?? ""
 0195                                });
 196                            }
 197                        }
 198                    }
 0199                }
 0200                catch (Exception ex)
 201                {
 202                    System.Diagnostics.Debug.WriteLine($"Ошибка поиска через UPCItemDB: {ex.Message}");
 0203                }
 204
 205                try
 206                {
 0207                    var eanSearchUrl = $"https://api.ean-search.org/api?token=demo&op=barcode-lookup&ean={ean}";
 0208                    var eanSearchResponse = await _proxyHttpClientService.GetJsonAsync(eanSearchUrl);
 209
 0210                    if (eanSearchResponse != null &&
 0211                        eanSearchResponse.RootElement.TryGetProperty("name", out var nameElement))
 212                    {
 0213                        string? name = nameElement.GetString();
 0214                        string? categoryName = null;
 0215                        string? vendor = null;
 216
 0217                        if (eanSearchResponse.RootElement.TryGetProperty("categoryName", out var categoryElement))
 0218                            categoryName = categoryElement.GetString();
 219
 0220                        if (eanSearchResponse.RootElement.TryGetProperty("vendor", out var vendorElement))
 0221                            vendor = vendorElement.GetString();
 222
 0223                        if (!string.IsNullOrEmpty(name))
 224                        {
 0225                            searchResults.Add(new
 0226                            {
 0227                                source = "ean-search",
 0228                                name = name,
 0229                                category = categoryName ?? "",
 0230                                vendor = vendor ?? "",
 0231                                ean = ean
 0232                            });
 233                        }
 234                    }
 0235                }
 0236                catch (Exception ex)
 237                {
 238                    System.Diagnostics.Debug.WriteLine($"Ошибка поиска через EAN-Search: {ex.Message}");
 0239                }
 240
 0241                if (searchResults.Any())
 242                {
 0243                    return Ok(new
 0244                    {
 0245                        found = true,
 0246                        source = "external",
 0247                        ean = ean,
 0248                        model = foundModel,
 0249                        results = searchResults,
 0250                        message = $"Найдено {searchResults.Count} результат(ов) во внешних базах данных"
 0251                    });
 252                }
 253
 0254                return Ok(new
 0255                {
 0256                    found = false,
 0257                    source = "external",
 0258                    ean = ean,
 0259                    model = (string?)null,
 0260                    message = "Артикул не найден ни в базе данных, ни во внешних источниках"
 0261                });
 262            }
 0263            catch (Exception ex)
 264            {
 0265                return StatusCode(500, new { error = $"Ошибка при поиске: {ex.Message}" });
 266            }
 0267        }
 268
 269        [HttpGet]
 270        [Route("GetClientStatusesByClient")]
 271        public async Task<IActionResult> GetClientStatusesByClient(int clientId)
 272        {
 273            try
 274            {
 0275                var client = await _ozonClientServices.GetOzonClientAsync(clientId);
 0276                if (client == null)
 0277                    return Json(new { success = false, message = "Клиент не найден" });
 278
 0279                var allStatuses = await _clientStatusServices.GetClientStatuses();
 0280                var clientStatuses = allStatuses
 0281                    .Where(s => s.ClientTypeId == null || s.ClientTypeId == (int)client.ClientType)
 0282                    .OrderBy(s => s.Name)
 0283                    .ToList();
 284
 0285                var statusList = clientStatuses.Select(s => new { id = s.Id, name = s.Name }).ToList();
 0286                return Json(new { success = true, statuses = statusList });
 287            }
 0288            catch (Exception ex)
 289            {
 0290                return Json(new { success = false, message = ex.Message });
 291            }
 0292        }
 293
 294        [HttpGet]
 295        [Route("GetClientStatusesByClientType")]
 296        public async Task<IActionResult> GetClientStatusesByClientType(int? clientTypeId)
 297        {
 298            try
 299            {
 0300                var allStatuses = await _clientStatusServices.GetClientStatuses();
 0301                var clientStatuses = allStatuses
 0302                    .Where(s => s.ClientTypeId == null ||
 0303                                (clientTypeId.HasValue && s.ClientTypeId == clientTypeId.Value))
 0304                    .OrderBy(s => s.Name)
 0305                    .ToList();
 306
 0307                var statusList = clientStatuses.Select(s => new { id = s.Id, name = s.Name }).ToList();
 0308                return Json(new { success = true, statuses = statusList });
 309            }
 0310            catch (Exception ex)
 311            {
 0312                return Json(new { success = false, message = ex.Message });
 313            }
 0314        }
 315
 316        [HttpGet]
 317        [Route("GetCurrencyByCode")]
 318        public async Task<IActionResult> GetCurrencyByCode(string currencyCode)
 319        {
 320            try
 321            {
 0322                var currency = await _context.Currencys.FirstOrDefaultAsync(c => c.Name == currencyCode);
 0323                if (currency == null)
 0324                    return Json(new { success = false, message = "Валюта не найдена" });
 325
 0326                return Json(new { success = true, currencyId = currency.Id });
 327            }
 0328            catch (Exception ex)
 329            {
 0330                return Json(new { success = false, message = ex.Message });
 331            }
 0332        }
 333
 334        [HttpGet]
 335        [Route("SearchEtProducers")]
 336        public async Task<IActionResult> SearchEtProducers(string query)
 337        {
 338            try
 339            {
 0340                if (string.IsNullOrWhiteSpace(query))
 0341                    return Json(new { success = true, producers = new List<object>() });
 342
 0343                var allEtProducers = await _etProducerDataServices.GetEtProducers();
 0344                var filteredProducers = allEtProducers
 0345                    .Where(e => e.Id == e.RealId && !string.IsNullOrEmpty(e.MarketPrefix))
 0346                    .ToList();
 347
 0348                var searchQuery = query.Trim().ToLower();
 0349                var results = filteredProducers
 0350                    .Where(e =>
 0351                        (!string.IsNullOrEmpty(e.Name) && e.Name.ToLower().Contains(searchQuery)) ||
 0352                        (!string.IsNullOrEmpty(e.MarketPrefix) && e.MarketPrefix.ToLower().Contains(searchQuery)))
 0353                    .OrderBy(e => e.Name)
 0354                    .Take(20)
 0355                    .Select(e => new { id = e.Id, name = e.Name, marketPrefix = e.MarketPrefix })
 0356                    .ToList();
 357
 0358                return Json(new { success = true, producers = results });
 359            }
 0360            catch (Exception ex)
 361            {
 0362                return Json(new { success = false, message = ex.Message });
 363            }
 0364        }
 365
 366        [HttpGet]
 367        [Route("GetEtProducerMarketPrefix")]
 368        public async Task<IActionResult> GetEtProducerMarketPrefix(string producerName)
 369        {
 370            try
 371            {
 0372                if (string.IsNullOrWhiteSpace(producerName))
 0373                    return Json(new { success = true, marketPrefix = "" });
 374
 0375                var allEtProducers = await _etProducerDataServices.GetEtProducers();
 0376                var foundProducer = allEtProducers
 0377                    .FirstOrDefault(e => e.Id == e.RealId &&
 0378                                         !string.IsNullOrEmpty(e.MarketPrefix) &&
 0379                                         e.Name.Equals(producerName.Trim(), StringComparison.OrdinalIgnoreCase));
 380
 0381                return Json(foundProducer != null
 0382                    ? new { success = true, marketPrefix = foundProducer.MarketPrefix }
 0383                    : new { success = true, marketPrefix = "" });
 384            }
 0385            catch (Exception ex)
 386            {
 0387                return Json(new { success = false, message = ex.Message });
 388            }
 0389        }
 390
 391        [HttpGet]
 392        [Route("GetEtProducersByArticle")]
 393        public async Task<IActionResult> GetEtProducersByArticle(string article)
 394        {
 395            try
 396            {
 0397                if (string.IsNullOrWhiteSpace(article))
 0398                    return Json(new { success = true, producers = new List<object>() });
 399
 0400                var normalizedArticle = System.Text.RegularExpressions.Regex
 0401                    .Replace(article, @"[^a-zA-Zа-яА-Я0-9]", "").ToLower();
 402
 0403                if (string.IsNullOrEmpty(normalizedArticle))
 0404                    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
 0407                var prefix = normalizedArticle.Length >= 4
 0408                    ? normalizedArticle[..4]
 0409                    : normalizedArticle;
 410
 0411                var orders = await _context.Orders
 0412                    .Where(o => o.Article != null &&
 0413                                o.EtProducerId != null &&
 0414                                EF.Functions.Like(o.Article, $"%{prefix}%"))
 0415                    .Include(o => o.EtProducer)
 0416                    .AsNoTracking()
 0417                    .ToListAsync();
 418
 0419                var matchingOrders = orders
 0420                    .Where(o => !string.IsNullOrEmpty(o.Article) &&
 0421                                System.Text.RegularExpressions.Regex
 0422                                    .Replace(o.Article, @"[^a-zA-Zа-яА-Я0-9]", "").ToLower() == normalizedArticle)
 0423                    .ToList();
 424
 0425                var producersByCount = matchingOrders
 0426                    .Where(o => o.EtProducer != null &&
 0427                                o.EtProducer.Id == o.EtProducer.RealId &&
 0428                                !string.IsNullOrEmpty(o.EtProducer.MarketPrefix))
 0429                    .GroupBy(o => o.EtProducerId)
 0430                    .Select(g =>
 0431                    {
 0432                        var producer = g.First().EtProducer!;
 0433                        return new
 0434                        {
 0435                            producer,
 0436                            count = g.Count(),
 0437                            productName = g
 0438                                .Where(o => !string.IsNullOrEmpty(o.ProductName))
 0439                                .GroupBy(o => o.ProductName)
 0440                                .OrderByDescending(pg => pg.Count())
 0441                                .Select(pg => pg.Key)
 0442                                .FirstOrDefault() ?? string.Empty
 0443                        };
 0444                    })
 0445                    .OrderByDescending(x => x.count)
 0446                    .ThenBy(x => x.producer.Name)
 0447                    .Take(10)
 0448                    .Select(x => new
 0449                    {
 0450                        id = x.producer.Id,
 0451                        name = x.producer.Name,
 0452                        marketPrefix = x.producer.MarketPrefix,
 0453                        orderCount = x.count,
 0454                        productName = x.productName
 0455                    })
 0456                    .ToList();
 457
 0458                return Json(new { success = true, producers = producersByCount });
 459            }
 0460            catch (Exception ex)
 461            {
 0462                return Json(new { success = false, message = ex.Message });
 463            }
 0464        }
 465
 466        [HttpGet]
 467        [Route("CheckOrderUnique")]
 468        public async Task<IActionResult> CheckOrderUnique(string shipmentNumber, string productKey)
 469        {
 470            try
 471            {
 0472                if (string.IsNullOrWhiteSpace(shipmentNumber) || string.IsNullOrWhiteSpace(productKey))
 0473                    return Json(new { success = true, isUnique = true, message = "" });
 474
 0475                var compositeKey = $"{shipmentNumber.Trim()}{productKey.Trim()}";
 0476                var existingOrder = await _context.Orders
 0477                    .Where(o => o.ShipmentNumber != null && o.ProductKey != null &&
 0478                                (o.ShipmentNumber.Trim() + o.ProductKey.Trim()) == compositeKey)
 0479                    .AsNoTracking()
 0480                    .FirstOrDefaultAsync();
 481
 0482                if (existingOrder != null)
 483                {
 0484                    return Json(new
 0485                    {
 0486                        success = true,
 0487                        isUnique = false,
 0488                        message = $"Заказ с таким номером отправления и ключом продукта уже существует (ID: {existingOrd
 0489                    });
 490                }
 491
 0492                return Json(new { success = true, isUnique = true, message = "" });
 493            }
 0494            catch (Exception ex)
 495            {
 0496                return Json(new { success = false, message = ex.Message });
 497            }
 0498        }
 499
 500        [HttpGet]
 501        [Route("GetProductByArticle")]
 502        public async Task<IActionResult> GetProductByArticle(string article)
 503        {
 504            try
 505            {
 0506                if (string.IsNullOrWhiteSpace(article))
 0507                    return Json(new { success = true, product = (object?)null });
 508
 0509                var normalizedArticle = System.Text.RegularExpressions.Regex
 0510                    .Replace(article, @"[^a-zA-Zа-яА-Я0-9]", "").ToLower();
 511
 0512                if (string.IsNullOrEmpty(normalizedArticle))
 0513                    return Json(new { success = true, product = (object?)null });
 514
 0515                var product = await _context.Products
 0516                    .AsNoTracking()
 0517                    .FirstOrDefaultAsync(p => p.Article != null &&
 0518                                              System.Text.RegularExpressions.Regex
 0519                                                  .Replace(p.Article, @"[^a-zA-Zа-яА-Я0-9]", "").ToLower() ==
 0520                                              normalizedArticle);
 521
 0522                if (product != null)
 523                {
 0524                    return Json(new
 0525                    {
 0526                        success = true,
 0527                        product = new
 0528                        {
 0529                            commercialCategory = product.CommercialCategory,
 0530                            volumetricWeight = product.VolumetricWeight,
 0531                            currentPriceWithDiscount = product.CurrentPriceWithDiscount,
 0532                            weight = product.Weight,
 0533                            volume = product.Volume,
 0534                            barcode = product.Barcode
 0535                        }
 0536                    });
 537                }
 538
 0539                return Json(new { success = true, product = (object?)null });
 540            }
 0541            catch (Exception ex)
 542            {
 0543                return Json(new { success = false, message = ex.Message });
 544            }
 0545        }
 546    }
 547}