Example #1
0
        public async Task <AzureFileInfo> GetPartNumberThumbnailAsync(
            string brandName,
            string partNumber)
        {
            var imageKey       = PartNumberCleaner.GetCleanedBrandPartNumber(brandName, partNumber);
            var cachedImageUrl = await _ekImageCache.GetValueAsync(imageKey, CancellationToken.None);

            if (cachedImageUrl == null)
            {
                // try find in TecDoc
                var fileInfo = await FindAndUploadPartNumberThumbnailAsync(
                    brandName : brandName,
                    partNumber : partNumber,
                    imageKey : imageKey);

                // cache both success and not found
                var imageUrlValue = fileInfo?.Url ?? AbsentImageUrlValue;
                await _ekImageCache.SetValueAsync(imageKey, imageUrlValue, CancellationToken.None);

                return(fileInfo);
            }

            if (cachedImageUrl == AbsentImageUrlValue)
            {
                return(null);
            }

            return(new AzureFileInfo()
            {
                Url = cachedImageUrl,
            });
        }
Example #2
0
        public static IndexAction ElitUaApiModelToIndexAction(
            ElitPriceListRecord apiProduct,
            long updatedOnUtcTimestamp)
        {
            if (apiProduct == null)
            {
                return(null);
            }

            var source     = EkProductSourceEnum.ElitUa;
            var productKey = new EkProductKey(source, ReplaceInvalidAzureSearchKeySymbolsWithDash(apiProduct.ActiveItemNo))
                             .ToKey();
            var nameRu  = GetValueOrFallback(apiProduct.EcatDescription, apiProduct.ItemDescription);
            var product = new Document()
            {
                ["key"] = productKey,
                ["updatedOnUtcTimestamp"] = updatedOnUtcTimestamp,
                ["source"]                 = (int)source,
                ["sourceId"]               = apiProduct.ActiveItemNo,
                ["partNumber"]             = apiProduct.PartNumber,
                ["cleanedPartNumber"]      = PartNumberCleaner.GetCleanedPartNumber(apiProduct.PartNumber),
                ["brandName"]              = apiProduct.Brand,
                ["cleanedBrandPartNumber"] = PartNumberCleaner.GetCleanedBrandPartNumber(
                    brandName: apiProduct.Brand,
                    partNumber: apiProduct.PartNumber),
                ["name_ru"] = SearchTextHelpers.TrimNameAndAddBrandIfMissed(
                    productName: nameRu,
                    brandName: apiProduct.Brand),
                ["price"] = (double)apiProduct.CustomerPrice,
            };

            return(IndexAction.MergeOrUpload(product));
        }
Example #3
0
        public static IndexAction OmegaAutoBizApiModelToIndexAction(
            ProductSearchRecord apiProduct,
            long updatedOnUtcTimestamp)
        {
            if (apiProduct == null)
            {
                return(null);
            }

            var source     = EkProductSourceEnum.OmegaAutoBiz;
            var productKey = new EkProductKey(source, apiProduct.ProductId.ToString())
                             .ToKey();
            var price = GetValueOrFallback(apiProduct.CustomerPrice, apiProduct.Price);

            if (price == 0)
            {
                return(IndexAction.Delete(new Document()
                {
                    ["key"] = productKey,
                }));
            }

            var partNumber  = apiProduct.Number?.Trim();
            var brandName   = apiProduct.BrandDescription?.Trim();
            var nameRu      = apiProduct.Description?.Trim();
            var nameUk      = GetValueOrFallback(apiProduct.DescriptionUkr?.Trim(), nameRu);
            var description = apiProduct.Info?.Trim();

            var product = new Document()
            {
                ["key"] = productKey,
                ["updatedOnUtcTimestamp"] = updatedOnUtcTimestamp,
                ["source"]                 = (int)source,
                ["sourceId"]               = apiProduct.ProductId.ToString(),
                ["partNumber"]             = partNumber,
                ["cleanedPartNumber"]      = PartNumberCleaner.GetCleanedPartNumber(partNumber),
                ["brandName"]              = brandName,
                ["cleanedBrandPartNumber"] = PartNumberCleaner.GetCleanedBrandPartNumber(
                    brandName: brandName,
                    partNumber: partNumber),
                ["name_ru"] = SearchTextHelpers.TrimNameAndAddBrandIfMissed(
                    productName: nameRu,
                    brandName: brandName),
                ["name_uk"] = SearchTextHelpers.TrimNameAndAddBrandIfMissed(
                    productName: nameUk,
                    brandName: brandName),
                ["description_ru"] = description,
                ["description_uk"] = description,
                ["price"]          = (double)price,
            };

            return(IndexAction.MergeOrUpload(product));
        }
Example #4
0
        public override async Task <EkKioskProductSearchByPartNumberGetResponse> ExecuteAsync(EkKioskProductSearchByPartNumberGetRequest request)
        {
            var cleanedPartNumber = PartNumberCleaner.GetCleanedPartNumber(request.PartNumber);

            if (string.IsNullOrEmpty(cleanedPartNumber))
            {
                return(GetEmptyResponse());
            }

            // cancellation token
            var cancellationToken = _httpContextAccessor.HttpContext?.RequestAborted ?? CancellationToken.None;

            var indexedPartNumberBrands = await FindIndexedPartNumberBrandsAsync(cleanedPartNumber, cancellationToken);

            if (indexedPartNumberBrands.Length > 0)
            {
                // return only found product brands
                // TBD: we can attach other brands from Omega for wider results
                return(new EkKioskProductSearchByPartNumberGetResponse()
                {
                    Brands = indexedPartNumberBrands,
                });
            }

            // if no products are found in index, look for brands in Omega to allow buying of at least replacements
            // todo: add cancellationToken support to proxy based clients
            var response = await _omegaAutoBizClient.ProductSearchAsync(cleanedPartNumber, 0, 50);

            var partNumberBrands = response.Result?
                                   .Select(x => new EkPartNumberBrand()
            {
                ProductKey = new EkProductKey(EkProductSourceEnum.OmegaAutoBiz, x.ProductId.ToString()).ToKey(),
                BrandName  = x.BrandDescription?.Trim(),
                PartNumber = x.Number?.Trim(),
                Name       = new MultiLanguageString()
                {
                    [Languages.RussianCode] = x.Description?.Trim(),
                },
            })
                                   .ToArray();

            return(new EkKioskProductSearchByPartNumberGetResponse()
            {
                Brands = partNumberBrands ?? new EkPartNumberBrand[0],
            });
        }
Example #5
0
 private static string GetBrandKey(string brandName)
 {
     return(PartNumberCleaner.GetCleanedBrandName(brandName)?.ToLower());
 }
        public override async Task <EkKioskProductAndReplacementsByPartNumberGetResponse> ExecuteAsync(EkKioskProductAndReplacementsByPartNumberGetRequest request)
        {
            Assure.ArgumentNotNull(request.PartNumberBrand, nameof(request.PartNumberBrand));
            Assure.ArgumentNotNull(request.PartNumberBrand.ProductKey, nameof(request.PartNumberBrand.ProductKey));
            Assure.ArgumentNotNull(request.PartNumberBrand.PartNumber, nameof(request.PartNumberBrand.PartNumber));

            var response = new EkKioskProductAndReplacementsByPartNumberGetResponse();

            // cancellation token
            var cancellationToken = _httpContextAccessor.HttpContext?.RequestAborted ?? CancellationToken.None;

            using (var searchIndexClient = AzureSearchHelper.CreateSearchIndexClient(_ekSearchSettings.ServiceName, _ekSearchSettings.QueryKey))
            {
                searchIndexClient.IndexName = _ekSearchSettings.ProductsIndexName;

                // FIND PRODUCT
                try
                {
                    var indexProduct = await searchIndexClient.Documents.GetAsync <IndexProduct>(
                        request.PartNumberBrand.ProductKey,
                        cancellationToken : cancellationToken);

                    response.Product = EkConvertHelper.EkNewIndexProductToProduct(indexProduct);

                    // todo: add search by Brand/PartNumber to find all direct matches since many products sources are supported
                    // it's not done since new product search model is planned anyway
                }
                catch (CloudException)
                {
                    response.Product = EkConvertHelper.EkOmegaPartNumberBrandToProduct(request.PartNumberBrand);
                }

                // FIND REPLACEMENTS
                var cleanedPartNumber = PartNumberCleaner.GetCleanedPartNumber(request.PartNumberBrand.PartNumber);

                // TecDoc replacements
                // todo: add cancellationToken support to proxy based clients
                var tecDocReplacements = await _tecDocWsClient.SearchByArticleNumberAsync(cleanedPartNumber);

                tecDocReplacements = tecDocReplacements
                                     // except direct match
                                     .Where(x => x.NumberType != ArticleNumberTypeEnum.ArticleNumber)
                                     .ToArray();

                var replacementCleanedBrandPartNumbers = tecDocReplacements
                                                         .Select(x => PartNumberCleaner.GetCleanedBrandPartNumber(x.BrandName, x.ArticleNo))
                                                         .Take(100)
                                                         .ToArray();

                if (replacementCleanedBrandPartNumbers.Length > 0)
                {
                    var replacementsIndexSearchParameters = new SearchParameters()
                    {
                        Top          = 100,
                        SearchFields = new[] { "cleanedBrandPartNumber" },
                    };
                    var searchTerm   = string.Join("|", replacementCleanedBrandPartNumbers);
                    var searchResult = await searchIndexClient.Documents.SearchAsync <IndexProduct>(
                        searchTerm,
                        replacementsIndexSearchParameters,
                        cancellationToken : cancellationToken);

                    response.Replacements = searchResult.Results
                                            .Select(x => EkConvertHelper.EkNewIndexProductToProduct(x.Document))
                                            .ToArray();
                }
                else
                {
                    response.Replacements = new EkProduct[0];
                }

                return(response);
            }
        }
Example #7
0
        private async Task <AzureFileInfo> FindAndUploadPartNumberThumbnailAsync(
            string brandName,
            string partNumber,
            string imageKey)
        {
            try
            {
                var cleanedBrandName  = PartNumberCleaner.GetCleanedBrandName(brandName);
                var cleanedPartNumber = PartNumberCleaner.GetCleanedPartNumber(partNumber);

                var articles = await _tecDocWsClient.SearchByArticleNumberAsync(cleanedPartNumber, null, ArticleNumberTypeEnum.ArticleNumber);

                var article = articles
                              .Where(x => PartNumberCleaner.GetCleanedBrandName(x.BrandName)?.Equals(cleanedBrandName, StringComparison.OrdinalIgnoreCase) == true)
                              .FirstOrDefault();
                if (article == null)
                {
                    return(null);
                }

                var articleExtendedInfos = await _tecDocWsClient.GetArticlesExtendedInfoAsync(new long[] { article.ArticleId });

                var articleExtendedInfo = articleExtendedInfos.FirstOrDefault();
                if (articleExtendedInfo?.ArticleDocuments == null ||
                    articleExtendedInfo.ArticleDocuments.Length == 0)
                {
                    return(null);
                }

                var imageDocument = articleExtendedInfo.ArticleDocuments
                                    .Where(x => x.DocFileName != null)
                                    .Where(x => x.DocFileName.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) ||
                                           x.DocFileName.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) ||
                                           x.DocFileName.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase) ||
                                           x.DocFileName.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
                                    .FirstOrDefault();
                if (imageDocument == null)
                {
                    return(null);
                }

                var fileName = $"{imageKey}{Path.GetExtension(imageDocument.DocFileName)}";

                var fileInfo = await _azureStorageClient.GetBlobFileIfExistsAsync(
                    EkBlobContainerNames.Images,
                    fileName);

                if (fileInfo != null)
                {
                    // if uploaded earlier
                    return(fileInfo);
                }

                var imageContent = await _tecDocWsClient.GetDocumentAsync(imageDocument.DocId, imageDocument.DocTypeId);

                if (imageContent.Content == null)
                {
                    return(null);
                }

                const string ImageBodyStartMarker = "base64,";
                var          imageBodyStartIndex  = imageContent.Content.IndexOf(ImageBodyStartMarker, StringComparison.Ordinal);
                if (imageBodyStartIndex == -1)
                {
                    return(null);
                }

                imageBodyStartIndex += ImageBodyStartMarker.Length;
                var imageBodyBase64 = imageContent.Content.Substring(imageBodyStartIndex);
                var imageBody       = Convert.FromBase64String(imageBodyBase64);

                fileInfo = await _azureStorageClient.UploadBlobFileAsync(
                    EkBlobContainerNames.Images,
                    fileName,
                    imageBody);

                return(fileInfo);
            }
            catch (Exception ex)
            {
                _logger.LogError(LoggingEvents.UnhandledException, nameof(FindAndUploadPartNumberThumbnailAsync), ex);

                return(null);
            }
        }
Example #8
0
        public override async Task <EkKioskProductSearchByCategoryGetResponse> ExecuteAsync(EkKioskProductSearchByCategoryGetRequest request)
        {
            var response = new EkKioskProductSearchByCategoryGetResponse();

            // cancellation token
            var cancellationToken = _httpContextAccessor.HttpContext?.RequestAborted ?? CancellationToken.None;

            // todo: add cancellationToken support to proxy based clients

            // determine TecDoc car type of modification first
            CarTypeEnum carType;
            // request categories for cars first
            var categories = await _tecDocWsClient.GetCategoriesAsync(CarTypeEnum.Car, request.ModificationId, null, childNodes : false);

            if (categories?.Length > 0)
            {
                carType = CarTypeEnum.Car;
            }
            else
            {
                // then request for trucks
                categories = await _tecDocWsClient.GetCategoriesAsync(CarTypeEnum.Truck, request.ModificationId, null, childNodes : false);

                if (categories?.Length > 0)
                {
                    carType = CarTypeEnum.Truck;
                }
                else
                {
                    return(response);
                }
            }

            const int MaxProductCount = 200;

            // TecDoc articles
            var tecDocProducts = await _tecDocWsClient.GetArticlesCompactInfoAsync(carType, request.ModificationId, request.CategoryId);

            var productCleanedBrandPartNumbers = tecDocProducts
                                                 .Select(x => PartNumberCleaner.GetCleanedBrandPartNumber(x.BrandName, x.ArticleNo))
                                                 .Take(MaxProductCount)
                                                 .ToArray();

            if (productCleanedBrandPartNumbers.Length == 0)
            {
                return(response);
            }

            // FIND PRODUCTS IN STOCK
            using (var searchIndexClient = AzureSearchHelper.CreateSearchIndexClient(_ekSearchSettings.ServiceName, _ekSearchSettings.QueryKey))
            {
                searchIndexClient.IndexName = _ekSearchSettings.ProductsIndexName;

                var replacementsIndexSearchParameters = new SearchParameters()
                {
                    Top          = MaxProductCount,
                    SearchFields = new[] { "cleanedBrandPartNumber" },
                };
                var searchTerm   = string.Join("|", productCleanedBrandPartNumbers);
                var searchResult = await searchIndexClient.Documents.SearchAsync <IndexProduct>(
                    searchTerm,
                    replacementsIndexSearchParameters,
                    cancellationToken : cancellationToken);

                response.Products = searchResult.Results
                                    .Select(x => EkConvertHelper.EkNewIndexProductToProduct(x.Document))
                                    .ToArray();

                return(response);
            }
        }