Skip to content

POS / Phone Order Creation

Flow ID: AD-54 | Module(s): eshop | Complexity: Medium Last Updated: 2026-05-29

Business Context

The "POS" (Point of Sale) / phone order screen lets administrators create orders on behalf of customers who place them by phone, in person, or via any channel outside the storefront. Despite the label, POS is not a separate module — it is the exact same add() method of Adv_orders_admin used for any manual or walk-in order. Only the ScribeHow tooltip, the dashboard quick tile, and an admin menu label tie the URL to the "POS" name.

The admin selects or creates a customer, searches for products via AJAX, picks shipping/billing addresses, chooses a payment method, and submits. On submit, the controller rebuilds a "fake cart" from POST inputs, runs a relaxed version of the checkout pipeline, and persists the order with a distinct set of default values (webignore=1, status=PENDING_ACCEPTED, order_currency=EUR, immediate stock decrement).


API Reference

REST Endpoints

No REST API. POS order creation is a legacy admin-only feature.

Legacy Admin Routes

RouteControllerMethodHTTPDescription
orders_admin/addAdv_orders_adminadd()GET/POSTManual / phone / walk-in order creation form and submission (Adv_orders_admin.php:474-582)
orders_admin/repeat/{id}Adv_orders_adminrepeat($orderId)GETPre-fill the add form from an existing order (Adv_orders_admin.php:2913)
orders_admin/edit/{id}Adv_orders_adminedit($id)GET/POSTEdit a persisted order (Adv_orders_admin.php:1131)
  • Application wrapper: application/modules/eshop/controllers/Orders_admin.php (class Orders_admin extends Adv_orders_admin {}).
  • Form view: application/views/admin/orders/create.php (534 lines).
  • ScribeHow embed lives at application/views/admin/orders/create.php:7-14. The tooltip label "Τηλεφωνική Παραγγελία (POS)" comes from scribe.how.orders.pos.title / scribe.how.orders.pos.iframe.link at ecommercen/language/greek/adv_scribe_lang.php:10,111.
  • Admin side menu entry at application/config/admin_menu.php:393-399 (group ORDERS).
  • Admin dashboard quick tile ("manual") at application/views/admin/panel.php:79-86 links to orders_admin/add.

AJAX Search Endpoints

The add-order form (create.php) uses route #1 for its live product picker. The other four exist for related admin pickers or Vue admin pages and are documented here so future work avoids confusing them.

#RouteFile:LinePOST / Body ParamsResponseUsed By
1orders_admin/searchOrdersProductAdv_orders_admin.php:1311-1338queryString, shippingCountry, invoiceHTML fragment application/views/ajaxparts/admin_search_order_product.php — tile grid; tiles call select_this(productCodeId); tiles disabled for inactive / soft-deleted / out-of-stock (unless negative stock allowed) productsPOS create form (primary)
2orders_admin/productAjaxOrderItemAdv_orders_admin.php:1343-1356product_code_id, shippingCountry, invoiceHTML fragment ajaxparts/admin_add_order_item appended into .insert-item; contains hidden inputs product[<productId>][price], product[<productId>][weight], etc.POS create form (append single item)
3orders_admin/productOrderItemsValueAdv_orders_admin.php:1358-1375productCodeIds[] (array), shippingCountry, invoiceJSON [{productCodeId, finalPrice}, ...]POS create form (refresh prices when address / invoice toggles)
4adminrun/search_append_orderapplication/controllers/Adminrun.php:1140-1159queryStringHTML fragment via Adv_admin_live_search_model::liveSearchAppendOrder($term, 21) (Adv_admin_live_search_model.php:9-47). Does not rebind VatForOrderRelated products / bundle editor pickers — not the POS form
5adminrun/apiSearchProductsapplication/controllers/Adminrun.php:959-985JSON body {"data": "<term>"} via inputStream()JSONVue admin pages — assets/admin/js/common/components/CoreAdminProductsSearch.vue:115 via assets/admin/js/api.js:10. Not the POS form

Endpoints #1–#3 rebind the VatForOrder singleton on every request (Adv_orders_admin.php:1317, :1350, :1366) so subsequent price computations reflect the country + invoice flag currently selected in the form.

The DB-level helper Adv_admin_live_search_model::liveSearchAppendOrder() at Adv_admin_live_search_model.php:9-47 matches shop_product_mui.name via LIKE, or product_codes.product_code, shop_product_barcodes.barcode, product_id exactly. The POS create form uses the sibling helper product_model->searchProductsForOrder($term), which returns the same shape but lives on Adv_product_model.


Code Flow

High-Level Sequence

GET  orders_admin/add        -- render form (create.php)
POST orders_admin/add        -- Adv_orders_admin::add() at :474-582
      |
      +--> Read POST product[] array
      +--> $this->validation()                                -- :481, sets form rules
      |    (validation rules defined at :611-671 — relaxed)
      |
      +--> Derive $deliveryCountry from 'sameaddress':         -- :485-497
      |      ORDER_ADDRESS_SHIPPING (0) -> sendto_country
      |      ORDER_ADDRESS_BILLING  (1) -> country
      |      ORDER_ADDRESS_ESHOP    (2) -> 'GR'
      |
      +--> VatForOrder::getInstance()                          -- :498-500
      |      ->setInvoice($paymerch === 'invoice')
      |      ->setDeliverAreaType($deliveryCountry)
      |
      +--> setOrderAddressData()                               -- :502, normalise address fields
      +--> $this->fakeCart = order_model->createAdminFakeCart($products)  -- :503
      |
      +--> form_validation->run()                              -- :507, evaluate the rules set by :481
      +--> order_model->recheckCart($this->fakeCart, true)     -- :509, admin_negative_stock_qty
      |
      +--> Customer resolution                                 -- :513-531
      |      1) customer_id POSTed -> orderAddUpdateCustomerData()
      |      2) lookup by email (or synthesized uniqid() + order.invalid.email)
      |      3) create via add_customer_front()
      |
      +--> processGiftSelections($otherPostElements)           -- :533
      +--> setDeliveryWarningMessage($this->fakeCart, true)    -- :535
      |
      +--> $orderSerial = order_model->create_order_admin($this->fakeCart, $otherPostElements)  -- :541
      +--> addOrderNote()                                      -- :544
      +--> If payway === 'paybybank':                          -- :546-548
      |      createAdminPayByBankOrder($order)
      |        +--> PayByBank::createOrder()
      |        +--> order_model->set_is_paid($serial) on success
      +--> afterOrderSuccessHooks($order->id)                  -- :550, ERP push + cancel traits
      +--> sendOrderCompleteEmail()                            -- :553, gated by registry
      +--> sendSmsSuccess()                                    -- :554, empty stub
      +--> redirect(site_url('orders_admin'))                  -- :556

Fake Cart Builder

The "fake cart" is literal — there is no shared admin cart session, no Fake_cart library, and no AdminCart class. Line items live as DOM hidden inputs in the admin's browser until submit:

html
<!-- application/views/admin/orders/js_add_item.php:13-14 -->
<input name="product[<productId>][price]"  ...>
<input name="product[<productId>][weight]" ...>

On submit, Adv_orders_admin::add() at Adv_orders_admin.php:476-503 reads $products = $this->input->post('product') and calls $this->order_model->createAdminFakeCart($products).

The builder is Adv_order_model::createAdminFakeCart($productsData) at ecommercen/eshop/models/Adv_order_model.php:1471-1571. It:

  1. Reads registry keys OTHER / ORDER_EDIT_KEEP_PRODUCT_VALUES and POINT_SYSTEM / ORDER_EDIT_KEEP_PRODUCT_POINTS.
  2. Loads live product data, VAT, product codes, images, and MUI rows.
  3. Applies Rule 13 "cheapest free" gift adjustments to paid quantity.
  4. Builds per-item arrays: productCodeId, productId, quantity, price, name, subtotal, original_price, discount_price, discount_string, vat_rate_captured, vendor_code, points, options, shelfcode (shelfcode via product_shelfcode()).
  5. Aggregates total_weight, total_items, cart_total, cart_total_vat.
  6. Returns the array.

Storage: the fake cart exists only as:

  1. Hidden inputs in the admin's browser DOM until submit.
  2. The in-memory PHP array $this->fakeCart for the duration of the add() request.
  3. After create_order_admin(), serialize()d into shop_order.cart_contents, then unserialized into shop_order_basket rows by Adv_order_model::processOrder() at Adv_order_model.php:801-843.

The customer's storefront session cart is never read or touched.

Cart Validation

Adv_orders_admin::add() calls $this->order_model->recheckCart($this->fakeCart, true). The $forAdmin = true flag at Adv_order_model.php:1214-1253 switches to the admin_negative_stock_qty config key, which is more permissive than the storefront's negative_stock_qty. It still rejects items where !$product->active || $product->soft_delete.


VAT Rebinding

VatForOrder is a singleton rebuilt multiple times per request so product and cart prices always reflect the currently-selected delivery country and invoice flag.

Files

  • Application stub: application/modules/eshop/libraries/VatForOrder.php (class VatForOrder extends AdvVatForOrder {}).
  • Real implementation: ecommercen/eshop/libraries/AdvVatForOrder.php (extends Singleton).

Rebinding Sites

SiteFile:LineTrigger
Start of add() on submitAdv_orders_admin.php:498-500POST submit
Product search tile gridAdv_orders_admin.php:1317AJAX #1
Append single product itemAdv_orders_admin.php:1350AJAX #2
Refresh item pricesAdv_orders_admin.php:1366AJAX #3
Gifts-for-products recalcAdv_orders_admin.php:1553-1555Gift rule recompute
Edit flowAdv_orders_admin.php:1144,1172-1174setVatForOrderBasedOnOrder($record) at :3339

enableOrderVatManipulation — default FALSE

AdvVatForOrder::__construct() reads config_item('enableOrderVatManipulation'), which defaults to FALSE in application/config/app.php:524.

When false (the default), VatForOrder is a pass-through. POS orders use the standard product VAT rates regardless of delivery country. The country-specific branches below are only reachable in deployments that explicitly enable the flag.

For the full OrderDeliverAreaType enum mapping, dead-code analysis (GreeceReduced unreachable from POS, invoiceVat() TODO), and the complete AdvVatForOrder pipeline, see AD-50 VAT Management.


Order Finalization & Field Defaults

create_order_admin

Signature: Adv_order_model::create_order_admin($fakeCart, $otherPostElements, $calculateTransportationCost = true) at Adv_order_model.php:1581-1821.

Forced field values:

FieldValueNotes
cart_contentsserialize($fakeCart)Unserialized into shop_order_basket rows later by processOrder() at :801-843
webignore$otherPostElements['webignore'] ?? 1Default 1 for manual/phone. See below.
total_qty$totalitems
status'PENDING_ACCEPTED'Default. Exception: payway === 'paybybank''PENDING'
entry_date$otherPostElements['entry_date'] ?? time()
entry_datetimedate('Y-m-d H:i:s', $entry_date)
order_currency'EUR'Hard-coded — POS ignores session currency

After persisting, it calls _proccess_admin_order($adminOrderData), then SmartPoint and DHL/ASAP external-rate handling. Finally it calls removeOrderStock($processedOrder->order_serial) at Adv_order_model.php:1819stock decrement is unconditional at creation time regardless of payway.

php
// Adv_order_model.php:783-793
public function removeOrderStock($orderSerial)
{
    $orderCart = $this->order_basket_model->getQuantitiesByOrderSerial([$orderSerial]);
    foreach ($orderCart as $item) {
        $this->db->set('stock', "stock - {$item->qty}", false)
                 ->where('id', $item->product_code_id)
                 ->update($this->tableProductCodes);
    }
}

Combined with admin_negative_stock_qty, an admin can push stock arbitrarily negative.

webignore — the POS / manual flag

Schema: shop_order.webignore int(1) NOT NULL DEFAULT 0 at database/initial/initial.sql:1310, with index at :1396.

  • Storefront default: 0 (DB default, never overridden).
  • create_order_admin default: 1.

Labels: 'eshop.admin.order.webignore.true' => 'Manual entry', 'eshop.admin.order.webignore.false' => 'Online entry' at ecommercen/language/english/adv_advisable_lang.php:492-493.

Used as a filter discriminator in the order list dropdown at application/views/admin/orders/list.php:286-303 with options Online(0) / Telephone(1) / Marketplace(2). The filter is applied at Adv_order_model.php:2187-2195:

sql
WHERE shop_order.webignore = filters.type

Semantically it means "ignore on web" — it flags rows so any downstream storefront-sync module can skip them. Note that Webrun.php:217,219 uses payway-based logic (COD formatting), not webignore.

Payment / paid status

  • status defaults PENDING_ACCEPTED (vs storefront PENDING).
  • Exception: payway = 'paybybank'status = 'PENDING', then Adv_orders_admin::createAdminPayByBankOrder() at :714-741 calls PayByBank::createOrder() and, on success, order_model->set_is_paid($order->serial).
  • paid / is_paid is not set by create_order_admin itself. It stays at the DB default 0 except for PayByBank success.
  • No auto-mark-paid for delivery, byphone, paid_at_store, or bank_transfer at creation time. Admins mark orders paid later via the batch action set_batch_paid_2 at Adv_order_model.php:1887.
  • COD detection in views: ($payway == 'delivery' || $payway == 'byphone') && $status !== 'PAID' → displayed as "ΑΝΤΙΚΑΤΑΒΟΛΗ ΜΕΤΡΗΤΟΙΣ" (cash on delivery).

Admin payways

Admin payways come from the METHODS.PAYWAY_ADMIN registry key (ecommercen/eshop/libraries/AdvPaymentsRegistry.php:11,367), rendered as radio buttons at application/views/admin/orders/create.php:55-77. This is a different key from METHODS.PAYWAY used by the storefront, so admins can expose payways that are not available online.

Post-submit hooks

php
// Adv_orders_admin.php:541-557
$orderSerial = $this->order_model->create_order_admin($this->fakeCart, $otherPostElements);
$order = $this->order_model->getRecord(['conditions' => ['order_serial' => $orderSerial]]);
$this->addOrderNote($otherPostElements, $order->id);
if ($order->payway === 'paybybank' && !$this->createAdminPayByBankOrder($order)) {
    return;
}
$this->afterOrderSuccessHooks($order->id);  // ERP + cancel traits
$otherPostElements = $this->getDataForOrderCompleteEmail($order->id, $otherPostElements);
$this->sendOrderCompleteEmail($otherPostElements);
$this->sendSmsSuccess($otherPostElements);
redirect(site_url('orders_admin'));
  • The order-complete email is gated by the registry key EMAIL.SEND_EMAIL_TO_CLIENT_FOR_ADMIN_ORDER at Adv_orders_admin.php:2283-2290.
  • sendSmsSuccess() at :1914-1917 is an empty stub kept for client overrides.
  • ERP push runs via OrderForErpHookFireTrait (Adv_orders_admin.php:27).

Customer Linking & Walk-in Creation

Resolution logic at Adv_orders_admin.php:513-531:

php
if (empty($this->input->post('customer_id'))) {
    $mail = $this->input->post('mail') ?: uniqid() . $this->config->item('order.invalid.email');
    $customerId = $this->customer_model->getRegisteredCustomerIdByEmail($mail);
    if ($customerId) {
        $otherPostElements = $this->orderAddUpdateCustomerData($customerId, $otherPostElements);
    } else {
        $customerData = $this->setUpCustomerData(true, $otherPostElements['useAddress'], $mail);
        $otherPostElements['customer_id'] = $this->customer_model->add_customer_front($customerData);
        $otherPostElements = array_merge($otherPostElements, $customerData);
    }
} else {
    $otherPostElements = $this->orderAddUpdateCustomerData($this->input->post('customer_id'), $otherPostElements);
}

3-tier resolution

  1. Explicit customer_id POSTed (from the dropdown) → orderAddUpdateCustomerData() updates the master customer record with the form data.
  2. No customer_id, email provided → look up by email. If found, master-record update as above.
  3. No match (or no email) → synthesize a placeholder email uniqid() . $this->config->item('order.invalid.email') and create a new customer via customer_model->add_customer_front() (ecommercen/eshop/models/Adv_customer_model.php:79-91), which generates an encrypted password from uniqid().

Master-record overwrite gotcha

Selecting an existing customer silently overwrites that customer's master record with the form data, via orderAddUpdateCustomerDatacustomer_model->updateCustomerAdmin(). There is no UI warning. Administrators need to be careful when reusing an existing customer for a one-off address — any differences they enter will persist on the customer.

Customer dropdown data

The dropdown is populated client-side via adminrun/getCustomerData at Adminrun.php:1184-1202, which calls Adv_admin_live_search_model::liveSearchCustomerData() at :138-157.

is_admin checkbox → guest vs registered

setUpNewCustomerData() at Adv_orders_admin.php:2384-2391:

php
return [
    'mail' => $mail,
    'password' => uniqid(),
    'is_guest' => !$this->input->post('is_admin')
];

The create form has a pre-checked is_admin checkbox at application/views/admin/orders/create.php:265 ('checked' => true, label eshop.admin.order.isAdmin):

  • Checked (default) → is_guest = 0 — creates a real account.
  • Unchecked → is_guest = 1 — creates a guest record.

Differences vs Storefront Checkout

AspectStorefront (CF-06 / CF-07)POS / Phone (AD-54)
ValidationFull — required mail, postal, phones, region, AFM/ΔΟΥ/company format, VIESRelaxed (Adv_orders_admin.php:611-671) — only trim/required on name, surname, address, city, county, country, payway. No mail, postal, phones, region required. AFM/ΔΟΥ/company only trim, no format check, no VIES
Stock config keynegative_stock_qtyadmin_negative_stock_qty (more permissive)
Coupon checksFull (checkCoupon() at :2529-2558)Full for existing customers; new POS customers have no audiences yet so audience-restricted coupons fail. checkCouponClone() at :2564-2590 relaxes for clone case
Loyalty points spendAutomaticOnly for existing customers via redeemOtherPostElementsPointsAdd() at :2235-2248loyalty->removePointsFromCustomer($customer_id). New POS customers ignored
Loyalty points earnAutomatic at order completionNOT automatic — requires orders_admin/addPointsToCustomer/{serial} at :1824-1841 (manual per-order) or addPointsSelected batch at :1872-1886. Gated by POINT_SYSTEM.IS_ENABLED
Delivery cost calctransfer_cost()transfer_cost_admin(); zero when useAddress == ORDER_ADDRESS_ESHOP
Default statusPENDINGPENDING_ACCEPTED (except paybybankPENDING)
Stock decrement timingPayway-dependent (deferred for many paywais)Unconditional at creation
order_currencySession currencyHard-coded 'EUR'
webignore01
Delivery warningsetDeliveryWarningMessage($cart, false)setDeliveryWarningMessage($this->fakeCart, true)
Payway sourceMETHODS.PAYWAY registryMETHODS.PAYWAY_ADMIN registry (different key)
Order-complete emailAlways (subject to notification config)Gated by EMAIL.SEND_EMAIL_TO_CLIENT_FOR_ADMIN_ORDER

Data Model

Phone / POS orders are stored in the same shop_order table as storefront orders. The distinguishing flag is:

  • shop_order.webignore — default 1 for POS/manual, 0 for storefront. See the webignore section above.

Unserialized line items land in shop_order_basket exactly like storefront orders.


Architecture

ComponentPathPurpose
Adv_orders_adminecommercen/eshop/controllers/Adv_orders_admin.phpAdmin orders controller — add() at :474-582, AJAX product endpoints at :1311-1375
Orders_admin (wrapper)application/modules/eshop/controllers/Orders_admin.phpThin app-level wrapper, overrideable in client repos
Adv_order_modelecommercen/eshop/models/Adv_order_model.phpcreateAdminFakeCart() at :1471-1571, create_order_admin() at :1581-1821, recheckCart() at :1214-1253, removeOrderStock() at :783-793
Adv_customer_modelecommercen/eshop/models/Adv_customer_model.phpadd_customer_front() at :79-91, customer lookup and update
Adv_admin_live_search_modelecommercen/eshop/models/Adv_admin_live_search_model.phpliveSearchAppendOrder() at :9-47, productItem() at :87-123, liveSearchCustomerData() at :138-157
VatForOrder (stub)application/modules/eshop/libraries/VatForOrder.phpApp-level stub class VatForOrder extends AdvVatForOrder {}
AdvVatForOrderecommercen/eshop/libraries/AdvVatForOrder.phpSingleton VAT calculator — reads enableOrderVatManipulation (default FALSE → pass-through)
Admin menuapplication/config/admin_menu.php:393-399Sidebar entry for orders_admin/add, group ORDERS
Dashboard tileapplication/views/admin/panel.php:79-86"manual" quick-tile link
Form viewapplication/views/admin/orders/create.php534-line form; AJAX driven
Line-item templateapplication/views/admin/orders/js_add_item.php:13-14Hidden-input template for fake cart rows

Configuration

  • Admin menu entry: application/config/admin_menu.php:393-399 (group ORDERS).
  • VAT flag: enableOrderVatManipulation in application/config/app.php:524 (default FALSE — POS VAT is pass-through).
  • Admin-only payway registry key: METHODS.PAYWAY_ADMIN (AdvPaymentsRegistry.php:11,367).
  • Invalid email domain: $this->config->item('order.invalid.email') — appended to uniqid() when no mail is provided.
  • Stock permissiveness: admin_negative_stock_qty config key (vs storefront negative_stock_qty).
  • Email gate: registry EMAIL.SEND_EMAIL_TO_CLIENT_FOR_ADMIN_ORDER (Adv_orders_admin.php:2283-2290).
  • Loyalty gate: POINT_SYSTEM.IS_ENABLED.
  • Edit-mode preservation: OTHER.ORDER_EDIT_KEEP_PRODUCT_VALUES, POINT_SYSTEM.ORDER_EDIT_KEEP_PRODUCT_POINTS (read by createAdminFakeCart).

RBAC

All POS access gates are enforced in the constructor at Adv_orders_admin.php:39-47:

php
[AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, AUTH_ROLE_ORDERS]

Role constants from application/config/constants.php:99-108:

ConstantValue
AUTH_ROLE_ADVISABLE1
AUTH_ROLE_ADMIN3
AUTH_ROLE_ORDERS6

The same gate is applied at the menu level (admin_menu.php:397). There are no per-method overrides — the constructor covers all methods including the AJAX endpoints. Users whose only roles are PRODUCTS, CMS, MARKETING, REPORTING, MEDIA, or DEVELOPER cannot place phone orders.


Receipts / Printing

There is no thermal printer, ESC-POS, PrintNode, Epson / Star, or receipt-printer integration anywhere in the codebase. Searches for escpos, ESC/POS, receipt_printer, PrintNode, star_printer, and thermal print return only 'transporters.label.thermal.printing' => 'Thermal Printing' at ecommercen/language/english/adv_advisable_lang.php:2263, which labels a transporter voucher option — not a POS receipt printer.

All "printing" is browser-side: HTML or PDF rendering followed by the browser's native Print dialog.

MethodFile:LinePurpose
invoice($orderSerial)Adv_orders_admin.php:777-829HTML page with Picqer barcode and optional voucher embed (application/views/admin/orders/invoice*)
printSelected($selected)Adv_orders_admin.php:1763-1790invoice_batch view; if printSelectedAsPdf, renders via Dompdf and streams as orders_<datetime>.pdf
download_invoice($orderSerial)Adv_orders_admin.php:3427Dompdf PDF download
publicDownloadPdf($orderCode)Adv_orders_admin.php:3350Public PDF download
printVoucher($providerId, $voucher)Adv_orders_admin.php:2087Transporter voucher
printVouchersPdf(...)Adv_orders_admin.php:2065Batch voucher PDFs
printAcsPrickUpList(...)Adv_orders_admin.php:2438ACS pickup list

If a client deployment needs a thermal receipt printer, it has to be added from scratch in the client repo — no upstream scaffolding exists.


Client Extension Points

  • Override controller: extend Orders_admin in application/modules/eshop/controllers/ (or the client repo equivalent) to customise any add() step.
  • Override fake-cart builder: override Adv_order_model::createAdminFakeCart() in a client order model to inject custom line-item fields.
  • Custom validation: override validation() at Adv_orders_admin.php:611-671 for stricter field requirements.
  • Custom address handling: override setOrderAddressData().
  • Custom SMS on success: override the empty sendSmsSuccess() stub at :1914-1917.
  • Custom VAT logic: override the VatForOrder application stub or flip enableOrderVatManipulation to TRUE and rely on AdvVatForOrder::vat() / invoiceVat() (note the latter is // TODO not yet implemented).

Business Rules

  1. POS is not a separate module. It is the same Adv_orders_admin::add() used for phone, walk-in, and manual entry. Only the ScribeHow embed, dashboard tile, and menu label tie the URL to the "POS" name.
  2. Fake cart is literal. No session cart, no AdminCart, no persistence until submit. Items live as hidden DOM inputs, then as an in-memory PHP array, then serialized into shop_order.cart_contents.
  3. webignore = 1 is the POS/manual flag and drives the Telephone vs Online filter in the order list.
  4. VAT is pass-through by default. enableOrderVatManipulation defaults FALSE, so POS orders always use standard product VAT rates regardless of delivery country. Greek-island reduced VAT, VIES, and digital-product branches are coded but unreachable from POS.
  5. invoiceVat() is TODO — marked // TODO not yet implemented in AdvVatForOrder.
  6. Status defaults PENDING_ACCEPTED, not PENDING (except when payway === 'paybybank').
  7. Stock is reserved at creation, regardless of payway — the intentional anti-overselling model shared with the legacy storefront (restored on abandonment by the AdvCancelIncompleteOrders cron). The per-product negative-stock allowance is the deliberate opt-out: with it enabled, an admin can place orders into negative stock.
  8. Loyalty points are not auto-awarded for POS orders — an admin must click "Add points" per order or run the batch action.
  9. Selecting an existing customer overwrites their master record with the form data. There is no UI warning (silent gotcha).
  10. No thermal printer integration. Printing is HTML/PDF via the browser's Print dialog; no ESC-POS, no PrintNode, no Star/Epson driver.
  11. Currency is hard-coded EUR — POS ignores session currency.
  12. Order-complete email is gated by the registry key EMAIL.SEND_EMAIL_TO_CLIENT_FOR_ADMIN_ORDER.

Known Issues & Security Gaps

  1. enableOrderVatManipulation defaults false — VatForOrder is a pass-through -- POS orders always use standard product VAT rates. The country-aware and invoice-aware VAT branches are unreachable unless a deployment explicitly enables the flag (application/config/app.php:524).
  2. invoiceVat() is // TODO: not yet implemented -- invoice-specific VAT calculation never landed. The method stub exists but returns nothing (ecommercen/eshop/libraries/AdvVatForOrder.php).
  3. Greek island reduced VAT branch is dead code from POS -- no caller in the POS path passes the $greekArea argument required to reach the GreeceReduced branch in AdvVatForOrder.
  4. No thermal/ESC-POS/PrintNode receipt printer integration -- the transporters.label.thermal.printing language string at ecommercen/language/english/adv_advisable_lang.php:2263 labels a transporter voucher option, not a POS receipt printer. No upstream scaffolding for receipt printing exists.
  5. Loyalty points are NOT auto-awarded for POS orders -- admin must manually click per-order via orders_admin/addPointsToCustomer/{serial} (Adv_orders_admin.php:1824-1841) or use the addPointsSelected batch action (Adv_orders_admin.php:1872-1886).
  6. Selecting an existing customer silently overwrites their master record -- orderAddUpdateCustomerData() calls customer_model->updateCustomerAdmin() with the form data, with no UI warning or confirmation dialog (Adv_orders_admin.php:595-606).
  7. Status defaults to PENDING_ACCEPTED not PENDING -- surprising for anyone comparing POS and storefront order rows. Storefront orders start as PENDING; POS orders skip that state (Adv_order_model.php:1690-1694).
  8. Stock is reserved at creation regardless of payway -- POS calls removeOrderStock() immediately (Adv_order_model.php:1819). This is consistent with the legacy storefront, which also reserves stock at placement for every payway (Adv_checkout.php set_status(..., stockMode='-')); abandoned PENDING orders are restored by the AdvCancelIncompleteOrders cron, and the per-product negative-stock allowance is the opt-out. This reserve-at-placement model is the intentional anti-overselling design — POS conforms to it. (The modern REST checkout deviates by deferring online-payway stock to confirmation; tracked in Advisable-com/ecommercen#282, not a POS issue.)
  9. Currency hard-coded to 'EUR' -- POS ignores the session currency entirely (Adv_order_model.php:1704).
  10. sendSmsSuccess() is an empty stub -- no SMS ever fires for POS orders. The method exists only as a client extension point (Adv_orders_admin.php:1914-1917).
  11. Email gated by EMAIL.SEND_EMAIL_TO_CLIENT_FOR_ADMIN_ORDER -- if the registry key is unset or false, POS customers receive no order confirmation email (Adv_orders_admin.php:2283-2290).