/// <summary> /// Checks the warehouse assigned to an order line item to determine if it is a valid shipment fulfillment location /// (i.e. will ship items to a customer address) /// </summary> /// <param name="warehouse">The intended source warehouse.</param> /// <param name="lineItem">The order line item.</param> /// <param name="checkInventory">Set to false to override the check against current stock.</param> /// <returns> /// true if the warehouse can ship the item and a pickup method is not already chosen; otherwise false /// (i.e. the warehouse cannot ship the item, or the line item has an in-store pickup method selected). /// </returns> private bool IsValidFulfillment(IWarehouse warehouse, LineItem lineItem, CheckInventoryMode checkInventory) { if (warehouse == null || lineItem == null) { throw new ArgumentNullException(); } var shippingMethod = ShippingManager.GetShippingMethod(lineItem.ShippingMethodId).ShippingMethod.FirstOrDefault(); if (shippingMethod != null && ShippingManager.IsHandoffShippingMethod(shippingMethod.Name)) { return(false); } var lineApplicationId = lineItem.Parent.Parent.ApplicationId; if ((warehouse.ApplicationId != lineApplicationId) || !warehouse.IsActive || !warehouse.IsFulfillmentCenter) { return(false); } if (checkInventory == CheckInventoryMode.Ignore) { return(true); } var catalogKey = new Mediachase.Commerce.Catalog.CatalogKey(lineItem.Parent.Parent.ApplicationId, lineItem.CatalogEntryId); var inventory = InventoryService.Service.Get(catalogKey, warehouse); return(IsEnoughQuantity(inventory, lineItem)); }
/// <summary> /// Matches the shipment information attached to an order line item to against the pickup warehouses to find the source warehouse, or /// validates the assigned warehouse if one is set already /// </summary> /// <param name="orderGroup">The order group parent for the line item.</param> /// <param name="lineItem">The item being processed.</param> /// <param name="checkInventory">Set to false to override the check against current stock.</param> /// <returns>the matching warehouse if all of the following are true: the shipment is set, is a valid active pickup location, and matches the line /// item warehouse (if set); otherwise null.</returns> private IWarehouse GetValidPickupFromShipment(OrderGroup orderGroup, LineItem lineItem, CheckInventoryMode checkInventory) { var shippingMethod = ShippingManager.GetShippingMethod(lineItem.ShippingMethodId).ShippingMethod.FirstOrDefault(); if (shippingMethod == null || !ShippingManager.IsHandoffShippingMethod(shippingMethod.Name)) { return(null); } var pickupAddress = orderGroup.OrderAddresses.ToArray().FirstOrDefault(x => x.Name == lineItem.ShippingAddressId); if (pickupAddress == null) { return(null); } var pickupWarehouse = ShippingManager.GetHandoffLocationFromAddressName(pickupAddress.Name); if (pickupWarehouse == null || (!string.IsNullOrEmpty(lineItem.WarehouseCode) && (pickupWarehouse.Code != lineItem.WarehouseCode))) { return(null); } var lineApplicationId = lineItem.Parent.Parent.ApplicationId; if ((pickupWarehouse.ApplicationId != lineApplicationId) || !pickupWarehouse.IsActive || (!pickupWarehouse.IsPickupLocation && !pickupWarehouse.IsDeliveryLocation)) { return(null); } if (checkInventory == CheckInventoryMode.Check) { var catalogKey = new Mediachase.Commerce.Catalog.CatalogKey(lineApplicationId, lineItem.CatalogEntryId); var pickupInventory = InventoryService.Service.Get(catalogKey, pickupWarehouse); if (pickupInventory == null) { return(null); } if (!IsEnoughQuantity(pickupInventory, lineItem)) { pickupWarehouse = null; } } return(pickupWarehouse); }
/// <summary> /// Validates the current warehouse selection for a line item or assigns and returns the appropriate warehouse if needed /// </summary> /// <param name="orderGroup">The order group that contains the line item.</param> /// <param name="lineItem">The item being purchased.</param> /// <returns>The Code of the warehouse that should provide the line item, or the empty string if no warehouse can supply the item</returns> /// <remarks> /// This is the main processing step for the fulfillment process. Customer implementations that wish to override the fulfillment process /// with custom logic (e.g. to enable true multi-warehouse fulfillment) should start by looking here. /// IsValidPickupByShipment() and CanBeValidPickupByLineItem() are necessary to support the in-store pickup feature and generally will not /// require changes, but please verify that for your implementation. IsValidPickupByShipment() checks line items that already have a shipment /// set, and CanBeValidPickupByLineItem() validates that an order with no shipment can be fulfilled as a pickup. CanBeValidPickupByLineItem() /// can generate false positives so it may need a closer look. /// IsValidFulfillment() is a straightforward check to validate that a (non-pickup) order with a warehouse already assigned can actually /// provide the goods. The only tricky point is that it does check that the line item is not configured for pickup, and that may not be /// appropriate in some custom setups. /// TryDetermineFulfillmentWarehouse() is the holder for the actual warehouse assignment when it has not been assigned already (by the UI or /// in an earlier pass) and will hold the key logic for any custom fulfillment implementation. /// </remarks> private string GetFulfillmentWarehouseForLineItem(OrderGroup orderGroup, LineItem lineItem) { var returnCode = String.Empty; var catalogKey = new Mediachase.Commerce.Catalog.CatalogKey(lineItem.Parent.Parent.ApplicationId, lineItem.CatalogEntryId); IWarehouse warehouse = null; if (!String.IsNullOrEmpty(lineItem.WarehouseCode)) { // If a warehouse is already assigned, still need to validate whether it can fulfill the order (e.g. activity, fulfillment/pickup) or not // Allowing the logic in CheckInventoryActivity deal with the inventory issues, as otherwise the double evaluation can lead to larger order amounts being rejected warehouse = WarehouseRepository.Service.Get(lineItem.WarehouseCode); if (IsValidPickupFromShipment(orderGroup, lineItem, CheckInventoryMode.Ignore) || IsValidPickupFromLineItem(orderGroup, lineItem, CheckInventoryMode.Ignore) || IsValidFulfillment(warehouse, lineItem, CheckInventoryMode.Ignore)) { return(warehouse.Code); } // Redundant to the inventory amount that will be shown too, but provides useful detail Warnings.Add("LineItemUnfulfilled-" + lineItem.Id.ToString(), String.Format("Item \"{0}\" cannot be added to the cart because the assigned warehouse cannot fulfill or provide pickup services for the request.", lineItem.DisplayName)); return(string.Empty); } warehouse = GetValidPickupFromShipment(orderGroup, lineItem, CheckInventoryMode.Check); if (warehouse != null) { return(warehouse.Code); } warehouse = GetFulfillmentWarehouse(lineItem, checkInventory: CheckInventoryMode.Check); if (warehouse != null) { return(warehouse.Code); } warehouse = GetFulfillmentWarehouse(lineItem, CheckInventoryMode.Ignore); if (warehouse != null) { return(warehouse.Code); //TODO: Validate - effectively gets overridden in CheckInventoryActivity, but lets us set an error for the bad config } // No fallback assignment, this should be an error case not a best guess Warnings.Add("LineItemUnfulfilled-" + lineItem.Id.ToString(), String.Format("Item \"{0}\" cannot be added to the cart because of a configuration error.", lineItem.DisplayName)); return(string.Empty); }
/// <summary> /// Checks the warehouse assigned to an order line item to determine if it is a valid pickup location /// </summary> /// <param name="orderGroup">The order group parent for the line item.</param> /// <param name="lineItem">The item being processed.</param> /// <param name="checkInventory">Set to false to override the check against current stock.</param> /// <returns>true if the assigned warehouse is an active pickup location; otherwise false.</returns> /// <remarks> /// With no shipping method selected, this can only be a "could be valid" check rather than an /// explicit "is valid", and thus this can report positives that will eventually be processed for /// delivery instead of pickups. /// </remarks> private bool IsValidPickupFromLineItem(OrderGroup orderGroup, LineItem lineItem, CheckInventoryMode checkInventory) { if (string.IsNullOrEmpty(lineItem.WarehouseCode)) { return(false); } var lineApplicationId = lineItem.Parent.Parent.ApplicationId; var lineItemWarehouse = WarehouseRepository.Service.List() .Where(w => w.ApplicationId == lineApplicationId && w.Code == lineItem.WarehouseCode && w.IsActive && (w.IsPickupLocation || w.IsDeliveryLocation) // TODO: Validate that should be both rather than just pickup ) .FirstOrDefault(); if (lineItemWarehouse == null) { return(false); } // In case lineItem shipping method is InStorePickup , check if lineItemWarehouse is pickup location // If not, return false. if (lineItem.ShippingMethodName == ShippingManager.PickupShippingMethodName && !lineItemWarehouse.IsPickupLocation) { return(false); } if (checkInventory == CheckInventoryMode.Ignore) { return(true); } var catalogKey = new Mediachase.Commerce.Catalog.CatalogKey(lineApplicationId, lineItem.CatalogEntryId); var pickupInventory = InventoryService.Service.List(catalogKey).FirstOrDefault(i => i.WarehouseCode.Equals(lineItem.WarehouseCode, StringComparison.OrdinalIgnoreCase)); return(IsEnoughQuantity(pickupInventory, lineItem)); }
/// <summary> /// Checks the warehouse assigned to an order line item to determine if it is a valid pickup location /// </summary> /// <param name="orderGroup">The order group parent for the line item.</param> /// <param name="lineItem">The item being processed.</param> /// <param name="checkInventory">Set to false to override the check against current stock.</param> /// <returns>true if the assigned warehouse is an active pickup location; otherwise false.</returns> /// <remarks> /// With no shipping method selected, this can only be a "could be valid" check rather than an /// explicit "is valid", and thus this can report positives that will eventually be processed for /// delivery instead of pickups. /// </remarks> private bool IsValidPickupFromLineItem(OrderGroup orderGroup, LineItem lineItem, CheckInventoryMode checkInventory) { if (string.IsNullOrEmpty(lineItem.WarehouseCode)) { return false; } var lineApplicationId = lineItem.Parent.Parent.ApplicationId; var lineItemWarehouse = WarehouseRepository.Service.List() .Where(w => w.ApplicationId == lineApplicationId && w.Code == lineItem.WarehouseCode && w.IsActive && (w.IsPickupLocation || w.IsDeliveryLocation) // TODO: Validate that should be both rather than just pickup ) .FirstOrDefault(); if (lineItemWarehouse == null) { return false; } // In case lineItem shipping method is InStorePickup , check if lineItemWarehouse is pickup location // If not, return false. if (lineItem.ShippingMethodName == ShippingManager.PickupShippingMethodName && !lineItemWarehouse.IsPickupLocation) { return false; } if (checkInventory == CheckInventoryMode.Ignore) { return true; } var catalogKey = new Mediachase.Commerce.Catalog.CatalogKey(lineApplicationId, lineItem.CatalogEntryId); var pickupInventory = InventoryService.Service.List(catalogKey).FirstOrDefault(i=>i.WarehouseCode.Equals(lineItem.WarehouseCode, StringComparison.OrdinalIgnoreCase)); return IsEnoughQuantity(pickupInventory, lineItem); }
/// <summary> /// Checks the warehouse assigned to an order line item to determine if it is a valid shipment fulfillment location /// (i.e. will ship items to a customer address) /// </summary> /// <param name="warehouse">The intended source warehouse.</param> /// <param name="lineItem">The order line item.</param> /// <param name="checkInventory">Set to false to override the check against current stock.</param> /// <returns> /// true if the warehouse can ship the item and a pickup method is not already chosen; otherwise false /// (i.e. the warehouse cannot ship the item, or the line item has an in-store pickup method selected). /// </returns> private bool IsValidFulfillment(IWarehouse warehouse, LineItem lineItem, CheckInventoryMode checkInventory) { if (warehouse == null || lineItem == null) { throw new ArgumentNullException(); } var shippingMethod = ShippingManager.GetShippingMethod(lineItem.ShippingMethodId).ShippingMethod.FirstOrDefault(); if (shippingMethod != null && ShippingManager.IsHandoffShippingMethod(shippingMethod.Name)) { return false; } var lineApplicationId = lineItem.Parent.Parent.ApplicationId; if ((warehouse.ApplicationId != lineApplicationId) || !warehouse.IsActive || !warehouse.IsFulfillmentCenter) { return false; } if (checkInventory == CheckInventoryMode.Ignore) { return true; } var catalogKey = new Mediachase.Commerce.Catalog.CatalogKey(lineItem.Parent.Parent.ApplicationId, lineItem.CatalogEntryId); var inventory = InventoryService.Service.Get(catalogKey, warehouse); return IsEnoughQuantity(inventory, lineItem); }
/// <summary> /// Matches the shipment information attached to an order line item to against the pickup warehouses to find the source warehouse, or /// validates the assigned warehouse if one is set already /// </summary> /// <param name="orderGroup">The order group parent for the line item.</param> /// <param name="lineItem">The item being processed.</param> /// <param name="checkInventory">Set to false to override the check against current stock.</param> /// <returns>the matching warehouse if all of the following are true: the shipment is set, is a valid active pickup location, and matches the line /// item warehouse (if set); otherwise null.</returns> private IWarehouse GetValidPickupFromShipment(OrderGroup orderGroup, LineItem lineItem, CheckInventoryMode checkInventory) { var shippingMethod = ShippingManager.GetShippingMethod(lineItem.ShippingMethodId).ShippingMethod.FirstOrDefault(); if (shippingMethod == null || !ShippingManager.IsHandoffShippingMethod(shippingMethod.Name)) { return null; } var pickupAddress = orderGroup.OrderAddresses.ToArray().FirstOrDefault(x => x.Name == lineItem.ShippingAddressId); if (pickupAddress == null) { return null; } var pickupWarehouse = ShippingManager.GetHandoffLocationFromAddressName(pickupAddress.Name); if (pickupWarehouse == null || (!string.IsNullOrEmpty(lineItem.WarehouseCode) && (pickupWarehouse.Code != lineItem.WarehouseCode))) { return null; } var lineApplicationId = lineItem.Parent.Parent.ApplicationId; if ((pickupWarehouse.ApplicationId != lineApplicationId) || !pickupWarehouse.IsActive || (!pickupWarehouse.IsPickupLocation && !pickupWarehouse.IsDeliveryLocation)) { return null; } if (checkInventory == CheckInventoryMode.Check) { var catalogKey = new Mediachase.Commerce.Catalog.CatalogKey(lineApplicationId, lineItem.CatalogEntryId); var pickupInventory = InventoryService.Service.Get(catalogKey, pickupWarehouse); if (pickupInventory == null) { return null; } if (!IsEnoughQuantity(pickupInventory, lineItem)) { pickupWarehouse = null; } } return pickupWarehouse; }
/// <summary> /// Validates the current warehouse selection for a line item or assigns and returns the appropriate warehouse if needed /// </summary> /// <param name="orderGroup">The order group that contains the line item.</param> /// <param name="lineItem">The item being purchased.</param> /// <returns>The Code of the warehouse that should provide the line item, or the empty string if no warehouse can supply the item</returns> /// <remarks> /// This is the main processing step for the fulfillment process. Customer implementations that wish to override the fulfillment process /// with custom logic (e.g. to enable true multi-warehouse fulfillment) should start by looking here. /// IsValidPickupByShipment() and CanBeValidPickupByLineItem() are necessary to support the in-store pickup feature and generally will not /// require changes, but please verify that for your implementation. IsValidPickupByShipment() checks line items that already have a shipment /// set, and CanBeValidPickupByLineItem() validates that an order with no shipment can be fulfilled as a pickup. CanBeValidPickupByLineItem() /// can generate false positives so it may need a closer look. /// IsValidFulfillment() is a straightforward check to validate that a (non-pickup) order with a warehouse already assigned can actually /// provide the goods. The only tricky point is that it does check that the line item is not configured for pickup, and that may not be /// appropriate in some custom setups. /// TryDetermineFulfillmentWarehouse() is the holder for the actual warehouse assignment when it has not been assigned already (by the UI or /// in an earlier pass) and will hold the key logic for any custom fulfillment implementation. /// </remarks> private string GetFulfillmentWarehouseForLineItem(OrderGroup orderGroup, LineItem lineItem) { var returnCode = String.Empty; var catalogKey = new Mediachase.Commerce.Catalog.CatalogKey(lineItem.Parent.Parent.ApplicationId, lineItem.CatalogEntryId); IWarehouse warehouse = null; if (!String.IsNullOrEmpty(lineItem.WarehouseCode)) { // If a warehouse is already assigned, still need to validate whether it can fulfill the order (e.g. activity, fulfillment/pickup) or not // Allowing the logic in CheckInventoryActivity deal with the inventory issues, as otherwise the double evaluation can lead to larger order amounts being rejected warehouse = WarehouseRepository.Service.Get(lineItem.WarehouseCode); if (IsValidPickupFromShipment(orderGroup, lineItem, CheckInventoryMode.Ignore) || IsValidPickupFromLineItem(orderGroup, lineItem, CheckInventoryMode.Ignore) || IsValidFulfillment(warehouse, lineItem, CheckInventoryMode.Ignore)) { return warehouse.Code; } // Redundant to the inventory amount that will be shown too, but provides useful detail Warnings.Add("LineItemUnfulfilled-" + lineItem.Id.ToString(), String.Format("Item \"{0}\" cannot be added to the cart because the assigned warehouse cannot fulfill or provide pickup services for the request.", lineItem.DisplayName)); return string.Empty; } warehouse = GetValidPickupFromShipment(orderGroup, lineItem, CheckInventoryMode.Check); if (warehouse != null) { return warehouse.Code; } warehouse = GetFulfillmentWarehouse(lineItem, checkInventory: CheckInventoryMode.Check); if (warehouse != null) { return warehouse.Code; } warehouse = GetFulfillmentWarehouse(lineItem, CheckInventoryMode.Ignore); if (warehouse != null) { return warehouse.Code; //TODO: Validate - effectively gets overridden in CheckInventoryActivity, but lets us set an error for the bad config } // No fallback assignment, this should be an error case not a best guess Warnings.Add("LineItemUnfulfilled-" + lineItem.Id.ToString(), String.Format("Item \"{0}\" cannot be added to the cart because of a configuration error.", lineItem.DisplayName)); return string.Empty; }