CoolCMS API

OrderFormTrait

Edit/add form of an order, saved through the API instead of the generic Admin `save()`.

The Admin layer cannot compute catalog prices, VAT or the stock breakdown, so both the AJAX recalculation preview and the final save go through the single orchestration API action api/eshop/order/build (OrderPresenter::internalActionBuild(), the persist flag tells them apart), called directly via $this->apiBridge.

Item, shipping and payment rows carry their own editable price and VAT rate; the catalog only prefills them (itemsPrefill, shippingRefresh, paymentRefresh force-submits) and saving never silently reprices. Edit defaults come exclusively from the order snapshot, never from the catalog.

IMPORTANT: this form never changes the order status; only OrderStatusChangeFormTrait does, via OrderTransitionManager::apply().

Table of Contents

Methods

hasVatControls()  : bool
Returns whether the form may contain VAT controls at all (`withVat` checkbox, row `vat`/ `priceWithVat`/`totalPriceWithVat`, `shippingVat`, `paymentVat`).
shouldShowVatFields()  : bool
Returns the initial visibility of the VAT fields (not of the checkbox, see {@see hasVatControls()}).
createComponentEditForm()  : Form
addOrderBasicFields()  : void
addOrderCustomerContactRefreshButton()  : void
Adds the force-submit button `order-item-calc.js` clicks after the `customerContactId` select changes (`data-refresh-target`, same generic mechanism as `tariffId` -> `shippingRefresh`).
addOrderCustomerFields()  : void
addOrderCustomerRefreshButton()  : void
Adds the force-submit button picker.js clicks after a customer is picked.
addOrderGiftsMultiplier()  : void
Adds the gifts multiplier: row = gift picker + quantity.
addOrderItemsMultiplier()  : void
Adds the items multiplier: row = product set picker + editable price breakdown.
addOrderItemsPrefillButton()  : void
Adds the shared hidden button picker.js clicks after a product set is picked in any row (`OrderItemPickerTrait::orderItemProductSetPicker()` with `submitButton: 'itemsPrefill'`).
addOrderPaymentRefreshButton()  : void
Force-submit after `paymentMethodId` changes (JS clears paymentPrice/Vat before the click).
addOrderPickupPointFields()  : void
Adds the carrier pickup point controls for order edit.
addOrderPickupRefreshButton()  : void
Adds the force-submit button used after a point is picked in the carrier widget.
addOrderRecalculateButton()  : void
Adds the "Recalculate" button.
addOrderSellerFields()  : void
Adds the seller (shop operator) snapshot fields stored on the order.
addOrderSellerRefreshButton()  : void
Adds the hidden force-submit button that picker.js clicks after a seller is picked.
addOrderShippingFields()  : void
Adds shipping/payment with an editable price (without VAT) and rate.
addOrderShippingRefreshButton()  : void
Force-submit after `tariffId` changes (JS clears shippingPrice/Vat before the click).
addOrderStockCheckFields()  : void
Adds the stock check fieldset below "Shipping and payment" (separate so the override of the strict stock gate is visually apart from shipping/payment, which it does not relate to).
addOrderVouchersMultiplier()  : void
Limit from the raw HTTP data (`onAnchor`) plus `$maxCopies` as a second defense.
allowPartialAdminPromptKey()  : string
Returns the effective `4/order.partial.adminAllowed`; missing in DB means "require full stock" (same semantics as the `$value === '1'` fallback in {@see \App\UI\Api\Eshop\Models\Managers\Traits\OrderBuildTrait::resolveAllowPartial()}, where a missing or other value is false).
applyOrderCustomerUserLink()  : void
Syncs the order's user picker (`userId`, {@see \App\UI\Admin\Eshop\Presenters\Traits\Pickers\OrderCustomerPickerTrait}) and `userTypeId` from the customer record's linked user account (`Customer.userId`).
buildOrderPayload()  : array<string, mixed>
Builds the raw payload for `api/eshop/order/build`: no catalog logic here, only a transcription of form values (as in {@see \App\UI\Api\Eshop\Presenters\OrderPresenter::internalActionBuild()}). `lines` carries trusted price/vat/name/code/unit; shipping/payment go as nested `shipping`/`payment` (tariffId + price + vat).
callOrderBuild()  : array<string, mixed>
controlValue()  : mixed
Safely reads a field value from the container (form or multiplier row).
extractOrderWarnings()  : array<int, string>
Returns the `warnings` of the `api/eshop/order/build` response (currently only the code `paymentMethodUnavailable`, see the API `OrderPresenter::internalActionBuild()` docblock) with a runtime guard to `list<string>`, like {@see orderStringKeyedArray()}.
fillOrderCustomerDeliveryFromContact()  : void
Copies the contact's delivery address (`CustomerContact.useDelivery`/`dlv*`) into the order's `useDelivery`/`customerDlv*` fields.
fillOrderCustomerFieldsFromContact()  : void
Overwrites the form's `customer*` fields from the given contact using the same `fieldMap` as the client picker ({@see \App\UI\Admin\Eshop\Presenters\Traits\Pickers\OrderCustomerLookupPickerTrait::orderCustomerLookupFieldMap()}), so the data-key to form-field mapping has a single source of truth.
formComponent()  : IComponent|null
Calls `getComponent()` through the `Container` type, bypassing the faulty vendor annotation `@method BaseControl getComponent(string $name)` on Contributte `BootstrapContainerTrait` (the real component-model signature has 2 arguments and a nullable return; the annotation on a BootstrapForm-typed variable makes phpstan fail).
loadOrderCustomerAccountUser()  : User|null
Loads the user account by id, or null when not found.
onlyPricedLines()  : array<int, array<string, mixed>>
Drops rows without a price from the payload; used only by the shipping/payment refresh, which must not fail on `linePriceRequired` while some items are still unpriced (weight/cartTotal then count only trusted rows). Final save and "Recalculate" send all rows unchanged: a missing price must fail visibly there, not vanish silently.
orderBuildFailureMessage()  : string
Returns the preview error text from a failed `order/build` response. A technical failure (DB/ORM/transport) has an empty `message` (the detail is only in the server log) and a generic translated error is shown instead of an empty line. A domain validation message passes unchanged.
orderControlValues()  : ArrayHash<string|int, mixed>
Returns the form values for the AJAX button handlers.
orderPayloadGifts()  : array<int, array{giftId: int|string, amount?: int}>
orderPayloadId()  : int|string|null
Returns an id for the build payload: canonical becomes int, empty or `0` becomes `null`. Any other value goes on unchanged, so the API (the single authority, {@see \App\UI\Api\Eshop\Models\Helpers\OrderPayloadHelper::findInvalidIdPath()}) rejects it; `intOrNull()` would turn a tampered hidden `'1.9'` into item #1 here.
orderPayloadLines()  : array<int, array{setId: int|string, quantity: int, orderItemId?: int|string, price?: string, vat?: string, name?: string, code?: string, unit?: string, discountPercents?: string}>
Builds the trusted rows: `price`/`vat` are required on the way to the API (missing: hard fail `linePriceRequired` there, no silent catalog fallback here). The form always carries them from prefill/snapshot, so they are just transcribed.
orderPayloadPickupManual()  : array{code: ?string, name: ?string, street: ?string, city: ?string, zip: ?string, country: ?string}|null
Returns the manually entered pickup point from the six text fields. `null` when both `code` and `name` are empty (both are required for a valid point); the API then ignores the manual path and decides only from the raw widget payload.
orderPayloadPickupPoint()  : array<string, mixed>|null
Decodes the hidden `pickupPoint`, which carries the raw JSON object of the widget (in the edit defaults `OrderShipper::$pickupPointData`), so the API gets the same field as the front checkout. Anything that is not a JSON object becomes `null`: the driver would not recognize an array/scalar and the fail-closed guard would turn it into `pickupPointRequired`.
orderPayloadVoucherCodes()  : array<int, string>
orderPreviewFromOrder()  : array<string, mixed>
Builds a deterministic preview snapshot straight from the saved order, without calling `api/eshop/order/build` (GET only, `OrderPresenter::actionEdit()`).
orderSellerDefaultsFromSettings()  : array<string, scalar|null>
Returns the default seller from settings for an order that has no own snapshot (add, or a record from before snapshots). The snapshot is built from the live directory because no other source exists: right for a new order, the best available estimate for an old one, which the admin sees and can overwrite before saving.
orderSellerDefaultsWithNotice()  : array<string, scalar|null>
Returns {@see orderSellerDefaultsFromSettings()} and reports the state to the admin. The flash is part of the contract: the seller is required, so without it the admin would learn of the problem only from a save error, and with empty settings would not know where to look (the shop seller setting).
orderSettingSellerId()  : int|null
Resolves the seller id from settings: `4/invoicer.sellerId`, falling back to `7/invoice.default.sellerId` (invoice defaults `invoice.default.*`), the same chain the invoice issue uses ({@see \App\UI\Api\Eshop\Models\Generators\OrderInvoiceGenerator::settingIntOrNull()}). An empty string in DB means "does not exist" (`is_numeric('')` is false).
orderStringKeyedArray()  : array<string, mixed>|null
Runtime guard to `array<string, mixed>`; the `ApiBridge` response is typed `mixed`.
prefillOrderItemRows()  : void
Picker prefill: for every row with a selected `productSetId` that has no price yet, fetches code/productSetName/unit/vat/price from the catalog (`api/eshop/order/item-prefill`, {@see \App\UI\Api\Eshop\Models\Services\OrderSnapshotBuilder::resolveItemPrefill()}).
recalculateOrderForm()  : void
Calls `build` (persist=false) with the current form data, including untouched fields.
refreshOrderCustomer()  : void
Loads the customer with all contacts, fills the address select and prefills the fields from the first contact (same semantics as `refreshOrderSeller()`).
refreshOrderCustomerContact()  : void
Ownership guard: looks the contact up only among the chosen customer's contacts (`Customer::getContact()`). A foreign or nonexistent `customerContactId` (e.g. the select was filled by AJAX from elsewhere, a race) is silently ignored, no failure.
refreshOrderPayment()  : void
Same logic as {@see refreshOrderShipping()}, for the payment method.
refreshOrderPickupPoint()  : void
Handles the pickup widget selection (`pickupRefresh`): calls `build` (persist=false) with the raw widget object in the `pickupPoint` payload and prefills the six text fields from the snapshot in the response (`order.shipper.pickup*`). Same logic as {@see refreshOrderShipping()}, different field set.
refreshOrderSeller()  : void
Loads the seller snapshot from the Invoicer directory into the form.
refreshOrderShipping()  : void
Calls `build` (persist=false) after the tariff changes, with the same payload as `recalculateOrderForm` but without incomplete rows (no price).
resolveOrderCustomerIdForContactSelect()  : int|null
Returns the `customerId` used to build the items of the `customerContactId` select.
resolveOrderPreviewCurrency()  : Currency|null
Returns the preview currency from `currencyId` in the serialized `order` of the build response, mapped to the entity from `$orderCurrencyEntities` (built in `loadOrderSelectData()`, no extra query).
resolveOrderSellerUseVat()  : bool
Returns whether the shop operator is a VAT payer, read live from the Invoicer seller.
rowValue()  : mixed
saveOrderForm()  : bool
setControlValue()  : void
Force-sets a field value in the container (form or multiplier row) if it exists.
setOrderFormDefaults()  : void
syncPickupControls()  : void
Fills the seven pickup point controls from the shipper snapshot in the `build` response: six text fields, hidden `pickupPoint` with the raw widget object (so saving unchanged keeps exactly what the widget issued) and hidden `pickupPointTariffId` with the tariff the selection belongs to.
wireOrderMultiplierButtons()  : void
Multiplier add/remove buttons, as in {@see GiftFormTrait::addGiftOfferSetFields()}.

Methods

hasVatControls()

Returns whether the form may contain VAT controls at all (`withVat` checkbox, row `vat`/ `priceWithVat`/`totalPriceWithVat`, `shippingVat`, `paymentVat`).

public hasVatControls() : bool

On add it depends on the shop seller being a VAT payer (resolveOrderSellerUseVat()); a non-payer gets no controls, since a live toggle would have nothing to switch. On edit always true: the admin must be able to turn VAT on for an order that lacks it, and off for one that has it.

Return values
bool

shouldShowVatFields()

Returns the initial visibility of the VAT fields (not of the checkbox, see {@see hasVatControls()}).

public shouldShowVatFields() : bool

On add it follows the seller's VAT-payer status (same condition as hasVatControls()); on edit only the saved order.withVat, so an order created with VAT keeps showing VAT fields even if the operator has since turned VAT off, and a withVat=false order never shows them. Called from edit.latte ($presenter->shouldShowVatFields()); order-item-calc.js toggles it live, this is only the server-side initial state.

Return values
bool

addOrderCustomerContactRefreshButton()

Adds the force-submit button `order-item-calc.js` clicks after the `customerContactId` select changes (`data-refresh-target`, same generic mechanism as `tariffId` -> `shippingRefresh`).

private addOrderCustomerContactRefreshButton(Form $form) : void

Prefills the fields from the chosen (not necessarily first) address.

Parameters
$form : Form

addOrderCustomerRefreshButton()

Adds the force-submit button picker.js clicks after a customer is picked.

private addOrderCustomerRefreshButton(Form $form) : void

The server fills the address select and prefills the customer fields from the first contact authoritatively; the client fieldMap on the picker only gives an instant response from the same data.

Parameters
$form : Form

addOrderGiftsMultiplier()

Adds the gifts multiplier: row = gift picker + quantity.

private addOrderGiftsMultiplier(Form $form) : void

Limit from the raw HTTP data (onAnchor) plus $maxCopies as a second defense.

Parameters
$form : Form

addOrderItemsMultiplier()

Adds the items multiplier: row = product set picker + editable price breakdown.

private addOrderItemsMultiplier(Form $form) : void

Modeled on InvoiceFormTrait::addLineControls(): code/quantity/unit/price/vat/ discountPercents are trusted (the admin edits them) plus three read-only client-side computed fields (order-item-calc.js). IMPORTANT: a plain Container inside the Multiplier does not decorate form-control itself, so the class is added by hand on every field.

Parameters
$form : Form

addOrderItemsPrefillButton()

Adds the shared hidden button picker.js clicks after a product set is picked in any row (`OrderItemPickerTrait::orderItemProductSetPicker()` with `submitButton: 'itemsPrefill'`).

private addOrderItemsPrefillButton(Form $form) : void

validationScope([]) keeps the force-submit from blocking on required fields; createForm() lets save through only via update/save, so this never saves.

Parameters
$form : Form

addOrderPaymentRefreshButton()

Force-submit after `paymentMethodId` changes (JS clears paymentPrice/Vat before the click).

private addOrderPaymentRefreshButton(Form $form) : void
Parameters
$form : Form

addOrderPickupPointFields()

Adds the carrier pickup point controls for order edit.

private addOrderPickupPointFields(Form $form) : void

The admin either picks the point in the carrier widget ("pick on map": JS writes the raw object into hidden pickupPoint and clicks pickupRefresh) or types it into six text fields (payload pickupPointManual). Only the server-side driver normalizes either path; JS maps nothing.

IMPORTANT: the controls are always added, regardless of the selected tariff; the template decides whether to render the block ($presenter->orderPickupInfo()), and hidden pickupPoint must be in the HTML even outside that condition, otherwise a skipped hidden widget loses its value.

Parameters
$form : Form

addOrderPickupRefreshButton()

Adds the force-submit button used after a point is picked in the carrier widget.

private addOrderPickupRefreshButton(Form $form) : void

JS writes the raw widget object into hidden pickupPoint and clicks this button. The server sends the payload to order/build (persist=false), the driver normalizes it and the six text fields are prefilled from the response; the driver is the only source of normalization (as with shippingRefresh).

Parameters
$form : Form

addOrderRecalculateButton()

Adds the "Recalculate" button.

private addOrderRecalculateButton(Form $form) : void

validationScope([]) so it does not block on required fields while the admin is still composing the order; it does not save (outside the update/save gate in createForm()), only calls build with persist=false and redraws the preview panel. It never overwrites the form's row prices: the preview is read-only totals.

Parameters
$form : Form

addOrderSellerFields()

Adds the seller (shop operator) snapshot fields stored on the order.

private addOrderSellerFields(Form $form) : void

The sellerId picker only prefills; the texts stay editable because for an old order that is the only way to issue a document with the details valid at the time (the directory already holds newer ones). sellerId is required (mirrors the API guard sellerRequired); setOrderFormDefaults() prefills it from settings on add and on orders without a snapshot. sellerContactId/sellerBankAccountId only record which directory contact/account was the source; the server fills them in refreshOrderSeller(), admins never edit them.

Parameters
$form : Form

addOrderSellerRefreshButton()

Adds the hidden force-submit button that picker.js clicks after a seller is picked.

private addOrderSellerRefreshButton(Form $form) : void

The client fills the text fields from fieldMap, but the server overwrites them authoritatively and also fills sellerContactId/sellerBankAccountId, which the client cannot see. validationScope([]) skips validation (required seller/items do not block the prefill); createForm() lets save through only via update/save, so this never saves.

Parameters
$form : Form

addOrderShippingFields()

Adds shipping/payment with an editable price (without VAT) and rate.

private addOrderShippingFields(Form $form) : void

Edit defaults come from the snapshot (setOrderFormDefaults); changing the tariff/payment method select force-submits shippingRefresh/paymentRefresh (JS data-refresh-target) and the server recomputes the price from the resolver, including free_from, only on prefill, not on save.

Parameters
$form : Form

addOrderShippingRefreshButton()

Force-submit after `tariffId` changes (JS clears shippingPrice/Vat before the click).

private addOrderShippingRefreshButton(Form $form) : void
Parameters
$form : Form

addOrderStockCheckFields()

Adds the stock check fieldset below "Shipping and payment" (separate so the override of the strict stock gate is visually apart from shipping/payment, which it does not relate to).

private addOrderStockCheckFields(Form $form) : void

Overrides the strict stock gate for this save only. IMPORTANT: it is not a checkbox. The API applies the override only when allowPartial is explicitly bool, so a checkbox would always override 4/order.partial.adminAllowed and "leave it to the setting" would be impossible. A tri-state select with an empty prompt sends null, meaning the key is absent from the payload and the setting applies. Keys are words, not '1'/'0': PHP casts numeric string keys to int and 0 is falsy, so "require full stock" would merge with "not chosen".

The prompt is dynamic: it reads 4/order.partial.adminAllowed from the settings context (getSettingContext()->getSettings()) and picks one of two ready NEON texts (no value interpolated into the translation, only a key choice), so the admin sees what the setting currently means.

Parameters
$form : Form

addOrderVouchersMultiplier()

Limit from the raw HTTP data (`onAnchor`) plus `$maxCopies` as a second defense.

private addOrderVouchersMultiplier(Form $form) : void
Parameters
$form : Form

allowPartialAdminPromptKey()

Returns the effective `4/order.partial.adminAllowed`; missing in DB means "require full stock" (same semantics as the `$value === '1'` fallback in {@see \App\UI\Api\Eshop\Models\Managers\Traits\OrderBuildTrait::resolveAllowPartial()}, where a missing or other value is false).

private allowPartialAdminPromptKey() : string
Return values
string

Syncs the order's user picker (`userId`, {@see \App\UI\Admin\Eshop\Presenters\Traits\Pickers\OrderCustomerPickerTrait}) and `userTypeId` from the customer record's linked user account (`Customer.userId`).

private applyOrderCustomerUserLink(Form $form, Customer $customer) : void

userTypeId feeds the API-derived buyerType (OrderBuildTrait::resolveBuyerType()), so it is written directly after a customer switch, not via setControlValue(), which skips null and would leave it stale. When userId is null (customer without an account) the field is left alone, so a manual or anonymous admin choice survives picking a customer record without a link.

The picker label is resolved at render from the current userId value (Picker::render()); {control picker-userId} only has to sit inside the snippet the handler redraws (customerFields, see edit.latte).

Parameters
$form : Form
$customer : Customer

buildOrderPayload()

Builds the raw payload for `api/eshop/order/build`: no catalog logic here, only a transcription of form values (as in {@see \App\UI\Api\Eshop\Presenters\OrderPresenter::internalActionBuild()}). `lines` carries trusted price/vat/name/code/unit; shipping/payment go as nested `shipping`/`payment` (tariffId + price + vat).

private buildOrderPayload(ArrayHash<string|int, mixed> $values, bool $persist) : array<string, mixed>
Parameters
$values : ArrayHash<string|int, mixed>
$persist : bool
Return values
array<string, mixed>

callOrderBuild()

private callOrderBuild(array<string, mixed> $payload) : array<string, mixed>
Parameters
$payload : array<string, mixed>
Return values
array<string, mixed>

controlValue()

Safely reads a field value from the container (form or multiplier row).

private controlValue(Container $container, string $field) : mixed
Parameters
$container : Container
$field : string

extractOrderWarnings()

Returns the `warnings` of the `api/eshop/order/build` response (currently only the code `paymentMethodUnavailable`, see the API `OrderPresenter::internalActionBuild()` docblock) with a runtime guard to `list<string>`, like {@see orderStringKeyedArray()}.

private extractOrderWarnings(array<string, mixed> $response) : array<int, string>
Parameters
$response : array<string, mixed>
Return values
array<int, string>

fillOrderCustomerDeliveryFromContact()

Copies the contact's delivery address (`CustomerContact.useDelivery`/`dlv*`) into the order's `useDelivery`/`customerDlv*` fields.

private fillOrderCustomerDeliveryFromContact(Form $form, CustomerContact $contact) : void

Used by both selection paths (picker and address select) via fillOrderCustomerFieldsFromContact(). useDelivery=false on the contact means no separate delivery address, so customerDlv* are always cleared, not just hidden; otherwise the previous contact's address would linger in the form. ?? '' instead of a plain (string): with useDelivery=true but an empty (null) dlv* field, setControlValue() and its is_scalar guard would skip it silently and keep the old value.

Parameters
$form : Form
$contact : CustomerContact

fillOrderCustomerFieldsFromContact()

Overwrites the form's `customer*` fields from the given contact using the same `fieldMap` as the client picker ({@see \App\UI\Admin\Eshop\Presenters\Traits\Pickers\OrderCustomerLookupPickerTrait::orderCustomerLookupFieldMap()}), so the data-key to form-field mapping has a single source of truth.

private fillOrderCustomerFieldsFromContact(Form $form, CustomerContact $contact) : void
Parameters
$form : Form
$contact : CustomerContact

formComponent()

Calls `getComponent()` through the `Container` type, bypassing the faulty vendor annotation `@method BaseControl getComponent(string $name)` on Contributte `BootstrapContainerTrait` (the real component-model signature has 2 arguments and a nullable return; the annotation on a BootstrapForm-typed variable makes phpstan fail).

private formComponent(Container $container, string $name) : IComponent|null
Parameters
$container : Container
$name : string
Return values
IComponent|null

loadOrderCustomerAccountUser()

Loads the user account by id, or null when not found.

private loadOrderCustomerAccountUser(int $id) : User|null

Admin Manager::find() returns a SingleQuery, not an entity, hence findAll()->first() (as in SellerPickerTrait::loadSeller()). $this->orderCustomerUserManager is injected by OrderCustomerPickerTrait (same presenter, properties shared across traits).

Parameters
$id : int
Return values
User|null

onlyPricedLines()

Drops rows without a price from the payload; used only by the shipping/payment refresh, which must not fail on `linePriceRequired` while some items are still unpriced (weight/cartTotal then count only trusted rows). Final save and "Recalculate" send all rows unchanged: a missing price must fail visibly there, not vanish silently.

private onlyPricedLines(mixed $lines) : array<int, array<string, mixed>>

Takes mixed (read back from the payload typed array<string, mixed>) and narrows the rows itself, so callers need no casts.

Parameters
$lines : mixed
Return values
array<int, array<string, mixed>>

orderBuildFailureMessage()

Returns the preview error text from a failed `order/build` response. A technical failure (DB/ORM/transport) has an empty `message` (the detail is only in the server log) and a generic translated error is shown instead of an empty line. A domain validation message passes unchanged.

private orderBuildFailureMessage(array<string, mixed> $response) : string
Parameters
$response : array<string, mixed>
Return values
string

orderControlValues()

Returns the form values for the AJAX button handlers.

private orderControlValues(Form $form) : ArrayHash<string|int, mixed>

IMPORTANT: every AJAX button handler of this form must read values from the controls, never via $form->getValues(). The helper buttons have setValidationScope([]), so Nette marks controls outside the scope as omitted and Container::getValues() skips them: getValues()->items returns [}], the lines payload is empty and the API reports missing items (recalculate and shipping/payment prefill would always fail with "the order must have at least one item"). Native "Save" validates the whole form, so nothing is omitted there. getContainers() also forces createCopies() (multiplier rows from httpData).

Parameters
$form : Form
Return values
ArrayHash<string|int, mixed> —

same shape getValues() would return (for buildOrderPayload(), unchanged)

orderPayloadGifts()

private orderPayloadGifts(mixed $rows) : array<int, array{giftId: int|string, amount?: int}>
Parameters
$rows : mixed
Return values
array<int, array{giftId: int|string, amount?: int}>

orderPayloadId()

Returns an id for the build payload: canonical becomes int, empty or `0` becomes `null`. Any other value goes on unchanged, so the API (the single authority, {@see \App\UI\Api\Eshop\Models\Helpers\OrderPayloadHelper::findInvalidIdPath()}) rejects it; `intOrNull()` would turn a tampered hidden `'1.9'` into item #1 here.

private orderPayloadId(mixed $value) : int|string|null
Parameters
$value : mixed
Return values
int|string|null

orderPayloadLines()

Builds the trusted rows: `price`/`vat` are required on the way to the API (missing: hard fail `linePriceRequired` there, no silent catalog fallback here). The form always carries them from prefill/snapshot, so they are just transcribed.

private orderPayloadLines(mixed $rows) : array<int, array{setId: int|string, quantity: int, orderItemId?: int|string, price?: string, vat?: string, name?: string, code?: string, unit?: string, discountPercents?: string}>
Parameters
$rows : mixed
Return values
array<int, array{setId: int|string, quantity: int, orderItemId?: int|string, price?: string, vat?: string, name?: string, code?: string, unit?: string, discountPercents?: string}>

orderPayloadPickupManual()

Returns the manually entered pickup point from the six text fields. `null` when both `code` and `name` are empty (both are required for a valid point); the API then ignores the manual path and decides only from the raw widget payload.

private orderPayloadPickupManual(ArrayHash<string|int, mixed> $values) : array{code: ?string, name: ?string, street: ?string, city: ?string, zip: ?string, country: ?string}|null
Parameters
$values : ArrayHash<string|int, mixed>
Return values
array{code: ?string, name: ?string, street: ?string, city: ?string, zip: ?string, country: ?string}|null

orderPayloadPickupPoint()

Decodes the hidden `pickupPoint`, which carries the raw JSON object of the widget (in the edit defaults `OrderShipper::$pickupPointData`), so the API gets the same field as the front checkout. Anything that is not a JSON object becomes `null`: the driver would not recognize an array/scalar and the fail-closed guard would turn it into `pickupPointRequired`.

private orderPayloadPickupPoint(mixed $value) : array<string, mixed>|null
Parameters
$value : mixed
Return values
array<string, mixed>|null

orderPayloadVoucherCodes()

private orderPayloadVoucherCodes(mixed $rows) : array<int, string>
Parameters
$rows : mixed
Return values
array<int, string>

orderPreviewFromOrder()

Builds a deterministic preview snapshot straight from the saved order, without calling `api/eshop/order/build` (GET only, `OrderPresenter::actionEdit()`).

private orderPreviewFromOrder(Order $order) : array<string, mixed>

The shape must be identical to OrderPayloadHelper::serializeOrder(): {snippet orderPreview} reads the same keys whether the preview came from a build (recalculate/ shippingRefresh/paymentRefresh) or from here. No repricing, only reading saved entity snapshots (items/shipper/paymentMethod/vouchers/gifts/totals), exactly as setOrderFormDefaults() sees them.

Parameters
$order : Order
Return values
array<string, mixed>

orderSellerDefaultsFromSettings()

Returns the default seller from settings for an order that has no own snapshot (add, or a record from before snapshots). The snapshot is built from the live directory because no other source exists: right for a new order, the best available estimate for an old one, which the admin sees and can overwrite before saving.

private orderSellerDefaultsFromSettings() : array<string, scalar|null>
Return values
array<string, scalar|null> —

empty = settings have no seller (or none exists for the configured id), so the form stays empty and the required field asks the admin for one

orderSellerDefaultsWithNotice()

Returns {@see orderSellerDefaultsFromSettings()} and reports the state to the admin. The flash is part of the contract: the seller is required, so without it the admin would learn of the problem only from a save error, and with empty settings would not know where to look (the shop seller setting).

private orderSellerDefaultsWithNotice(bool $legacyOrder) : array<string, scalar|null>
Parameters
$legacyOrder : bool

true = edit of an order without its own snapshot; false = add, where prefill is expected and nothing is reported

Return values
array<string, scalar|null>

orderSettingSellerId()

Resolves the seller id from settings: `4/invoicer.sellerId`, falling back to `7/invoice.default.sellerId` (invoice defaults `invoice.default.*`), the same chain the invoice issue uses ({@see \App\UI\Api\Eshop\Models\Generators\OrderInvoiceGenerator::settingIntOrNull()}). An empty string in DB means "does not exist" (`is_numeric('')` is false).

private orderSettingSellerId() : int|null
Return values
int|null

orderStringKeyedArray()

Runtime guard to `array<string, mixed>`; the `ApiBridge` response is typed `mixed`.

private orderStringKeyedArray(mixed $value) : array<string, mixed>|null
Parameters
$value : mixed
Return values
array<string, mixed>|null

prefillOrderItemRows()

Picker prefill: for every row with a selected `productSetId` that has no price yet, fetches code/productSetName/unit/vat/price from the catalog (`api/eshop/order/item-prefill`, {@see \App\UI\Api\Eshop\Models\Services\OrderSnapshotBuilder::resolveItemPrefill()}).

private prefillOrderItemRows(Form $form) : void

Rows where the admin already entered a price are skipped. Resolver query only: no order context, no saving. Quantity is left unchanged (it only feeds the price tier).

Parameters
$form : Form

recalculateOrderForm()

Calls `build` (persist=false) with the current form data, including untouched fields.

private recalculateOrderForm(Form $form) : void
Parameters
$form : Form

refreshOrderCustomer()

Loads the customer with all contacts, fills the address select and prefills the fields from the first contact (same semantics as `refreshOrderSeller()`).

private refreshOrderCustomer(Form $form) : void

A deleted or unselected customer empties the select (prompt only).

Parameters
$form : Form

refreshOrderCustomerContact()

Ownership guard: looks the contact up only among the chosen customer's contacts (`Customer::getContact()`). A foreign or nonexistent `customerContactId` (e.g. the select was filled by AJAX from elsewhere, a race) is silently ignored, no failure.

private refreshOrderCustomerContact(Form $form) : void
Parameters
$form : Form

refreshOrderPayment()

Same logic as {@see refreshOrderShipping()}, for the payment method.

private refreshOrderPayment(Form $form) : void
Parameters
$form : Form

refreshOrderPickupPoint()

Handles the pickup widget selection (`pickupRefresh`): calls `build` (persist=false) with the raw widget object in the `pickupPoint` payload and prefills the six text fields from the snapshot in the response (`order.shipper.pickup*`). Same logic as {@see refreshOrderShipping()}, different field set.

private refreshOrderPickupPoint(Form $form) : void

IMPORTANT: the preview (persist=false) returns no error for a missing or unusable point (contract), so a nonsense widget answer shows only as fields not being prefilled; hence no already filled values are cleared here.

Parameters
$form : Form

refreshOrderSeller()

Loads the seller snapshot from the Invoicer directory into the form.

private refreshOrderSeller(Form $form) : void

Uses the first contact and first bank account (same semantics as SellerPickerTrait::sellerPrefillData()). Values are always overwritten: the admin picked the seller again, so the current details are wanted.

Parameters
$form : Form

refreshOrderShipping()

Calls `build` (persist=false) after the tariff changes, with the same payload as `recalculateOrderForm` but without incomplete rows (no price).

private refreshOrderShipping(Form $form) : void

JS clears shippingPrice/shippingVat beforehand. Without dropping those rows the strict linePriceRequired would block even the shipping prefill. Empty shippingPrice/Vat in the payload makes buildShipperFromRow() compute fresh through ShippingPriceResolver (including free_from), only here, not on final save.

Parameters
$form : Form

resolveOrderCustomerIdForContactSelect()

Returns the `customerId` used to build the items of the `customerContactId` select.

private resolveOrderCustomerIdForContactSelect() : int|null

Evaluated at form construction, not in onValidate:

  • POST/AJAX (save or any force-submit): takes the submitted value from httpData (Nette\Http\Request::getPost()), not from the control, which has not read httpData yet at this point. Otherwise SelectBox drops the submitted address because items were built from an empty customerId (classic dynamic-select trap, see DependentSelectTrait).
  • GET edit: the saved order's customerId (items must contain the saved customerContactId that setOrderFormDefaults() sets).
  • GET add: no customer yet, null, the select stays empty with a prompt.
Return values
int|null

resolveOrderPreviewCurrency()

Returns the preview currency from `currencyId` in the serialized `order` of the build response, mapped to the entity from `$orderCurrencyEntities` (built in `loadOrderSelectData()`, no extra query).

private resolveOrderPreviewCurrency(array<string, mixed>|null $orderData) : Currency|null

null (missing or unknown id) makes the priceFormat filter leave numbers raw, see OrderPresenter::orderCurrencyById().

Parameters
$orderData : array<string, mixed>|null
Return values
Currency|null

resolveOrderSellerUseVat()

Returns whether the shop operator is a VAT payer, read live from the Invoicer seller.

private resolveOrderSellerUseVat() : bool

IMPORTANT: do not read the settings copy 4/useVat; nothing synchronizes it with the seller, so after the company is edited in Invoicer (VAT registered or canceled) the form would offer VAT controls that contradict how the document will be issued. The seller is the authority.

The seller is resolved by the same chain as the invoice generator (OrderInvoiceGenerator::buildFromOrder()): the order snapshot order.sellerId, then 4/invoicer.sellerId, then 7/invoice.default.sellerId; orderSettingSellerId() handles the settings half (including skipping non-numeric or <= 0 values). The seller's tax status as of today is taken from its history (Seller::resolveUseVatOn()): a payer gets VAT controls, an identified person or non-payer does not. The per-document mode at the tax point stays with the composer/generator.

A missing or unconfigured seller yields false (no VAT controls) and is logged; without the log VAT would vanish from the whole screen with no trace of why.

NOTE: not the seller's display flag show_prices_with_vat, which only controls how prices are shown on the front end (the price list is always net).

Return values
bool

rowValue()

private rowValue(mixed $row, string $key) : mixed
Parameters
$row : mixed
$key : string

saveOrderForm()

private saveOrderForm(ArrayHash<string|int, mixed> $values) : bool
Parameters
$values : ArrayHash<string|int, mixed>
Return values
bool

setControlValue()

Force-sets a field value in the container (form or multiplier row) if it exists.

private setControlValue(Container $container, string $field, mixed $value) : void
Parameters
$container : Container
$field : string
$value : mixed

syncPickupControls()

Fills the seven pickup point controls from the shipper snapshot in the `build` response: six text fields, hidden `pickupPoint` with the raw widget object (so saving unchanged keeps exactly what the widget issued) and hidden `pickupPointTariffId` with the tariff the selection belongs to.

private syncPickupControls(Form $form, array<string, mixed>|null $shipper, int|null $tariffId) : void

IMPORTANT: without a pickup code in the snapshot (the tariff needs none, or build cleared it after a tariff change) all seven are emptied; otherwise the previous carrier's point would stay in the fields and the next submit would send it back.

Parameters
$form : Form
$shipper : array<string, mixed>|null

shipping snapshot from order.shipper of the response

$tariffId : int|null

tariff just submitted by the form (not in the build response)

wireOrderMultiplierButtons()

Multiplier add/remove buttons, as in {@see GiftFormTrait::addGiftOfferSetFields()}.

private wireOrderMultiplierButtons(Multiplier $multiplier, string $snippet) : void
Parameters
$multiplier : Multiplier
$snippet : string
On this page

Search results