Skip to content

Product Management (Admin)

Flow ID: AD-02 Module(s): eshop, variations, attributes, coupons, badges, video, events, cart_product_customizations Complexity: Very High Last Updated: 2026-06-04

Business Overview

Product management is the largest admin flow in Ecommercen, covering product creation, editing, cloning, soft-deletion, image/media management, and 30+ batch operations. The admin controller (Adv_products_admin) loads 30 models and coordinates complex entity relationships: categories, variations, attributes, tags, product lines, related products, downloads, metadata, promos, feeds, events, videos, bundles, badges, and barcodes.

Products use a master + MUI (multi-language) pattern. The master record holds pricing, stock flags, vendor, VAT, and display settings. Per-language MUI records hold name, descriptions (short, long, extra, ingredients, usage), SEO metadata, slug, and video embed. Product codes (SKUs) are child records that carry stock counts and attribute assignments, with media files attached at the product-code level rather than directly on the product.

The flow serves both the legacy admin panel (CodeIgniter HMVC views) and a modern REST API with full CRUD. Both layers share the same database tables and business rules.

API Reference

REST API (Modern Layer)

MethodEndpointActionAuth
GET/rest/product/productList products (paginated, filterable)JWT
GET/rest/product/product/{id}Get single product by IDJWT
GET/rest/product/product/itemGet single product by filterJWT
POST/rest/product/productCreate productJWT
POST/rest/product/product/{id}Update productJWT
DELETE/rest/product/product/{id}Delete productJWT

All REST routes support locale-prefixed variants (/{locale}/rest/product/product).

Filters: id, shelfcodeId, vatId, price, vendorId, badgeId, vendorCode (partial), active, softDelete, negativeStock, weight, productView, dateChanged, name.{locale} (partial), slug.{locale} (exact), metaTitle.{locale} (partial), barcode (partial).

Sorts: id, price, vatId, vendorId, active, softDelete, negativeStock, weight, hits, dateChanged, productView, name.{locale}, slug.{locale}.

Relations (via ?with=): translations, vendor, vat, shelfcode, badge, productCodes, barcodes, categories, tags, lines, variationValues, videos, events, articles.

Context-sensitive fields: The Resource class returns a subset of fields for frontend (storefront) contexts. Backend-only fields (exposed only when context->isBackend()) include: shelfcodeId, wholesalePrice, acquisitionValue, cartProductCustomizationSchemaId, cartProductCustomizationRangeFrom/To, vendorCode, active, skroutzName, softDelete, hits, productView, hsCode, dateChanged. negativeStock and pointFactor are both exposed to all contexts (public, customer, and backend): negativeStock so headless consumers can determine true availability when stock is zero; pointFactor is assigned unconditionally in the base data array before the isBackend() guard block and is not gated by context. (src/Rest/Product/Resources/Product/Resource.php)

Routes defined in application/config/rest_routes.php.

The Product REST context exposes 36 controllers for all product-adjacent entities:

Endpoint GroupControllerDescription
/rest/product/categoryCategoryProduct categories (CRUD + upload)
/rest/product/vendorVendorVendors/brands (CRUD + upload)
/rest/product/mediaMediaProduct code media (CRUD)
/rest/product/attribute-groupAttributeGroupAttribute groups (CRUD)
/rest/product/attributeAttributeAttribute values (CRUD)
/rest/product/product-code-attributeProductCodeAttributeSKU-attribute assignments (CRUD)
/rest/product/tag-categoryTagCategoryTag categories (CRUD + upload)
/rest/product/tagTagTags (CRUD + upload)
/rest/product/supplierSupplierSuppliers (CRUD + upload)
/rest/product/lineLineProduct lines (CRUD + upload)
/rest/product/promoPromoPromo groups (CRUD + upload)
/rest/product/variationVariationProduct-variation assignments (CRUD)
/rest/product/variation-groupVariationGroupVariation groups (CRUD)
/rest/product/variation-valueVariationValueVariation values (CRUD)
/rest/product/reviewReviewProduct reviews (CRUD)
/rest/product/relatedRelatedRelated product assignments (CRUD)
/rest/product/related-groupRelatedGroupRelated product groups (CRUD)
/rest/product/bundleBundleProduct bundles (CRUD)
/rest/product/bundle-displayBundleDisplayBundle display messages (CRUD)
/rest/product/bundle-criteriaBundleCriteriaBundle criteria (CRUD)
/rest/product/bundle-criteria-referenceBundleCriteriaReferenceBundle criteria references (CRUD)
/rest/product/bundle-builder-referenceBundleBuilderReferenceBundle builder references (CRUD)
/rest/product/bundle-pricingBundlePricingBundle pricing rules (CRUD)
/rest/product/downloadDownloadProduct downloads (CRUD + upload)
/rest/product/badgeBadgeProduct badges (CRUD + upload)
/rest/product/barcodeBarcodeProduct barcodes (CRUD)
/rest/product/product-listProductListProduct lists (CRUD + upload)
/rest/product/product-list-groupProductListGroupProduct list groups (CRUD)
/rest/product/product-list-product-lpProductListProductLpProduct list product assignments (CRUD)
/rest/product/product-metaProductMetaProduct metadata (CRUD)
/rest/product/shelfcodeShelfcodeShelfcodes (CRUD)
/rest/product/wishlistWishlistCustomer wishlists (CRUD)
/rest/product/waiting-listWaitingListStock waiting lists (CRUD)
/rest/product/customization-schemaCustomizationSchemaCart customization schemas (CRUD)
/rest/product/price-trackingPriceTrackingPrice history (read-only)

Admin Routes (Legacy Layer)

RouteController MethodDescription
products_adminindex($offset)Paginated product listing with search/filter
products_admin/addadd()Create product form and handler
products_admin/edit/{id}edit($id)Edit product form and handler
products_admin/cloneProduct/{id}cloneProduct($id)Clone existing product
products_admin/delete/{id}delete($id)Soft-delete product
products_admin/revert_deleted/{id}revert_deleted($id)Restore soft-deleted product
products_admin/batch_actionbatch_action()Multi-product batch operations
products_admin/product_prices_csvproduct_prices_csv()Update prices via CSV upload (barcode, wholesale, price)
products_admin/import_products_from_excelimport_products_from_excel()Import/update products from Excel file
products_admin/new_products_listnew_products_list()ERP import queue listing
products_admin/add_new_product/{id}add_new_product($id)Add product from ERP import queue
products_admin/delete_new_product/{id}delete_new_product($id)Delete ERP import queue entry
products_admin/new_products_batch_actionnew_products_batch_action()Batch delete import queue entries
products_admin/cloneNewProductcloneNewProduct()Clone product from new products queue

Admin menu entry under the PRODUCTS group. Requires roles: AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, or AUTH_ROLE_PRODUCTS.

Code Flow

Create Product (addProduct(), line 266)

  1. Validation: validation() sets form rules -- category required, at least one SKU, VAT required, vendor required, price required, name per language required, slug unique per language.
  2. Upload downloads: uploadProductDownloads() processes file uploads via advuploader.
  3. Master data assembly: getProductMasterPostData() extracts from POST: vat_id, shelfcode_id, price, point_factor, wholesale_price, acquisition_value, discount_persent, weight, vendor_id, product_view, active, product_img_type (badge), skroutz_name, negative_stock, pieces, cart_limit, hs_code, date_changed, customization schema fields, and conditionally special discount fields (special_from, special_to, special_discount_percent, disable_special_discounts).
  4. MUI data assembly: getAddProductDataMuiPost() extracts per language: name, description, short_description, ingredients, usage, extra_description, video, slug (auto-generated from name). SEO fields (meta_title, meta_keywords, meta_description) and URL fields (url, redirect_url) only for ADVISABLE role.
  5. Before hook: beforeAddEntityRecord($data, $dataMui) -- identity by default, overridable in client repos.
  6. Insert: product_model->add_record($data, $dataMui) -- inserts master row, sets old_id = id, inserts MUI rows per language.
  7. Set relationships: setNewEntityRelations($productId) handles category LP, feed LP, images, and setGlobalEntityRelations().
  8. Email notification: If EMAIL.NOTIFICATION_NEW_PRODUCT registry is enabled, sends notification via adv_mailer->inform_new_product().
  9. After hook: afterAddProduct($id).

Update Product (edit(), line 768)

Same as create with these differences:

  • Calls update_record($data, $dataMui, $id) instead of add_record().
  • Calls setUpdateEntityRelations($id) instead of setNewEntityRelations().
  • Slug preservation: existing slug preserved unless user has ADVISABLE role (who can manually set slugs).
  • MUI update uses updateOrInsertMui() (upsert pattern).
  • beforeEditEntityRecord($id, $data, $dataMui) hook before update.
  • afterEdit($id) hook after update.

Clone Product (cloneProduct(), line 711)

  1. Requires enableCloneProduct config flag.
  2. Loads existing product data as template.
  3. On submit, runs the same flow as create (add_record() + setNewEntityRelations()).
  4. Fires afterCloneProduct($id) hook (not afterAddProduct()).
  5. Redirects to edit page of the newly created product.

Soft Delete (delete(), line 900)

  1. Calls product_model->delete($id) which sets soft_delete = 1, active = 0, date_changed = now().
  2. Removes category associations from shop_product_category_lp.
  3. Fires afterDelete($id) hook.

Restore (revert_deleted(), line 926)

Sets soft_delete = 0, active = 0 (product remains inactive until manually re-activated). Fires afterRevertDeleted($id) hook.

Entity Relationships Set on Save

setGlobalEntityRelations() (line 2186) runs for both create and update:

  1. Attributes: manageProductAttributes($productId) -> processProductCodes() -- syncs product codes (SKUs) and their attribute assignments.
  2. Variations: manageProductVariation($productId) -> processProductVariation() -- assigns product to variation group with variation values.
  3. Barcodes: product_model->update_barcodes() -- syncs barcodes for the product.
  4. Tags: manageProductTags($productId) -> product_tag_lp_model->update() -- syncs product-to-tag associations.
  5. Downloads: manageProductDownloads() -- handles uploaded download files, removes deleted ones.
  6. Related products: saveRelatedProducts($productId) -- saves per-group related product associations.
  7. Product lines: manageProductLines($productId) -- syncs line associations via shop_line_products.
  8. Metadata: updateProductMetaDataFromPost($productId) + saveProductMetaDataFiles() -- saves key-value metadata and file-type metadata.

Additional relationships handled by setNewEntityRelations() / setUpdateEntityRelations():

  1. Categories: Updates shop_product_category_lp junction table.
  2. Feeds: product_model->updateFeedsLp() -- syncs shop_product_feed_lp with optional price modifiers.
  3. Images/Media: manageProductImages() -- processes JSON image data via product_media_model->updateMediaRelations().

Batch Actions (batch_action(), line 1068)

30+ batch operations on selected products. Two-phase process: batchActionSelect() prepares the form, batchActionSubmit() executes.

Auto-return batch actions (execute immediately without confirmation form):

ActionEffect
set_active / set_inactiveToggle active flag
disable_special_discountsSet disable_special_discounts = true, clear special discount fields
enable_special_discountsSet disable_special_discounts = false
set_{feed}_xml / unset_{feed}_xmlAdd/remove products from specific XML feed
set_all_xml / unset_all_xmlAdd/remove products from all enabled XML feeds
set_gift_requirementsRedirect to gifts admin with selected product IDs

Form-based batch actions (show confirmation/input form):

ActionEffect
vat_valueChange VAT rate for all selected products
alter_shelfcodeChange shelfcode assignment
put_in_categoryAdd products to a category
put_in_category_and_remove_other_categoriesMove products to a single category
change_products_categoryMove products from source to destination category
assign_products_to_variation_groupAssign to existing variation group
assign_products_to_new_variation_groupCreate new variation group and assign
assign_customization_group_to_productsAssign customization schema with date range
put_in_linesAssign to product line
put_in_promo / remove_from_promoAdd/remove from promo group
put_in_couponAssign to coupon
put_iconSet badge/icon
assign_products_to_videoAssign to video
assign_product_to_product_list / remove_from_product_listAdd/remove from product list
set_tags / delete_tagAdd/remove tags
change_products_vendorChange vendor
set_weightSet weight
set_product_page_viewSet product page layout variant
special_discountsSet special discount with date range (respects per-product disable_special_discounts)
negative_stockToggle negative stock flag
alter_cart_limitSet cart limit
discount_percent-extra-valueApply additive discount percentage
change_price_with_rateChange prices by percentage rate
change_xml_price_with_rateChange XML feed price modifier
add_product_relationsVue component for adding related products
add_eventAssign to event
gift_per_countCreate mass gift rules

CSV / Excel Import

  • product_prices_csv(): Upload CSV with barcode,wholesale_price,price format. Updates prices by matching barcode.
  • import_products_from_excel(): Controlled by ESHOP.IMPORT_PRODUCTS_FROM_EXCEL_ENABLED registry. Supports: update products by barcode, update by product code, insert new products, and bulk image upload via ZIP. Uses PhpSpreadsheet.
  • New products list: ERP-imported products appear in a queue (new_products_model). Admin can review, add to catalogue (add_new_product()), or delete.

Domain Layer

Modern Domain (src/Domains/Product/)

The Product domain is the largest in the system with 35 sub-domains registered in a single container.php.

Core Product

ClassResponsibility
Product\Repository\EntityEntity mapping for shop_product. Properties: id, shelfcode_id, vat_id, point_factor, price, wholesale_price, acquisition_value, discount_persent, show_discount_value, disable_special_discounts, special_discount_percent, special_from/to, available_from/until, vendor_code, vendor_id, product_img_type, active, weight, pieces, cart_limit, soft_delete, csv_updated, negative_stock, hits, cart_product_customization_schema_id, cart_product_customization_range_from/to, product_view, hs_code, date_changed. Implements FilterTranslation.
Product\Repository\MuiEntityEntity mapping for shop_product_mui. Properties: id, product_id, name, description, short_description, ingredients, usage, extra_description, meta_title, meta_keywords, meta_description, url, redirect_url, video, slug, lang.
Product\Repository\RepositoryRead repository. Table: shop_product.
Product\Repository\MuiRepositoryRead repository for translations. Table: shop_product_mui.
Product\Repository\RepositoryConfiguratorDefines 13 relations (see Relations section below).
Product\Repository\MuiRepositoryConfiguratorEmpty relations (MUI has no nested relations).
Product\Repository\WriteRepositoryWrite repository for master table.
Product\Repository\MuiWriteRepositoryWrite repository for translations. insertForEntity(), replaceForEntity(), deleteForEntity().
Product\ServiceRead service. Builds specifications from ListRequest -- filters, sorts (including FilterByTranslation and SortByTranslation for locale-aware queries), pagination, relations. Methods: all(), item(), get().
Product\WriteServiceCRUD service. create() validates via Validator, inserts master + translations transactionally. update() uses excludeNull to only send changed fields. delete() removes translations first, then master.
Product\WriteDataValue object for master write fields. 33 properties mapping camelCase input to snake_case DB columns. OpenAPI schema Product.
Product\MuiWriteDataValue object for translation write fields. 14 properties. OpenAPI schema ProductTranslation. Required: lang, name, slug.
Product\ValidatorValidates create/update data. Currently validates translation lang is required.
Product\ListRequestDefines allowed filters, sorts, and relation sorts for query building.

Product Relations (RepositoryConfigurator)

Relation NameTypeTargetFK / Junction Table
vendorBELONGS_TOVendor\Repositoryvendor_id
vatBELONGS_TOOrder\Vat\Repositoryvat_id
shelfcodeBELONGS_TOShelfcode\Repositoryshelfcode_id
badgeBELONGS_TOBadge\Repositoryproduct_img_type
productCodesONE_TO_MANYProductCode\Repositoryproduct_id
barcodesONE_TO_MANYBarcode\Repositoryproduct_id
translationsONE_TO_MANYMuiRepositoryproduct_id
categoriesMANY_TO_MANYCategory\Repositoryshop_product_category_lp (product_id, category_id)
tagsMANY_TO_MANYTag\Tag\Repositoryshop_product_product_tags (product_id, tag_id)
linesMANY_TO_MANYLine\Repositoryshop_line_products (product_id, line_id)
variationValuesMANY_TO_MANYVariation\Value\Repositoryproduct_variation_values (product_id, variation_id)
videosMANY_TO_MANYCms\Video\Repositoryshop_product_video_lp (product_id, video_id)
eventsMANY_TO_MANYEvent\Event\Repositoryshop_product_event_lp (product_id, event_id)
articlesMANY_TO_MANYCms\Blog\Article\Repositoryproduct_blog (product_id, blog_id)

ProductCode Relations (ProductCode\RepositoryConfigurator)

Relation NameTypeTargetFK
mediaONE_TO_MANYMedia\Repositoryproduct_code_id
productBELONGS_TOProduct\Repositoryproduct_id
attributesONE_TO_MANYProductCodeAttribute\Repositoryproduct_code_id

All Sub-Domains

Sub-DomainTableMUIWriteDescription
Productshop_productshop_product_muiYesCore product entity
Categoryshop_product_categoryshop_product_category_muiYesProduct categories (tree hierarchy)
ProductCodeproduct_codes--No (read-only)SKUs with stock and active flag
ProductCodeAttributeproduct_code_attributes--YesSKU-to-attribute value assignment
Mediaproduct_media--YesImages/videos per product code
Vendorshop_vendorshop_vendor_muiYesBrands/vendors
Attribute\Groupattribute_groupsattribute_groups_muiYesAttribute groups (e.g., "Color", "Size")
Attribute\Attributeattribute_valuesattribute_values_muiYesAttribute values (e.g., "Red", "XL")
Tag\Categoryshop_product_tag_categoriesshop_product_tag_categories_muiYesTag categories (grouping)
Tag\Tagshop_product_tagsshop_product_tags_muiYesTags within categories
Lineshop_lineshop_line_muiYesProduct lines (within vendor)
Promoshop_promoshop_promo_muiYesPromotional groups
Variationproduct_variations--YesProduct-to-variation-group assignment
Variation\Groupvariation_groupsvariation_groups_muiYesVariation groups (e.g., "Color + Size")
Variation\Valuevariation_valuesvariation_values_muiYesVariation values (e.g., "Red / Large")
Badgeshop_product_badgesshop_product_badges_muiYesProduct badges/icons
Barcodeshop_product_barcodes--YesEAN/UPC barcodes per product+code
Relatedshop_related_products--YesRelated product assignments
Related\Grouprelated_groupsrelated_groups_muiYesRelated product group types
ProductListproduct_listproduct_list_muiYesCurated product lists
ProductList\Groupproduct_list_group--YesProduct list groups
ProductList\ProductLpproduct_list_product_lp--YesProduct-to-list assignments
ProductMetaproduct_meta--YesKey-value metadata (type, category, lang)
Shelfcodeshop_product_shelfcodes--YesWarehouse shelfcode grouping
Reviewshop_product_reviews--YesCustomer product reviews
Downloadproduct_downloads--YesDownloadable files per product
Bundleshop_product_bundles--YesProduct bundle definitions
Bundle\Displayshop_product_bundles_display--YesBundle UI messages (per lang)
Bundle\Criteriashop_product_bundles_criteria--YesBundle matching criteria
Bundle\CriteriaReferenceshop_product_bundles_criteria_references--YesCriteria reference items (products/categories)
Bundle\BuilderReferenceshop_product_bundles_builder_references--YesBundle builder reference items
Bundle\Pricingshop_product_bundles_pricing--YesBundle pricing rules
Wishlistshop_wishlist--YesCustomer wishlists
WaitingListshop_waiting_list--YesStock notification waiting list
CustomizationSchemacart_product_customizations_schema--YesCart product customization schemas
PriceTrackingprice_tracking--No (read-only)Price change history

Legacy Model (ecommercen/eshop/models/Adv_product_model.php)

Extends Adv_base_model. Primary model for admin CRUD operations. Defines 23 table name properties for all product-related tables.

Key methods:

MethodDescription
get_master_record($id)Fetch single master record by ID
getProductAdmin($productId)Fetch product with categories, tags, feeds, lines, barcodes, MUI, media -- used by edit form
add_record($data, $dataMui)Insert master + set old_id = id + insert MUI rows per language
update_record($data, $dataMui, $id)Update master + upsert MUI via updateOrInsertMui()
delete($id)Soft delete: soft_delete = 1, active = 0 + remove category LP
undelete_record($id)Restore: soft_delete = 0, active = 0
getProductCodes($productIds)Fetch SKUs with attribute assignments (joined query)
getProductCodeImages($productIds)Fetch media per product code
batchMasterUpdate($ids, $data)Bulk update master records by ID list
updateFeedsLp($productIds, $feedIds, $modifiers)Sync feed assignments with price modifiers
insertFeedsLp($productIds, $feedIds)Batch add products to feeds
removeFeedsLp($productIds, $feedIds)Batch remove products from feeds
update_barcodes($vendorCode, $productId, $codes)Sync barcode records
updatePriceOnBarcode($barcode, $price, $wholesale)CSV import price update by barcode
change_price_with_discount_value($ids, $rate)Batch adjust prices by percentage
change_xml_price_modifier($ids, $rate, $feedIds)Batch adjust XML feed price modifiers
updateProductsVat($ids, $vatId)Batch update VAT
setProductsVendor($ids, $vendorId)Batch update vendor
update_batch_icons($ids, $iconId)Batch update badge/icon

Supporting Models

ModelTable(s)Purpose
Adv_product_media_modelproduct_media, product_codesMedia CRUD, insertMediaRelations(), updateMediaRelations(), per-product-code images
Adv_product_category_modelshop_product_category, shop_product_category_lpCategory tree and product-category junction
Adv_product_tag_lp_modelshop_product_product_tagsProduct-to-tag assignments
Adv_product_tags_modelshop_product_tags, shop_product_tags_muiTag CRUD and batch operations
Adv_related_product_modelshop_related_products, related_groupsRelated product group management
Adv_lines_modelshop_line, shop_line_productsProduct line management
Adv_promo_modelshop_promo, shop_promo_productsPromo group management
AdvProductDownloadsModelproduct_downloadsDownloadable files per product
AdvProductMetaModelproduct_metaKey-value metadata with file uploads
Adv_product_list_modelproduct_list, product_list_product_lpCurated product lists
Adv_product_blog_modelproduct_blogProduct-to-blog article associations
Adv_attribute_values_modelattribute_values, attribute_groupsAttribute value management
Adv_attribute_groups_modelattribute_groups, attribute_groups_muiAttribute group management
Adv_new_products_modelERP import tableNew product queue from ERP
Adv_product_reviews_modelshop_product_reviewsCustomer review management
Adv_shelfcodes_modelshop_product_shelfcodesShelfcode grouping
Adv_vendors_modelshop_vendor, shop_vendor_muiVendor/brand management
Adv_vats_modelshop_product_vatsVAT rate management

Architecture

REST Controller (src/Rest/Product/Controllers/Product.php)

Extends HandlesRestfulActions with HandlesWriteActions trait. Constructor receives ReadService, resource/collection classes, ListRequest class, and WriteService.

Fully annotated with OpenAPI attributes for spec generation. Supports collection (index()), single item by ID (show()), filter-based single item (item()), and full CRUD (store(), update(), destroy()).

REST Resources

ClassSchema NameDescription
ResourceProductResourceFull product entity with context-sensitive backend fields, 13 nested relations
CollectionProductCollectionPaginated product list
MuiResourceProductMuiResourceTranslation fields
MuiCollectionProductMuiCollectionTranslation list

The Resource class conditionally includes backend-only fields when context->isBackend() is true, keeping storefront API responses lean.

DI Container

Domain registration (src/Domains/Product/container.php):

  • All 35 sub-domains registered with Repository, RepositoryConfigurator, Service, and where applicable WriteRepository, MuiRepository, MuiWriteRepository, Validator, WriteService.
  • MUI repositories using NullRelationConfigurator for sub-domains without MUI relations.
  • Product MUI repository uses custom MuiRepositoryConfigurator.

REST registration (src/Rest/Product/container.php):

  • 36 controllers wired with corresponding Service, Resource, Collection, ListRequest, WriteService references.
  • Upload-enabled controllers (Category, Vendor, Tag, TagCategory, Supplier, Line, Promo, Download, Badge, ProductList) additionally receive UploadService.

Legacy Controller (ecommercen/eshop/controllers/Adv_products_admin.php)

Extends Admin_c (2802 lines). Uses ProductVariationsTrait and aiContentGenerationTrait. Loads 30 models in constructor. Provides DownloadsToProduct helper and BlockBuilder for builder block integration.

Default ordering: shop_product.date_changed DESC. Session-persisted pagination offset and limit.

Data Model

shop_product (Master)

ColumnTypeDescription
idint PK AIProduct ID
shelfcode_idint FK nullableWarehouse shelfcode group
vat_idint FKVAT rate reference
point_factorfloat nullableLoyalty points multiplier
pricedecimal nullableRetail price
wholesale_pricedecimal nullableWholesale price
acquisition_valuedecimal nullableCost/acquisition value
discount_persentdecimalDefault discount percentage
show_discount_valuetinyintDisplay discount value on storefront
disable_special_discountstinyintOpt-out from special discount campaigns
special_discount_percentdecimalSpecial/promotional discount percentage
special_fromdatetime nullableSpecial price start date
special_todatetime nullableSpecial price end date
available_fromdatetime nullableProduct availability window start
available_untildatetime nullableProduct availability window end
vendor_codevarchar nullableVendor-assigned product code
vendor_idint FK nullableVendor/brand reference
product_img_typeint FKBadge/icon type (references shop_product_badges.id)
activetinyintProduct visible on storefront
skroutz_namevarchar nullableOverride name for Skroutz feed
weightintWeight in grams
piecesintNumber of pieces per unit
cart_limitintMaximum quantity in cart (0 = unlimited)
soft_deletetinyintSoft-delete flag (1 = trashed)
csv_updatedtinyintUpdated via CSV/Excel import
old_idint nullableLegacy system ID (set to id on creation)
old_imagevarchar nullableLegacy image filename
old_img_proceedtinyintLegacy image migration flag
img_problemtinyintImage processing error flag
date_changeddatetime nullableLast modification timestamp
negative_stocktinyintAllow orders when stock is zero
hitsintStorefront view counter
cart_product_customization_schema_idint FK nullableCart customization schema
cart_product_customization_range_fromdatetime nullableCustomization active period start
cart_product_customization_range_todatetime nullableCustomization active period end
product_viewintProduct page layout variant
hs_codevarchar nullableHarmonized System customs code

shop_product_mui (Translations)

ColumnTypeDescription
idint PK AIMUI record ID
product_idint FKParent product ID
langvarcharLanguage code (el, en, etc.)
namevarcharProduct name
descriptiontextFull HTML description
short_descriptiontextShort description
ingredientstextIngredients (pharmaceutical/cosmetic)
usagetextUsage instructions
extra_descriptiontextAdditional description tab
meta_titlevarcharSEO title
meta_keywordsvarcharSEO keywords
meta_descriptiontextSEO meta description
urlvarcharCustom URL path
redirect_urlvarcharRedirect URL (301)
videovarcharVideo URL or embed code
slugvarcharURL-friendly slug (auto-generated, unique per language)
builder_block_idint nullableFK to builder_blocks.id for page builder content

product_codes (SKUs)

ColumnTypeDescription
idint PK AIProduct code ID
product_idint FKParent product
product_codevarcharSKU string
stockintCurrent stock quantity
soft_deletedtinyintSoft-delete flag
activetinyintActive flag

product_media (Media)

ColumnTypeDescription
idint PK AIMedia record ID
product_code_idint FKParent product code
urivarchar nullableImage path or embed URL
is_maintinyint nullablePrimary image flag
ref_codevarchar nullableExternal reference code
priorityintSort order
typeenumMedia type: image, youtube, tiktok, vimeo, instagram, facebook
metadatatext nullableJSON metadata (alt text, etc.)

product_code_attributes (SKU Attribute Assignments)

ColumnTypeDescription
idint PK AIRecord ID
product_code_idint FKProduct code reference
attribute_value_idint FKAttribute value reference

attribute_groups / attribute_groups_mui (Attribute Groups)

Column (master)TypeDescription
idint PK AIGroup ID
display_typevarcharRendering type (dropdown, radio, etc.)

MUI: name, lang per group.

attribute_values / attribute_values_mui (Attribute Values)

Column (master)TypeDescription
idint PK AIValue ID
attribute_group_idint FKParent group
display_valuevarchar nullableDisplay override (e.g., hex colour)
activetinyintActive flag

MUI: name, lang per value.

shop_product_barcodes (Barcodes)

ColumnTypeDescription
idint PK AIBarcode record ID
product_idint FKProduct reference
product_codevarcharSKU string
barcodevarcharEAN/UPC barcode

shop_vendor / shop_vendor_mui (Vendors)

Column (master)TypeDescription
idint PK AIVendor ID
logovarcharLogo filename
vendor_banner / vendor_medium_banner / vendor_small_bannervarchar nullableBanner images
is_promotinyintFeatured vendor flag
is_exclusivetinyintExclusive vendor flag
orderintSort order
supplier_idint FK nullableParent supplier
slider_idint FK nullableAssociated slider

shop_product_tags / shop_product_tags_mui (Tags)

Column (master)TypeDescription
idint PK AITag ID
tag_cat_idint FKParent tag category
tag_imagevarchar nullableTag image
orderintSort order

shop_product_tag_categories / shop_product_tag_categories_mui (Tag Categories)

Column (master)TypeDescription
idint PK AITag category ID
tag_category_imagevarchar nullableCategory image
tag_category_behaviorintDisplay behaviour
tag_values_behaviorintValue selection behaviour
orderintSort order

shop_line / shop_line_mui (Product Lines)

Column (master)TypeDescription
idint PK AILine ID
vendor_idint FKParent vendor
line_image / line_front_imagevarcharLine images
is_promotinyintFeatured line flag
orderintSort order

shop_promo / shop_promo_mui (Promo Groups)

Column (master)TypeDescription
idint PK AIPromo ID
promo_image / promo_small_bannervarcharPromo images
view_typeintDisplay type
header_color / text_colorvarcharStyling colours
do_followtinyintSEO follow flag

Variation Tables

TableKey ColumnsDescription
variation_groupsid, display_type, step, display_position, priority, force_display_all_valuesVariation groups (e.g., "Colour + Size")
variation_groups_muigroup_id, name, langGroup translations
variation_valuesid, variation_group_id, display_value, priorityValues within a group
variation_values_muivariation_value_id, name, langValue translations
product_variationsid, group_id, product_id, is_master, priorityProduct-to-group assignment

Bundle Tables

TableKey ColumnsDescription
shop_product_bundlesid, internal_name, is_active, weight, bundle_behavior, pricing_strategy, last_modifiedBundle definitions
shop_product_bundles_displayid, bundle_id, message_key, message_value, langBundle display messages
shop_product_bundles_criteriaid, bundle_id, internal_name, discount, weight, reference_typeBundle matching criteria
shop_product_bundles_criteria_referencesid, reference_id, quantity, criterion_idCriteria reference items
shop_product_bundles_builder_referencesid, bundle_id, reference_id, reference_type, quantityBuilder reference items
shop_product_bundles_pricingid, bundle_id, criterion_id, discount, match_quantityBundle pricing rules

Auxiliary Tables

TableKey ColumnsDescription
shop_product_badges / _muiid, name, badgeProduct badge/icon images
shop_supplier / shop_supplier_muiid, logo, orderSuppliers (parent of vendors)
product_list / product_list_muiid, image, small_banner, header_color, text_color, ord, group_idCurated product lists
product_list_groupid, name, slugProduct list groups
product_metaid, product_id, meta_type, category, value, langKey-value metadata
shop_product_shelfcodesid, codeWarehouse shelf codes
shop_product_reviewsid, product_id, customer_id, star_points, nickname, review_date, active, content, langCustomer reviews
product_downloadsid, product_id, file_type, fileDownloadable files
shop_wishlistid, customer_id, product_idCustomer wishlists
shop_waiting_listid, email, lang, product_id, creation_date, waiting_status, email_sent_dateStock notification queue
cart_product_customizations_schemaid, name, is_active, schemaCart customization JSON schemas
price_trackingid, product_id, price_date, pricePrice change audit trail
shop_product_vatsid, ...VAT rate definitions
shop_prices_view(database view)Computed final prices (used for price range filtering)

Junction Tables

TableColumnsPurpose
shop_product_category_lpproduct_id, category_idProduct-to-category (many-to-many). Now has UNIQUE INDEX product_category (product_id, category_id) per migration 20260529120000_dedup_product_category_lp_unique.
shop_product_product_tagsproduct_id, tag_idProduct-to-tag (many-to-many)
shop_line_productsproduct_id, line_idProduct-to-line (many-to-many)
shop_promo_productsproduct_id, promo_idProduct-to-promo (many-to-many)
shop_product_feed_lpproduct_id, feed_id, price_modifierProduct-to-XML-feed with price modifier
product_variation_valuesproduct_id, variation_idProduct-to-variation-value (many-to-many)
shop_product_video_lpproduct_id, video_idProduct-to-video (many-to-many)
shop_product_event_lpproduct_id, event_idProduct-to-event (many-to-many)
product_blogproduct_id, blog_idProduct-to-blog-article (many-to-many)
product_list_product_lpid, ord, product_list_id, product_idProduct-to-product-list with ordering
shop_related_productsid, product_id, related_product_id, group_idRelated product pairs within groups
related_groups / related_groups_muiid, relationRelated product group types

Relevant Migrations

MigrationDescription
20240923141428_new_bridge_tablesCreates bridge/junction tables for product relationships
20241002113650_xml_price_modifierAdds price_modifier to shop_product_feed_lp
20241030144405_update_product_images_to_product_mediaRenames product images to product media, adds type and metadata columns
20241031164345_add_product_block_idAdds builder block ID to product MUI
20250124104616_add_hs_code_to_productsAdds hs_code column to shop_product
20260529120000_dedup_product_category_lp_uniqueDeduplicates shop_product_category_lp and replaces the old non-unique product_category KEY with UNIQUE INDEX product_category (product_id, category_id). down() empty (forward-only). (#279)

Configuration

ConfigLocationDefaultDescription
enableCloneProductapplication/config/main.phpvariesEnables product cloning
enableProductVariationsapplication/config/main.phpvariesEnables variation system
ENABLE_SPECIAL_DISCOUNTS (as OTHER.ENABLE_SPECIAL_DISCOUNTS)RegistryvariesEnables special discount fields on products
POINT_SYSTEM.IS_ENABLEDRegistryvariesEnables point_factor field
ESHOP.POINT_FACTOR_TYPERegistryvariesPoint factor calculation mode
ESHOP.DEFAULT_POINT_FACTORRegistryvariesDefault point multiplier
EMAIL.NOTIFICATION_NEW_PRODUCTRegistryvariesSend email on product creation
ESHOP.IMPORT_PRODUCTS_FROM_EXCEL_ENABLEDRegistryvariesEnable Excel import feature
ESHOP.BLOG_PRODUCT_CATEGORY_ENABLEDRegistryvariesEnable product-blog associations

Images are stored under files/ subdirectories via the storage abstraction layer (local/S3/SFTP). Product media (product_media.uri) references paths within the storage system.

Validation Rules

FieldRuleNotes
category_ids[]requiredAt least one category
productcodesrequiredAt least one SKU
vat_valuegreater_than[0]VAT selection mandatory
vendor_valuerequired, numeric, >0Vendor mandatory
pricerequired, numericBase price mandatory
name_{lang}requiredProduct name per language
slug_{lang}unique (per language/product)Auto-generated from name
SEO fields (url, meta_*)Only if ADVISABLE roleAccess-controlled via allowMetaTags

REST API validation via Validator class currently enforces lang required on each translation entry. Master field validation in WriteData is minimal (relies on DB constraints).

Client Extension Points

The admin controller provides 30+ empty hook methods designed for client repo overrides:

Core Hooks

HookTrigger
beforeAddEntityRecord($data, $dataMui)Before product creation (can modify data)
beforeEditEntityRecord($id, $data, $dataMui)Before product update (can modify data)
afterAddProduct($id)After successful product creation
afterEdit($id)After successful product update
afterCloneProduct($id)After successful product clone
afterDelete($id)After successful soft delete
afterRevertDeleted($id)After successful undelete
afterAddRender()After add form render setup
afterEditRender($productId)After edit form render setup

Batch Action Hooks

HookTrigger
afterBatchActionSubmitAssignProductsToVideo($products, $videoIds)After video assignment
afterBatchActionSubmitAssignCustomizationGroupToProducts($products, $id, $from, $to)After customization schema assignment
afterBatchActionSubmitAssignProductToProductList($products, $prodListId)After product list assignment
afterBatchActionSubmitRemoveFromProductList($products, $prodListId)After product list removal
afterBatchActionSubmitSetTags($products, $tagIds)After tag assignment
afterBatchActionDeleteProductsFromTag($products, $tagId)After tag removal
afterBatchActionSubmitSetWeight($products, $weight)After weight change
afterBatchActionSubmitSetProductPageView($products, $productView)After page view change
afterBatchActionSubmitChangeProductsVendor($products, $vendorId)After vendor change
afterBatchActionSubmitSpecialDiscounts($products, $updateData)After special discount change
afterBatchActionSubmitUpdateCategories($products, $categoryIds)After category assignment
afterBatchActionSubmitRemoveChangeProductCategory($products, $destinationCategoryId)After category replacement
afterBatchActionSubmitChangeProductCategory($products, $sourceCategoryId, $destinationCategoryId)After category move
afterBatchActionSubmitUpdateLine($products, $lineId)After line assignment
afterBatchActionSubmitUpdatePromo($products, $promoId)After promo assignment
afterBatchActionSubmitRemovePromo($products, $promoId)After promo removal
afterBatchActionSubmitUpdateCoupon($products, $couponId)After coupon assignment
afterBatchActionSubmitUpdateIcon($products, $iconId)After badge change
afterBatchActionSubmitGiftPlusOne($products, $giftData)After gift rule creation
afterBatchActionSubmitVatValue($products, $vatValue)After VAT change
afterBatchActionSubmitAlterShelfCode($products, $data)After shelfcode change
afterBatchActionSubmitChangeRow($products, $data)Generic batch update fallback
afterDeleteNewProduct($id)After ERP import queue deletion
afterNewProductsBatchActionDelete($productIds)After batch queue deletion
afterSetImportRecordRead($id, $addedProductId)After ERP product adopted

Client repos override the controller in application/controllers/ and implement these hooks for custom logic (e.g., Solr reindex, ERP sync, external API calls, cache invalidation).

The REST layer can be extended via Custom\Domains\Product\ and Custom\Rest\Product\ with DI alias overrides in custom/Domains/container.php and custom/Rest/container.php.

Business Rules

  1. Slug auto-generated -- From product name via createSlug(), unique per language. Only ADVISABLE role can manually edit slugs. (ecommercen/eshop/controllers/Adv_products_admin.php, getAddProductDataMuiPost())
  2. SEO fields restricted -- meta_title, meta_keywords, meta_description, url, redirect_url editable only by users with AUTH_ROLE_ADVISABLE. (ecommercen/eshop/controllers/Adv_products_admin.php, allowMetaTags check)
  3. Soft delete, not hard delete -- soft_delete = 1, active = 0. Category LP removed. Reversible via revert_deleted() (restores as inactive). (ecommercen/eshop/models/Adv_product_model.php, delete() / undelete_record())
  4. Media at product-code level -- Images/videos attached to product_codes via product_media, not directly to shop_product. Supports 6 types: image, youtube, tiktok, vimeo, instagram, facebook. (ecommercen/eshop/models/Adv_product_media_model.php)
  5. JSON media upload -- Frontend sends JSON image data, processed via product_media_model->updateMediaRelations(). Media includes metadata, priority, and main flag. Media files stored via SY-28 Storage Abstraction.
  6. Email notification on create -- If EMAIL.NOTIFICATION_NEW_PRODUCT registry enabled, sends notification email via SY-24 Email Dispatch. (ecommercen/eshop/controllers/Adv_products_admin.php, line ~293)
  7. Special discounts optional -- Controlled by OTHER.ENABLE_SPECIAL_DISCOUNTS registry. Per-product opt-out via disable_special_discounts. Date-ranged (special_from / special_to). (ecommercen/eshop/controllers/Adv_products_admin.php, getProductMasterPostData())
  8. Point system optional -- POINT_SYSTEM.IS_ENABLED controls point_factor field visibility and processing. (ecommercen/eshop/controllers/Adv_products_admin.php, getProductMasterPostData())
  9. Stock via product codes -- Stock tracked per SKU (product_codes.stock), not on master product. negative_stock flag allows ordering at zero stock. (ecommercen/eshop/models/Adv_product_model.php, getProductCodes())
  10. Availability window -- available_from / available_until restrict product visibility on storefront. (shop_product columns)
  11. Cart limit -- cart_limit restricts maximum quantity per cart (0 = unlimited). (shop_product.cart_limit)
  12. Price tracking -- Price changes recorded in price_tracking table for audit/compliance. See SY-04 Price Tracking. (src/Domains/Product/PriceTracking/WriteService.php)
  13. Customization schemas -- Products can reference a JSON schema for cart customization with active date range. (cart_product_customizations_schema table)
  14. HS codes -- Harmonized System customs codes for international shipping compliance. (shop_product.hs_code)
  15. Clone preserves data -- Product cloning copies all data and relationships but creates a new entity. (ecommercen/eshop/controllers/Adv_products_admin.php, cloneProduct())
  16. Batch respects per-product flags -- Special discount batch operations skip products with disable_special_discounts = true. (ecommercen/eshop/controllers/Adv_products_admin.php, batchActionSubmit())
  17. old_id self-reference -- On creation, old_id is set to the new id for legacy system compatibility. (ecommercen/eshop/models/Adv_base_model.php, add_record())
  18. REST context-sensitive output -- Backend-only fields (wholesalePrice, acquisitionValue, etc.) excluded from storefront API responses. negativeStock and pointFactor are exceptions: both are exposed to all contexts (public, customer, and backend). negativeStock allows headless consumers to determine true availability when stock is zero; pointFactor is assigned unconditionally in the base data array before the isBackend() guard and is not context-gated. (src/Rest/Product/Resources/Product/Resource.php, context->isBackend())
  19. Variation custom translation loading -- Variation\Repository\Repository uses custom OneToMany loader that resolves translations via group_id instead of id. (src/Domains/Product/Variation/Repository/Repository.php)
  20. Image storage via abstraction layer -- Product media files are stored and retrieved through the storage abstraction layer (local/S3/SFTP). See SY-28 Storage Abstraction and the Storage Guide.
  21. Product-category links are unique -- (product_id, category_id) pairs in shop_product_category_lp are DB-enforced unique since #279 (4.103). All write paths are idempotent through linkProductCategory() in Adv_product_category_model. (database/migrations/20260529120000_dedup_product_category_lp_unique)

Known Issues & Security Gaps

No known issues at the time of writing.

Customer Flows

Admin Flows

Integration Flows

System Flows

Wiki Guides: Storage Guide | REST API Modules Guide | Migrations Guide