Appearance
Product Variations Management (Admin)
Flow ID: AD-16 Module(s): variations Complexity: High Last Updated: 2026-04-04
Business Context
Product variations allow grouping products by shared dimensions (e.g., Size, Color, Material) so customers can switch between related products on the storefront. The admin panel provides three management areas: variation group definitions (the dimension types), variation values (the options within each dimension), and product variation groups (which products belong together and which is the master). The entire feature is gated by the enableProductVariations config flag (default: false). When disabled, variation menu items are hidden and product-level variation UI is suppressed.
Architecture: Entirely legacy HMVC -- no modern Domain/REST layer exists for admin variations. The HMVC module lives in ecommercen/variations/ with controllers, models, DTOs, and helpers. A separate API controller (AdvApiVariationsController) serves the Vue frontend for product-level variation editing. The admin Vue app (AdminVariationsPage) manages groups/values, while AdminVariationProductsPage handles per-product variation assignment within the product edit form.
Database Schema
6 Tables
| Table | Purpose | Key Columns |
|---|---|---|
variation_groups | Dimension definitions (Size, Color, etc.) | id, display_type, step, display_position, priority, force_display_all_values |
variation_groups_mui | MUI names per language | variation_group_id, name, lang |
variation_values | Options within a group | id, variation_group_id, display_value, priority |
variation_values_mui | MUI names per language | variation_value_id, name, lang |
product_variations | Product-to-group assignments | id, group_id, product_id, is_master, priority |
product_variations_mui | MUI names for product variation groups | group_id, name, lang |
product_variation_values | Product-to-variation-value links | product_id, variation_id (unique composite) |
Key relationships: variation_values.variation_group_id -> variation_groups.id; product_variations.group_id is an auto-incremented group identifier (not an FK to variation_groups); product_variation_values.variation_id -> variation_values.id.
Important distinction: variation_groups defines dimension types (e.g., "Size"), while product_variations.group_id defines product groupings (e.g., "Nike Air Max Family"). These are separate concepts with separate MUI tables.
Feature Gate
Config: $config['enableProductVariations'] = false; in application/config/app.php
When disabled:
- Admin menu hides the "variations" route via
AdminMenu::parseProductVariationMenu() AdvVariationsProductsAdminandAdvVariationsPageAdminredirect toadvisable- Product edit form hides the
<admin-variation-products-page>component - Batch update actions
assign_products_to_variation_groupandassign_products_to_new_variation_groupare suppressed
Authorization
Base class: AdvVariationsAdminBase extends Admin_c, requires roles: AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, or AUTH_ROLE_PRODUCTS.
All four admin controllers inherit this base and share the same role check. The API controller (AdvApiVariationsController) extends Front_c (no admin auth -- it serves the product edit page's Vue component which is already behind admin auth).
Area 1: Variation Groups (Dimension Types)
Controller: ecommercen/variations/controllers/AdvVariationsAdmin.phpPage controller: ecommercen/variations/controllers/AdvVariationsPageAdmin.phpModel: ecommercen/variations/models/AdvVariationGroupsModel.phpView: application/views/admin/variations/vue.php (Vue SPA mount point) Vue component: assets/admin/js/variations/AdminVariationsPage.vueRoute: /variations/variations
Display Types
Defined in AdvVariationsHelper::getVariationTypes():
| Key | Description | Frontend rendering |
|---|---|---|
text | Plain text labels | Text buttons/pills |
hex-color | Color swatches | VariationHexSelector.vue renders color circles |
image | Image thumbnails | VariationImageSelector.vue renders image pills |
text-range-per-value | Numeric ranges by value | Groups values into ranges based on display_value and step |
text-range-per-count | Numeric ranges by count | Groups values into ranges of step items each |
Display Positions
Defined in AdvVariationsHelper::getVariationGroupDisplayPositionTypes():
| Value | Meaning |
|---|---|
| 0 | ALL -- show on product page and in category filters |
| 1 | PRODUCT only -- show on product page, hide from filters |
| 2 | FILTER only -- show in category filters, hide from product page |
| 3 | NONE -- hidden everywhere (data-only) |
CRUD Operations
Add (POST /variations/variations/add):
- Validates:
display_type(required),step(numeric),display_position,priority,force_display_all_values - Validates MUI:
name_{lang}required for each admin language AdvVariationGroupsModel::add()inserts intovariation_groups+variation_groups_mui- Returns JSON
{id}on success
Edit (POST /variations/variations/edit):
- Same validation + requires
id AdvVariationGroupsModel::update()updates master row + upserts MUI viaupdateOrInsertMui()- Returns JSON
{id}on success
Order (POST /variations/variations/order):
- Accepts JSON array of
{id, priority}pairs AdvVariationGroupsModel::order()batch-updatesprioritycolumn
Delete: Handled by AdvVariationGroupsModel::delete() -- checks canDelete() first: a group cannot be deleted if it has child variation values. Deletion cascades to variation_groups_mui within a transaction.
Page Data Endpoint
GET /variations/variations/getPageData returns all data needed by the Vue SPA:
variationGroups-- allvariation_groupsrowsvariationGroupsMui-- allvariation_groups_muirowsvariationValues-- all values joined with group infovariationValuesMui-- all value MUI for current languagevariationTypes-- display type optionsvariationGroupDisplayPositionTypes-- position type optionsconfigs,userRoles,configLanguages,translations
Area 2: Variation Values (Options)
Controller: ecommercen/variations/controllers/AdvVariationsValuesAdmin.phpModel: ecommercen/variations/models/AdvVariationValuesModel.phpRoute: /variations/values/{action}
Values are managed inline within the groups Vue page. Clicking a group opens its values list (VariationValuesList.vue), then values can be added/edited via VariationValuesForm.vue.
CRUD Operations
Add (POST /variations/values/add):
- Validates:
display_value(trim), MUIname_{lang}(required per language) - Validates
variation_group_idexists viaAdvVariationGroupsModel::getMain() AdvVariationValuesModel::add()inserts intovariation_values+variation_values_mui
Edit (POST /variations/values/edit):
- Same validation + requires
id AdvVariationValuesModel::update()updates master + upserts MUI
Order (POST /variations/values/order):
- Accepts JSON array of
{variation_value_id, priority}pairs - Batch-updates
prioritycolumn
Delete (POST /variations/values/delete):
- Accepts variation value ID as JSON
canDelete()check: if the value is referenced inproduct_variation_values, deletion is blocked and the referencing product IDs are returned- On success: deletes from
variation_values_mui+variation_valuesin a transaction
Batch Actions on Values
Two batch actions available via BatchAction.vue when values are selected:
Move to Another Group (BatchActionMoveValueToAnotherGroup.vue):
- Calls
api/variations/batchActionMoveVariationValues - Model:
AdvVariationModel::moveVariationValuesToAnotherGroup()-- raw SQL UPDATE with JOIN to reassignvariation_values.variation_group_id - Confirmation via SweetAlert2; page reloads after success
Mass Delete Values (BatchActionMassDeleteValues.vue):
- Calls
api/variations/batchActionMassDeleteValues - Model:
AdvVariationModel::massDeleteVariationValues()-- deletes from bothvariation_valuesandvariation_values_mui - No referential integrity check (unlike single-value delete) -- caution: can orphan
product_variation_valuesrows
Area 3: Product Variation Groups
Controller: ecommercen/variations/controllers/AdvVariationsProductsAdmin.phpModel: ecommercen/variations/models/AdvVariationModel.phpViews: application/views/admin/variations/list.php, group.php, group_create.phpVue entry: assets/admin/js/variations/store/variations-page.jsRoutes: /variations/products, /variations/products/create, /variations/products/edit/{groupId}, /variations/products/delete/{groupId}
Product variation groups link multiple products together (e.g., same shoe in different colors). Each group has a MUI name and a designated master product.
List Page
AdvVariationsProductsAdmin::index():
- Paginated list (20 per page) from
product_variations_muifor current language - Displays group ID, name, edit/delete actions
- Server-rendered table (no Vue)
Create
AdvVariationsProductsAdmin::create():
- Renders
group_create.php-> mountsAdminProductVariationGroupVue component - Vue receives JSON state:
configLanguages,allVariation(all variation values with groups),allAttributes,isNewGroup=true - On submit: parses
variationProducts(product IDs with serialized variation selections),variationGroupName(MUI),masterProductId AdvVariationModel::createVariationProductGroup():- Begins transaction
- Auto-generates new
group_idviacreateNewGroupId()(SELECT MAX + 1) - For each product: clears any existing variation data, inserts into
product_variationsandproduct_variation_values - Inserts MUI names into
product_variations_mui - Commits or rolls back
Edit
AdvVariationsProductsAdmin::edit($groupId):
- Loads existing group data via
getVariationProductsByGroupId()-> returnsVariationGroupProductDTO[] - Renders
group.php-> mountsAdminProductVariationGroupVue component with existing data - On submit:
processAllVariationGroupProducts():- For each product in POST: delete-then-reinsert pattern (
processProductVariation) - Removes products no longer in the submitted list via
removeOtherProductIds() - Updates MUI names separately
- For each product in POST: delete-then-reinsert pattern (
Delete
AdvVariationsProductsAdmin::delete($groupId):
deleteProductVariationGroup()removes allproduct_variationsandproduct_variations_muirows for the group in a transaction- Note: does NOT clean up
product_variation_valuesfor affected products
Master Product Management
Each product group designates one product as master (is_master = 1). The master product:
- Appears as the primary product in category listings (non-master variants are hidden via
ProductVariationsListingTrait) - Serves as the canonical URL/display product
updateGroupMaster(): Transactional unset-all then set-one pattern. setAnotherProductAsMaster(): When removing the current master, auto-promotes the next product by priority.
Area 4: Per-Product Variation Assignment (Product Edit Page)
Vue component: assets/admin/js/products/variations/AdminVariationProductsPage.vueAPI controller: ecommercen/api/controllers/AdvApiVariationsController.phpApplication wrapper: application/modules/api/controllers/Api_variations.phpRoutes: /api/variations/{action}
This component appears as a tab in the product edit form (when enableProductVariations is true). It allows:
- Selecting which variation groups apply to this product (multiselect dropdown)
- Choosing specific variation values per group (via
VariationGroupQuickActions) - Viewing/managing the product's variation group (modal with full group management)
Internal API Endpoints
| Method | Action | Purpose |
|---|---|---|
| POST | getVariationGroupProductsApi | Get all products in a product's variation group |
| POST | updateProductVariation | Save variation value selections for a product |
| POST | updateVariationGroupMaster | Change master product in a group |
| POST | getAllVariation | Get all variation values with group info |
| POST | getVariationGroupMui | Get MUI names for a group |
| POST | getVariationGroupName | Get group name by product ID |
| POST | getVariationGroupId | Get group ID by product ID |
| POST | getProductVariations | Get variation data for multiple product IDs |
| POST | deleteProductFromVariationGroup | Remove product from its group |
| POST | addVariationProductToGroup | Add product to existing group, or create new group |
| POST | batchActionMoveVariationValues | Move variation values between groups |
| POST | changeVariationGroupProductsPriority | Reorder products within a group |
| POST | batchActionMassDeleteValues | Bulk delete variation values |
| POST | updateProductVariationGroupNameMui | Update group MUI names |
| POST | updateProductStock | Update stock for a product code (SKU) |
| POST | getVariationByVariationValueId | Lookup variation details by value ID |
Validation Rules
All endpoints validate via CI form_validation. Responses follow sendOutput()/sendError() pattern:
- Success:
{data}with 200 - Validation error:
{errors}with 400 - Business rule violation:
{error}with 422 (e.g., product already in another group, empty variation group names)
Area 5: Batch Product Assignment (Product Listing)
Controller: ecommercen/eshop/controllers/Adv_products_admin.php (batch update actions) View: application/views/admin/products/batch_update.php
Two batch actions on the product listing page:
Assign to Existing Group
Action: assign_products_to_variation_group
- Admin selects products and chooses an existing variation group from dropdown
batchActionDropdown()provides the group combo (name + group_id fromproduct_variations_mui)- Pre-check: if any selected product already belongs to a variation group, the entire batch is rejected with error message
batchAssignProductsToVariationGroup(): insert_batch intoproduct_variations(all as non-master)
Assign to New Group
Action: assign_products_to_new_variation_group
- Admin selects products, provides MUI group names per language
- If any products are already in groups,
manageExistingProductsInGroups()handles removal:- If product is master and group has >1 member: auto-promotes another member, then removes
- If product is the only member: deletes entire group
- If product is non-master: just removes from group
batchAssignProductsToNewVariationGroup(): creates new group_id, inserts all products (first product becomes master), inserts MUI names
DTOs
Three DTOs in ecommercen/variations/DTO/ provide typed data transfer:
VariationGroupProductDTO: Represents a product within a variation group with its assigned variation values. Properties: id, product_id, group_id, variation_value_id, variation_group_id, variation_value_name, variation_group_name, is_master, priority.
VariationProductDTO: Represents a variation group with all its values for a product. Properties: variationGroupName, variationGroupId, variationDisplayType, variationGroupStep, variationGroupDisplayPosition, variationGroupOrder, variationGroupForceDisplayAllValues, variations[] (array of VariationValueDTO), variationProductVariationId, variationProductVariationGroupId.
VariationValueDTO: Represents a single variation value. Properties: variationId, variationName, variationDisplayValue, variationOrder.
Frontend Architecture (Admin Vue)
Variation Groups/Values Page
Entry point: assets/admin/js/variations/store/variations-page.js mounts the Vue app.
Component tree:
AdminVariationsPage
├── VariationGroupList (list all groups, drag-to-reorder, click to open values)
├── VariationGroupForm (add/edit group with display type, step, position, MUI names)
├── VariationValuesList (list values for selected group, drag-to-reorder, batch actions)
│ └── BatchAction
│ ├── BatchActionMoveValueToAnotherGroup
│ └── BatchActionMassDeleteValues
└── VariationValuesForm (add/edit value with display_value and MUI names)Product Variation Group Page
Entry point: same variations-page.js, also mounts AdminProductVariationGroup.
Component tree:
AdminProductVariationGroup
├── AdminProductVariationGroupSearch (product search to add)
├── AdminProductVariationGroupList (product cards in group)
│ └── AdminProductVariationGroupCard (individual product with variation selectors)
│ ├── AdminProductVariationGroupVariations (variation value dropdowns)
│ ├── AdminProductVariationGroupMaster (master toggle)
│ └── AdminProductVariationGroupActions (remove, stock edit, priority)
└── AdminProductVariationGroupInput (hidden inputs for form submission)Per-Product Variation (Product Edit Page)
Component: AdminVariationProductsPage.vue (mounted in product edit template). Uses Vuex stores: product (product store) and variation (variation store via variationStore.js).
Storefront Integration
Listing Behavior
ProductVariationsListingTrait (used by category/vendor/search controllers):
processProductsQueryForVariationMaster(): Filters product listings to show only master products; non-master variants are hiddenfillVariationMasterInfo(): Adds group name and member count to master product displayrenderListingVariationGroupCombosJsonData(): Provides variation group combos for listing-level variation selectors
Filter Behavior
ProductVariationsListingTrait::initializeVariationFiltersJson():
- Query string parameter per language:
el->προιοντα-με,en->products-with - Registry setting
VARIATIONS.MERGE_DUPLICATE_VALUES_BY_NAMEcontrols whether values with identical names across groups are merged in filters - Active filters validated against all variation records; invalid combinations trigger 404
Product Page Behavior
ProductVariationsTrait::getVariationGroupProducts():
- Retrieves all products in the same variation group
- Merges live product data (prices, stock, images, URLs) via
mergeProductVariationsWithLiveData() - Returns unique products with variation metadata for the Vue variation selector
Registry Settings
| Group | Key | Purpose |
|---|---|---|
VARIATIONS | MERGE_DUPLICATE_VALUES_BY_NAME | When true, variation values with the same name across groups are merged in filter display |
Key Files Reference
| Category | Path |
|---|---|
| Controllers | ecommercen/variations/controllers/AdvVariationsAdminBase.php |
ecommercen/variations/controllers/AdvVariationsAdmin.php | |
ecommercen/variations/controllers/AdvVariationsValuesAdmin.php | |
ecommercen/variations/controllers/AdvVariationsProductsAdmin.php | |
ecommercen/variations/controllers/AdvVariationsPageAdmin.php | |
| API Controller | ecommercen/api/controllers/AdvApiVariationsController.php |
| App Wrapper | application/modules/api/controllers/Api_variations.php |
| Models | ecommercen/variations/models/AdvVariationGroupsModel.php |
ecommercen/variations/models/AdvVariationValuesModel.php | |
ecommercen/variations/models/AdvVariationModel.php | |
| DTOs | ecommercen/variations/DTO/VariationGroupProductDTO.php |
ecommercen/variations/DTO/VariationProductDTO.php | |
ecommercen/variations/DTO/VariationValueDTO.php | |
| Helper | ecommercen/variations/helpers/AdvVariationsHelper.php |
| Traits | ecommercen/eshop/traits/ProductVariationsTrait.php |
ecommercen/eshop/traits/ProductVariationsListingTrait.php | |
| Views | application/views/admin/variations/vue.php |
application/views/admin/variations/list.php | |
application/views/admin/variations/group.php | |
application/views/admin/variations/group_create.php | |
| Vue (Groups/Values) | assets/admin/js/variations/AdminVariationsPage.vue |
assets/admin/js/variations/VariationGroupList.vue | |
assets/admin/js/variations/VariationGroupForm.vue | |
assets/admin/js/variations/VariationValuesList.vue | |
assets/admin/js/variations/VariationValuesForm.vue | |
| Vue (Batch Actions) | assets/admin/js/variations/batchActions/BatchAction.vue |
assets/admin/js/variations/batchActions/value/BatchActionMoveValueToAnotherGroup.vue | |
assets/admin/js/variations/batchActions/value/BatchActionMassDeleteValues.vue | |
| Vue (Product Groups) | assets/admin/js/variations/productGroups/AdminProductVariationGroup.vue |
assets/admin/js/variations/productGroups/AdminProductVariationGroupList.vue | |
assets/admin/js/variations/productGroups/AdminProductVariationGroupCard.vue | |
assets/admin/js/variations/productGroups/AdminProductVariationGroupSearch.vue | |
assets/admin/js/variations/productGroups/AdminProductVariationGroupMaster.vue | |
assets/admin/js/variations/productGroups/AdminProductVariationGroupActions.vue | |
assets/admin/js/variations/productGroups/AdminProductVariationGroupVariations.vue | |
assets/admin/js/variations/productGroups/AdminProductVariationGroupInput.vue | |
| Vue (Per-Product) | assets/admin/js/products/variations/AdminVariationProductsPage.vue |
assets/admin/js/products/variations/ProductVariationInput.vue | |
assets/admin/js/products/variations/VariationGroupQuickActions.vue | |
| Vuex Store | assets/admin/js/variationStore.js |
assets/admin/js/variations/store/state.js | |
assets/admin/js/variations/store/actions.js | |
assets/admin/js/variations/store/mutations.js | |
assets/admin/js/variations/store/getters.js | |
assets/admin/js/variations/store/variations-page.js | |
| Routes | application/config/routes.php (lines 351-376, 573-576) |
| Config | application/config/app.php (enableProductVariations) |
| Admin Menu | application/libraries/AdminMenu.php (parseProductVariationMenu) |
Related Flows
- CF-25 Product Variations -- frontend variation selectors, filtering, SKU-level stock
- AD-02 Product Management -- product edit form integration
- AD-15 Attributes & Tags -- complementary product dimension system (attributes at SKU level vs. variations at product level)
- AD-13 Settings --
enableProductVariationsconfig andVARIATIONS.MERGE_DUPLICATE_VALUES_BY_NAMEregistry - SY-23 MUI Translation Pattern --
variation_groups_mui,variation_values_mui, andproduct_variations_muicompanion tables store per-language names for each variation dimension