Appearance
Supplier Management (Admin)
Flow ID: AD-20 Module(s): eshop Complexity: Low-Medium Last Updated: 2026-04-04
Business Context
Suppliers represent the upstream entities (manufacturers, distributors) that provide products to the shop. Each supplier groups one or more vendors (brands/companies) under a single umbrella. The relationship is many-to-one: many vendors can belong to one supplier, but a vendor has at most one supplier.
The feature is toggleable via the registry setting SUPPLIER.ENABLED (managed in Admin Settings > General). When disabled, the supplier menu entry and filtering are hidden from the admin panel.
Database Schema
shop_supplier -- main entity:
| Column | Type | Notes |
|---|---|---|
id | int(11) PK AI | |
logo | text, nullable | Filename relative to files/suppliers/ |
order | int(11), default 0 | Sort priority |
shop_supplier_mui -- translations:
| Column | Type | Notes |
|---|---|---|
id | int(11) PK AI | |
supplier_id | int(11), FK | References shop_supplier.id |
name | varchar(255) | Translated supplier name |
lang | varchar(2), default 'el' | Language code |
shop_vendor.supplier_id -- the foreign key on the vendor table linking vendors to their supplier (nullable int).
Architecture
The supplier domain is implemented in both legacy and modern layers.
Legacy Layer (Admin CRUD)
- Controller:
ecommercen/eshop/controllers/Adv_suppliers_admin.php(191 lines) - Model:
ecommercen/eshop/models/Adv_suppliers_model.php(109 lines) - Views:
application/views/admin/suppliers/--list.php,create.php,update.php - Vendor model methods:
Adv_vendors_model::updateSupplierIds(),get_vendors_of_supplier(),get_vendors_list_combo()
Modern Domain Layer (REST API)
- Namespace:
Advisable\Domains\Product\Supplier\ - Entity:
Repository\Entity-- properties:id,logo,order - MUI Entity:
Repository\MuiEntity-- properties:id,supplier_id,name,lang - Repository:
Repository\Repository(tableshop_supplier) withRepositoryConfiguratordefiningtranslationsrelation (ONE_TO_MANY viaMuiRepositoryonsupplier_id) - Service:
Service-- read operations:all(),item(),get()with Specification pattern (Filter, Sort, Pagination, WithRelations) - WriteService:
WriteService-- transactionalcreate(),update(),delete()with MUI handling (delete+re-insert on update) - WriteData / MuiWriteData: Value objects with OpenAPI schema annotations
- Validator: Validates
langrequirement on translations; create/update hooks are extensible (currently no-op) - WriteRepository / MuiWriteRepository: Extend
BaseWriteRepository; MUI repo providesinsertForEntity(),replaceForEntity(),deleteForEntity() - ListRequest: Allowed filters:
id(Exact),priority(Exact),name.{locale}(Partial). Allowed sorts:id,priority,name.{locale}.
REST API Layer
- Controller:
Advisable\Rest\Product\Controllers\Supplier(extendsHandlesRestfulActions, usesHandlesUploadActions) - Resource:
Rest\Product\Resources\Supplier\Resource-- outputsid,logo(formatted with/files/suppliers/prefix),priority(mapped fromorder),translations(MuiCollection) - Collection:
Rest\Product\Resources\Supplier\Collection - MUI Resource/Collection:
MuiResourceoutputsid,supplierId,name,lang
DI Container
- Domain registration:
src/Domains/Product/container.php-- registers Repository, RepositoryConfigurator, MuiRepository (with NullRelationConfigurator), Service, WriteRepository, MuiWriteRepository, Validator, WriteService - REST registration:
src/Rest/Product/container.php-- wires controller with Service, Resource/Collection classes, ListRequest, WriteService, and UploadService
Admin Controller Flow
Auth: AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, AUTH_ROLE_PRODUCTS
Routes (in application/config/routes.php):
eshop/suppliers_admin -> Adv_suppliers_admin::index
eshop/suppliers_admin/(.+) -> Adv_suppliers_admin::$1Menu location: Admin sidebar > Shop > Products group (icon: fa-home, label: "Suppliers" / "Promutheutes")
index()
Lists all suppliers ordered by shop_supplier.order ASC, joined with MUI for current language. Listing view shows ID, thumbnail image (resized to 96px width via resizeImageOnTheFly), name, and action buttons (edit/delete). Includes sortable jQuery for drag-and-drop reordering (delegates to vendors_admin/update_order).
add()
- Validates MUI
nameper language (required, trimmed) - Uploads logo image via
AdvUploadertofiles/suppliers/ - Inserts
shop_supplierrecord with logo filename - Inserts
shop_supplier_muirows per active language - Updates vendor associations:
vendors_model->updateSupplierIds($supplierId, $vendorIds)-- clears all vendors'supplier_idfor this supplier, then sets it on selected vendors - Calls
afterAdd()hook (empty, overridable in client repos) - Redirects to listing with success flash
Create form fields: MUI name (per language tab), logo image upload, vendor multi-select (Chosen.js dropdown)
edit($id)
- Same validation as add
- Uploads replacement logo to
files/vendors/(note: legacy inconsistency -- edit uploads tofiles/vendors/, add uploads tofiles/suppliers/) - Supports image deletion via
del_imagecheckbox (setslogo = null) - Updates
shop_supplierandshop_supplier_muirecords - Reassigns vendor associations via
updateSupplierIds() - Calls
afterEdit()hook
Edit form fields: Same as create, plus: current image preview with popup, image delete checkbox, pre-selected vendors
delete($id)
- Checks
canDelete($id)-- returns false if any vendor hassupplier_id = $id - If deletable: removes
shop_supplierandshop_supplier_muirows - If blocked: sets error flash "Cannot delete this supplier as it is used by companies"
- Calls
afterDelete()hook - Redirects to listing
REST API Endpoints
Policy: Backend auth required, roles AUTH_ROLE_ADMIN or AUTH_ROLE_PRODUCTS (defined in application/config/rest_policies.php).
| Method | Route | Action | Description |
|---|---|---|---|
| GET | /rest/product/supplier | index() | Paginated collection with filtering/sorting |
| GET | /rest/product/supplier/item | item() | Single resource by filter criteria |
| GET | /rest/product/supplier/{id} | show($id) | Single resource by ID |
| POST | /rest/product/supplier | store() | Create (JSON or multipart with logo upload) |
| POST | /rest/product/supplier/{id} | update($id) | Update (JSON or multipart with logo upload) |
| DELETE | /rest/product/supplier/{id} | destroy($id) | Delete supplier and all translations |
All routes support locale prefix: /{locale}/rest/product/supplier/...
Upload handling: Logo field configured via uploadFieldsConfig() with path files/suppliers/. Accepts multipart/form-data for file uploads or application/json for data-only operations. On delete, physical files are removed automatically by UploadService.
Vendor-Supplier Relationship
The supplier-vendor link lives on shop_vendor.supplier_id (nullable FK). Management flows:
From Supplier admin: Multi-select vendors in create/edit forms.
updateSupplierIds()first nullifies all vendor rows matching the supplier, then sets the new selection. This means reassigning vendors between suppliers is handled atomically.From Vendor admin (
Adv_vendors_admin.php): Vendor create/edit forms include a supplier dropdown (suppliers_model->dropdown()), storingsupplier_iddirectly on the vendor record.REST API: Vendor Resource exposes
supplierIdfield and can include the fullsupplierrelation (BELONGS_TO) via?with=supplier.Product filtering: Product admin listing uses suppliers as a filter.
Adv_product_model::get_products()joinsshop_vendorand filters bysupplier_idwhenproductSupplierscondition is present. This enables filtering products by their vendor's supplier.
Extension Points
afterAdd(),afterEdit(),afterDelete(): Protected empty methods on the controller, designed for client repo overrides (e.g., clearing caches, syncing with external systems)- Client DI override: Register
Custom\Domains\Product\Supplier\Repository\RepositoryConfiguratoraliased to the upstream class to customize relations - Public2P integration: Commented-out job
UpdatePublic2PSupplierinapplication/config/jobs.php(cron0 23 * * *). When enabled, syncs product data to Public2P API usingPUBLIC_2P.SUPPLIER_IDregistry setting. The job class is atecommercen/job/libraries/AdvUpdatePublic2PSupplier.php.
Business Rules
| Rule | Description |
|---|---|
| Feature toggle | SUPPLIER.ENABLED registry flag controls visibility |
| Deletion guard | Cannot delete supplier if any vendor references it |
| MUI name required | Name is required for each active admin language |
| Vendor reassignment | Saving a supplier clears then reassigns all vendor links atomically |
| Single supplier per vendor | A vendor can belong to at most one supplier (supplier_id is scalar, not a join table) |
| Logo storage | Uploaded to files/suppliers/ (add) or files/vendors/ (edit -- legacy inconsistency) |
Test Coverage
- Integration tests:
tests/Integration/Domains/Product/Supplier/ServiceTest.php-- read operations (all, filter, pagination, item, get with relations), write operations (create, create with translations, update, update translations replacement, delete with cascade, round-trip) - Integration tests:
tests/Integration/Domains/Product/Supplier/RepositoryTest.php-- get by ID, match with filters, matchOne, count, sorting, pagination, relation loading, MUI repository operations - Unit tests:
tests/Unit/Rest/Product/Resources/Supplier/ResourceTest.php-- resource array structure, logo path formatting, null logo handling, null resource handling
Related Flows
- AD-19 Vendor Management -- vendors reference suppliers via
supplier_id - AD-02 Product Management -- product listing filters by supplier (via vendor join)
- AD-13 Settings --
SUPPLIER.ENABLEDtoggle andPUBLIC_2P.SUPPLIER_IDconfig - SY-23 MUI Translation Pattern --
shop_supplier_muicompanion table stores per-language supplier names - SY-25 File Upload & Storage --
AdvUploaderandUploadServicehandle logo image uploads tofiles/suppliers/(andfiles/vendors/on legacy edit)