CoolCMS API

OrderPickupPointApplier
in package

FinalYes

Applies the pickup point of an external carrier to an order.

Shared by the front end checkout (OrderCheckoutManager) and the admin order build (OrderBuildTrait::getBuildData()). The build rebuilds the shipper snapshot and would otherwise lose the pickup* columns, so both paths call this one code.

IMPORTANT: reads only through raw DBAL (loadTariffContext()), never through Api find() or findAll(). getBuildData() runs after the order graph is loaded and every Api read does UnitOfWork::clear(), which would break the loaded graph (see the OrderBuildTrait docblock).

Table of Contents

Properties

$carrierRegistry  : CarrierRegistry
$em  : EntityManagerInterface
$manualPickupPointFactory  : ManualPickupPointFactory

Methods

__construct()  : mixed
apply()  : array{success: false, errorCode: string, message: string}|null
Processes the choice of an external carrier pickup point: normalizes the raw widget payload through the carrier driver (sanitizing happens inside the driver) or the manual admin input, and writes the result to the 1:1 snapshot `OrderShipper.pickup*` and to the order delivery address (like `applyBranchToDeliveryAddress()` for own branches).
applyFromPayload()  : array{success: false, errorCode: string, message: string}|null
Single entry of the admin path `api/eshop/order/build` ({@see \App\UI\Api\Eshop\Models\Managers\Traits\OrderBuildTrait::getBuildData()}): extracts the tariff context from the payload, applies the provenance gate ({@see selectionBelongsToTariff()}) and only then calls {@see apply()}. The decision whether a selection belongs to a tariff lives in a testable service and the payload reaches the applier in exactly one way.
clearDeliveryAddressIfMatchesPickup()  : void
Counterpart of {@see applyToDeliveryAddress()} for a switch from pickup to non-pickup.
loadTariffContext()  : array{externalPickup: bool, shipperId: ?int, scriptName: ?string}|null
Returns `external_pickup` of the tariff and the `script_name` of its carrier in one query.
selectionBelongsToTariff()  : bool
Tells whether a pickup point selection belongs to the given tariff (selection provenance).
applyToDeliveryAddress()  : void
Turns the chosen pickup point into the order delivery address, the same principle as `applyBranchToDeliveryAddress()` for own branches (the schema has no other field for it).
manualFields()  : array{code: mixed, name: mixed, street: mixed, city: mixed, zip: mixed, country: mixed}
Takes the six manual fields; nothing else from the payload is taken over, and {@see \App\UI\Api\Eshop\Carriers\PickupPointSanitizerTrait::buildManualPickupPoint()} sanitizes them.
pickupRequired()  : array{success: false, errorCode: string, message: string}
stringKeyed()  : array<string, mixed>
Narrows the payload to `array<string, mixed>` (not `array_filter(..., ARRAY_FILTER_USE_KEY)`): PHPStan cannot narrow `array<mixed, mixed>` from a bare `is_array()`, while a widget payload is always a JSON object (string keys).

Properties

Methods

apply()

Processes the choice of an external carrier pickup point: normalizes the raw widget payload through the carrier driver (sanitizing happens inside the driver) or the manual admin input, and writes the result to the 1:1 snapshot `OrderShipper.pickup*` and to the order delivery address (like `applyBranchToDeliveryAddress()` for own branches).

public apply(Order $order, array{externalPickup: bool, shipperId: ?int, scriptName: ?string}|null $tariffPickup, mixed $rawPickupPoint, mixed $manualFields, bool $persist) : array{success: false, errorCode: string, message: string}|null

The raw $rawPickupPoint (widget) takes precedence; $manualFields is used only when the raw value is missing or the driver does not normalize it (null). The admin JS empties the raw hidden field when fields are overwritten by hand, so the two paths do not cross.

Fail-closed:

  • With externalPickup=false (the tariff does not need it) both inputs are always ignored silently, even if a client sends them for another tariff: no write, no error. A stale pickup snapshot (a switch from another tariff, or an admin turning off external_pickup on the same tariff) is cleared; OrderSnapshotBuilder clears only when $tariffId changes, so this is a defensive second guard on the same snapshot.
  • With externalPickup=true and no valid PickupPoint obtainable (missing payload, missing Shipper::$scriptName, no driver in CarrierRegistry, or normalization returns null) all cases yield the same single error pickupPointRequired.
  • A carrier without a driver does not block manual input: the admin must be able to type an address where no widget exists; the snapshot carrier is then ManualPickupPointFactory::CARRIER ('manual').
  • A preview ($persist === false) returns no error, because the customer or admin is still choosing.
Parameters
$order : Order
$tariffPickup : array{externalPickup: bool, shipperId: ?int, scriptName: ?string}|null
$rawPickupPoint : mixed

raw widget object (payload pickupPoint)

$manualFields : mixed

manual admin fields (payload pickupPointManual); the front end checkout always passes null here (customers have no manual path)

$persist : bool
Return values
array{success: false, errorCode: string, message: string}|null —

null = OK (either the point was written or the tariff has no pickup)

applyFromPayload()

Single entry of the admin path `api/eshop/order/build` ({@see \App\UI\Api\Eshop\Models\Managers\Traits\OrderBuildTrait::getBuildData()}): extracts the tariff context from the payload, applies the provenance gate ({@see selectionBelongsToTariff()}) and only then calls {@see apply()}. The decision whether a selection belongs to a tariff lives in a testable service and the payload reaches the applier in exactly one way.

public applyFromPayload(Order $order, int $tariffId, array<string, mixed> $payload, bool $persist) : array{success: false, errorCode: string, message: string}|null
Parameters
$order : Order
$tariffId : int
$payload : array<string, mixed>

the whole build payload (reads pickupPoint, pickupPointManual, pickupPointTariffId)

$persist : bool
Return values
array{success: false, errorCode: string, message: string}|null —

null = OK

clearDeliveryAddressIfMatchesPickup()

Counterpart of {@see applyToDeliveryAddress()} for a switch from pickup to non-pickup.

public static clearDeliveryAddressIfMatchesPickup(Order $order, OrderShipper $snapshot) : void

Reconstructs what that method wrote for the last saved point (the same street/city fallback to name as for Balikovna) and clears customerDlv* only on an exact match of all fields; a single difference (the admin edited the address) means it is no longer the pickup address and it stays untouched.

public static (no service state is used). Also called from OrderSnapshotBuilder::buildShipper() and buildShipperFromRow(), where clearPickupPoint() runs on a tariff change, before apply() is reached and the snapshot would already be empty.

IMPORTANT: always call before $snapshot->clearPickupPoint(), which erases the old pickup* values this method needs.

Parameters
$order : Order
$snapshot : OrderShipper

loadTariffContext()

Returns `external_pickup` of the tariff and the `script_name` of its carrier in one query.

public loadTariffContext(int $tariffId) : array{externalPickup: bool, shipperId: ?int, scriptName: ?string}|null

Called after OrderSnapshotBuilder::buildShipper() or buildShipperFromRow(), which already verified that $tariffId exists; a null return is safe and is not assumed to mean an invalid tariff.

Parameters
$tariffId : int
Return values
array{externalPickup: bool, shipperId: ?int, scriptName: ?string}|null

selectionBelongsToTariff()

Tells whether a pickup point selection belongs to the given tariff (selection provenance).

public static selectionBelongsToTariff(bool $provenanceSent, int|null $selectionTariffId, int $tariffId) : bool

Problem: the admin form keeps the chosen point in two persistent controls (a hidden pickupPoint with the raw widget object and six pickupPoint* text fields). When the admin switches the tariff, the JS force-submits shippingRefresh and both sets go to the server with the old values. OrderSnapshotBuilder::buildShipperFromRow() clears the snapshot on a tariff change, but apply() runs after it and refills it from the same payload: for another tariff of the same carrier the old raw value passes the driver normalization, for another carrier the old six fields pass the manual branch. The result is a pickup code of carrier A stored under carrier B without any new selection.

The server cannot tell this on its own: the previous tariff is read from the DB, but nothing is saved until the final submit, so even after a legitimate new selection the DB still holds the original tariff. Only the client knows, hence it sends pickupPointTariffId, the tariff the selection was made for.

Contract: when the key is in the payload, it must match strictly (a mismatch discards both inputs as if nothing was chosen, so persist=true fails closed with pickupPointRequired). When the key is absent the caller does not take part in provenance (the front end checkout OrderCheckoutManager always sends the selection from the current step) and behavior is unchanged. A mere recalculation or save of an unchanged tariff passes (the hidden field carries the same tariff), so a valid point is never lost.

Parameters
$provenanceSent : bool
$selectionTariffId : int|null
$tariffId : int
Return values
bool

applyToDeliveryAddress()

Turns the chosen pickup point into the order delivery address, the same principle as `applyBranchToDeliveryAddress()` for own branches (the schema has no other field for it).

private applyToDeliveryAddress(Order $order, PickupPoint $point) : void

IMPORTANT: Balikovna deliberately does not map street/city (see BalikovnaCarrier::normalizePickupPoint()): the Czech Post manual forbids using the unstructured point.address on a label, because it may carry a different postcode than the real point.zip. To keep the delivery address from staying half empty, a missing street/city is filled from PickupPoint::$name; zip is never filled from anything but PickupPoint::$zip.

Parameters
$order : Order
$point : PickupPoint

manualFields()

Takes the six manual fields; nothing else from the payload is taken over, and {@see \App\UI\Api\Eshop\Carriers\PickupPointSanitizerTrait::buildManualPickupPoint()} sanitizes them.

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

pickupRequired()

private pickupRequired() : array{success: false, errorCode: string, message: string}
Return values
array{success: false, errorCode: string, message: string}

stringKeyed()

Narrows the payload to `array<string, mixed>` (not `array_filter(..., ARRAY_FILTER_USE_KEY)`): PHPStan cannot narrow `array<mixed, mixed>` from a bare `is_array()`, while a widget payload is always a JSON object (string keys).

private stringKeyed(array<string|int, mixed> $value) : array<string, mixed>
Parameters
$value : array<string|int, mixed>
Return values
array<string, mixed>
On this page

Search results