Skip to main content
Version: Thelia 3

Quick Order and Purchase Lists

Added in Thelia 3.2.

A signed-in customer can order by reference: they type or paste references and quantities, the shop checks every line, and the lines it could resolve go to the cart. Orders that come back regularly are saved as purchase lists and loaded into the same table later.

Both features belong to the customer account. A visitor who is not signed in has neither.

What the customer does​

The table takes one row per reference, with a quantity. Rows come from three places: typed one by one, pasted from a spreadsheet, or imported from a .csv or .txt file.

Checking the table writes nothing. Each line comes back with a status, and the customer fixes the lines that need it before adding anything to the cart. Only the lines the last check called resolved, and that were not edited since, reach the cart.

A purchase list is filled from the cart, from a past order, or from the quick order table. Opening a list loads its lines into the table, checked, where they can be edited and saved back. A reference the shop no longer sells stays on the list and shows as unknown, so the customer sees which references an old list still carries.

A list can be renamed, duplicated and deleted.

How a reference is resolved​

The shop looks for each reference in this order:

  1. On the sale elements, by reference and by EAN code.
  2. When no sale element carries it, on the products, by product reference. The line then goes to the default sale element of the product, or its first one when none is the default.

Case is ignored. Non-breaking spaces and the invisible characters a copy from a spreadsheet brings along are removed, and runs of spaces become one. A reference given twice for the same sale element is kept once, with the quantities added up.

Only visible sale elements of visible products are searched. A product hidden from the customer by a reserved sale answers as an unknown reference, so the answer never reveals that it exists.

Each line ends up with one of five statuses:

StatusMeaning
resolvedOne sale element, priced and in stock. The line can go to the cart.
ambiguousSeveral sale elements carry the reference. The customer picks one.
unknownNothing the customer may order carries the reference.
unavailableThe sale element exists but cannot be sold: out of stock, or without a price.
quantity_refusedThe quantity asked for is more than the stock holds.

The core gives every sale element of a product the product's reference by default, so ambiguous is common. The line then lists its candidates with their attributes. When they all belong to one product, the default sale element is preselected. The choice is sent back with the line as a productSaleElementsId, which must be one of the candidates.

The stock checks follow the check-available-stock setting, and a virtual product is never short of stock. Prices are unit prices with the customer's discount included.

Adding to the cart resolves the lines again rather than trusting the table the browser holds. The resolved lines are added in one transaction, which rolls back on an unexpected error. A reference already in the cart adds to its quantity. A line the cart refuses comes back marked as not added, and the other lines stay in the cart.

Limits​

LimitValue
Lines checked or added at once500
Purchase lists per customer100
Lines per purchase list500
Quantity per line999 999
Length of a reference255 characters
Length of a list title255 characters
Quick order requests per customer30 a minute

The last limit is the quick_order_per_customer rate limiter, a sliding window declared by the core. Checking a table, adding it to the cart and loading a list into the table each count as one request, whether they come from the theme or from the API. Every answer carries titles, prices and stock levels, and the cap keeps a signed-in account from reading the whole catalog through it.

Flexy adds its own bounds on pasted text and imported files: 100 000 bytes at most, and the 500 line limit above. The file is read in the browser and sent to the server as text, the same way a paste is.

In Flexy​

Flexy 1.2 renders both features in the customer account, which links to them as "My purchase lists" and "Quick order":

PageRoutePath
Quick order tableaccount_quick_order/account/quick-order
Purchase listsaccount_purchase_lists/account/purchase-lists
One purchase listaccount_purchase_list/account/purchase-lists/{listId}

The table is the QuickOrderTable live component (components/Organisms/QuickOrderTable). It checks the table only on an explicit action: the check button, the button that imports pasted lines, or the choice of a file.

The cart page and the order page of the account carry a SaveToPurchaseList form, which saves the cart or the order into a new list or adds it to an existing one.

A pasted line holds a reference then a quantity, separated by a tab (what a spreadsheet copies), a semicolon or a comma. Blank lines are skipped. A first line of two cells whose quantity is not a whole number is read as a header. A line that cannot be read is reported with its line number instead of being dropped.

API​

The front API serves both features to a signed-in customer (ROLE_CUSTOMER):

OperationPurpose
POST /api/front/account/quick-order/resolveCheck lines. Writes nothing.
POST /api/front/account/quick-order/{cartId}/addAdd the resolved lines to a cart of the account.
GET /api/front/account/purchase-listsThe lists of the account, without their lines.
POST /api/front/account/purchase-listsCreate a list, with optional first lines.
GET /api/front/account/purchase-lists/{id}A list and its lines.
GET /api/front/account/purchase-lists/{id}/tableLoad a list as a checked table, in the format of resolve.
PATCH /api/front/account/purchase-lists/{id}Rename a list.
DELETE /api/front/account/purchase-lists/{id}Delete a list.
POST /api/front/account/purchase-lists/{id}/duplicateCopy a list.
POST /api/front/account/purchase-lists/{id}/itemsAdd lines after the current ones.
PUT /api/front/account/purchase-lists/{id}/itemsReplace the lines of a list.
POST /api/front/account/purchase-lists/from-cart/{cartId}Save a cart as a new list.
POST /api/front/account/purchase-lists/from-order/{orderId}Save a past order as a new list.

The body of resolve, add and the two items operations is the same:

{
"lines": [
{ "reference": "TSHIRT-01", "quantity": 3 },
{ "reference": "3760123450012", "quantity": 1 }
]
}

A cart, an order or a list of another account answers 404, the same answer as one that does not exist. A request over the rate limit answers 429, and a line the shop cannot take (no reference, a quantity below one, more than 500 lines) answers 422.

For developers​

PieceWhere
Quick order entry pointThelia\Domain\QuickOrder\QuickOrderFacade: resolve(), resolvePurchaseList(), addToCart()
Purchase list entry pointThelia\Domain\CustomerList\PurchaseListFacade
Resolution rulesThelia\Domain\QuickOrder\Service\ReferenceResolver
Line statusesThelia\Domain\QuickOrder\Enum\LineStatus
Input lines and their limitsThelia\Domain\Catalog\DTO\ReferenceQuantityLines
Who may read or change a listThelia\Domain\CustomerList\Service\PurchaseListAccessPolicy
List eventsTheliaEvents::PURCHASE_LIST_CREATE, PURCHASE_LIST_UPDATE, PURCHASE_LIST_DELETE

The API and the theme both call the two facades, so they share the rules and the rate limit. The lists are stored in the customer_list and customer_list_item tables.