Appearance
Product Lines Management (Admin)
Flow ID: AD-17 Module(s): eshop Complexity: Medium Last Updated: 2026-04-04
Business Context
Product lines (also called "series") are merchandising groupings that sit beneath a vendor (brand). Each vendor can have multiple lines, and each line can contain multiple products. Lines provide a curated, brand-specific product collection with dedicated landing pages accessible at /vendors/{vendor_slug}/{line_slug}. They support promotional flagging, custom imagery, rich-text descriptions, per-language SEO metadata, and drag-and-drop ordering.
Lines are distinct from categories and product lists: a line is always scoped to exactly one vendor, while categories and product lists are vendor-agnostic. Products can belong to multiple lines simultaneously (many-to-many via shop_line_products).
Database Schema
Tables: shop_line, shop_line_mui, shop_line_products
| Table | Column | Type | Notes |
|---|---|---|---|
shop_line | id | INT PK | Auto-increment |
vendor_id | INT NOT NULL | FK to shop_vendor.id | |
line_image | VARCHAR | Detail/banner image, stored in files/lines/ | |
line_front_image | VARCHAR | Listing thumbnail image, stored in files/lines/ | |
is_promo | TINYINT | Promotional flag (1=yes); promo lines sort first in storefront | |
order | INT | Drag-and-drop sort position | |
shop_line_mui | id | INT PK | Auto-increment |
line_id | INT | FK to shop_line.id | |
lang | VARCHAR | Language code (e.g. el, en) | |
name | VARCHAR | Line name (required) | |
slug | VARCHAR | URL-safe slug, auto-generated from name | |
description | TEXT | Rich-text description (TinyMCE) | |
meta_title | VARCHAR | SEO title (restricted to ADVISABLE role on create) | |
meta_keywords | VARCHAR | SEO keywords (restricted to ADVISABLE role on create) | |
meta_description | VARCHAR | SEO description (restricted to ADVISABLE role on create) | |
shop_line_products | id | INT PK | Auto-increment |
line_id | INT | FK to shop_line.id | |
product_id | INT | FK to shop_product.id | |
order | INT | Sort position within line |
Architecture
The lines feature spans both the legacy CI layer and the modern domain/REST layer.
Legacy Layer (admin UI):
- Controller:
ecommercen/eshop/controllers/Adv_lines_admin.php(384 lines) - Model:
ecommercen/eshop/models/Adv_lines_model.php(416 lines) - Views:
application/views/admin/lines/--list.php,create.php,update.php,products.php
Modern Domain Layer (src/Domains/Product/Line/):
- Entity:
Repository/Entity.php-- extendsBaseEntity, implementsFilterTranslation - MuiEntity:
Repository/MuiEntity.php-- translation fields - Repository:
Repository/Repository.php-- tableshop_line, Specification pattern - MuiRepository:
Repository/MuiRepository.php-- tableshop_line_mui - RepositoryConfigurator:
Repository/RepositoryConfigurator.php-- definestranslations(ONE_TO_MANY),vendor(BELONGS_TO),products(MANY_TO_MANY viashop_line_products) - WriteRepository:
Repository/WriteRepository.php-- insert/update/delete onshop_line - MuiWriteRepository:
Repository/MuiWriteRepository.php--insertForEntity(),replaceForEntity(),deleteForEntity()onshop_line_mui - Service:
Service.php-- read service withall(),item(),get()using Specification pattern - WriteService:
WriteService.php--create(),update(),delete()with transactional MUI handling - WriteData:
WriteData.php-- DTO for line master fields (vendorId,lineImage,lineFrontImage,isPromo,order) - MuiWriteData:
MuiWriteData.php-- DTO for translation fields (lang,metaTitle,metaKeywords,metaDescription,slug,name,description) - Validator:
Validator.php--validateForCreate(),validateForUpdate(),validateTranslations()(requires non-emptylang) - ListRequest:
ListRequest.php-- allowed filters:id,priority,isPromo,vendorId,name.{locale}(partial),slug.{locale}(exact); sorts:id,priority,name.{locale}
REST Layer (src/Rest/Product/Controllers/Line.php):
- Extends
HandlesRestfulActionswithHandlesUploadActionstrait - Full CRUD:
index(),show($id),item(),store(),update($id),destroy($id) - File upload fields:
line_imageandline_front_imageboth stored infiles/lines/ - Resources:
Resource.php,Collection.php,MuiResource.php,MuiCollection.php
DI Registration:
- Domain services registered in
src/Domains/Product/container.php(Line section: Repository, RepositoryConfigurator, MuiRepository, Service, WriteRepository, MuiWriteRepository, Validator, WriteService) - REST controller registered in
src/Rest/Product/container.phpwith explicit$service,$resourceClass,$collectionClass,$listRequestClass,$writeService,$uploadServicearguments - Both containers loaded via
application/config/container/modules.php
Admin UI Flow
Authorization: AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, AUTH_ROLE_PRODUCTS
Admin Menu: Products section, route lines_admin, icon from admin.menu.shop.products.lines.icon
Line Listing (index)
- Loads all lines joined with vendor MUI names, ordered by
shop_line.order ASC - Displays table: ID, thumbnail image (resized to w96), line name, vendor name
- Vendor filter dropdown at top of listing
- Drag-and-drop sortable rows -- AJAX POST to
lines_admin/update_orderupdatesshop_line.orderfor each row - Action buttons per row: live preview (opens storefront URL
/vendors/{vendor_slug}/{line_slug}), edit, manage products, delete (with confirmation dialog)
Create Line (add)
- Multipart form with language tabs (via
mixed_crud_lang_tabspartial) - General Information section:
- Name (per language, required) -- auto-generates slug on insert via
createSlug() - Vendor (required, dropdown from
vendors_model->vendorsCombo(), validatedgreater_than[0]) - Is Promo (toggle switch checkbox)
- Line Image (file upload to
files/lines/) - Line Front Image / listing thumbnail (file upload to
files/lines/)
- Name (per language, required) -- auto-generates slug on insert via
- SEO section (collapsible, restricted to users with
metatagsaccess):- Meta Title, Meta Description, Meta Keywords (per language)
- Series Details section:
- Description (per language, TinyMCE rich-text editor)
- On submit: validates form, uploads files via
advuploader->uploadFilesSameFolder(), inserts master record + MUI records vialines_model->add_record()
Edit Line (edit/$id)
- Same form layout as create, pre-populated from
lines_model->getAdminRecord($id) - Image fields show current image link + delete checkbox (
del_image,del_image2) to clear - Slug editing (available to users with
urlFieldsaccess): shown as readonly field with a "change" checkbox per language that unlocks editing. ADVISABLE role can force slug regeneration viachange_slugcheckbox. - Slug auto-generation: if no slug exists and no manual slug provided, regenerated from name. Existing slugs preserved by default.
- On submit: updates master record + upserts MUI records via
lines_model->update_record()usingupdateOrInsertMui()pattern
Delete Line (delete/$id)
- Deletes product associations from
shop_line_products - Deletes MUI records from
shop_line_mui - Deletes master record from
shop_line - No deletion guard -- line can be deleted even with products assigned (associations are cleaned up)
Manage Line Products (products/$lineId)
- Lists products assigned to the line, joined with product MUI names and thumbnail images
- Ordered by
shop_line_products.order ASC - Drag-and-drop sortable -- AJAX POST to
lines_admin/update_products_order - Select-all checkbox for bulk operations
- Batch remove: form POSTs selected product IDs to
batchRemoveFromLine/$lineId-- removes entries fromshop_line_products - Per-product actions: edit product (redirects to
products_admin/edit/{product_id}), delete association (single product)
Product-to-Line Assignment
Lines are also managed from the product edit screen:
Product edit/clone form (
Adv_products_admin.php): Multi-select dropdown (line_ids[]) populated fromlines_model->dropdown(). On save, callsmanageProductLines($id)which delegates tolines_model->updateProductLines($productId, $lineIds)-- deletes all existingshop_line_productsrows for the product, then batch-inserts the new selection.Batch action from product listing: The "put in lines" batch action posts
line_idand callslines_model->update_batch_product_line($products, $lineId)-- inserts intoshop_line_productsonly if the product-line pair does not already exist (de-duplicated).Product clone:
manageCloneProductLines()copies all line associations from the source product to the clone viagetLineIdsByProductId()+updateProductLines().
REST API Endpoints
All endpoints require JWT authentication.
| Method | Path | Action | Description |
|---|---|---|---|
| GET | /rest/product/line | index | Paginated collection with filters and sorts |
| GET | /rest/product/line/item | item | Single resource by filter criteria |
| GET | /rest/product/line/{id} | show | Single resource by ID |
| POST | /rest/product/line | store | Create new line (JSON or multipart with file uploads) |
| POST | /rest/product/line/{id} | update | Update existing line (JSON or multipart) |
| DELETE | /rest/product/line/{id} | destroy | Delete line + translations |
Locale-prefixed variants (/{lang}/rest/product/line/...) also registered.
Available filters: id (exact), priority (exact), isPromo (exact), vendorId (exact), name.{locale} (partial/LIKE), slug.{locale} (exact)
Available sorts: id, priority, name.{locale}
Available relations (via ?with=): translations, vendor, products
Resource output (ProductLineResource):
json
{
"id": 1,
"vendorId": 42,
"image": "https://cdn.example.com/files/lines/banner.jpg",
"frontImage": "https://cdn.example.com/files/lines/thumb.jpg",
"isPromo": false,
"priority": 0,
"translations": [...],
"vendor": {...},
"products": [...]
}Feed Integration
Lines Catalog XML (ecommercen/feeds/controllers/AdvLinesCatalog.php):
- Route:
/xml/lines - Uses
OutputLinesXmlfeed output class (src/Feeds/Output/OutputLinesXml.php) - Actually outputs vendor data (not individual lines) via
vendors_model->getVendorsForXml() - XML structure:
<lines>root,<line>items withid,title,url,discount,items
Business Rules
| Rule | Description |
|---|---|
| Vendor required | Every line must belong to exactly one vendor (vendor_id > 0 validated) |
| Many-to-many products | Products can belong to multiple lines; lines can contain multiple products |
| Promo sorting | Lines with is_promo = 1 sort before non-promo lines on storefront (shop_line.is_promo, shop_line.order asc) |
| Auto-slug generation | On create, slug generated from line name per language. On edit, existing slugs preserved unless explicitly changed. |
| Slug editing restricted | Only users with urlFields access can manually edit slugs. ADVISABLE role has additional slug regeneration control. |
| SEO fields restricted | Meta title/keywords/description editable only for ADVISABLE role on create; on edit accessible to users with metatags permission |
| Image storage | Both images stored in files/lines/ directory |
| Cascade delete | Deleting a line removes all product associations and MUI records |
| No deletion guard | Lines can be deleted regardless of product count (unlike vendors) |
| Product ordering | Products within a line have independent sort order via shop_line_products.order |
| Product assignment from product edit | Product can be assigned to lines via multi-select on the product edit form; delete-and-reinsert pattern |
| Product clone | Line associations copied when cloning a product |
Storefront Behavior
Lines are displayed within vendor pages at /vendors/{vendor_slug}/{line_slug}:
- The
Adv_vendorscontroller (ecommercen/eshop/controllers/Adv_vendors.php) checks if the path after the vendor slug matches a line slug - If a line record is found for the given slug and vendor, it renders the line listing page with products
- Cached via
pscachefor performance
Sitemap Integration
lines_model->getLinesForSiteMap() returns all line URLs (vendor_slug + line slug) for sitemap generation.
Test Coverage
Unit tests: tests/Unit/Domains/Product/Line/ServiceTest.php -- 8 tests covering all(), item(), get() with mocked repository, pagination assertions
Integration tests:
tests/Integration/Domains/Product/Line/RepositoryTest.php-- 14 tests coveringget(),match(),matchOne(),count(), sorting, pagination, relation loading (translations, vendor), MUI repository queriestests/Integration/Domains/Product/Line/ServiceTest.php-- 10 tests covering full read/write round-trip:create()with/without translations,update()field modification and translation replacement,delete()cascade, validation error on empty lang, and a complete create-update-delete round-trip test
Key File Paths
| Component | Path |
|---|---|
| Admin Controller | ecommercen/eshop/controllers/Adv_lines_admin.php |
| Legacy Model | ecommercen/eshop/models/Adv_lines_model.php |
| Admin Views | application/views/admin/lines/{list,create,update,products}.php |
| Entity | src/Domains/Product/Line/Repository/Entity.php |
| MuiEntity | src/Domains/Product/Line/Repository/MuiEntity.php |
| Repository | src/Domains/Product/Line/Repository/Repository.php |
| RepositoryConfigurator | src/Domains/Product/Line/Repository/RepositoryConfigurator.php |
| WriteRepository | src/Domains/Product/Line/Repository/WriteRepository.php |
| MuiWriteRepository | src/Domains/Product/Line/Repository/MuiWriteRepository.php |
| Service | src/Domains/Product/Line/Service.php |
| WriteService | src/Domains/Product/Line/WriteService.php |
| WriteData | src/Domains/Product/Line/WriteData.php |
| MuiWriteData | src/Domains/Product/Line/MuiWriteData.php |
| Validator | src/Domains/Product/Line/Validator.php |
| ListRequest | src/Domains/Product/Line/ListRequest.php |
| REST Controller | src/Rest/Product/Controllers/Line.php |
| REST Resource | src/Rest/Product/Resources/Line/Resource.php |
| REST Collection | src/Rest/Product/Resources/Line/Collection.php |
| REST MuiResource | src/Rest/Product/Resources/Line/MuiResource.php |
| REST MuiCollection | src/Rest/Product/Resources/Line/MuiCollection.php |
| Domain DI Container | src/Domains/Product/container.php (Line section) |
| REST DI Container | src/Rest/Product/container.php (Line entry) |
| REST Routes | application/config/rest_routes.php (lines 448-453, 1444-1450) |
| Admin Routes | application/config/routes.php (lines 323-326) |
| Admin Menu | application/config/admin_menu.php (line 444) |
| Feed Controller | ecommercen/feeds/controllers/AdvLinesCatalog.php |
| Feed Output | src/Feeds/Output/OutputLinesXml.php |
| Unit Tests | tests/Unit/Domains/Product/Line/ServiceTest.php |
| Integration Tests | tests/Integration/Domains/Product/Line/{ServiceTest,RepositoryTest}.php |
Related Flows
- AD-02 Product Management -- product edit form includes line multi-select; batch assign to line
- AD-19 Vendor Management -- vendors are parent entities of lines
- AD-14 SEO Management -- per-line SEO fields (meta title, keywords, description)
- CF-01 Product Browsing -- line landing pages at
/vendors/{vendor_slug}/{line_slug} - IN-01 Feed Generation -- lines catalog XML feed
- SY-23 MUI Translation Pattern --
shop_line_muicompanion table stores per-language names, slugs, and descriptions - SY-25 File Upload & Storage --
advuploader->uploadFilesSameFolder()andUploadServicehandle line image and front image uploads tofiles/lines/