Appearance
Shelf Code Management
Flow ID: AD-51 | Module(s): eshop | Complexity: Medium Last Updated: 2026-09-29
Business Context
Shelf code management provides CRUD operations for warehouse shelf/location codes. Each product can be assigned a shelf code that indicates its physical location in the warehouse, enabling faster order picking and inventory management. This is primarily used by businesses with physical warehouse operations (e.g., pharmaceutical distributors, large e-commerce warehouses).
The admin interface at /eshop/shelfcodes_admin.htm is a simple form-based CRUD for creating, editing, and deleting shelf code entries. Products reference shelf codes via a plain non-unique shelfcode_id foreign key (no DB-level FK constraint) and the shelf label is surfaced throughout the order picking pipeline -- order detail, packing slips, build summaries, and customer order history -- so warehouse staff can locate each line item.
API Reference
REST Endpoints
| Method | Endpoint | Actions | Auth | Roles |
|---|---|---|---|---|
| GET | /rest/product/shelfcode | index, item, show | Backend | ADMIN, PRODUCTS |
| POST | /rest/product/shelfcode | store, update | Backend | ADMIN, PRODUCTS |
| DELETE | /rest/product/shelfcode | destroy | Backend | ADMIN, PRODUCTS |
Read routes registered in application/config/rest_routes.php:382-387, write routes in application/config/rest_routes.php:1635-1640. RBAC defaults in application/config/rest_policies.php:745.
Legacy Admin Routes
| Route | Controller | Method | HTTP | Description |
|---|---|---|---|---|
eshop/shelfcodes_admin | Adv_shelfcodes_admin | index() | GET | List all shelf codes |
eshop/shelfcodes_admin/add | Adv_shelfcodes_admin | add() | GET/POST | Create shelf code form |
eshop/shelfcodes_admin/edit/{id} | Adv_shelfcodes_admin | edit($id) | GET/POST | Edit shelf code form |
eshop/shelfcodes_admin/delete/{id} | Adv_shelfcodes_admin | delete($id) | GET | Delete a shelf code (soft-dereferences products first) |
Route definitions at application/config/routes.php:376-379 (default + locale-prefixed variants).
Code Flow
Admin CRUD
Adv_shelfcodes_admin::add() / edit($id)
|
+--> GET: render form view (create.php / update.php)
+--> POST submit -> $this->validation() at Adv_shelfcodes_admin.php:105-108
| |-- single rule: set_rules('shelfcode_value', ..., 'trim|numeric')
|
+--> Build $data = ['code' => $post['shelfcode_value']]
+--> shelfcodes_model->add_record($data) | edit_record($id, $data)
+--> afterAdd($id) / afterEdit($id) hook (empty stub, lines 110-123)
+--> set_userdata('eshop_success', ...)
\--> redirect('eshop/shelfcodes_admin')Controller methods live at ecommercen/eshop/controllers/Adv_shelfcodes_admin.php:35-103. RBAC enforced in the constructor at lines 17-33: [AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, AUTH_ROLE_PRODUCTS].
Soft-Dereference Delete (legacy)
php
// ecommercen/eshop/models/Adv_shelfcodes_model.php:113-118
function delete_record($id) {
$this->db->update($this->tableProduct, ['shelfcode_id' => null], ['shelfcode_id' => $id]);
$this->db->delete($this->table, ['id' => $id]);
$this->session->set_userdata('eshop_success', t('eshop.admin.success.entrydelete'));
}The legacy delete always succeeds: every shop_product.shelfcode_id that references the deleted shelf is first NULLed, then the shelf row is removed. There is no confirmation dialog, no blocking if products still reference the shelf, and no count of affected products shown to the admin.
Product Admin Integration
The product admin controller Adv_products_admin.php wires shelf codes into create/edit/clone/ERP-import forms and the list-view filter/batch action surface:
- Model loaded in constructor at line 32.
- List filter: multiselect
shelvesfilter populated viashelfcodes_model->getCombo()at line 147, rendered inapplication/views/admin/products/list.php:194-200. - Form integration via
shelfcodes_comborender variable at lines 689, 749, 806, and 1848. Consumed by:application/views/admin/products/create.php:583-589application/views/admin/products/update.php:749-762application/views/admin/products/clone.php:662-673application/views/admin/products/add_erp_product.php:571-577application/views/admin/products/new_products_create.php:343-349
- Save logic defaults
shelfcode_idtonull(line 392) and sets it from postedshelfcode_valueonly if non-empty (lines 411-413). - Product-level validation:
set_rules('shelfcode_value', 'Shelfcode', 'trim')at line 999. Note thatshelfcode_valuemeans something different here than in the shelf codes admin: inAdv_products_adminit is the selected shelf's id, assigned straight toshop_product.shelfcode_id(line 412); inAdv_shelfcodes_adminit is the shelf code string written toshop_product_shelfcodes.code(Adv_shelfcodes_admin.php:54,:78). The two rule sets validate different data, so neither constrains the other's field. - Batch action
alter_shelfcode: handler at line 1123, submit at 1611-1613. CallsbatchMasterUpdate($products, ['shelfcode_id' => ..., 'date_changed' => ...]). HookafterBatchActionSubmitAlterShelfCodeat line 2099. UI inapplication/views/admin/products/list.php:549-550andbatch_update.php:314-315. - Search filter:
Adv_product_model::fixAdminSearch()(declared atAdv_product_model.php:2092) accepts ashelvescondition and appliesWHERE shop_product.shelfcode_id IN (...)atAdv_product_model.php:2180-2182.fixAdminSearch()is called bygetProductsAdminResults()(:2344) andgetProductsAdminNumRows()(:2410), which is how the filter actually reaches the admin product list query.
Modern Domain Layer
A full Domain + REST layer was added in 4.99.0 (see docs/changelog/Changelog.4.99.md:1623-1629). It lives under src/Domains/Product/Shelfcode/ and src/Rest/Product/.
Domain Files
| File | Role | Notes |
|---|---|---|
src/Domains/Product/Shelfcode/Repository/Entity.php | Entity | Fields: int $id, ?string $code |
src/Domains/Product/Shelfcode/Repository/Repository.php | Read repository | table = 'shop_product_shelfcodes' |
src/Domains/Product/Shelfcode/Repository/RepositoryConfigurator.php | Relations config | getRelations(): array { return []; } -- no relations |
src/Domains/Product/Shelfcode/Repository/WriteRepository.php | Write repository | Extends BaseWriteRepository, primary key id; adds nullProductReferences() at lines 18-23 |
src/Domains/Product/Shelfcode/Service.php | Read service | all(), item(), get(). As of #650 composes use BuildsFilterSpecifications; (:17) instead of owning buildSpecifications() directly -- see Filter Specification Seam below |
src/Domains/Product/Shelfcode/WriteService.php | Write service | create(), update(), delete() (lines 47-54; transactional soft-dereference) |
src/Domains/Product/Shelfcode/WriteData.php | Write DTO | Single ?string $code field, OpenAPI schema name Shelfcode |
src/Domains/Product/Shelfcode/Validator.php | Validator | Empty stub -- no length / format / uniqueness rules |
src/Domains/Product/Shelfcode/ListRequest.php | List filter spec | Filters id (Exact), code (Partial); sorts id, code. Comment explicitly notes "Shelfcode does not have MUI translations." |
Filter Specification Seam (BuildsFilterSpecifications)
As of #650, Service.php (84 → 43 lines) no longer owns buildSpecifications() directly; it composes use BuildsFilterSpecifications; (src/Domains/Product/Shelfcode/Service.php:17) in place of the removed use HandlesNotEmptyFilters; -- that trait no longer exists in the codebase; it was deleted and its logic absorbed into the shared trait. BuildsFilterSpecifications (src/Domains/Support/Service/BuildsFilterSpecifications.php:29-51) exposes four graduated protected seams for client overrides: filterOperator(), filterSpecification(), sortSpecification(), additionalSpecifications(). FilterOperatorMapInterface is also DI-aliasable from custom/Domains/container.php. This supersedes the "replace the whole Service" note that previously described Shelfcode's only client-override option -- see Client Extension Points.
The refactor is behaviour-neutral for Shelfcode (verified): the removed private match mapped Partial → LIKE, Exact → =, default → =; the shared FilterOperatorMap::operatorFor() (src/Domains/Support/Request/QueryListBuilder/FilterOperatorMap.php:32-48) maps the same two operators identically, and the NotEmpty short-circuit ordering is preserved. The ListRequest filters (id Exact, code Partial) and the Service's all()/item()/get() behaviour documented above are unchanged.
Fork hazard: the shared loop forwards the ListRequest visibilityExemptions into WithRelations, so a fork that copies the loop to tweak one operator and later misses an upstream change to that argument breaks visibility scoping with no error and a green container boot (BuildsFilterSpecifications.php:43-51).
BuildsFilterSpecifications/buildListRequest()are shared platform facts with no canonicaldocs/flows/home today -- this section covers only Shelfcode's use of the seam.
Modern Delete Behavior
php
// src/Domains/Product/Shelfcode/WriteService.php:47-54
public function delete(int|string $id): bool {
return (bool) $this->writeRepository->transactional(function () use ($id) {
$this->writeRepository->nullProductReferences($id);
return $this->writeRepository->delete($id);
});
}php
// src/Domains/Product/Shelfcode/Repository/WriteRepository.php:18-23
public function nullProductReferences(int|string $id): void {
$this->db
->where('shelfcode_id', $id)
->update('shop_product', ['shelfcode_id' => null]);
}The modern write service now mirrors the legacy soft-dereference: nullProductReferences() NULLs every shop_product.shelfcode_id that references the deleted shelf before removing the shelf row. In addition, both operations are wrapped in transactional(), making the modern path atomically safer than the legacy delete_record(), which issues the two statements without a transaction (ecommercen/eshop/models/Adv_shelfcodes_model.php:113-118).
REST Layer
| File | Role |
|---|---|
src/Rest/Product/Controllers/Shelfcode.php | REST controller (lines 1-150). Extends HandlesRestfulActions, uses HandlesWriteActions. Methods: index, show, item, store, update, destroy. Full OpenAPI annotations. |
src/Rest/Product/Resources/Shelfcode/Resource.php | Resource transformer. OpenAPI schema ProductShelfcodeResource. Fields: id, code. |
src/Rest/Product/Resources/Shelfcode/Collection.php | Collection wrapper. OpenAPI schema ProductShelfcodeCollection. |
Product Relation
The modern Product domain declares shelf code as a BELONGS_TO relation:
php
// src/Domains/Product/Product/Repository/RepositoryConfigurator.php:50
'shelfcode' => new Relation(Relation::BELONGS_TO, ShelfcodeRepository::class, 'shelfcode_id'),src/Domains/Product/Product/Repository/Entity.php:11declares?int $shelfcode_id.src/Domains/Product/Product/WriteData.php:12, 79, 120, 161acceptsshelfcodeIdon create / update.src/Rest/Product/Resources/Product/Resource.php:107-108exposesshelfcodeIdonly when$this->context?->isBackend()is true -- this field is backend-only on the Product REST resource (seedocs/changelog/Changelog.4.99.md:691).src/Rest/Product/Resources/Product/Resource.php:63declares the optional embeddedshelfcoderelation's OA schema asProductShelfcodeResource; it is populated viaaddResourceToData()at line 129. This once referenced a mismatched schema name -- resolved, see Known Issues #8.
DI Registration
- Domain:
src/Domains/Product/container.php:355-361 - REST:
src/Rest/Product/container.php:249-254
Tests
| File | Scope |
|---|---|
tests/Integration/Domains/Product/Shelfcode/RepositoryTest.php | Integration -- repository |
tests/Integration/Domains/Product/Shelfcode/ServiceTest.php | Integration -- read service |
tests/Unit/Domains/Product/Shelfcode/ServiceTest.php | Unit -- read service |
tests/Unit/Domains/Product/Shelfcode/WriteServiceTest.php | Unit -- write service |
tests/Unit/Rest/Product/Resources/Shelfcode/ResourceTest.php | Unit -- Resource transformer |
tests/Unit/Rest/Product/Resources/Shelfcode/CollectionTest.php | Unit -- Collection transformer |
Architecture
| Component | Path | Purpose |
|---|---|---|
Shelfcodes_admin (wrapper) | application/modules/eshop/controllers/Shelfcodes_admin.php:1-12 | Empty client-override shim |
Adv_shelfcodes_admin | ecommercen/eshop/controllers/Adv_shelfcodes_admin.php:1-124 | Legacy admin CRUD, extends Admin_c |
Shelfcodes_model (wrapper) | application/modules/eshop/models/Shelfcodes_model.php:1-6 | Empty client-override shim |
Adv_shelfcodes_model | ecommercen/eshop/models/Adv_shelfcodes_model.php:1-119 | Legacy model, extends Adv_base_model |
| Domain Entity | src/Domains/Product/Shelfcode/Repository/Entity.php | Modern entity |
| Domain Service | src/Domains/Product/Shelfcode/Service.php | Modern read service |
| Domain WriteService | src/Domains/Product/Shelfcode/WriteService.php | Modern write service |
| REST Controller | src/Rest/Product/Controllers/Shelfcode.php | REST endpoints |
| Routes | application/config/routes.php:376-379 | Legacy admin routes |
| REST routes | application/config/rest_routes.php:382-387,1635-1640 | REST read + write routes |
| Admin menu | application/config/admin_menu.php:591-597 | Menu registration |
Legacy Model Method Inventory (Adv_shelfcodes_model.php)
| Method | Lines | Purpose |
|---|---|---|
get_records($cond, $asrow) | 24-40 | List shelf codes with optional conditions |
shelfcode_label($shelfcode_id) | 42-47 | Return the code string for a given PK |
getCombo($emptyFirst = false) | 55-71 | Return [id => code] map for form_dropdown() |
add_record($data) | 73-77 | Insert a shelf code |
edit_record($id, $data) | 85-88 | Update a shelf code |
product_shelfcode($productId) | 90-97 | Dead code -- unreachable and broken, see note below |
productShelfCodeLabel($productId) | 103-111 | Join product -> shelfcode, return label string |
delete_record($id) | 113-118 | Soft-dereference then delete |
product_shelfcode()is dead code. No call site invokes$this->product_shelfcode()-- all four order fake-cart builders (three marketplace importers --Adv_skroutz_orders_model.php:565,Adv_shopflix_orders_model.php:374,Adv_public_orders_model.php:253-- plus the admin order builder atAdv_order_model.php:2009) callproduct_shelfcode($productId)as a bare function, which resolves to the global helper below, not to this model method. It also would not work as written even if reached: it selects{$this->table}.idbut returns$get->row()->shelfcode_id, a column absent from the select list.
Deprecated Global Helper
ecommercen/helpers/eshop_helper.php:3-26 defines a global product_shelfcode($productId) function that delegates to productShelfCodeLabel(). It is marked @deprecated but is still called by all four order fake-cart builders -- three marketplace order-ingestion models plus the admin order builder (see Marketplace Order Imports). productShelfCodeLabel() (Adv_shelfcodes_model.php:103-111) is the live, correct path: it selects code and returns ->code.
Admin Menu
Registered in application/config/admin_menu.php:591-597 inside the PRODUCTS group with label "Shelves" and icon <i class="mdi mdi-server"></i>. Visible to roles [AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, AUTH_ROLE_PRODUCTS].
Views
| View | Purpose |
|---|---|
application/views/admin/shelfcodes/list.php | Simple table: ID, Code, Actions |
application/views/admin/shelfcodes/create.php | Single-field form (shelfcode_value) |
application/views/admin/shelfcodes/update.php | Same form, pre-populated |
Data Model
shop_product_shelfcodes Table
Defined in database/initial/initial.sql:1824-1829. No Phinx migration has ever altered the schema.
| Column | Type | Null | Default | Description |
|---|---|---|---|---|
id | INT(10) AUTO_INCREMENT | No | -- | Primary key |
code | VARCHAR(255) | Yes | '0' | Shelf code string. The DDL has no NOT NULL, so the column is technically nullable, but the default is the literal string "0" rather than NULL. |
Indexes
PRIMARY KEY (id)KEY code (code)-- plain non-unique index
No _mui table. Shelf codes have no translations by design (see comment in ListRequest.php).
Reference from shop_product
database/initial/initial.sql declares:
shelfcode_id INT(11) DEFAULT NULLat:1523KEY shelfcode_id (shelfcode_id)at:1574-- plain non-unique index- No
FOREIGN KEYconstraint. Referential integrity is maintained entirely in PHP (legacy soft-dereference on delete).
No other tables in the schema reference shop_product_shelfcodes.
Picking Workflow
Shelf codes surface at multiple points in the order fulfillment pipeline via joins in Adv_order_basket_model:
php
// ecommercen/eshop/models/Adv_order_basket_model.php:22
protected $tableShelfCodes = 'shop_product_shelfcodes';Order Line Shelf Code Join (lines 377-422)
getRecordsByOrderIds() selects {$this->tableShelfCodes}.code as shelf_code at :384 and LEFT JOINs shop_product_shelfcodes on shop_product.shelfcode_id at :411, resolving each line item's current shelf code. This single method feeds all four view surface points listed below -- admin order detail, order build summary, logistics packing slip, and customer order history -- as well as the checkout and analytics-listener order paths. Because the join resolves live, a later shelf code rename or delete changes what a historical order displays.
Orders CSV Special Export (lines 500-538)
getBasketsForSpecialExport() runs a similar SELECT with a GROUP_CONCAT of barcodes, selecting shop_product_shelfcodes.code (unaliased) at :505 and LEFT JOINing the shelf table at :532. Its only caller repo-wide is the "searched orders" CSV export, Adv_orders_admin::exportSearchedOrders() (ecommercen/eshop/controllers/Adv_orders_admin.php:2963, call at :2968), which emits a SHELF CODE column at :2973. It does not feed the logistics packing slip -- see AD-03 Order Management Admin.
View Surface Points
| View | Line | Context |
|---|---|---|
application/views/admin/orders/update.php | 232 | Admin order detail -- label eshop.admin.shelfcode.shelfcode_label |
application/views/admin/orders/build_summary.php | 28-30 | Order build summary partial |
application/views/admin/orders/invoice_logistics.php | 13-15 | Logistics packing slip |
application/views/admin/customers/order_history.php | 20 | Customer order history panel |
Marketplace Order Imports
The order fake-cart builders below each set a shelfcode key on every cart line at order time via the deprecated global product_shelfcode() helper. Three are marketplace order ingestion models; the fourth (Adv_order_model.php:2009) is the admin order builder used by the POS / phone-order flow, documented in AD-54 POS / Phone Orders:
| File | Line |
|---|---|
ecommercen/eshop/models/Adv_skroutz_orders_model.php | 565 |
ecommercen/eshop/models/Adv_shopflix_orders_model.php | 374 |
ecommercen/eshop/models/Adv_public_orders_model.php | 253 |
ecommercen/eshop/models/Adv_order_model.php | 2003 |
The shelfcode key is never persisted. shop_order_basket has no shelf-code column (database/initial/initial.sql:1425-1450), and the fake-cart-to-basket column mapper getCartFromUnserializedContents() (ecommercen/helpers/eshop_helper.php:110-125) does not carry shelfcode through, so add_record() (ecommercen/eshop/models/Adv_order_basket_model.php:81-85) never writes it. No code repo-wide reads the key. Shelf codes shown against an order are therefore always live-joined from shop_product.shelfcode_id (see Picking Workflow), never snapshotted -- a later rename or delete does change what a historical order displays. See Known Issues.
Configuration
- Legacy routes:
application/config/routes.php:376-379(default + locale-prefixed). - REST routes:
application/config/rest_routes.php:382-387(read),1635-1640(write). - Admin menu:
application/config/admin_menu.php:591-597. - REST policies:
application/config/rest_policies.php:745. - DI:
src/Domains/Product/container.php:355-361,src/Rest/Product/container.php:249-254. - No environment variables. Shelf codes have no registry keys, no
.envdependencies, and no integration toggles.
Required roles
- Legacy admin CRUD + admin menu:
[AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, AUTH_ROLE_PRODUCTS] - REST API:
[AUTH_ROLE_ADMIN, AUTH_ROLE_PRODUCTS]. ADVISABLE is intentionally not listed —application/config/rest_policies.php:33-34documents that ADVISABLE is a superuser role that auto-bypasses every roles array viaRequestContext::isSuperuser()(src/Rest/Middleware/AuthorizationMiddleware.php:69,src/Rest/Middleware/RequestContext.php:47-49). Effective access is identical to the legacy controller's allow-list.
Client Extension Points
- Override admin controller: Create a same-named class in
application/modules/eshop/controllers/Shelfcodes_admin.php(the empty wrapper atapplication/modules/eshop/controllers/Shelfcodes_admin.php:1-12is where the override lives). - Override model: Extend
Shelfcodes_modelatapplication/modules/eshop/models/Shelfcodes_model.php. - Empty hook stubs in
Adv_shelfcodes_admin.php:110-123(afterAdd,afterEdit,afterDelete) are available for client overrides to attach side effects. - Override modern domain / REST: In client repos, register
Custom\Domains\Product\Shelfcode\...classes and alias the upstream services via$services->alias(...)incustom/.../container.php. - Filter/sort behavior (graduated seams): Since #650,
Service.phpno longer requires a full-Service override to customize filtering or sorting.BuildsFilterSpecifications(src/Domains/Support/Service/BuildsFilterSpecifications.php:29-51) exposes four protected, individually-overridable seams --filterOperator(),filterSpecification(),sortSpecification(),additionalSpecifications()-- andFilterOperatorMapInterfaceis DI-aliasable fromcustom/Domains/container.php. See Filter Specification Seam above, including the fork hazard onvisibilityExemptions.
Business Rules
- Optional assignment --
shop_product.shelfcode_iddefaults to NULL; products do not require a shelf code. - Legacy numeric-only enforcement -- the shelf codes admin form validates
shelfcode_valuewithtrim|numeric, forcing numeric codes in the UI even though the column isVARCHAR(255). - Default code "0" on insert -- the column default is the literal string
'0', so inserts that omit thecodefield materialize as shelf "0" rather than NULL. - Both delete paths soft-dereference first -- legacy
delete_record()(ecommercen/eshop/models/Adv_shelfcodes_model.php:113-118) NULLs every referencingshop_product.shelfcode_idthen drops the row; modernWriteService::delete()(src/Domains/Product/Shelfcode/WriteService.php:47-54) does the same viaWriteRepository::nullProductReferences()(src/Domains/Product/Shelfcode/Repository/WriteRepository.php:18-23). The modern path additionally wraps both operations in a transaction; the legacy path does not. Both always succeed with no confirmation, no blocking, and no count shown. - No shelf code snapshot on order lines -- all four order fake-cart builders compute a
shelfcodelabel via the deprecatedproduct_shelfcode()helper, but the value is dropped before theshop_order_basketinsert (ecommercen/helpers/eshop_helper.php:110-125) and the table has no column for it (database/initial/initial.sql:1425-1450). Shelf codes on an order are always resolved live throughshop_product.shelfcode_id, so a later rename or delete does change what a historical order displays. - Backend-only on Product REST --
shelfcodeIdonProductResourceis only exposed when the request context is backend (Resource.php:107-108), hiding warehouse location data from the storefront. - No MUI, no feeds, no Solr -- shelf codes are not translated, not indexed, and not published externally.
Known Issues & Security Gaps
Modern— RESOLVED.WriteService::delete()skips soft-dereferencesrc/Domains/Product/Shelfcode/WriteService.php:47-54now callsWriteRepository::nullProductReferences()(src/Domains/Product/Shelfcode/Repository/WriteRepository.php:18-23) inside atransactional()block before deleting the shelf row, matching legacy behavior and adding the bonus of atomic execution (the legacy path issues the two statements outside a transaction).- No DB-level FK constraint --
shop_product.shelfcode_idhas noFOREIGN KEYdeclaration, so orphans cannot be prevented at the database layer. - Legacy
trim|numericvalidation vsVARCHAR(255)column -- the admin form appliesnumericto non-empty submissions ofshelfcode_value(Adv_shelfcodes_admin.php:107), but nothing at the DB or modern REST layer enforces this. Direct inserts or RESTPOSTs can store arbitrary strings. An empty submission bypasses the rule entirely -- see Known Issues #10. - No uniqueness constraint -- neither the DB index nor the modern
Validator.phpenforces unique codes, so duplicates are allowed at every layer. - Default
codevalue is literal'0'-- brand-new rows created without specifyingcodebecome shelf "0", which can collide with legitimate codes. RBAC divergence between layers— RESOLVED (closed as Advisable-com/ecommercen#54 — not-a-bug). Earlier read ofrest_policies.phpflagged the missingAUTH_ROLE_ADVISABLEin the shelfcode roles array as a divergence. It is not. ADVISABLE is a project-wide superuser role that auto-bypasses every roles array in REST middleware (src/Rest/Middleware/AuthorizationMiddleware.php:69,src/Rest/Middleware/RequestContext.php:47-49); the convention atrest_policies.php:33-34is that ADVISABLE is never listed in roles arrays. Effective access on the REST endpoint matches the legacy controller's allow-list.— RESOLVED. The code was removed wholesale (not fixed in place) byAdv_orders_admin.php:1082-1085copy-paste bug5771bfca7("fix(security): remove create_export method from Adv_orders_admin", 2026-04-06), which also removed a SQL-injection sink in the same method.create_exportandset_export_datano longer exist anywhere in the repo. The marketplace snapshot path was never affected -- all four order fake-cart builders (three marketplace importers plus the admin order builder, see Marketplace Order Imports) use the globalproduct_shelfcode()helper, which is correct.OA schema name mismatch on Product relation— RESOLVED (closed as Advisable-com/ecommercen#92, 2026-04-15). Both sides now agree onProductShelfcodeResource--src/Rest/Product/Resources/Product/Resource.php:63(new OA\Property(property: 'shelfcode', ref: '#/components/schemas/ProductShelfcodeResource', nullable: true)) andsrc/Rest/Product/Resources/Shelfcode/Resource.php:11(schema: 'ProductShelfcodeResource'). The original entry's citation was also line-drifted -- the annotation is at:63, not:46.product_shelfcode()is computed then discarded on every order line -- all four order fake-cart builders (ecommercen/eshop/models/Adv_skroutz_orders_model.php:565,Adv_shopflix_orders_model.php:374,Adv_public_orders_model.php:253,Adv_order_model.php:2009) set ashelfcodekey via the deprecated global helper (ecommercen/helpers/eshop_helper.php:13-25), which runs an extra joined query per line item. The key is dropped by the basket column mapper (ecommercen/helpers/eshop_helper.php:110-125),shop_order_baskethas no column for it (database/initial/initial.sql:1425-1450), and no code repo-wide reads it. Net effect: a wasted DB round-trip per line item on every marketplace import and POS order, and the appearance of a historical shelf-code snapshot that does not exist.- Empty shelf-code submission bypasses the
numericrule and inserts a blank code -- the legacy form registersshelfcode_valuewithoutrequired(trim|numericonly,ecommercen/eshop/controllers/Adv_shelfcodes_admin.php:107). CodeIgniter's validator skips every non-required/isset/matchesrule when the input is empty (system/libraries/Form_validation.php:557-565), sonumericnever runs on a blank submission. The controller then builds['code' => '']and callsadd_record()(Adv_shelfcodes_admin.php:54-55,ecommercen/eshop/models/Adv_shelfcodes_model.php:73-77), inserting an explicit empty string. The DDL default of'0'(database/initial/initial.sql:1826) does not help here -- a default only applies when the column is omitted from the INSERT, not when''is explicitly supplied. The modern write path does not close the gap either:src/Domains/Product/Shelfcode/Validator.php:9-25is an empty stub with no length or emptiness check. This is distinct from Known Issue 5, which covers the omitted-code-column case where the DDL default of'0'does apply -- here the value is an explicit empty string, not an omission.
Edge Cases & Non-Integration
- No marketplace feeds -- shelf codes are never published in marketplace XMLs. No references in
src/Feeds/orecommercen/feeds/. See IN-01 Feed Generation. - No Solr indexing -- no references in
ecommercen/search/; shelves cannot be used as a storefront facet. - No multi-language support -- no
*_muitable exists, andListRequest.phpexplicitly documents this ("Shelfcode does not have MUI translations"). - No audit trail -- the legacy model does not write to any history / journal table; edits and deletes leave no record.
- No batch import -- there is no CSV / Excel / ERP import path dedicated to shelf codes; they can only be created one at a time via the legacy admin form (
ecommercen/eshop/controllers/Adv_shelfcodes_admin.php:48-70) or the RESTstoreendpoint. The product admin cannot create them: everyshelfcode_valueinput is aform_dropdownover existing codes, andAdv_products_adminonly reads the model viagetCombo()(ecommercen/eshop/controllers/Adv_products_admin.php:147,:689,:749,:806,:1124,:1848).
Changelog Highlights
docs/changelog/Changelog.4.99.md:1623-1629-- Shelfcode Domain + REST layer introduced in 4.99.0.docs/changelog/Changelog.4.99.md:691--shelfcodeIdon Product REST resource restricted to backend context.docs/changelog/Changelog.4.99.md:1177-- removal of the unused$tableShelfCodesproperty fromAdv_product_model(legacy cleanup). The identically-named shelf-code join property inAdv_order_basket_modelis still live (ecommercen/eshop/models/Adv_order_basket_model.php:22, used at:384,:411,:505,:532) -- only the unreferenced duplicate was removed.
Related Flows
- AD-02 Product Management Admin -- products are assigned a shelf code via the create/edit/clone/ERP-import forms, the
shelveslist filter, and thealter_shelfcodebatch action. - AD-03 Order Management Admin -- shelf codes surface on admin order detail, build summary, and logistics packing slip views during picking.
- IN-01 Feed Generation -- shelf codes are intentionally not included in marketplace feeds.