Skip to content

Admin Order Management

Flow ID: AD-03 Module(s): eshop Complexity: Very High Last Updated: 2026-04-08

Business Context

Order management covers the full order lifecycle: viewing, status transitions, voucher generation for shipping providers, shipment closure, invoice generation, and order editing (which is actually clone + cancel). The status state machine governs stock adjustments, payment reconciliation, and loyalty point handling.

Entry Points

TypePath / TriggerControllerMethod
AdminOrder listingecommercen/eshop/controllers/Adv_orders_admin.phpindex()
AdminOrder detailsameview($id)
AdminEdit ordersameedit($id)
AdminBatch cancelsamesetBatchCanceled()
AdminCreate vouchersamesetPendingWithVoucher()
AdminClose shipmentssameclose_pending_jobs()

Order Status State Machine

StatusMeaningNext
PENDINGAwaiting payment/acceptance→ PENDING_ACCEPTED, CANCELED
PENDING_ACCEPTEDAccepted, payment received (COD)→ PENDING_ACCEPTED_VOUCHER, CANCELED
PENDING_ACCEPTED_VOUCHERVoucher created, awaiting shipment→ SENT / PAID_SENT, CANCELED
PAIDPayment received (prepaid)→ SENT / PAID_SENT
SENTCOD orders shipped→ INVOICED
PAID_SENTPrepaid orders shipped→ INVOICED
INVOICEDCompleted(terminal)
CANCELEDCanceled(terminal)
RETURNReturned(terminal)

SENT vs PAID_SENT determined by payment method: COD (delivery/byphone) → SENT; prepaid (card, paypal, etc.) → PAID_SENT.

Stock Management

TransitionStock EffectignoreStock
Order creation (PENDING)Deducted (-qty)false
Any → CANCELEDReturned (+qty)false
PENDING_ACCEPTED_VOUCHER → SENT/PAID_SENTNo changetrue
Edit (clone + cancel)Cancel returns stock, new order deducts

Stock is adjusted via returnOrderStock() (+qty per basket item to product_codes.stock) and removeOrderStock() (-qty).

Voucher Lifecycle

  1. Create: setPendingWithVoucher() → calls transporter API (ACS, Geniki, DHL, etc.) → saves gtcode + gtjobcode → status = PENDING_ACCEPTED_VOUCHER
  2. Close: close_pending_jobs() → calls transporter pickup/closure API → status = SENT or PAID_SENT (stock NOT adjusted)
  3. Cancel: cancelVoucher() → calls transporter cancel API → clears gtcode/gtjobcode → reverts to PENDING_ACCEPTED or PAID

Supported transporters: ACS, Geniki (v1/v2), DHL, ELTA, Speedex, Center, EasyMail, FIS, BoxNow, DailyCourier, Taxydema (v1/v2), Skroutz, ASAP.

Order Editing

edit() (line 1051-1189) is actually clone + cancel:

  1. Cancel original order (returns stock, rolls back points/coupons)
  2. Create new order with modified items/prices/addresses
  3. Clone order notes, send new confirmation email
  4. Configurable: keepProductPriceForEdit — retain old or use current prices

Cancellation Restrictions

Cannot cancel if:

  • Already CANCELED
  • Payway is bank card provider (eurobank, alpha, ethniki, piraeus, apcopay) — refund not automated
  • PayByBank PAID orders — payment already settled
  • Status = PENDING with non-PayByBank card payment

Business Rules

  1. Stock deducted at creation, returned at cancel -- Never double-adjusted during voucher lifecycle. Stock adjustments happen via returnOrderStock() (+qty) and removeOrderStock() (-qty) on product_codes.stock. (ecommercen/eshop/controllers/Adv_orders_admin.php)
  2. Loyalty points rolled back on cancel -- Both spent points (returned to customer) and earned points (removed) are reversed. (ecommercen/eshop/controllers/Adv_orders_admin.php, cancel flow)
  3. Edit = clone + cancel -- Original order canceled, new order created. Configurable via keepProductPriceForEdit whether to retain old or use current prices. (ecommercen/eshop/controllers/Adv_orders_admin.php, edit() line 1051-1189)
  4. Voucher status blocks cancel -- Must cancel voucher before canceling order. Voucher cancellation calls transporter API and clears gtcode/gtjobcode. (ecommercen/eshop/controllers/Adv_orders_admin.php, cancelVoucher())
  5. Invoice uses order barcode -- Generated via BarcodeGeneratorPNG. (ecommercen/eshop/controllers/Adv_orders_admin.php)
  6. Batch cancel validates each order -- Per-order validation before canceling, respects payment method and status restrictions. (ecommercen/eshop/controllers/Adv_orders_admin.php, setBatchCanceled())
  7. Payment method determines SENT vs PAID_SENT -- COD/byphone orders transition to SENT; prepaid (card, paypal, etc.) transition to PAID_SENT. (ecommercen/eshop/controllers/Adv_orders_admin.php, close_pending_jobs())
  8. Cancel blocked for card payments -- Cannot cancel orders with bank card payment methods (eurobank, alpha, ethniki, piraeus, apcopay) since refunds are not automated. (ecommercen/eshop/controllers/Adv_orders_admin.php, cancellation restrictions)
  9. Order status emails on transition -- Status changes trigger automated email and SMS notifications via the SY-24 Email Dispatch system. The email templates used (order_created, order_update, order_on_store, notify_admin) are catalogued in AD-53 Email Template Viewer.
  10. Coupon usage rolled back on cancel -- If order used a coupon, the coupon usage count is decremented. (ecommercen/eshop/controllers/Adv_orders_admin.php, cancel flow)
  11. Gift stock rolled back on cancel -- Gift product stock is returned when an order containing gift items is canceled. See AD-09 Gift Rules.

Order Lifecycle Hooks

The order system fires HTTP webhook notifications on key lifecycle transitions using OrderForErpHookFireTrait and OrderCancelHookFireTrait (Guzzle HTTP with Bearer token authentication).

Configuration: application/config/api.php under internalApi.apiOrderWebHooks:

  • erpReady -- URL for order-ready webhook
  • cancel -- URL for order-cancel webhook
  • return -- URL for order-return webhook
Hook MethodTrigger PointHTTP CallDescription
afterOrderSuccessHooksOrder successinternalApiOrderForErpHook($orderId)HTTP GET to configured ERP webhook URL -- notifies ERP that order is ready
afterOrderCancelHooksOrder cancellation (controller)internalApiOrderCancelHook($orderId)HTTP GET to cancel webhook URL -- notifies ERP of cancellation
afterCancelOrderHooksOrder cancellation (model-level)internalApiOrderCancelHook($orderId)Same cancel notification fired from model layer
(orphaned)Never firedinternalApiOrderReturnHook($orderId)Exists in code but is not called from any trigger point -- dead code

Note: internalApiOrderReturnHook is implemented but orphaned -- no code path currently invokes it. If return webhook notifications are needed, a trigger must be wired into the return status transition.

Data Model

shop_order Table (Primary)

ColumnTypeDescription
idINT(11) PK AIInternal order ID
cancel_idINT(11)ID of canceled predecessor (for edit/clone). Default 0.
store_idINT(11) NULLPickup store ID (NULL for delivery orders)
transport_idINT(11) NULLTransporter FK (NULL for store pickup)
order_serialVARCHAR(255)Human-readable serial number (generated via createSerial())
webignoreINT(1)0 = online order, 1 = phone/admin order
total_qtyINT(11)Total item quantity in basket
gen_tax_serviceVARCHAR(2)Tax service code (default AM)
order_currencyVARCHAR(3)ISO currency code
currency_rateDECIMAL(11,4)Exchange rate at order time
currency_idINT(11)Currency FK
pricing_*VARCHARBilling address fields: name, surname, address, city, region, postal, county, country, phone, mobile
shipping_*VARCHARShipping address fields: same set as billing
afmVARCHAR(30)Tax ID (VAT number) for invoices
doyVARCHAR(100)Tax office for invoices
professionVARCHAR(255)Profession (invoice)
companyVARCHAR(255)Company name (invoice)
company_addressVARCHAR(255)Company address (invoice)
paywayVARCHAR(255)Payment method slug (e.g., delivery, paypal, stripe, eurobank, paybybank)
viva_paywayVARCHAR(255)Viva Wallet sub-payway code
paymerchVARCHAR(25)receipt or invoice
customer_idINT(11)Customer FK
customer_commentsTEXTCustomer notes
admin_commentsTEXTAdmin notes
courier_commentsTEXTCourier delivery notes
totalDECIMAL(11,2)Subtotal without VAT
total_vatDECIMAL(11,2)Grand total with VAT + transport + delivery - reward + gift packaging
transportation_costDECIMAL(11,2)Shipping cost
delivery_costDECIMAL(11,2)COD fee
statusVARCHAR(30)Order status (see state machine above)
gtcodeVARCHAR(30)Transporter voucher code
gtjobcodeVARCHAR(255)Transporter job/tracking code
gtstatusVARCHAR(255)Voucher status from transporter API
gtflagTINYINT(1)Voucher processing flag
gtdatetimeDATETIMEVoucher creation timestamp
gtcountINT(3)Voucher request attempt counter
notesMEDIUMTEXTLegacy order notes
charge_toTINYINT(1)Charge recipient flag
entry_dateVARCHAR(30)UNIX timestamp string of order creation
entry_datetimeDATETIMEOrder creation datetime
canceled_dateDATETIME NULLCancellation timestamp
ip_addressVARCHAR(45)Client IP at order time
user_agentTEXTBrowser user agent
admin_order_checkINT(1)1 = admin has viewed this order
is_email_sentTINYINT(1)Status email sent flag
is_paidTINYINT(1)Payment confirmed by gateway (0=unknown, 1=paid)
paidTINYINT(1)Admin-toggled paid flag (differs from is_paid)
is_visibleTINYINT(1)Customer-facing visibility
invoised_date_timeDATETIME NULLInvoice generation timestamp
printed_date_timeDATETIME NULLPrint timestamp
sent_date_timeDATETIME NULLShipment closure timestamp
sms_statusTINYINT(1) NULLnull=no SMS, 1=requested, 2=sent success, 3=sent fail, 4=pending callback
coupon_idINT(11) NULLApplied coupon FK
coupon_valueDECIMAL(11,2)Coupon discount amount
points_spendINT(11)Loyalty points redeemed
points_rewardDECIMAL(11,2)Cash value of points reward
points_addedTINYINT(1)0=product points not added to customer, 1=added
skroutz_refererTINYINT(1) NULLReferrer tracking code
delivery_warningTINYINT(1)Delivery delay warning flag
delivery_warning_msg_statusTINYINT(1)0=not sent, 1=sent, 2=admin opted out
meta_dataVARCHAR(255)Serialized extra data
remindTINYINT(1)Reminder flag
remind_atDATETIME NULLReminder scheduled time
pbb_payment_codeVARCHAR(255)PayByBank payment reference
tran_ticketVARCHAR(32)Viva Wallet transaction ticket
providerVARCHAR(50)Marketplace provider tag (skroutz_smart_cart, shopflix_marketplace, etc.)
invoice_pdfVARCHAR(255)Uploaded invoice PDF filename
gift_packagingTINYINT(1)Gift wrapping enabled
gift_packaging_messageTEXTGift wrapping message
gift_packaging_costDECIMAL(11,2)Gift wrapping cost
json_invoiceLONGTEXTJSON invoice data (e-invoicing)

Indexes (22 indexes for query optimization):

  • order_serial -- unique order lookup
  • status, entry_datetime, status_entry_datetime -- listing and filtering
  • customer_id, customer_id_status_is_visible -- customer order history
  • cancel_id -- edit chain lookup
  • gtcode_gtflag_gtcount -- voucher processing queries
  • paid, sms_status, coupon_id, skroutz_referer -- filtering
  • store_id, store_payway_customer, store_customer -- store-based queries
  • transport_id -- transporter filtering
  • delivery_warning_msg -- composite for delivery delay job
  • remind_remind_at -- reminder job
  • points_added -- loyalty points job
  • status_invoised_date_time -- invoice reporting

shop_order_basket Table

ColumnTypeDescription
idINT(11) PK AIBasket item ID
order_idINT(11)FK to shop_order.id
product_code_idINT(11)FK to product_codes.id (SKU)
product_vatDECIMAL(11,2)VAT percentage at time of order
priceDECIMAL(11,2)Unit price (VAT-inclusive)
item_pointsINT(11) NULLLoyalty points per unit
qtyINT(11)Quantity ordered
subtotalDECIMAL(11,2)Line total (price * qty)
discount_stringVARCHAR(10)Discount label (e.g., 10%)
discount_priceDECIMAL(11,2)Discounted unit price
original_priceDECIMAL(11,2)Pre-discount unit price
gift_idINT(11) NULLGift rule FK (non-NULL = gift item)
track_typeTINYINT(1)0=normal, smart/AI recommendation types
added_atINT(11) NULLUNIX timestamp when added to cart
track_idVARCHAR(255) NULLRecommendation tracking ID
optionsTEXT NULLSerialized product options (bundles, variations)

Supporting Tables

TablePurpose
shop_order_dhl_vouchersDHL-specific voucher data: dispatch confirmation, tracking number, product code
shop_order_smart_pointSmart point/locker delivery data: transporter_id, shop_id, json_data
shop_order_tagsOrder tag definitions (id, tag name, active flag)
shop_order_tags_lpOrder-to-tag linking table (order_id, tag_id)
shop_order_basket_options_applied_bundlesBundle application records for basket items

Complete Batch Operations Reference

The batch actions system in doBatchActions() dispatches through three method chains: doBatchActionsGeniki() for per-transporter operations, doBatchActionsSystem() for system operations, and doBatchActionsClient() for client-repo overrides.

Per-Transporter Batch Actions (dynamic, one per active transporter)

Action KeyMethodDescription
set_pending_with_voucher_{transporterId}setPendingWithVoucher()Create vouchers via transporter API for selected orders
cancel_jobs_{transporterId}cancelVoucher()Cancel vouchers via transporter API for selected orders
print_vouchers_pdf_{transporterId}printVouchersPdf()Print voucher PDFs for selected orders

System Batch Actions

Action KeyMethodDescription
set_status_canceledsetBatchCanceled()Cancel selected orders with per-order validation, stock return, points rollback
set_paidsetPaid()Mark selected orders as paid
set_unpaidunsetPaid()Unmark selected orders as paid
print_selectedprintSelected()Print invoices for selected orders (HTML or PDF via DomPDF)
print_selected_xlsexportXls()Export selected orders to Excel (XLSX via PhpSpreadsheet)
print_selected_products_xlsexportProductsXls()Export product breakdown from selected orders to Excel
addPointsaddPointsSelected()Add earned loyalty points to customers for selected orders
removePointsremovePointsSelected()Remove earned loyalty points from customers for selected orders
markInformDelayOrdermarkInformDelayOrderSelected()Mark delivery delay notification sent
unMarkInformDelayOrderunMarkInformDelayOrderSelected()Clear delivery delay notification flag
set_order_tagsbatch_action()Assign tags to selected orders (redirect to tag selection UI)
unset_order_tagsbatch_action()Remove tags from selected orders (redirect to tag selection UI)

Client Extension Point

doBatchActionsClient() is an empty hook method that client repos can override to add custom batch actions (e.g., ERP sync, custom exports).

Close Shipments (Transporter Integration)

The close_pending_jobs($transporterId) method implements the shipment closure flow for 15 supported transporter providers:

Provider ClassClosure MethodAPI Integration
ACSclosePendingJobsAcs()ACS issuePickupList() API -- remote pickup request
GT (Geniki v1)closePendingJobsGeniki()Geniki closeOpenJobs() API -- remote closure
GTV2 (Geniki v2)closePendingJobsGenikiV2()GenikiV2 closeOpenJobs() API -- remote closure
DHLclosePendingJobsDhl()DHL API -- remote closure with dispatch confirmation
ELTAclosePendingJobsLocally()Local status update only (no remote API)
SPEEDEXclosePendingJobsLocally()Local status update only
CENTERclosePendingJobsLocally()Local status update only
EASYMAILclosePendingJobsLocally()Local status update only
FISclosePendingJobsLocally()Local status update only
BOXNOWclosePendingJobsLocally()Local status update only
DAILYCOURIERclosePendingJobsLocally()Local status update only
TAXYDEMAclosePendingJobsLocally()Local status update only
SKROUTZclosePendingJobsLocally()Local status update only
ASAPclosePendingJobsLocally()Local status update only
TAXYDEMAV2closePendingJobsLocally()Local status update only

Closure flow:

  1. Fetch all orders with status PENDING_ACCEPTED_VOUCHER for the transporter
  2. Call transporter API (for ACS/Geniki/DHL) or update locally
  3. For each successfully closed order: set status to SENT (COD) or PAID_SENT (prepaid) via orderGetSentStatusForPayWayVoucher()
  4. Send shipment email via adv_mailer->order_has_been_updated()
  5. Set is_email_sent = true

Invoice Generation

The invoice($orderSerial) method generates printable invoices:

  • Loads full order data via getRecordForInvoice()
  • Generates barcode image using BarcodeGeneratorPNG (Code 128 format)
  • Optionally includes Geniki voucher barcode if GT provider is configured
  • Supports batch printing via printSelected() which can output HTML or PDF (DomPDF, A4 format)

Export Capabilities

ExportMethodFormatContent
Order XLSexportXls()XLSX (PhpSpreadsheet)Customer ID, address, phone, postal, weight, COD amount, courier notes
Products XLSexportProductsXls()XLSXProduct ID, name, quantity, order count
Orders CSVexportOrders()CSV (semicolon, UTF-8 BOM)28 columns: order ID/serial, billing/shipping addresses, payway, totals, status
Searched Orders CSVexportSearchedOrders()CSV (semicolon, UTF-8 BOM)Product-level: product code, shelf code, barcodes, VAT, price, qty, vendor, order serial

Order Repeat (Without Cancellation)

The repeat($orderId) method creates a new order from an existing order without canceling the original:

  • Pre-fills form with original order's customer/address/products data
  • Creates new order with fresh entry_datetime but preserves entry_date and skroutz_referer
  • Supports SmartPoint (locker) delivery selection
  • Fires ERP hooks and sends confirmation email/SMS for the new order
  • Does NOT cancel the original order (unlike edit() which does clone+cancel)

Admin Order Creation (POS / Phone)

Manual order creation via Adv_orders_admin::add() — including the fake cart mechanism, 5 AJAX product-search endpoints, POS-specific field defaults (webignore=1, status=PENDING_ACCEPTED, order_currency=EUR, unconditional stock decrement), and customer 3-tier resolution — is documented in AD-54 POS / Phone Orders.

SMS Provider Integration

Status notification SMS is sent via multiple provider integrations (sendSmsForOrder() helper):

ProviderSMS Status Mapping
plivo2 (sent) or 3 (fail)
routeeViberViber first, fallback to SMS; status from provider
routee2 (delivered), 4 (pending), 3 (fail)
bulker4 (pending) or 3 (fail)
yuboto_omni4 (pending) or 3 (fail)
omni_messaging4 (pending) or 3 (fail)
yubotoDirect status from provider

ERP Integration (Farmakon)

The postErpOrdersJob() method queues a FarmakonPostOrders job for ERP synchronization. This is available when FARMAKON.IS_ENABLED registry flag is set. The job processes orders with status PENDING_ACCEPTED or PAID via getOrdersForFarmakon().

REST API Endpoints

All REST endpoints require JWT authentication. Routes support an optional (\w{2})/ language prefix (e.g., /el/rest/...). POST is used for both create and update operations (not PUT).

Order (8 operations)

MethodPathAuthDescription
GET/rest/order/orderJWTList orders (paginated, filterable)
GET/rest/order/order/itemJWTGet order field metadata (schema introspection)
GET/rest/order/order/{id}JWTGet single order by ID
POST/rest/order/orderJWTCreate new order
POST/rest/order/order/{id}JWTUpdate existing order
DELETE/rest/order/order/{id}JWTDelete order
POST/rest/order/order/{id}/cancelJWTCancel order (triggers stock return, points rollback)
GET/rest/order/order/{id}/trackingJWTGet order tracking information

Note (6 operations)

Notes are polymorphic -- the entity_type field supports customer, product, and order scoping. Stored in shop_notes table.

MethodPathAuthDescription
GET/rest/order/noteJWTList notes (paginated, filterable by entity_type)
GET/rest/order/note/itemJWTGet note field metadata
GET/rest/order/note/{id}JWTGet single note by ID
POST/rest/order/noteJWTCreate new note
POST/rest/order/note/{id}JWTUpdate existing note
DELETE/rest/order/note/{id}JWTDelete note

Vat (6 operations)

MethodPathAuthDescription
GET/rest/order/vatJWTList VAT rates (paginated, filterable)
GET/rest/order/vat/itemJWTGet VAT rate field metadata
GET/rest/order/vat/{id}JWTGet single VAT rate by ID
POST/rest/order/vatJWTCreate new VAT rate
POST/rest/order/vat/{id}JWTUpdate existing VAT rate
DELETE/rest/order/vat/{id}JWTDelete VAT rate

For the canonical shop_product_vats schema, delete-guard gap in the modern REST layer, validator gaps, and all related security issues, see AD-50 VAT Management.

Basket (3 operations -- read-only)

Write routes exist in code but are commented out in route config. Basket items are managed through the order lifecycle, not directly via REST.

MethodPathAuthDescription
GET/rest/order/basketJWTList order basket items (paginated, filterable)
GET/rest/order/basket/itemJWTGet basket item field metadata
GET/rest/order/basket/{id}JWTGet single basket item by ID

DhlVoucher (3 operations -- read-only)

Write routes exist in code but are commented out in route config. DHL vouchers are created through the transporter integration flow, not directly via REST.

MethodPathAuthDescription
GET/rest/order/dhl-voucherJWTList DHL vouchers (paginated, filterable)
GET/rest/order/dhl-voucher/itemJWTGet DHL voucher field metadata
GET/rest/order/dhl-voucher/{id}JWTGet single DHL voucher by ID

SmartPoint (3 operations -- read-only)

Write routes exist in code but are commented out in route config. Smart point/locker delivery records are managed through the order creation flow, not directly via REST.

MethodPathAuthDescription
GET/rest/order/smart-pointJWTList smart point delivery records (paginated, filterable)
GET/rest/order/smart-point/itemJWTGet smart point field metadata
GET/rest/order/smart-point/{id}JWTGet single smart point record by ID

Route config: application/config/rest_routes.php -- read routes at lines 700-794, write routes at lines 1320-1346. Controllers: src/Rest/Order/Controllers/ -- Order, Note, Vat, Basket, DhlVoucher, SmartPoint.

Known Issues & Security Gaps

  1. No known issues at this time.

Customer Flows

Admin Flows

Integration Flows

System Flows

Wiki Guides: Job Manager Guide | DHL Guide | Stripe Guide