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, }); }
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)); }
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)); }
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], }); }
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); } }
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); } }
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); } }