Shopping List Page Validation
Note
The shopping list page validation is available as of OroCommerce version 6.1.7.
The feature enables validation of the shopping list before starting Checkout or Request for Quote (RFQ). It also prevents customers from proceeding until all product-related issues - such as inventory status, quantity limits, configuration errors, and similar conditions - are resolved or moved to the “Saved for Later” section within the same shopping list.
Configuration
The validation system is controlled by the enforce_separate_shopping_list_validations feature toggle.
To enable it, go to System > Configuration > Commerce > Sales > Shopping List and enable:
Enforce separate shopping list validations for checkout and RFQ
When this feature is enabled, the system uses separate validation groups for checkout and RFQ flows.
The saved_for_later feature toggle enables the ability to move invalid or unwanted shopping list items to a separate “Saved for Later” section instead of deleting them.
To enable it, go to System > Configuration > Commerce > Sales > Shopping List and enable:
Enable Save For Later
The feature toggle key is saved_for_later and the system configuration option is oro_shopping_list.saved_for_later_enabled.
Validation Architecture
The shopping list validation is based on a modular architecture that determines whether each line item can be used for Checkout or Request for Quote (RFQ).
The validation logic is encapsulated in the \Oro\Bundle\ShoppingListBundle\Provider\InvalidShoppingListLineItemsProvider class, which identifies invalid line items and determines their severity.
It provides three main public methods: 1. getInvalidLineItemsIds - returns a sorted array of line item IDs, with errors first, followed by warnings. 2. getInvalidLineItemsIdsBySeverity - returns line item IDs grouped by severity, e.g. [‘errors’ => […], ‘warnings’ => […]]. 3. getInvalidItemsViolations - returns detailed validation results grouped by severity, including messages and sub-item data.
Two validation groups are used: - datagrid_line_items_data_for_checkout - for checkout. - datagrid_line_items_data_for_rfq - for RFQ.
The system evaluates every shopping list line item against both groups to determine its severity status: 1. Error - defines the contract for component processors. 2. Warning - only one validation group fails (the item is invalid for either Checkout or RFQ, but not both). 3. Valid - both groups pass successfully.
To improve user experience, all line items in the shopping list are sorted by validation severity: 1. Items with errors are shown first. 2. Then items with warnings. 3. Finally, fully valid items.
Validation Group Resolvers
The validation groups are determined dynamically through validation group resolvers that implement ShoppingListValidationGroupResolverInterface:
ShoppingListToCheckoutValidationGroupResolver- determines if checkout validation group is applicableShoppingListToRequestQuoteValidationGroupResolver- determines if RFQ validation group is applicable
The ShoppingListValidationGroupsProvider aggregates all applicable validation groups from registered resolvers.
Each resolver implements three methods:
getType()- returns a unique type identifier for the resolverisApplicable()- determines if the resolver should be used based on current context (ACL, feature toggles, etc.)getValidationGroupName()- returns the validation group name to use
Service Configuration
Validation group resolvers are registered with priorities to control loading order:
services:
oro_checkout.resolver.shopping_list_to_checkout_validation_group:
class: Oro\Bundle\CheckoutBundle\Resolver\ShoppingListToCheckoutValidationGroupResolver
arguments:
- '@security.authorization_checker'
- '@oro_checkout.condition.is_workflow_start_from_shopping_list_allowed'
tags:
- { name: oro_shopping_list.validation_group, priority: 10 }
oro_rfp.resolver.shopping_list_to_request_quote_validation_group:
class: Oro\Bundle\RFPBundle\Resolver\ShoppingListToRequestQuoteValidationGroupResolver
arguments:
- '@security.authorization_checker'
tags:
- { name: oro_shopping_list.validation_group, priority: 5 }
- { name: oro_featuretogle.feature, feature: rfp_frontend }
Creating a Custom Validation Group Resolver
To add a new validation group resolver:
Create a class implementing
ShoppingListValidationGroupResolverInterface:Register the service with the
oro_shopping_list.validation_grouptag:
services:
acme_demo.resolver.custom_validation_group:
class: Acme\Bundle\DemoBundle\Resolver\CustomValidationGroupResolver
tags:
- { name: oro_shopping_list.validation_group, priority: 15 }
Handling Invalid Line Items
When invalid items are detected, users can choose one of two actions:
Save For Later & Proceed - moves invalid items to “Saved for Later” section (requires
saved_for_laterfeature toggle)Delete All & Proceed - permanently removes invalid items from the shopping list
The action is determined by the action parameter passed to AjaxShoppingListErrorsModalController:
save_for_later- saves items for laterdelete- deletes items
The controller automatically selects the appropriate action based on feature toggle availability.
Saved For Later Implementation
Entity Relationship
The LineItem entity has a savedForLaterList property that establishes a ManyToOne relationship with ShoppingList:
#[ORM\ManyToOne(targetEntity: ShoppingList::class, inversedBy: 'savedForLaterLineItems')]
#[ORM\JoinColumn(name: 'saved_for_later_list_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
protected ?ShoppingList $savedForLaterList = null;
A line item can belong to either:
- The main shopping list (shoppingList property is set, savedForLaterList is null)
- The saved for later section (savedForLaterList property is set, shoppingList is null)
The unique constraint on LineItem includes saved_for_later_list_id to allow the same product to exist in both the main list and saved for later.
Extending Shopping List Validation with a New Constraint
To add a custom validation rule for shopping list line items:
Use an Existing Constraint or Create a Custom One (following Symfony’s standard validation approach).
Register the Constraint in validation.yml and assign validation Groups:
Oro\Bundle\ShoppingListBundle\Entity\LineItem:
constraints:
- Oro\Bundle\InventoryBundle\Validator\Constraints\HasEnoughInventoryLevel:
groups:
- datagrid_line_items_data_for_checkout
Note
The enforce_separate_shopping_list_validations feature toggle must be enabled for validation groups datagrid_line_items_data_for_checkout and datagrid_line_items_data_for_rfq to be applied.
Note
If your constraint represents an error (i.e., it should prevent both Checkout and RFQ), add both validation groups - datagrid_line_items_data_for_checkout and datagrid_line_items_data_for_rfq.