Skip to content

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 ​

MethodEndpointActionsAuthRoles
GET/rest/product/shelfcodeindex, item, showBackendADMIN, PRODUCTS
POST/rest/product/shelfcodestore, updateBackendADMIN, PRODUCTS
DELETE/rest/product/shelfcodedestroyBackendADMIN, 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 ​

RouteControllerMethodHTTPDescription
eshop/shelfcodes_adminAdv_shelfcodes_adminindex()GETList all shelf codes
eshop/shelfcodes_admin/addAdv_shelfcodes_adminadd()GET/POSTCreate shelf code form
eshop/shelfcodes_admin/edit/{id}Adv_shelfcodes_adminedit($id)GET/POSTEdit shelf code form
eshop/shelfcodes_admin/delete/{id}Adv_shelfcodes_admindelete($id)GETDelete 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 shelves filter populated via shelfcodes_model->getCombo() at line 147, rendered in application/views/admin/products/list.php:194-200.
  • Form integration via shelfcodes_combo render variable at lines 689, 749, 806, and 1848. Consumed by:
    • application/views/admin/products/create.php:583-589
    • application/views/admin/products/update.php:749-762
    • application/views/admin/products/clone.php:662-673
    • application/views/admin/products/add_erp_product.php:571-577
    • application/views/admin/products/new_products_create.php:343-349
  • Save logic defaults shelfcode_id to null (line 392) and sets it from posted shelfcode_value only if non-empty (lines 411-413).
  • Product-level validation: set_rules('shelfcode_value', 'Shelfcode', 'trim') at line 999. Note that shelfcode_value means something different here than in the shelf codes admin: in Adv_products_admin it is the selected shelf's id, assigned straight to shop_product.shelfcode_id (line 412); in Adv_shelfcodes_admin it is the shelf code string written to shop_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. Calls batchMasterUpdate($products, ['shelfcode_id' => ..., 'date_changed' => ...]). Hook afterBatchActionSubmitAlterShelfCode at line 2099. UI in application/views/admin/products/list.php:549-550 and batch_update.php:314-315.
  • Search filter: Adv_product_model::fixAdminSearch() (declared at Adv_product_model.php:2092) accepts a shelves condition and applies WHERE shop_product.shelfcode_id IN (...) at Adv_product_model.php:2180-2182. fixAdminSearch() is called by getProductsAdminResults() (:2344) and getProductsAdminNumRows() (: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 ​

FileRoleNotes
src/Domains/Product/Shelfcode/Repository/Entity.phpEntityFields: int $id, ?string $code
src/Domains/Product/Shelfcode/Repository/Repository.phpRead repositorytable = 'shop_product_shelfcodes'
src/Domains/Product/Shelfcode/Repository/RepositoryConfigurator.phpRelations configgetRelations(): array { return []; } -- no relations
src/Domains/Product/Shelfcode/Repository/WriteRepository.phpWrite repositoryExtends BaseWriteRepository, primary key id; adds nullProductReferences() at lines 18-23
src/Domains/Product/Shelfcode/Service.phpRead serviceall(), item(), get(). As of #650 composes use BuildsFilterSpecifications; (:17) instead of owning buildSpecifications() directly -- see Filter Specification Seam below
src/Domains/Product/Shelfcode/WriteService.phpWrite servicecreate(), update(), delete() (lines 47-54; transactional soft-dereference)
src/Domains/Product/Shelfcode/WriteData.phpWrite DTOSingle ?string $code field, OpenAPI schema name Shelfcode
src/Domains/Product/Shelfcode/Validator.phpValidatorEmpty stub -- no length / format / uniqueness rules
src/Domains/Product/Shelfcode/ListRequest.phpList filter specFilters 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 canonical docs/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 ​

FileRole
src/Rest/Product/Controllers/Shelfcode.phpREST controller (lines 1-150). Extends HandlesRestfulActions, uses HandlesWriteActions. Methods: index, show, item, store, update, destroy. Full OpenAPI annotations.
src/Rest/Product/Resources/Shelfcode/Resource.phpResource transformer. OpenAPI schema ProductShelfcodeResource. Fields: id, code.
src/Rest/Product/Resources/Shelfcode/Collection.phpCollection 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:11 declares ?int $shelfcode_id.
  • src/Domains/Product/Product/WriteData.php:12, 79, 120, 161 accepts shelfcodeId on create / update.
  • src/Rest/Product/Resources/Product/Resource.php:107-108 exposes shelfcodeId only when $this->context?->isBackend() is true -- this field is backend-only on the Product REST resource (see docs/changelog/Changelog.4.99.md:691).
  • src/Rest/Product/Resources/Product/Resource.php:63 declares the optional embedded shelfcode relation's OA schema as ProductShelfcodeResource; it is populated via addResourceToData() 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 ​

FileScope
tests/Integration/Domains/Product/Shelfcode/RepositoryTest.phpIntegration -- repository
tests/Integration/Domains/Product/Shelfcode/ServiceTest.phpIntegration -- read service
tests/Unit/Domains/Product/Shelfcode/ServiceTest.phpUnit -- read service
tests/Unit/Domains/Product/Shelfcode/WriteServiceTest.phpUnit -- write service
tests/Unit/Rest/Product/Resources/Shelfcode/ResourceTest.phpUnit -- Resource transformer
tests/Unit/Rest/Product/Resources/Shelfcode/CollectionTest.phpUnit -- Collection transformer

Architecture ​

ComponentPathPurpose
Shelfcodes_admin (wrapper)application/modules/eshop/controllers/Shelfcodes_admin.php:1-12Empty client-override shim
Adv_shelfcodes_adminecommercen/eshop/controllers/Adv_shelfcodes_admin.php:1-124Legacy admin CRUD, extends Admin_c
Shelfcodes_model (wrapper)application/modules/eshop/models/Shelfcodes_model.php:1-6Empty client-override shim
Adv_shelfcodes_modelecommercen/eshop/models/Adv_shelfcodes_model.php:1-119Legacy model, extends Adv_base_model
Domain Entitysrc/Domains/Product/Shelfcode/Repository/Entity.phpModern entity
Domain Servicesrc/Domains/Product/Shelfcode/Service.phpModern read service
Domain WriteServicesrc/Domains/Product/Shelfcode/WriteService.phpModern write service
REST Controllersrc/Rest/Product/Controllers/Shelfcode.phpREST endpoints
Routesapplication/config/routes.php:376-379Legacy admin routes
REST routesapplication/config/rest_routes.php:382-387,1635-1640REST read + write routes
Admin menuapplication/config/admin_menu.php:591-597Menu registration

Legacy Model Method Inventory (Adv_shelfcodes_model.php) ​

MethodLinesPurpose
get_records($cond, $asrow)24-40List shelf codes with optional conditions
shelfcode_label($shelfcode_id)42-47Return the code string for a given PK
getCombo($emptyFirst = false)55-71Return [id => code] map for form_dropdown()
add_record($data)73-77Insert a shelf code
edit_record($id, $data)85-88Update a shelf code
product_shelfcode($productId)90-97Dead code -- unreachable and broken, see note below
productShelfCodeLabel($productId)103-111Join product -> shelfcode, return label string
delete_record($id)113-118Soft-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 at Adv_order_model.php:2009) call product_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}.id but 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 ​

ViewPurpose
application/views/admin/shelfcodes/list.phpSimple table: ID, Code, Actions
application/views/admin/shelfcodes/create.phpSingle-field form (shelfcode_value)
application/views/admin/shelfcodes/update.phpSame 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.

ColumnTypeNullDefaultDescription
idINT(10) AUTO_INCREMENTNo--Primary key
codeVARCHAR(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 NULL at :1523
  • KEY shelfcode_id (shelfcode_id) at :1574 -- plain non-unique index
  • No FOREIGN KEY constraint. 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 ​

ViewLineContext
application/views/admin/orders/update.php232Admin order detail -- label eshop.admin.shelfcode.shelfcode_label
application/views/admin/orders/build_summary.php28-30Order build summary partial
application/views/admin/orders/invoice_logistics.php13-15Logistics packing slip
application/views/admin/customers/order_history.php20Customer 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:

FileLine
ecommercen/eshop/models/Adv_skroutz_orders_model.php565
ecommercen/eshop/models/Adv_shopflix_orders_model.php374
ecommercen/eshop/models/Adv_public_orders_model.php253
ecommercen/eshop/models/Adv_order_model.php2003

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 .env dependencies, 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-34 documents that ADVISABLE is a superuser role that auto-bypasses every roles array via RequestContext::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 at application/modules/eshop/controllers/Shelfcodes_admin.php:1-12 is where the override lives).
  • Override model: Extend Shelfcodes_model at application/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(...) in custom/.../container.php.
  • Filter/sort behavior (graduated seams): Since #650, Service.php no 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() -- and FilterOperatorMapInterface is DI-aliasable from custom/Domains/container.php. See Filter Specification Seam above, including the fork hazard on visibilityExemptions.

Business Rules ​

  1. Optional assignment -- shop_product.shelfcode_id defaults to NULL; products do not require a shelf code.
  2. Legacy numeric-only enforcement -- the shelf codes admin form validates shelfcode_value with trim|numeric, forcing numeric codes in the UI even though the column is VARCHAR(255).
  3. Default code "0" on insert -- the column default is the literal string '0', so inserts that omit the code field materialize as shelf "0" rather than NULL.
  4. Both delete paths soft-dereference first -- legacy delete_record() (ecommercen/eshop/models/Adv_shelfcodes_model.php:113-118) NULLs every referencing shop_product.shelfcode_id then drops the row; modern WriteService::delete() (src/Domains/Product/Shelfcode/WriteService.php:47-54) does the same via WriteRepository::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.
  5. No shelf code snapshot on order lines -- all four order fake-cart builders compute a shelfcode label via the deprecated product_shelfcode() helper, but the value is dropped before the shop_order_basket insert (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 through shop_product.shelfcode_id, so a later rename or delete does change what a historical order displays.
  6. Backend-only on Product REST -- shelfcodeId on ProductResource is only exposed when the request context is backend (Resource.php:107-108), hiding warehouse location data from the storefront.
  7. No MUI, no feeds, no Solr -- shelf codes are not translated, not indexed, and not published externally.

Known Issues & Security Gaps ​

  1. Modern WriteService::delete() skips soft-dereference — RESOLVED. src/Domains/Product/Shelfcode/WriteService.php:47-54 now calls WriteRepository::nullProductReferences() (src/Domains/Product/Shelfcode/Repository/WriteRepository.php:18-23) inside a transactional() 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).
  2. No DB-level FK constraint -- shop_product.shelfcode_id has no FOREIGN KEY declaration, so orphans cannot be prevented at the database layer.
  3. Legacy trim|numeric validation vs VARCHAR(255) column -- the admin form applies numeric to non-empty submissions of shelfcode_value (Adv_shelfcodes_admin.php:107), but nothing at the DB or modern REST layer enforces this. Direct inserts or REST POSTs can store arbitrary strings. An empty submission bypasses the rule entirely -- see Known Issues #10.
  4. No uniqueness constraint -- neither the DB index nor the modern Validator.php enforces unique codes, so duplicates are allowed at every layer.
  5. Default code value is literal '0' -- brand-new rows created without specifying code become shelf "0", which can collide with legitimate codes.
  6. RBAC divergence between layers — RESOLVED (closed as Advisable-com/ecommercen#54 — not-a-bug). Earlier read of rest_policies.php flagged the missing AUTH_ROLE_ADVISABLE in 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 at rest_policies.php:33-34 is that ADVISABLE is never listed in roles arrays. Effective access on the REST endpoint matches the legacy controller's allow-list.
  7. Adv_orders_admin.php:1082-1085 copy-paste bug — RESOLVED. The code was removed wholesale (not fixed in place) by 5771bfca7 ("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_export and set_export_data no 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 global product_shelfcode() helper, which is correct.
  8. OA schema name mismatch on Product relation — RESOLVED (closed as Advisable-com/ecommercen#92, 2026-04-15). Both sides now agree on ProductShelfcodeResource -- src/Rest/Product/Resources/Product/Resource.php:63 (new OA\Property(property: 'shelfcode', ref: '#/components/schemas/ProductShelfcodeResource', nullable: true)) and src/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.
  9. 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 a shelfcode key 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_basket has 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.
  10. Empty shelf-code submission bypasses the numeric rule and inserts a blank code -- the legacy form registers shelfcode_value without required (trim|numeric only, ecommercen/eshop/controllers/Adv_shelfcodes_admin.php:107). CodeIgniter's validator skips every non-required/isset/matches rule when the input is empty (system/libraries/Form_validation.php:557-565), so numeric never runs on a blank submission. The controller then builds ['code' => ''] and calls add_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-25 is 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/ or ecommercen/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 *_mui table exists, and ListRequest.php explicitly 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 REST store endpoint. The product admin cannot create them: every shelfcode_value input is a form_dropdown over existing codes, and Adv_products_admin only reads the model via getCombo() (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 -- shelfcodeId on Product REST resource restricted to backend context.
  • docs/changelog/Changelog.4.99.md:1177 -- removal of the unused $tableShelfCodes property from Adv_product_model (legacy cleanup). The identically-named shelf-code join property in Adv_order_basket_model is still live (ecommercen/eshop/models/Adv_order_basket_model.php:22, used at :384, :411, :505, :532) -- only the unreferenced duplicate was removed.

  • AD-02 Product Management Admin -- products are assigned a shelf code via the create/edit/clone/ERP-import forms, the shelves list filter, and the alter_shelfcode batch 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.