> For the complete documentation index, see [llms.txt](https://docs.akinon.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.akinon.com/technical-guides/oms/commands/command-conditions.md).

# 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](#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](#viewing_store_is_delivery_store)).

### <mark style="color:red;">Command Condition Mechanism</mark>

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.

#### <mark style="color:red;">Basic Structure</mark>

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.

#### <mark style="color:red;">Registration</mark>

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.**

#### <mark style="color:red;">Attaching A Condition To A Transition</mark>

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.

{% hint style="info" %}
`klass` must be the **full import path**. This binding does not require a code deployment; it is done purely through database configuration.
{% endhint %}

### <mark style="color:red;">Runtime Flow</mark>

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.

### <mark style="color:red;">API Listing Available Conditions</mark>

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`.

### <mark style="color:red;">Steps For Attaching A Command Condition To A Transition</mark>

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.

{% hint style="info" %}
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.
{% endhint %}

{% stepper %}
{% step %}

#### <mark style="color:red;">Identify the Condition To Use</mark>

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:**

```http
GET /state-transition-conditions/available-command-conditions/
```

**Response (`200 OK`):**

```json
[
  {
    "slug": "RETAIL_STORE_PACKAGING_WITHOUT_SHIPMENT",
    "label": "Retail Store Packaging Without Shipment",
    "klass": "oms.packages.command_conditions.RetailStorePackagingWithoutShipmentCondition",
    "parameter_type": null,
    "exclusive_with": []
  },
  {
    "slug": "RETAIL_STORE_PACKAGING_WITH_SHIPMENT",
    "label": "Retail Store Packaging With Shipment",
    "klass": "oms.packages.command_conditions.RetailStorePackagingWithShipmentCondition",
    "parameter_type": null,
    "exclusive_with": []
  },
  {
    "slug": "ONLY_CLICK_AND_COLLECT_ORDERS",
    "label": "Only Click And Collect Orders",
    "klass": "oms.packages.command_conditions.OnlyClickAndCollectOrderPackagesCondition",
    "parameter_type": null,
    "exclusive_with": ["OTHER_THAN_CLICK_AND_COLLECT_ORDERS"]
  }
]
```

{% hint style="info" %}
The `klass` field is the exact reference you will write into the configuration in the next step.
{% endhint %}
{% endstep %}

{% step %}

#### <mark style="color:red;">Attach the Condition To the Transition</mark>

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):**

```http
POST /state-transition-conditions/
Content-Type: application/json

{
  "state_transition": 42,
  "rules_configuration": {
    "rules": [
      {
        "klass": "oms.packages.command_conditions.RetailStorePackagingWithoutShipmentCondition",
        "params": {}
      }
    ]
  }
}
```

**Response (`201 Created`):**

```json
{
  "id": 7,
  "state_transition": 42,
  "rules_configuration": {
    "rules": [
      {
        "klass": "oms.packages.command_conditions.RetailStorePackagingWithoutShipmentCondition",
        "params": {}
      }
    ]
  },
  "created_date": "2026-07-22T09:00:00Z",
  "modified_date": "2026-07-22T09:00:00Z"
}
```

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

```json
{
  "state_transition": 42,
  "rules_configuration": {
    "rules": [
      {
        "klass": "oms.packages.command_conditions.StockLocationListCondition",
        "params": {"stock_location_list": [101, 102]}
      }
    ]
  }
}
```

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

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

{% step %}

#### <mark style="color:red;">Avoid Conflicting Conditions</mark>

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.
{% endstep %}

{% step %}

#### <mark style="color:red;">Verify</mark>

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.
  {% endstep %}
  {% endstepper %}

### <mark style="color:red;">RETAIL\_STORE\_PACKAGING\_WITH\_SHIPMENT and WITHOUT\_SHIPMENT</mark>

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.

### <mark style="color:red;">Relationship With the Click & Collect Flow</mark>

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.

### <mark style="color:red;">P1 — Package created at the delivery point (M1) →</mark> <mark style="color:red;"></mark><mark style="color:red;">**WITHOUT\_SHIPMENT**</mark>

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

```
Waiting → Packed → Ready For Customer Pickup → Delivered
```

{% stepper %}
{% step %}

#### `COMPLETE_PACKAGING_WITHOUT_SHIPMENT`

Packaging without shipment. No shipment is created.
{% endstep %}

{% step %}

#### `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.
{% endstep %}

{% step %}

#### `SELF_SERVICE_PACKAGE_DELIVERY_COMMAND`

The customer picks up the item at the store.
{% endstep %}
{% endstepper %}

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

### <mark style="color:red;">P2 — Package created at a different location (M2) →</mark> <mark style="color:red;"></mark><mark style="color:red;">**WITH\_SHIPMENT**</mark>

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

```
Waiting → Packed → Shipped → Ready For Customer Pickup → Delivered
```

{% stepper %}
{% step %}

#### `COMPLETE_PACKAGING`

A **shipment record is created** at the time of packaging.
{% endstep %}

{% step %}

#### `SHIP_THE_PACKAGE`

The package is handed over to the carrier to be sent to M1.
{% endstep %}

{% step %}

#### `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](#viewing_store_is_delivery_store).
{% endstep %}

{% step %}

#### `SELF_SERVICE_PACKAGE_DELIVERY_COMMAND`

The customer picks up the item at the store.
{% endstep %}
{% endstepper %}

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

{% hint style="info" %}
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.
{% endhint %}

#### <mark style="color:red;">Important Limitation</mark>

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.

#### <mark style="color:red;">Conclusion & Guidance</mark>

* 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.

### <mark style="color:red;">VIEWING\_STORE\_IS\_DELIVERY\_STORE</mark>

The limitation described in [Important limitation](#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**.

#### <mark style="color:red;">Source of the Store Context</mark>

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.

#### <mark style="color:red;">Comparison & Rule</mark>

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.                                                               |

{% hint style="info" %}
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.
{% endhint %}

### <mark style="color:red;">Typical Usage</mark>

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:

```json
{
  "state_transition": 42,
  "rules_configuration": {
    "rules": [
      {
        "klass": "oms.packages.command_conditions.ViewingStoreIsDeliveryStoreCondition",
        "params": {}
      }
    ]
  }
}
```

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

```json
{
  "slug": "VIEWING_STORE_IS_DELIVERY_STORE",
  "label": "Viewing Store Is Delivery Store",
  "klass": "oms.packages.command_conditions.ViewingStoreIsDeliveryStoreCondition",
  "parameter_type": null,
  "exclusive_with": []
}
```

{% hint style="info" %}
**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.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.akinon.com/technical-guides/oms/commands/command-conditions.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
