Order Management feature integration

Edit on GitHub
Included features

The following feature integration guide expects the basic feature to be in place.

The current feature integration guide only adds the following functionalities:

  • Order cancellation behavior
    
  • Show `display names` for order item states 
    
  • Invoice generation
    

Install Feature Core

Follow the steps below to install the Order Management feature core.

Prerequisites

To start feature integration, overview and install the necessary features:

Name Version
Spryker Core 202005.0
Mailing & Notifications 202005.0

1) Install the required modules using Composer

Run the following command(s) to install the required modules:

composer require spryker-feature/order-management: "202005.0" --update-with-dependencies

Set up Database Schema and Transfer Objects

Run the following commands to apply database changes and generate transfer changes:

console transfer:generate
console propel:install
console transfer:entity:generate
Verification

Make sure that the following changes have been applied in database:

Datatabase Entity Type Event
spy_sales_order_invoice table created
Verification

Make sure that the following changes have been applied in transfer objects:

Transfer Type Event Path
OrderInvoice class created src/Generated/Shared/Transfer/OrderInvoiceTransfer
OrderInvoiceSendRequest class created src/Generated/Shared/Transfer/OrderInvoiceSendRequestTransfer
OrderInvoiceSendResponse class created src/Generated/Shared/Transfer/OrderInvoiceSendResponseTransfer
OrderInvoiceCriteria class created src/Generated/Shared/Transfer/OrderInvoiceCriteriaTransfer
OrderInvoiceCollection class created src/Generated/Shared/Transfer/OrderInvoiceCollectionTransfer
OrderInvoiceResponse class created src/Generated/Shared/Transfer/OrderInvoiceResponseTransfer
Mail.recipientBccs property created src/Generated/Shared/Transfer/MailTransfer

3) Set up Configuration

Set up the following configuration.

2.1) Configure OMS

Info

The cancellable flag allows to proceed to the order cancel process.

The display attribute allows to attach the display name attribute to specific order item states.

TheDummyInvoice sub-process allows triggering invoice-generate events.

Create the OMS sub-process file. **config/Zed/oms/DummySubprocess/DummyInvoice01.xml** ```xml
<process name="DummyInvoice">
    <states>
        <state name="invoice generated"/>
    </states>

    <transitions>
        <transition>
            <source>confirmed</source>
            <target>invoice generated</target>
            <event>invoice-generate</event>
        </transition>

        <transition>
            <source>invoice generated</source>
            <target>waiting</target>
            <target>invoice-generated</target>
        </transition>
    </transitions>

    <events>
        <event name="invoice-generate" manual="true" command="Invoice/Generate"/>
        <event name="invoice-generated" onEnter="true"/>
    </events>
</process>
```
Verification

The verification of the invoice state machine configuration will be checked in a later step.

Using the DummyPayment01.xml process as an example, adjust your OMS state-machine configuration according to your project’s requirements.

config/Zed/oms/DummyPayment01.xml
<?xml version="1.0"?>
<statemachine
        xmlns="spryker:oms-01"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="spryker:oms-01 http://static.spryker.com/oms-01.xsd">

    <process name="DummyPayment01" main="true">

        <subprocesses>
            <process>DummyInvoice</process>
        </subprocesses>

        <states>
            <state name="new" reserved="true" display="oms.state.new">
                <flag>cancellable</flag>
            </state>
            <state name="payment pending" reserved="true" display="oms.state.payment-pending">
                <flag>cancellable</flag>
            </state>
            <state name="invalid" display="oms.state.invalid">
                <flag>exclude from customer</flag>
            </state>
            <state name="cancelled" display="oms.state.canceled">
                <flag>exclude from customer</flag>
            </state>
            <state name="paid" reserved="true" display="oms.state.paid">
                <flag>cancellable</flag>
            </state>
            <state name="confirmed" reserved="true" display="oms.state.confirmed">
                <flag>cancellable</flag>
            </state>
            <state name="waiting" reserved="true" display="oms.state.waiting"/>
            <state name="exported" reserved="true" display="oms.state.exported"/>
            <state name="shipped" reserved="true" display="oms.state.shipped"/>
            <state name="delivered" display="oms.state.delivered"/>
            <state name="closed" display="oms.state.closed"/>
        </states>

        <transitions>
            <transition happy="true" condition="DummyPayment/IsAuthorized">
                <source>new</source>
                <target>payment pending</target>
                <event>authorize</event>
            </transition>

            <transition>
                <source>new</source>
                <target>invalid</target>
                <event>authorize</event>
            </transition>

            <transition>
                <source>new</source>
                <target>cancelled</target>
                <event>cancel</event>
            </transition>

            <transition happy="true" condition="DummyPayment/IsPayed">
                <source>payment pending</source>
                <target>paid</target>
                <event>pay</event>
            </transition>

            <transition>
                <source>payment pending</source>
                <target>cancelled</target>
                <event>pay</event>
            </transition>

            <transition>
                <source>payment pending</source>
                <target>cancelled</target>
                <event>cancel</event>
            </transition>

            <transition happy="true">
                <source>paid</source>
                <target>confirmed</target>
                <event>confirm</event>
            </transition>

            <transition happy="true">
                <source>confirmed</source>
                <target>waiting</target>
                <event>skip timeout</event>
            </transition>

            <transition>
                <source>confirmed</source>
                <target>cancelled</target>
                <event>cancel</event>
            </transition>

            <transition happy="true">
                <source>waiting</source>
                <target>exported</target>
                <event>check giftcard purchase</event>
            </transition>

            <transition happy="true" condition="GiftCard/IsGiftCard">
                <source>waiting</source>
                <target>gift card purchased</target>
                <event>check giftcard purchase</event>
            </transition>

            <transition happy="true">
                <source>gift card shipped</source>
                <target>delivered</target>
                <event>complete gift card creation</event>
            </transition>

            <transition happy="true">
                <source>exported</source>
                <target>shipped</target>
                <event>ship</event>
            </transition>

            <transition happy="true">
                <source>shipped</source>
                <target>delivered</target>
                <event>stock-update</event>
            </transition>

            <transition happy="true">
                <source>delivered</source>
                <target>closed</target>
                <event>close</event>
            </transition>

        </transitions>

        <events>
            <event name="authorize" onEnter="true"/>
            <event name="pay" manual="true" timeout="1 hour" timeoutProcessor="OmsTimeout/Initiation" command="DummyPayment/Pay"/>
            <event name="confirm" onEnter="true" manual="true" command="Oms/SendOrderConfirmation"/>
            <event name="skip timeout" manual="true" timeout="30 minute"/>
            <event name="cancel" manual="true"/>
            <event name="export" onEnter="true" manual="true" command="Oms/SendOrderShipped"/>
            <event name="ship" manual="true" command="Oms/SendOrderShipped"/>
            <event name="stock-update" manual="true"/>
            <event name="close" manual="true" timeout="1 hour"/>
        </events>
    </process>

    <process name="DummyInvoice" file="DummySubprocess/DummyInvoice01.xml"/>

</statemachine>
Verification

Ensure that you’ve configured the OMS:

  1. Go to the Back Office > Administration > OMS.

  2. Select DummyPayment01 [preview-version].

  3. Ensure that the new, payment pending, paid, and confirmed states keep the cancellable tag inside.

  4. Ensure that invoice generated state was added.

2.2) Configure Fallback Display Name Prefix

Adjust configuration according to your project’s needs:

src/Pyz/Zed/Oms/OmsConfig.php

<?php

namespace Pyz\Zed\Oms;

use Spryker\Zed\Oms\OmsConfig as SprykerOmsConfig;

class OmsConfig extends SprykerOmsConfig
{
    /**
     * Specification:
     * - Uses fallback prefix in concatenation with the normalized state name, in case the display property is not defined for the state.
     *    
     * @return string
     */
    public function getFallbackDisplayNamePrefix(): string
    {
        return 'oms.state.';
    }
}
Verification

Once you’ve finished setting up behavior, ensure that, on the following Storefront pages, the item states are displayed correctly even if the display property is not set in the process definition:

  • Customer overview
  • Order history
  • Order details
  • Returns
  • Return details
#### 3.2) Configure Order Invoice Template

Adjust configuration according to your project’s needs:

src/Pyz/Zed/SalesInvoice/SalesInvoiceConfig.php

<?php

namespace Pyz\Zed\SalesInvoice;

use Spryker\Zed\SalesInvoice\SalesInvoiceConfig as SprykerSalesInvoiceConfig;

class SalesInvoiceConfig extends SprykerSalesInvoiceConfig
{
    /**
     * @api
     *
     * @return string
     */
    public function getOrderInvoiceTemplatePath(): string
    {
        return 'SalesInvoice/Invoice/Invoice.twig';
    }
}

Add oder invoice twig template. For example:

src/Pyz/Zed/SalesInvoice/Presentation/Invoice/Invoice.twig
{# @var order \Generated\Shared\Transfer\OrderTransfer #}
{# @var invoice \Generated\Shared\Transfer\OrderInvoiceTransfer #}

<!doctype html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0 " />
    <title></title>

    <style type="text/css">
        body {
            margin: 0;
            padding: 0;
            font-size: 16px;
            box-sizing: border-box;
        }
        table {
            max-width: 600px;
            margin: 0 auto;
            border-collapse: collapse;
        }
        .products-table {
            border: 1px solid #000;
        }
        .products-table td,
        .products-table th {
            padding: 5px 10px;
        }
        .background-gray {
            background: #e6e6e6;
        }
        .text-small {
            font-size: 13px;
        }
        .spacing-bottom {
            padding-bottom: 15px;
        }
        .spacing-right {
            padding-right: 15px;
        }
        .align-top {
            vertical-align: top;
        }
        .text-left {
            text-align: left;
        }
        .text-center {
            text-align: center;
        }
    </style>
</head>
<body>
    <table>
        <tr>
            <td width="300" class="align-top">
                <img src="" width="200" alt="Logo">
            </td>
            <td width="300">
                <strong>{{ 'order_invoice.invoice_template.company.name' | trans }}</strong>
                <div class="spacing-bottom text-small">{{ 'order_invoice.invoice_template.company.group' | trans }}</div>
                <div class="spacing-bottom text-small">{{ 'order_invoice.invoice_template.company.address' | trans | raw }}</div>
            </td>
        </tr>
        <tr>
            <td width="300">
                <div class="spacing-bottom spacing-right">
                    <strong>{{ 'order_invoice.invoice_template.merchant.name' | trans }}</strong>
                    <div class="text-small">{{ 'order_invoice.invoice_template.merchant.address' | trans }}</div>
                </div>
                <div class="spacing-bottom">
                    {{ order.billingAddress.firstName }} {{ order.billingAddress.lastName }}<br>
                    {{ order.billingAddress.address1 }} {{ order.billingAddress.address2 }} {{ order.billingAddress.address3 }}<br>
                    {{ order.billingAddress.zipcode }} {{ order.billingAddress.city }}<br>
                    {{ order.billingAddress.region }}
                </div>
            </td>
            <td width="300" class="align-top">
                <div class="spacing-bottom">{{ invoice.issueDate | date('d. M Y') }}</div>
            </td>
        </tr>
    </table>

    <table>
        <tr>
            <td width="600">
                <div class="spacing-bottom">
                    <strong>{{ 'order_invoice.invoice_template.reference' | trans }} {{ invoice.reference }}</strong>
                </div>
            </td>
        </tr>
        <tr>
            <td width="600">
                <div class="spacing-bottom">{{ 'order_invoice.invoice_template.introduction' | trans }}</div>
            </td>
        </tr>
    </table>

    <table class="products-table">
        <thead>
            <tr class="background-gray">
                <th width="75"><strong>{{ 'order_invoice.invoice_template.table.number' | trans }}</strong></th>
                <th width="75"><strong>{{ 'order_invoice.invoice_template.table.quantity' | trans }}</strong></th>
                <th width="275" class="text-left"><strong>{{ 'order_invoice.invoice_template.table.name' | trans }}</strong></th>
                <th width="100"><strong>{{ 'order_invoice.invoice_template.table.tax' | trans }}</strong></th>
                <th width="75"><strong>{{ 'order_invoice.invoice_template.table.price' | trans | raw }}</strong></th>
            </tr>
        </thead>
        <tbody>
        {% set linenumber = 0 %}
        {% set renderedBundles = [] %}
        {% set taxes = {} %}
        {% set itemSumByTaxes = {} %}

        {% for item in order.items %}
            {# @var item \Generated\Shared\Transfer\ItemTransfer #}

            {% set taxRate = item.taxRate %}
            {% set rateSum = taxes[item.taxRate] | default(0) + item.sumTaxAmountFullAggregation %}
            {% set taxes = taxes | merge({ (taxRate): rateSum }) %}
            {% set rateItemSum = itemSumByTaxes[taxRate] | default(0) + item.sumPriceToPayAggregation %}
            {% set itemSumByTaxes = itemSumByTaxes | merge({ (taxRate): rateItemSum }) %}

            {% if item.productBundle is not defined or item.productBundle is null %}
                {% set linenumber = linenumber + 1 %}

                <tr>
                    <td class="text-center">{{ linenumber }}</td>
                    <td class="text-center">{{ item.quantity }}</td>
                    <td>{{ item.name }}</td>
                    <td class="text-center">{{ item.taxRate | number_format }}%</td>
                    <td class="text-center">{{ item.sumPriceToPayAggregation | money(true, order.currencyIsoCode) }}</td>
                </tr>
            {% endif %}

            {% if item.productBundle is defined and item.productBundle is not null %}
                {% if item.relatedBundleItemIdentifier not in renderedBundles %}
                    {# @var productBundle \Generated\Shared\Transfer\ItemTransfer #}

                    {% set linenumber = linenumber + 1 %}
                    {% set productBundle = item.productBundle %}

                    <tr>
                        <td class="text-center">{{ linenumber }}</td>
                        <td class="text-center">{{ productBundle.quantity }}</td>
                        <td>{{ productBundle.name }}</td>
                        <td class="text-center">{{ productBundle.taxRate | number_format }}%</td>
                        <td class="text-center">{{ productBundle.sumPriceToPayAggregation | money(true, order.currencyIsoCode) }}</td>
                    </tr>

                    {% for bundleditem in order.items %}
                        {% if item.relatedBundleItemIdentifier == bundleditem.relatedBundleItemIdentifier %}
                            <tr>
                                <td></td>
                                <td class="text-center">{{ bundleditem.quantity }}</td>
                                <td>{{ bundleditem.name }}</td>
                                <td class="text-center">{{ bundleditem.taxRate | number_format }}%</td>
                                <td class="text-center">{{ bundleditem.sumPriceToPayAggregation | money(true, order.currencyIsoCode) }}</td>
                            </tr>
                        {% endif %}
                    {% endfor %}

                    {% set renderedBundles = renderedBundles | merge([item.relatedBundleItemIdentifier]) %}
                {% endif %}
            {% endif %}
        {% endfor %}

        {% for expense in order.expenses %}
            {% set linenumber = linenumber + 1 %}
            {% set taxRate = expense.taxRate %}
            {% set rateSum = taxes[expense.taxRate] | default(0) + expense.sumTaxAmount %}
            {% set taxes = taxes | merge({ (taxRate): rateSum }) %}
            {% set rateItemSum = itemSumByTaxes[taxRate] | default(0) + expense.sumPriceToPayAggregation %}
            {% set itemSumByTaxes = itemSumByTaxes | merge({ (taxRate): rateItemSum }) %}

            <tr>
                <td class="text-center">{{ linenumber }}</td>
                <td></td>
                <td>{{ expense.name }}</td>
                <td class="text-center">{{ expense.taxRate | number_format }}%</td>
                <td class="text-center">{{ expense.sumPrice | money(true, order.currencyIsoCode) }}</td>
            </tr>
        {% endfor %}

        <tr class="background-gray">
            <td colspan="3"></td>
            <td>{{ 'order_invoice.invoice_template.table.subtotal' | trans }}</td>
            <td class="text-center">{{ order.totals.subtotal | money(true, order.currencyIsoCode) }}</td>
        </tr>
        <tr class="background-gray">
            <td colspan="3"></td>
            <td>{{ 'order_invoice.invoice_template.table.discount' | trans }}</td>
            <td class="text-center">{{ order.totals.discountTotal | money(true, order.currencyIsoCode) }}</td>
        </tr>

        {% for rate, tax in taxes %}
            <tr>
                <td colspan="2">{{ 'order_invoice.invoice_template.table.tax.included' | trans({ '%tax_rate%': rate | number_format }) }}</td>
                <td class="text-center">{{ (itemSumByTaxes[rate] - tax) | money(true, order.currencyIsoCode) }}</td>
                <td class="text-center">{{ 'order_invoice.invoice_template.table.tax.name' | trans }}</td>
                <td class="text-center">{{ tax | money(true, order.currencyIsoCode) }}</td>
            </tr>
        {% endfor %}

        <tr>
            <td colspan="2">{{ 'order_invoice.invoice_template.table.total.net' | trans }}</td>
            <td class="text-center">{{ (order.totals.grandTotal - order.totals.taxTotal.amount) | money(true, order.currencyIsoCode) }}</td>
            <td colspan="2"></td>
        </tr>
        <tr class="background-gray">
            <td colspan="3"></td>
            <td><strong>{{ 'order_invoice.invoice_template.table.grandtotal' | trans }}</strong></td>
            <td class="text-center">{{ order.totals.grandTotal | money(true, order.currencyIsoCode) }}</td>
        </tr>
        </tbody>
    </table>
</body>
</html>
Verification

The verification of the invoice template configuration will be checked in a later step.

4) Add Translations

An oms.state. prefixed translation key is a combination of the OmsConfig::getFallbackDisplayNamePrefix() and a normalized state machine name. If you have different OMS state-machine states or a fallback display name prefix, adjust the corresponding translations.

Normalized state machine names

By default, in state machine names:

  • Spaces are replaced with dashes.
  • All the words are decapitalized.

Add translations as follows:

  1. Append glossary according to your configuration:
sales.error.customer_order_not_found,Customer Order not found.,en_US
sales.error.customer_order_not_found,Die Bestellung wurde nicht gefunden.,de_DE
sales.error.order_cannot_be_canceled_due_to_wrong_item_state,Order cannot be canceled due to wrong item state.,en_US
sales.error.order_cannot_be_canceled_due_to_wrong_item_state,Die Bestellung kann wegen dem falschen Artikelstatus nicht storniert werden.,de_DE
oms.state.new,New,en_US
oms.state.new,Neu,de_DE
oms.state.payment-pending,Payment pending,en_US
oms.state.payment-pending,Ausstehende Zahlung,de_DE
oms.state.invalid,Ivalid,en_US
oms.state.invalid,Ungültig,de_DE
oms.state.canceled,Canceled,en_US
oms.state.canceled,Abgebrochen,de_DE
oms.state.paid,Paid,en_US
oms.state.paid,Bezahlt,de_DE
oms.state.confirmed,Confirmed,en_US
oms.state.confirmed,Bestätigt,de_DE
oms.state.waiting,Waiting,en_US
oms.state.waiting,Warten,de_DE
oms.state.exported,Exported,en_US
oms.state.exported,Exportiert,de_DE
oms.state.shipped,Shipped,en_US
oms.state.shipped,Versandt,de_DE
oms.state.delivered,Delivered,en_US
oms.state.delivered,Geliefert,de_DE
quote_request.status.closed,Closed,en_US
quote_request.status.closed,Geschlossen,de_DE
mail.order_invoice.subject,"Invoice: %invoiceReference%",en_US
mail.order_invoice.subject,"Rechnung: %invoiceReference%",de_DE
  1. Import data:
console data:import:glossary
Verification

Ensure that, in the database, the configured data has been added to the spy_glossary table.

5) Set up Behavior

Set up the following behaviors.

5.1) Set up Order Item Display Name

Plugin Specification Prerequisites Namespace
CurrencyIsoCodeOrderItemExpanderPlugin Expands order items with currency codes (ISO). None Spryker\Zed\Sales\Communication\Plugin\Sales
StateHistoryOrderItemExpanderPlugin Expands order items with history states. None Spryker\Zed\Oms\Communication\Plugin\Sales
ItemStateOrderItemExpanderPlugin Expands order items with its item states. None Spryker\Zed\Oms\Communication\Plugin\Sales
OrderAggregatedItemStateSearchOrderExpanderPlugin Expands orders with aggregated item states. None Spryker\Zed\Oms\Communication\Plugin\Sales

src/Pyz/Zed/Sales/SalesDependencyProvider.php

<?php

namespace Pyz\Zed\Sales;

use Spryker\Zed\Sales\Communication\Plugin\Sales\CurrencyIsoCodeOrderItemExpanderPlugin;
use Spryker\Zed\Oms\Communication\Plugin\Sales\ItemStateOrderItemExpanderPlugin;
use Spryker\Zed\Oms\Communication\Plugin\Sales\OrderAggregatedItemStateSearchOrderExpanderPlugin;
use Spryker\Zed\Sales\SalesDependencyProvider as SprykerSalesDependencyProvider;

class SalesDependencyProvider extends SprykerSalesDependencyProvider
{
    /**
     * @return \Spryker\Zed\SalesExtension\Dependency\Plugin\OrderItemExpanderPluginInterface[]
     */
    protected function getOrderItemExpanderPlugins(): array
    {
        return [
            new CurrencyIsoCodeOrderItemExpanderPlugin(),
            new StateHistoryOrderItemExpanderPlugin(),
            new ItemStateOrderItemExpanderPlugin(),
        ];
    }
    
    /**
     * @return \Spryker\Zed\SalesExtension\Dependency\Plugin\SearchOrderExpanderPluginInterface[]
     */
    protected function getSearchOrderExpanderPlugins(): array
    {
        return [
            new OrderAggregatedItemStateSearchOrderExpanderPlugin()
        ];
    }
}
Verification

Ensure that:

  • Every order item from the SalesFacade::getOrderItems() result contains:
    • Currency ISO code
    • State history code
    • Item state data
  • Every order from the SalesFacade::getCustomerOrders() result contains aggregated item state data.

5.2) Set up Order Cancellation Behavior

Plugin Specification Prerequisites Namespace
IsCancellableOrderExpanderPlugin Checks if each order item has the cancellable flag. None Spryker\Zed\Sales\Communication\Plugin\Sales
IsCancellableSearchOrderExpanderPlugin Checks if each order item has the cancellable flag. None Spryker\Zed\Oms\Communication\Plugin\Sales

src/Pyz/Zed/Sales/SalesDependencyProvider.php

<?php

namespace Pyz\Zed\Sales;

use Spryker\Zed\Sales\Communication\Plugin\Sales\CurrencyIsoCodeOrderItemExpanderPlugin;
use Spryker\Zed\Sales\SalesDependencyProvider as SprykerSalesDependencyProvider;

class SalesDependencyProvider extends SprykerSalesDependencyProvider
{
    /**
     * @return \Spryker\Zed\SalesExtension\Dependency\Plugin\SearchOrderExpanderPluginInterface[]
     */
    protected function getSearchOrderExpanderPlugins(): array
    {
        return [
            new IsCancellableSearchOrderExpanderPlugin(),
        ];
    }

    /**
     * @return \Spryker\Zed\SalesExtension\Dependency\Plugin\OrderExpanderPluginInterface[]
     */
    protected function getOrderHydrationPlugins(): array
    {
        return [
            new IsCancellableOrderExpanderPlugin(),
        ];
    }
}
Verification

Ensure that, on the following pages, each order contains the isCancellable flag:

  • The Storefront:
    • Order History
    • Overview
  • The Back Office:
    • Overview of Orders

5.3) Set up Order Invoice Generation Behavior

5.3.1) Setup Order Invoice Mail Type
Plugin Specification Prerequisites Namespace
OrderInvoiceMailTypePlugin An email type that prepares invoice email for an order. cell cell
src/Pyz/Zed/Mail/MailDependencyProvider.php
<?php

namespace Pyz\Zed\Mail;

use Spryker\Zed\Kernel\Container;
use Spryker\Zed\Mail\Business\Model\Mail\MailTypeCollectionAddInterface;
use Spryker\Zed\Mail\Business\Model\Provider\MailProviderCollectionAddInterface;
use Spryker\Zed\Mail\MailDependencyProvider as SprykerMailDependencyProvider;
use Spryker\Zed\SalesInvoice\Communication\Plugin\Mail\OrderInvoiceMailTypePlugin;

class MailDependencyProvider extends SprykerMailDependencyProvider
{
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return \Spryker\Zed\Kernel\Container
     */
    public function provideBusinessLayerDependencies(Container $container)
    {
        $container = parent::provideBusinessLayerDependencies($container);

        $container->extend(static::MAIL_TYPE_COLLECTION, function (MailTypeCollectionAddInterface $mailCollection) {
            $mailCollection
                ->add(new OrderInvoiceMailTypePlugin());

            return $mailCollection;
        });

        return $container;
    }
}
5.3.2) Set up Order Invoice OMS Command
Plugin Specification Prerequisites Namespace
GenerateOrderInvoiceCommandPlugin A command in OMS state machine, that generates invoice for order. None Spryker\Zed\SalesInvoice\Communication\Plugin\Oms

src/Pyz/Zed/Oms/OmsDependencyProvider.php

<?php

namespace Pyz\Zed\Oms;

use Spryker\Zed\Kernel\Container;
use Spryker\Zed\Oms\Dependency\Plugin\Command\CommandCollectionInterface;
use Spryker\Zed\Oms\OmsDependencyProvider as SprykerOmsDependencyProvider;
use Spryker\Zed\SalesInvoice\Communication\Plugin\Oms\GenerateOrderInvoiceCommandPlugin;

class OmsDependencyProvider extends SprykerOmsDependencyProvider
{
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return \Spryker\Zed\Kernel\Container
     */
    protected function extendCommandPlugins(Container $container): Container
    {
        $container->extend(static::COMMAND_PLUGINS, function (CommandCollectionInterface $commandCollection) {
            $commandCollection->add(new GenerateOrderInvoiceCommandPlugin(), 'Invoice/Generate');
            return $commandCollection;
        });

        return $container;
    }
}

5.3.3) Set up Order Invoice OMS Command

Plugin Specification Prerequisites Namespace
OrderInvoiceSendConsole A console command that sends not-yet-sent order invoices via email. None Spryker\Zed\SalesInvoice\Communication\Console

src/Pyz/Zed/Oms/OmsDependencyProvider.php

<?php

namespace Pyz\Zed\Oms;

use Spryker\Zed\Console\ConsoleDependencyProvider as SprykerConsoleDependencyProvider;
use Spryker\Zed\Kernel\Container;
use Spryker\Zed\SalesInvoice\Communication\Console\OrderInvoiceSendConsole;

class ConsoleDependencyProvider extends SprykerConsoleDependencyProvider
{
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return \Symfony\Component\Console\Command\Command[]
     */
    protected function getConsoleCommands(Container $container): array
    {
        $commands = [
            new OrderInvoiceSendConsole(),
        ];
        
        return $commands;
    }
}

Adjust scheduler project configuration: config/Zed/cronjobs/jenkins.php

/* Order invoice */
$jobs[] = [
    'name' => 'order-invoice-send',
    'command' => '$PHP_BIN vendor/bin/console order:invoice:send',
    'schedule' => '*/5 * * * *',
    'enable' => true,
    'stores' => $allStores,
];

To apply scheduler configuration update, run next commands:

vendor/bin/console scheduler:suspend
vendor/bin/console scheduler:setup
vendor/bin/console scheduler:resume
Verification

Once all invoice related configuration was set up, ensure that the right order invoice template was assigned to the order (spy_sales_order_invoice) after moving at least 1 item in the order to invoice generated state based on your DummyInvoice01.xml and SalesInvoiceConfig::getOrderInvoiceTemplatePath() configuration.

Verification

The email with the invoice based on the configured invoice template should be sent to the email address of the order’s customer within the scheduled (5 mins) time.

Install Feature Front End

Follow the steps below to install the feature front end.

Prerequisites

Overview and install the necessary features before beginning the integration step.

Name Version
Spryker Core 202005.0

1) Install the required modules using Composer

Run the following command(s) to install the required modules:

composer require spryker-feature/order-management: "202005.0" --update-with-dependencies

2) Add Translations

Add translations as follows:

  1. Append glossary according to your configuration:
order_cancel_widget.cancel_order,Cancel Order,en_US
order_cancel_widget.cancel_order,Bestellung stornieren,de_DE
order_cancel_widget.order.cancelled,Order was canceled successfully.,en_US
order_cancel_widget.order.cancelled,Die Bestellung wurde erfolgreich storniert.,de_DE
  1. Import data:
console data:import:glossary
Verification

Ensure that, in the database, the configured data has been added to the spy_glossary table.

3) Enable Controllers

Register the following route provider(s) on the Storefront:

Provider Namespace
OrderCancelWidgetRouteProviderPlugin SprykerShop\Yves\OrderCancelWidget\Plugin\Router

src/Pyz/Yves/Router/RouterDependencyProvider.php

<?php

namespace Pyz\Yves\Router;

use Spryker\Yves\Router\RouterDependencyProvider as SprykerRouterDependencyProvider;
use SprykerShop\Yves\OrderCancelWidget\Plugin\Router\OrderCancelWidgetRouteProviderPlugin;

class RouterDependencyProvider extends SprykerRouterDependencyProvider
{
    /**
     * @return \Spryker\Yves\RouterExtension\Dependency\Plugin\RouteProviderPluginInterface[]
     */
    protected function getRouteProvider(): array
    {
        return [
            new OrderCancelWidgetRouteProviderPlugin(),
        ];
    }
}
Verification

Ensure that the yves.mysprykershop.com/order/cancel route is available for POST requests.

4) Set up Behavior

Set up the following behaviors.

4.1) Set up Order Cancellation Behavior

Plugin Specification Prerequisites Namespace
OrderCancelButtonWidget Shows a Cancel button on the Storefront. None SprykerShop\Yves\OrderCancelWidget\Widget

src/Pyz/Yves/ShopApplication/ShopApplicationDependencyProvider.php

<?php

namespace Pyz\Yves\ShopApplication;

use SprykerShop\Yves\OrderCancelWidget\Widget\OrderCancelButtonWidget;
use SprykerShop\Yves\ShopApplication\ShopApplicationDependencyProvider as SprykerShopApplicationDependencyProvider;

class ShopApplicationDependencyProvider extends SprykerShopApplicationDependencyProvider
{
    /**
     * @return string[]
     */
    protected function getGlobalWidgets(): array
    {
        return [
            OrderCancelButtonWidget::class,
        ];
    }
}
Verification

Ensure that:

  • The OrderCancelButtonWidget widget has been registered.
  • The Cancel button is displayed on the Order Details page on the Storefront.

4.2) Set up Order Item Display Names

Verification

Ensure that:

  • You can see the aggregated order item states in the item state table column on the Customer Overview and Order History pages on the Storefront;
  • Aggregated return item states are displayed on the Return Page on the Storefront.
  • Item states are displayed on the Order Details and Return Details pages on the Storefront.