For the complete documentation index, see llms.txt. This page is also available as Markdown.

Command Conditions

This document explains, step by step, how a command condition is added to a state transition. It specifically covers the RETAIL_STORE_PACKAGING_WITH_SHIPMENT and RETAIL_STORE_PACKAGING_WITHOUT_SHIPMENT conditions used in Click & Collect scenarios.

It also clarifies an important limitation that arises because the existing conditions are unaware of the store context (see Important limitation), and describes the new VIEWING_STORE_IS_DELIVERY_STORE condition, which overcomes this limitation using the store context (the X-Store header) (see VIEWING_STORE_IS_DELIVERY_STORE).

Command Condition Mechanism

A command condition is a prerequisite that a package must satisfy for a command—and therefore a state transition—to run. If the condition is not satisfied, the transition is blocked and an exception is raised.

Basic Structure

The system has a two-layer condition structure: a most-general base condition, and a package-based condition type built on top of it. Newly written package conditions are derived from this package-based type.

Every condition has the following basic properties:

  • Unique identifier (slug): The unique name that identifies the condition and is used in configuration.

  • Readable label: The name shown in the UI in the list of available conditions.

  • Parameter type: Some conditions take external parameters—for example, a channel, a carrier, or a list of stock locations. In this case, the condition specifies which parameter type it expects; conditions that take no parameters leave this empty.

  • Exclusion info: The list of other conditions that cannot be used together with this one on the same transition because they are logically contradictory.

The condition's actual logic is defined inside a check operation. The following rule applies:

  • If the condition is satisfied, the check passes silently.

  • If it is not satisfied, an error is produced and the transition is blocked.

For package conditions, this is a standard "condition not satisfied" error. Some special conditions may also produce their own more descriptive error.

Registration

A condition class is registered using the @register_command_condition decorator. The decorator adds the condition to the REGISTERED_COMMAND_CONDITIONS dictionary keyed by slug.

If a second registration is attempted with the same slug, an error is raised. Therefore, every condition's slug must be unique.

Attaching A Condition To A Transition

A condition is attached to a transition through database configuration, not through code:

  • StateTransitionCondition has a one-to-one relationship with each StateTransition and carries a rules_configuration JSON field.

  • At runtime, each rule in this JSON is read. The corresponding condition class is located using the full import path in the rule's klass field, instantiated with params, and the result is cached.

The rules_configuration field contains a rules list. Each item contains:

  • klass: The full import path of the condition class.

  • params: Parameters passed to the condition.

For parameterless conditions, params is left empty. For parameterized conditions, the relevant parameters are placed here, such as a list of channels.

klass must be the full import path. This binding does not require a code deployment; it is done purely through database configuration.

Runtime Flow

When a command is executed, it goes through a pre-validation step before performing the actual operation. During this step, the system checks whether there is a condition definition attached to the transition being performed:

  • If there is no condition definition attached to the transition, no check is performed and the command runs freely.

  • If a condition definition exists, the rules in the configuration are evaluated in order, and the package is checked against each rule.

  • If multiple rules are defined, all of them must be satisfied using AND logic. The first rule that is not satisfied raises an error, the transition stops, and the command does not run.

This precondition check is applied on the package side to the key commands of the packaging and shipping flow, such as completing packaging, packaging without shipment, shipping the package, and transfer delivery steps.

API Listing Available Conditions

All registered conditions are listed via the available-command-conditions endpoint. The response includes each condition's:

  • slug

  • label

  • klass, the import path to use in configuration

  • parameter_type

  • exclusive_with information

This allows you to build the correct rules_configuration when attaching a condition to a transition and to detect conflicting conditions through exclusive_with.

Steps For Attaching A Command Condition To A Transition

This section covers attaching, setting up, or configuring an existing condition to a state transition without adding new code to the system. Attaching does not require a code deployment; it is done through configuration.

The condition you want to use must already be defined in the system. If the condition you need does not exist yet, it must first be defined and released by a developer. This section assumes the condition already exists.

1

Identify the Condition To Use

View the conditions defined in the system from the list of available conditions. This list provides each condition's identifier (slug), readable label, the reference to use in configuration (klass), the parameter type it expects, if any, and the conditions it excludes.

Select the condition that fits your need, such as RETAIL_STORE_PACKAGING_WITHOUT_SHIPMENT.

Request:

Response (200 OK):

The klass field is the exact reference you will write into the configuration in the next step.

2

Attach the Condition To the Transition

Create a condition configuration for the relevant state transition. In the request body, provide the state_transition ID you are attaching the condition to, and provide the klass reference obtained in the previous step inside rules_configuration, along with params if the condition takes parameters.

Multiple conditions can be added. In that case, all of them must be satisfied for the transition to occur.

Request (parameterless condition):

Response (201 Created):

For a condition that takes parameters, fill in params, for example with a list of stock locations:

To update an existing attachment, use PATCH /state-transition-conditions/{id}/.

To remove one, use DELETE /state-transition-conditions/{id}/.

3

Avoid Conflicting Conditions

Some conditions are mutually exclusive by logic, such as "Click & Collect orders only" versus "non-Click & Collect orders."

This information is visible in the exclusion field of the available conditions list. Do not attach two conflicting conditions to the same transition together; otherwise, no package will be able to make that transition.

4

Verify

After saving the configuration, verify that:

  • A package satisfying the condition can make the transition.

  • A package that does not satisfy the condition is blocked.

RETAIL_STORE_PACKAGING_WITH_SHIPMENT and WITHOUT_SHIPMENT

Both conditions compare the same two values:

  • Delivery store: The order's retail_store_id field; the store where the customer will pick up the item.

  • Packaging location: The package's stock_location source-location ID; the location where the package was actually packed.

Condition (slug)
Pass rule
Meaning

RETAIL_STORE_PACKAGING_WITHOUT_SHIPMENT

Passes if delivery store == packaging location

The package is packed at the same store as the order's delivery store → no shipment is needed, for on-site pickup.

RETAIL_STORE_PACKAGING_WITH_SHIPMENT

Passes if delivery store != packaging location

The package is packed at a location different from the delivery store → a shipment or transfer to the delivery store is needed.

Since these two conditions are the opposite of each other, they should not be used together on the same transition in practice.

Typical usage is to distinguish between two different transition variants of the same command, such as completing packaging:

  • Packaging-without-shipment transition: Attach RETAIL_STORE_PACKAGING_WITHOUT_SHIPMENT. Only packages packed at the delivery store can use this transition.

  • Packaging-with-shipment transition: Attach RETAIL_STORE_PACKAGING_WITH_SHIPMENT. Only packages packed at a different location, to be shipped to the delivery store, can use this transition.

Relationship With the Click & Collect Flow

The scenario in the koton-cnc.drawio diagram illustrates why two conditions are needed.

For example, an order with delivery store M1 selected is split into multiple packages based on stock distribution. A different flow is applied depending on where each package is packed.

P1 — Package created at the delivery point (M1) → WITHOUT_SHIPMENT

The packaging order is created at the delivery store itself, M1. Items are delivered directly to the customer and are not shipped.

1

COMPLETE_PACKAGING_WITHOUT_SHIPMENT

Packaging without shipment. No shipment is created.

2

READY_FOR_PICKUP

The order lines in Omnitron move to the ready_for_customer_pickup state. A "your package is ready" notification, email or SMS, is sent to the customer.

3

SELF_SERVICE_PACKAGE_DELIVERY_COMMAND

The customer picks up the item at the store.

Since the packaging location and the delivery store are the same for this package, it passes the RETAIL_STORE_PACKAGING_WITHOUT_SHIPMENT condition.

P2 — Package created at a different location (M2) → WITH_SHIPMENT

Packaging occurs at location M2. Items are shipped to M1, and delivery to the customer happens from M1.

1

COMPLETE_PACKAGING

A shipment record is created at the time of packaging.

2

SHIP_THE_PACKAGE

The package is handed over to the carrier to be sent to M1.

3

READY_FOR_PICKUP

Once shipment-status polling tasks confirm the package has arrived at M1, it moves to the Ready For Customer Pickup state.

The newly added VIEWING_STORE_IS_DELIVERY_STORE condition can be attached via configuration to this Shipped → Ready For Customer Pickup transition. When attached, the command is hidden at the source store where packaging took place, M2, and remains visible in all store contexts other than M2, including the delivery store M1. See VIEWING_STORE_IS_DELIVERY_STORE.

4

SELF_SERVICE_PACKAGE_DELIVERY_COMMAND

The customer picks up the item at the store.

Since the packaging location differs from the delivery store for this package, it passes the RETAIL_STORE_PACKAGING_WITH_SHIPMENT condition.

Even within the same order, a different packaging and shipping flow is required depending on where a package is packed. The two conditions guarantee that the correct command only runs for the correct package type: a package packed at the delivery store is routed to the no-shipment flow, and a package packed at a different location is routed to the with-shipment flow.

Important Limitation

When considering a requirement such as showing the "Ready for Customer Pickup" button only at the store where the item will be picked up, the following behavior becomes clear:

These two conditions alone cannot deliver the behavior of "show the button only at the pickup store."

The reasons are:

  1. stock_location remains fixed as the source store.

    The package's stock_location field represents the source location where packaging took place throughout the package's lifecycle. It is not updated to the delivery store during the delivery stage.

    Therefore, these conditions do not express "which store the package is currently physically located at," but rather "whether packaging took place at the delivery store." For a package that has arrived at the delivery store via shipment, such as P2, the packaging location and the delivery store still appear different even at the delivery store.

  2. The list of available commands is unaware of the store context.

    The available-commands endpoint statically returns all registered commands. It does not filter based on the requesting store or user context.

  3. The package filter uses OR logic and is unaware of the store context.

    The relevant package filter matches a package against both the source location and the delivery store using OR logic.

    In other words, a store can see both the packages it packed itself and the packages that will be delivered to it. The filter alone does not make the "delivery store only" distinction.

Conclusion & Guidance

  • The RETAIL_STORE_PACKAGING_WITH_SHIPMENT and RETAIL_STORE_PACKAGING_WITHOUT_SHIPMENT conditions are the right tool for separating the packaging flow, with shipment or without shipment. They determine whether a transition should run based on the package type.

  • However, a requirement such as "show the button only in the context of the requesting store" requires store context. Even though the identity of the requesting store, X-Store-Remote-Id, is available, as long as this context is not explicitly incorporated into command-visibility calculation and/or package filtering, the existing conditions cannot make the "is the viewing store the delivery store?" distinction on their own at the UI level.

VIEWING_STORE_IS_DELIVERY_STORE

The limitation described in Important limitation—that command conditions are unaware of the requesting store—is addressed by this condition.

Unlike the other conditions, VIEWING_STORE_IS_DELIVERY_STORE looks not only at the package's data but also at the context of the requesting store.

Source of the Store Context

The identity of the requesting store is carried via the X-Store-Remote-Id header. This store context is supplied to the condition both during command-visibility calculation and during command execution.

If the header is absent, the store context is empty. The condition is skipped without performing a comparison, and the command is not blocked.

Comparison & Rule

The condition compares the X-Store-Remote-Id value from the header with the ERP code of the package's packaging location, stock_location.

Case
Rule
Result

No context, no X-Store-Remote-Id in the header

No comparison is performed

The condition is skipped and the command is not blocked. Back-office, third-party, and calls without a store context are not blocked.

Viewing store == packaging location

If equal, fails

The command is hidden/blocked and is not shown at the source store where packaging took place.

Viewing store != packaging location

If different, passes

The command is visible/runnable at the delivery store and other stores.

The comparison is made against the package's stock_location, the packaging-location ERP code, not against the order's retail_store, the delivery-store value. This way, the command is hidden at the source store where packaging took place and remains visible at the delivery store.

Typical Usage

This condition is attached to a command that is only meaningful in the context of the delivery side, such as READY_FOR_PICKUP ("Ready for Customer Pickup").

This way, the button is not shown at the source store that performed the packaging, but the command remains usable in the context of the delivery store.

Attachment is done through rules_configuration, the same way as other conditions. It takes no parameters:

Its entry in the available conditions list, available-command-conditions, is:

Quick rule: RETAIL_STORE_PACKAGING_WITH_SHIPMENT and RETAIL_STORE_PACKAGING_WITHOUT_SHIPMENT distinguish "is this package being packed with shipment or without shipment?" VIEWING_STORE_IS_DELIVERY_STORE answers the question "should this command be shown based on the requesting store, the X-Store context?" In other words, it incorporates the store context into the command condition.

Last updated

Was this helpful?