private static void UpsertRemoteProductData(GetProductsServiceRequest request) { GetProductDataRealtimeRequest getProductDataServiceRequest = null; if (!request.ProductIds.IsNullOrEmpty()) { getProductDataServiceRequest = new GetProductDataRealtimeRequest(request.ProductIds); } else if (!request.ItemAndInventDimIdCombinations.IsNullOrEmpty()) { var itemIds = request.ItemAndInventDimIdCombinations.Select(i => i.ItemId); getProductDataServiceRequest = new GetProductDataRealtimeRequest(itemIds); } else { return; } var getProductDataResponse = request.RequestContext.Execute <GetProductDataRealtimeResponse>(getProductDataServiceRequest); var productsXml = getProductDataResponse.ProductDataXml; var manager = new ProductDataManager(request.RequestContext); manager.SaveProductData(productsXml); }
private static GetProductsServiceResponse GetVariants(GetVariantProductsServiceRequest request) { ThrowIf.Null(request, "request"); ThrowIf.Null(request.QueryResultSettings, "request.QueryResultSettings"); if (request.ChannelId < 0) { throw new ArgumentOutOfRangeException("request", request.ChannelId, InvalidChannelIdErrorMessage); } long variantId; if (!request.MatchingDimensionValues.IsNullOrEmpty()) { var getVariantProductIdsDataRequest = new GetVariantProductIdsDataRequest(request.ChannelId, request.MasterProductId, request.MatchingDimensionValues, request.QueryResultSettings); variantId = request.RequestContext.Execute <SingleEntityDataServiceResponse <long> >(getVariantProductIdsDataRequest).Entity; } else if (!request.MatchingSlotToComponentRelations.IsNullOrEmpty()) { var getVariantProductIdsDataRequest = new GetVariantProductIdsDataRequest(request.ChannelId, request.MasterProductId, request.MatchingSlotToComponentRelations, request.QueryResultSettings); variantId = request.RequestContext.Execute <SingleEntityDataServiceResponse <long> >(getVariantProductIdsDataRequest).Entity; } else { throw new NotSupportedException("Please specify exactly one of dimension value(s) or component(s) to retrieve variant products."); } var getVariantProductsDataRequest = new GetProductsServiceRequest(request.ChannelId, new List <long>() { variantId }, QueryResultSettings.AllRecords); var variantProducts = request.RequestContext.Execute <GetProductsServiceResponse>(getVariantProductsDataRequest).Products; return(new GetProductsServiceResponse(variantProducts)); }
private SimpleProduct GetSingleProductByItemId(string itemId, string inventDimId) { if (string.IsNullOrWhiteSpace(itemId)) { return(null); } var lookupClause = new ProductLookupClause(itemId, inventDimId); // Query first record (as opposed to single) to check if more products match criteria. var getProductRequest = new GetProductsServiceRequest( this.Context.GetPrincipal().ChannelId, new[] { lookupClause }, QueryResultSettings.FirstRecord); getProductRequest.SearchLocation = SearchLocation.Local; // Scanned products must be found locally. var products = this.Context.Execute <GetProductsServiceResponse>(getProductRequest).Products; if (products.Results.IsNullOrEmpty() || products.HasNextPage) { // if product is not found or multiple products founds (not exact match) return 'null' return(null); } return(products.Results.Single()); }
public static async Task <ProductsWithPageInitiation> GetProductsAsync(GetProductsServiceRequest serviceRequest, IProductStore productStore) { var getProductsStoreEntity = serviceRequest.ToEntity(); var getProductsResponse = await productStore.GetProductsAsync(getProductsStoreEntity); var coreProductResponse = getProductsResponse.ToModel(); return(coreProductResponse); }
/// <summary> /// Setup the variant data for the sale line. /// </summary> /// <param name="context">The CRT context.</param> /// <param name="inventDimensionId">The invent dimension identifier.</param> /// <param name="itemId">The item identifier.</param> /// <param name="lineItem">The sales line to update.</param> internal static void SetUpVariantAndProduct(RequestContext context, string inventDimensionId, string itemId, SalesLine lineItem) { // Get the product corresponding to this item. // It is expected to get product details // even if the product is not assorted, as long as // respected product exists on headquarters. long channelId = context.GetPrincipal().ChannelId; // Search product with itemId and InventDimensionId. var itemAndInventDimIdCombination = new List <ProductLookupClause> { new ProductLookupClause(itemId, inventDimensionId) }; var productsRequest = new GetProductsServiceRequest(channelId, itemAndInventDimIdCombination, QueryResultSettings.AllRecords) { SearchLocation = SearchLocation.All }; var product = context.Execute <GetProductsServiceResponse>(productsRequest).Products.Results.OrderBy(p => p.IsRemote).FirstOrDefault(); if (product == null) { throw new DataValidationException( DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_UnableToFindListing, string.Format("Unable to find listing for item {0}, variant {1}, channel {2}.", lineItem.ItemId, lineItem.InventoryDimensionId, context.GetPrincipal().ChannelId)); } // Populate variant information // Processing variants. if (!string.IsNullOrWhiteSpace(inventDimensionId)) { lineItem.Variant = null; if (product.IsDistinct) { lineItem.Variant = ProductVariant.ConvertFrom(product); } if (lineItem.Variant == null) { throw new DataValidationException( DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_UnableToFindVariant, string.Format("Unable to find variant for invent Dimension id {0}, channel {1}.", inventDimensionId, context.GetPrincipal().ChannelId)); } lineItem.InventoryDimensionId = lineItem.Variant.InventoryDimensionId; lineItem.ProductId = lineItem.Variant.DistinctProductVariantId; } else { lineItem.InventoryDimensionId = string.Empty; lineItem.ProductId = product.RecordId; } }
public static GetProductsStoreEntity ToEntity(this GetProductsServiceRequest request) { if (request.PagingInfo == null) { return(null); } GetProductsStoreEntity page = new GetProductsStoreEntity() { PagingInfo = request.PagingInfo, ProductSort = request.ProductSort }; return(page); }
private void ValidateTransactiondata(KitTransaction kitTransaction) { ThrowIf.Null(kitTransaction.KitTransactionLines, "kitTransaction.KitTransactionLines"); if (!kitTransaction.KitTransactionLines.Any()) { throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_RequiredValueNotFound, "Transaction does not contain any kit transaction line."); } // Validate transaction contains a valid kit variant foreach (KitTransactionLine kitline in kitTransaction.KitTransactionLines) { if (string.IsNullOrWhiteSpace(kitline.InventoryDimensionId)) { throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_RequiredValueNotFound, "InventoryDimensionId is not set for the Kit line transaction."); } var productLookupClauses = new List <ProductLookupClause> { new ProductLookupClause(kitline.ItemId, kitline.InventoryDimensionId) }; var request = new GetProductsServiceRequest(this.Context.GetChannelConfiguration().RecordId, productLookupClauses, QueryResultSettings.AllRecords); var results = this.Context.Runtime.Execute <GetProductsServiceResponse>(request, this.Context).Products.Results; if (results == null) { throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_UnableToFindListing, string.Format("Kit with itemId {0} and inventoryDimensionId {1} is not found.", kitline.ItemId, kitline.InventoryDimensionId)); } if (results.HasMultiple()) { throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_MultipleItemsForItemId, string.Format("ItemId {0} and inventoryDimensionId {1} returned multiple kit records. The ids do not represent a single kit product.", kitline.ItemId, kitline.InventoryDimensionId)); } SimpleProduct product = results.Single(); if (product.ProductType != ProductType.KitVariant) { throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_RequiredValueNotFound, string.Format("ItemId {0} is not a kit product.", kitline.ItemId)); } if (!product.Behavior.IsKitDisassemblyAllowed) { throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_RequiredValueNotFound, string.Format("Kit product '{0}' is not allowed to be disassembled at a register.", kitline.ItemId)); } } }
/// <summary> /// Retrieves the products ids (record ids) for reason codes that trigger upsell. /// </summary> /// <param name="context">The context.</param> /// <param name="reasonCodes">The reason codes.</param> private static void SetProductIdsForUpsell(RequestContext context, IEnumerable <ReasonCode> reasonCodes) { IEnumerable <ReasonSubCode> itemSubCodes = reasonCodes.SelectMany(reasonCode => reasonCode.ReasonSubCodes) .Where(subCode => subCode.TriggerFunctionType == TriggerFunctionType.Item); if (itemSubCodes.IsNullOrEmpty()) { return; } IEnumerable <ProductLookupClause> lookupClauses = itemSubCodes.Where(itemSubCode => !string.IsNullOrWhiteSpace(itemSubCode.TriggerCode)).Select(subCode => new ProductLookupClause(subCode.TriggerCode, inventDimId: null)); var productSearchRequest = new GetProductsServiceRequest( context.GetPrincipal().ChannelId, lookupClauses, QueryResultSettings.AllRecords); PagedResult <SimpleProduct> products = context.Execute <GetProductsServiceResponse>(productSearchRequest).Products; foreach (ReasonSubCode itemSubCode in itemSubCodes) { if (string.IsNullOrWhiteSpace(itemSubCode.TriggerCode)) { RetailLogger.Log.CrtServicesReasonCodeServiceUpsellSubCodeWithEmptyTriggerCode(itemSubCode.ReasonCodeId, itemSubCode.SubCodeId); continue; } SimpleProduct product = products.Results.FirstOrDefault(p => string.Equals(p.ItemId, itemSubCode.TriggerCode, StringComparison.OrdinalIgnoreCase)); if (product != null) { itemSubCode.ProductId = product.RecordId; } else { RetailLogger.Log.CrtServicesReasonCodeServiceProductNotFoundForTriggeredUpsell(itemSubCode.ReasonCodeId, itemSubCode.SubCodeId, itemSubCode.TriggerCode); } } }
public async Task <GetProductsServiceResponse> GetProductsAsync(GetProductsServiceRequest request) { var response = await Core.Product.GetProductsAsync(request, _productStore); return(response.ToModel()); }
/// <summary> /// Populates the product information on the sales lines. /// </summary> /// <param name="context">The request context.</param> /// <param name="salesTransactions">The list of sales transactions.</param> /// <param name="mustRemoveUnavailableProductLines">A flag indicating whether to remove cart lines with unavailable products.</param> /// <param name="productsSearchLocation">Products search location.</param> /// <returns>Collection of unavailable product identifiers.</returns> private static IDictionary <string, IList <SalesLine> > PopulateProductOnSalesLines(RequestContext context, IEnumerable <SalesTransaction> salesTransactions, bool mustRemoveUnavailableProductLines, SearchLocation productsSearchLocation) { var productIdToLineAndTransactionMapping = new Dictionary <long, List <Tuple <SalesLine, SalesTransaction> > >(); var linesWithUnavilableProducts = new Dictionary <string, IList <SalesLine> >(); foreach (SalesTransaction transaction in salesTransactions) { IEnumerable <SalesLine> productSalesLines = transaction.SalesLines.Where(line => line.ProductId != 0); foreach (SalesLine line in productSalesLines) { long productId = line.ProductId; if (!productIdToLineAndTransactionMapping.ContainsKey(productId)) { productIdToLineAndTransactionMapping.Add(productId, new List <Tuple <SalesLine, SalesTransaction> >()); } productIdToLineAndTransactionMapping[productId].Add(new Tuple <SalesLine, SalesTransaction>(line, transaction)); } } if (productIdToLineAndTransactionMapping.IsNullOrEmpty()) { return(linesWithUnavilableProducts); } // Get referenced productIds for CartLines to be created var productIds = productIdToLineAndTransactionMapping.Keys.ToList(); var request = new GetProductsServiceRequest(context.GetPrincipal().ChannelId, productIds, calculatePrice: false, settings: QueryResultSettings.AllRecords) { SearchLocation = productsSearchLocation }; var results = context.Runtime.Execute <GetProductsServiceResponse>(request, context).Products.Results.OrderBy(p => p.IsRemote); var productByRecordId = new Dictionary <long, SimpleProduct>(); foreach (var product in results) { if (!productByRecordId.ContainsKey(product.RecordId)) { productByRecordId[product.RecordId] = product; } } foreach (KeyValuePair <long, List <Tuple <SalesLine, SalesTransaction> > > mapping in productIdToLineAndTransactionMapping) { SimpleProduct product; bool productFound = productByRecordId.TryGetValue(mapping.Key, out product); foreach (Tuple <SalesLine, SalesTransaction> lineTransactionPair in mapping.Value) { SalesLine salesLine = lineTransactionPair.Item1; SalesTransaction transaction = lineTransactionPair.Item2; if (!productFound) { if (mustRemoveUnavailableProductLines) { transaction.SalesLines.Remove(salesLine); } else { salesLine.Variant = null; } if (!linesWithUnavilableProducts.ContainsKey(transaction.Id)) { linesWithUnavilableProducts.Add(transaction.Id, new List <SalesLine>()); } linesWithUnavilableProducts[transaction.Id].Add(salesLine); continue; } if (product.IsDistinct) { salesLine.Variant = ProductVariant.ConvertFrom(product); } } } return(linesWithUnavilableProducts); }
/// <summary> /// Gets the products requested based on their record identifiers. /// </summary> /// <param name="request">The request to retrieve <see cref="SimpleProduct"/> objects.</param> /// <returns>The response containing the collection of products requested.</returns> private static GetProductsServiceResponse GetProducts(GetProductsServiceRequest request) { ThrowIf.Null(request, "request"); ThrowIf.Null(request.QueryResultSettings, "request.QueryResultSettings"); if (request.ProductIds.IsNullOrEmpty() && request.ItemAndInventDimIdCombinations.IsNullOrEmpty()) { return(new GetProductsServiceResponse(PagedResult <SimpleProduct> .Empty())); } if (request.ProductIds != null && request.ProductIds.Any() && request.ItemAndInventDimIdCombinations != null && request.ItemAndInventDimIdCombinations.Any()) { throw new ArgumentOutOfRangeException("request", "The GetProductsServiceRequest cannot be processed when both product ids and item-inventdim ids are specified. Please specify only one."); } if (request.SearchLocation == SearchLocation.Remote) { throw new NotSupportedException(string.Format("SearchLocation {0} is not supported.", request.SearchLocation)); } bool?downloadedProductsFilter = null; switch (request.SearchLocation) { case SearchLocation.Local: downloadedProductsFilter = false; break; case SearchLocation.Remote: downloadedProductsFilter = true; break; case SearchLocation.All: downloadedProductsFilter = null; break; default: throw new InvalidOperationException(string.Format("SearchLocation '{0}' is not supported.", request.SearchLocation)); } if (request.QueryResultSettings.Sorting.IsSpecified) { // We have to enforce paging by "IsRemote" flag and retrieve local products first. Otherwise result set can be shifted once remote product is inserted to local database. throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidRequest, "When retrieving products by identifiers only default sort order is supported."); } request.QueryResultSettings.Sorting.Add(new SortColumn(SimpleProduct.IsRemoteColumnName, isDescending: false)); GetProductsDataRequest productsDataRequest; if (!request.ProductIds.IsNullOrEmpty()) { productsDataRequest = new GetProductsDataRequest(request.ProductIds, request.QueryResultSettings, downloadedProductsFilter); } else { productsDataRequest = new GetProductsDataRequest(request.ItemAndInventDimIdCombinations, request.QueryResultSettings, downloadedProductsFilter); } PagedResult <SimpleProduct> products = request.RequestContext.Execute <EntityDataServiceResponse <SimpleProduct> >(productsDataRequest).PagedEntityCollection; // If requested products are not found in the current store, download it to local database as assorted to current channel and then retrieve them. int numberOfProductsRequested = request.ProductIds.IsNullOrEmpty() ? request.ItemAndInventDimIdCombinations.Count : request.ProductIds.Count; int numberOfProductsExpected = request.QueryResultSettings.Paging.NoPageSizeLimit ? numberOfProductsRequested : Math.Min(numberOfProductsRequested, (int)request.QueryResultSettings.Paging.Top); if (request.SearchLocation.HasFlag(SearchLocation.Remote) && (products == null || products.Results.IsNullOrEmpty() || products.TotalCount < numberOfProductsExpected)) { try { UpsertRemoteProductData(request); } catch (FeatureNotSupportedException) { // Suppress FeatureNotSupportException for offline scenario. // Exception will be logged by ExceptionNotificationHandler. } products = request.RequestContext.Execute <EntityDataServiceResponse <SimpleProduct> >(productsDataRequest).PagedEntityCollection; } products = PopulateComplexProductProperties(request.ChannelId, products, request.RequestContext, request.SearchLocation, downloadedProductsFilter, request.CalculatePrice); // If a product is assorted locally and downloaded from virtual catalog, we only return the locally assorted version. // Ideally, collapsing these should happen from the underlying product data service. products.Results = products.Results.GroupBy(p => p.RecordId).Select(group => group.OrderBy(p => p.IsRemote).First()).AsReadOnly(); return(new GetProductsServiceResponse(products)); }
private static PagedResult <SimpleProduct> PopulateComplexProductProperties(long channelId, PagedResult <SimpleProduct> products, RequestContext context, SearchLocation searchLocation, bool?downloadedProductsFilter, bool calculatePrices) { // Retrieving all id collections needed to query the Data Service for complex properties. var productIds = products.Results.Select(p => p.RecordId); var masterTypeProductIds = products.Results.Where(p => p.ProductType == ProductType.Master).Select(p => p.RecordId); var kitVariantTypeProductIds = products.Results.Where(p => p.ProductType == ProductType.KitVariant).Select(v => v.RecordId); var kitMasterTypeProductIds = products.Results.Where(p => p.ProductType == ProductType.KitMaster).Select(v => v.RecordId); var variantTypeProductIds = products.Results.Where(p => p.ProductType == ProductType.Variant).Select(v => v.RecordId); // Products of types Master and KitMaster have dimensions that need to be retrieved. var idsOfProductsContainingDimensions = masterTypeProductIds.Concat(kitMasterTypeProductIds); IEnumerable <ProductComponent> components = new List <ProductComponent>(); IEnumerable <ProductDimension> productDimensions = new List <ProductDimension>(); IEnumerable <ProductDimensionValue> dimensionValues = new List <ProductDimensionValue>(); // Products of type KitVariant have components that need to be retrieved. if (kitVariantTypeProductIds.Any()) { var getProductComponentsDataRequestColummnSet = ProductComponent.DefaultColumnSet; getProductComponentsDataRequestColummnSet.Add("VARIANTPRODUCTID"); var getProductComponentsDataRequestSettings = new QueryResultSettings(getProductComponentsDataRequestColummnSet, PagingInfo.AllRecords); var getProductComponentsDataRequest = new GetProductComponentsForVariantProductsDataRequest(kitVariantTypeProductIds, getProductComponentsDataRequestSettings, downloadedProductsFilter); components = context.Execute <EntityDataServiceResponse <ProductComponent> >(getProductComponentsDataRequest).PagedEntityCollection.Results; } if (idsOfProductsContainingDimensions.Any()) { var getDimensionsDataRequest = new GetProductDimensionsDataRequest(idsOfProductsContainingDimensions, QueryResultSettings.AllRecords); productDimensions = context.Execute <EntityDataServiceResponse <ProductDimension> >(getDimensionsDataRequest).PagedEntityCollection.Results; } // Products of types Variant and KitVariant have dimension values that need to be retrieved. // This collection is populated after retrieving components so that dimension values of Variant type products can also be retrieved in the same transaction. var idsOfProductsContainingDimensionValues = variantTypeProductIds.Concat(kitVariantTypeProductIds).Concat(components.Where(c => c.ProductType == ProductType.Variant).Select(c => c.ProductId).Distinct()); if (idsOfProductsContainingDimensionValues.Any()) { var getDimensionValuesDataRequest = new GetProductDimensionValuesForVariantProductsDataRequest(idsOfProductsContainingDimensionValues, QueryResultSettings.AllRecords, downloadedProductsFilter); dimensionValues = context.Execute <EntityDataServiceResponse <ProductDimensionValue> >(getDimensionValuesDataRequest).PagedEntityCollection.Results; } var productIdsToFetchBehavior = productIds.Concat(components.Select(c => c.ProductId).Distinct()); var productBehaviorSettings = new QueryResultSettings(ProductBehavior.DefaultColumnSet, PagingInfo.CreateWithExactCount(productIdsToFetchBehavior.Count(), skip: 0)); var productsBehaviorDataRequest = new GetProductBehaviorDataRequest(productIdsToFetchBehavior, productBehaviorSettings, downloadedProductsFilter); PagedResult <ProductBehavior> productsBehavior = context.Execute <EntityDataServiceResponse <ProductBehavior> >(productsBehaviorDataRequest).PagedEntityCollection; var getLinkedProductRelationsDataRequest = new GetLinkedProductRelationsDataRequest(productIds, QueryResultSettings.AllRecords, downloadedProductsFilter); PagedResult <LinkedProductRelation> linkedProductRelations = context.Execute <EntityDataServiceResponse <LinkedProductRelation> >(getLinkedProductRelationsDataRequest).PagedEntityCollection; var linkedProductIds = linkedProductRelations.Results.Select(r => r.LinkedProductId).Distinct(); var getLinkedProductsDataRequest = new GetProductsServiceRequest(channelId, linkedProductIds, QueryResultSettings.AllRecords); getLinkedProductsDataRequest.SearchLocation = searchLocation; PagedResult <SimpleProduct> linkedProducts = context.Execute <GetProductsServiceResponse>(getLinkedProductsDataRequest).Products; PagedResult <ProductPrice> productPrices = PagedResult <ProductPrice> .Empty(); if (calculatePrices) { // Pricing APIs currently take Product instead of SimpleProduct. We manually build a Product object // and populate the required field in the interim until uptake of SimpleProduct in pricing service. List <Product> productsTransformedForPricing = ConvertSimpleProductsToProducts(products.Results).ToList(); var priceRequest = new GetProductPricesServiceRequest(productsTransformedForPricing); productPrices = context.Execute <GetProductPricesServiceResponse>(priceRequest).ProductPrices; } return(InsertProductPropertiesIntoProduct(products, productsBehavior, productPrices, linkedProductRelations, linkedProducts, components, productDimensions, dimensionValues)); }