<?php
// Copyright (c) Pickware GmbH. All rights reserved.
// This file is part of software that is released under a proprietary license.
// You must not copy, modify, distribute, make publicly available, or execute
// its contents or parts thereof without express permission by the copyright
// holder, unless otherwise permitted by law.

// Require composer dependecies if necessary
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
    require_once(__DIR__ . '/vendor/autoload.php');
}

if (!class_exists('ViisonCommon_Plugin_BootstrapV14')) {
    require_once('ViisonCommon/PluginBootstrapV14.php');
}

use Shopware\CustomModels\ViisonPickwareMobile\PickProfile\PickProfile;
use Shopware\Models\Config\Element as ConfigElement;
use Shopware\Models\Mail\Mail;
use Shopware\Models\Order\Status as OrderStatus;
use Shopware\Models\Payment\Payment;
use Shopware\Models\Shop\Shop;
use Shopware\Plugins\ViisonCommon\Classes\Exceptions\InstallationException;
use Shopware\Plugins\ViisonCommon\Classes\Installation\AclResource;
use Shopware\Plugins\ViisonCommon\Classes\Installation\AttributeColumn\AttributeColumnDescription;
use Shopware\Plugins\ViisonCommon\Classes\Installation\AttributeColumn\AttributeColumnInstaller;
use Shopware\Plugins\ViisonCommon\Classes\Installation\AttributeColumn\AttributeColumnUninstaller;
use Shopware\Plugins\ViisonCommon\Classes\Installation\AttributeConfiguration;
use Shopware\Plugins\ViisonCommon\Classes\Installation\BackendSessionLocaleClassMigration;
use Shopware\Plugins\ViisonCommon\Classes\Installation\InstallationCheckpointWriter;
use Shopware\Plugins\ViisonCommon\Classes\Installation\MediaAlbum\InstallationHelper as MediaAlbumInstallationHelper;
use Shopware\Plugins\ViisonCommon\Classes\Installation\MediaAlbum\UninstallationHelper as MediaAlbumUninstallationHelper;
use Shopware\Plugins\ViisonCommon\Classes\Installation\Menu;
use Shopware\Plugins\ViisonCommon\Classes\Installation\SQLHelper;
use Shopware\Plugins\ViisonCommon\Classes\Subscribers as ViisonCommonSubscribers;
use Shopware\Plugins\ViisonCommon\Classes\Util\Util as ViisonCommonUtil;
use Shopware\Plugins\ViisonCommon\Components\HiddenConfigStorageService;
use Shopware\Plugins\ViisonPickwareCommon\Classes\PickwareCommonInstaller;
use Shopware\Plugins\ViisonPickwareCommon\Classes\Subscribers\RestApiRequestCompatibilityLayerSubscriber;
use Shopware\Plugins\ViisonPickwareMobile\Components\PickingOrderFilter\StockBasedFilterConfiguration;
use Shopware\Plugins\ViisonPickwareMobile\Components\QueryBuilder\BooleanCompositionQueryComponent;
use Shopware\Plugins\ViisonPickwareMobile\Components\QueryBuilder\ComparisonQueryComponent;
use Shopware\Plugins\ViisonPickwareMobile\Components\QueryBuilder\FieldDescriptorQueryComponent;
use Shopware\Plugins\ViisonPickwareMobile\Components\QueryBuilder\ScalarValueQueryComponent;
use Shopware\Plugins\ViisonPickwareMobile\Components\QueryComponentArrayCoding\BooleanCompositionQueryComponentArrayCoder;
use Shopware\Plugins\ViisonPickwareMobile\Components\QueryComponentArrayCoding\ComparisonQueryComponentArrayCoder;
use Shopware\Plugins\ViisonPickwareMobile\Components\QueryComponentArrayCoding\FieldDescriptorQueryComponentArrayCoder;
use Shopware\Plugins\ViisonPickwareMobile\Components\QueryComponentFilter;
use Shopware\Plugins\ViisonPickwareMobile\Subscribers;

// Include the shipping provider interfaces, which cannot be loaded using namespaces
if (!interface_exists('ViisonPickwareMobile_Interfaces_ShippingProvider_ShippingProvider')) {
    require_once('Interfaces/ShippingProvider/ShippingDocumentType.php');
    require_once('Interfaces/ShippingProvider/ShippingProduct.php');
    require_once('Interfaces/ShippingProvider/ShippingProvider.php');
    require_once('Interfaces/ShippingProvider/ShippingDocument.php');
    require_once('Interfaces/ShippingProvider/ShippingLabelCreationResult.php');
}

/*
 * "Declare" some methods that are not required in our bootstrap, but whose existence is checked during Shopware's
 * code review:
 *
 * public function getVersion() {}
 */

final class Shopware_Plugins_Core_ViisonPickwareMobile_Bootstrap extends ViisonCommon_Plugin_BootstrapV14
{
    const MIN_SUPPORTED_UPDATE_VERSION = '4.2.0';

    const CONFIG_ELEMENT_NAME_LAST_SAFE_UNINSTALL_VERSION = 'pickwareMobileLastSafeUninstallVersion';

    /**
     * The constraints defining which version of the plugin 'ViisonPickwareERP' this plugin is compatible with.
     */
    const VIISON_PICKWARE_ERP_MIN_SUPPORTED_VERSION = '6.2.2';
    const VIISON_PICKWARE_ERP_EXCLUSIVE_MAX_SUPPORTED_VERSION = '7.0.0';

    /**
     * @inheritdoc
     */
    protected $codeVersion = '5.20.2.55';

    /**
     * @inheritdoc
     */
    protected $minRequiredShopwareVersion = '5.5.0';

    /**
     * @inheritdoc
     */
    protected function runUpdate($oldVersion)
    {
        // Don't allow installs/updates if licence check fails, the exception thrown gives the user a hint what to do
        $this->checkLicenseInternal();

        // Don't allow installs/updates if an incompatible version of ViisonPickwareERP is installed
        if (!self::isViisonPickwareErpActiveInSupportedVersion()) {
            $this->throwViisonPickwareErpVersionConstraintsException();
        }

        // Load the plugin's services
        $this->loadServicesForInstallation();

        // Prepare some helpers
        $form = $this->Form();
        /** @var \Shopware\Components\Model\ModelManager $modelManager */
        $modelManager = $this->get('models');
        /** @var Zend_Db_Adapter_Abstract $database */
        $database = $this->get('db');
        $sqlHelper = new SQLHelper($database);
        $attributeColumnUninstallationHelper = new AttributeColumnUninstaller($modelManager, $sqlHelper);
        $attributeColumnInstallationHelper = new AttributeColumnInstaller($modelManager, $sqlHelper);
        $attributeConfigurationInstallationHelper = new AttributeConfiguration\InstallationHelper($modelManager);
        $attributeConfigurationUninstallationHelper = new AttributeConfiguration\UninstallationHelper($modelManager);
        $mediaAlbumInstallationHelper = new MediaAlbumInstallationHelper($modelManager);
        $menuInstallationHelper = new Menu\InstallationHelper($modelManager, $this->getPlugin(), function (array $options) {
            // See Shopware_Components_Plugin_Bootstrap::createMenuItem for possible 'options' fields
            return $this->createMenuItem($options);
        });
        $pickwareCommonInstaller = new PickwareCommonInstaller($this);
        $installationCheckpointWriter = new InstallationCheckpointWriter($this);
        $pickProfilesService = $this->get('viison_pickware_mobile.pick_profiles_service');
        $queryComponentArrayCodingService = $this->get('viison_pickware_mobile.query_component_array_coding_service');
        $aclResourceHelper = new AclResource\AclResourceHelper($this->get('models'));
        $aclResourceCreator = new AclResource\AclResourceCreator($aclResourceHelper, $this->get('acl'), $this->get('models'));
        $isNewInstallation = !$this->getPlugin()->getInstalled() && !$this->getPlugin()->getUpdated();

        $oldInstallVersion = ViisonCommonUtil::convertBinaryVersionToInstallVersion($oldVersion);
        switch ($oldInstallVersion) {
            case 'install':
                // Prevent installation if the plugin was previously uninstalled without deleting data, and the version
                // installed at that time is not supported for incremental updates
                $lastSafeUninstallVersion = $this->get('viison_common.hidden_config_storage')->getConfigValue(
                    self::CONFIG_ELEMENT_NAME_LAST_SAFE_UNINSTALL_VERSION
                );
                if ($lastSafeUninstallVersion && version_compare($lastSafeUninstallVersion, self::MIN_SUPPORTED_UPDATE_VERSION, '<')) {
                    throw InstallationException::invalidReInstall(
                        $this,
                        $lastSafeUninstallVersion,
                        $this->getVersion(),
                        self::MIN_SUPPORTED_UPDATE_VERSION
                    );
                }

                /* Subscribes */

                $this->subscribeEvent(
                    'Enlight_Controller_Front_StartDispatch',
                    'onStartDispatch'
                );
                $this->subscribeEvent(
                    'Shopware_Console_Add_Command',
                    'onAddConsoleCommand'
                );

                /* Config elements */

                // Add a config element for the Picking app document generation mode
                $form->setElement(
                    'select',
                    'pickingAppDocumentGenerationMode',
                    [
                        'label' => 'Versand - Dokumentenerzeugung',
                        'description' => 'Wählen Sie den Modus, nach dem die Dokumente in der Versand App erzeugt werden sollen. Hinweis zu Teillieferungen: Rechnung wird nur bei der jeweils ersten Teillieferungen erzeugt.',
                        'value' => 0,
                        'store' => [
                            [
                                0,
                                'Immer nur Rechnung',
                            ],
                            [
                                1,
                                'Immer Rechnung und Lieferschein',
                            ],
                            [
                                2,
                                'Lieferschein STATT Rechnung bei abweichender Lieferadresse',
                            ],
                            [
                                3,
                                'Immer Rechnung UND zusätzlich Lieferschein bei abweichender Lieferadresse',
                            ],
                        ],
                    ]
                );
                // Add a config element for selecting payment methods which will print only
                // dispatch notes and no invoices (dynamic field type)
                $form->setElement(
                    'select',
                    'pickingAppPaymentMethodIdsWithDispatchNoteInsteadOfInvoice',
                    [
                        'label' => 'Versand - Zahlungsarten mit Lieferschein statt Rechnung',
                        'description' => 'Wählen Sie hier die Zahlungsarten aus, für die in der Versand App immer ein Lieferschein anstatt einer Rechnung gedruckt werden soll.',
                        'value' => [],
                        'store' => 'Shopware.apps.Base.store.Payment',
                        'displayField' => 'description',
                        'multiSelect' => true,
                        'editable' => false,
                    ]
                );
                // Add a config element for the Picking app document printing mode
                $form->setElement(
                    'select',
                    'pickingAppDocumentPrintingMode',
                    [
                        'label' => 'Versand - Dokumentendruck',
                        'description' => 'Wählen Sie den Modus, nach dem die Dokumente in der Versand App gedruckt werden sollen.',
                        'value' => 0,
                        'store' => [
                            [
                                0,
                                'Rechnungen, Lieferscheine und Versandetiketten drucken',
                            ],
                            [
                                1,
                                'Nur Versandetiketten und Lieferscheine drucken',
                            ],
                        ],
                    ]
                );
                // Add a config element for the number of printed copies per generated invoice
                $form->setElement(
                    'number',
                    'pickingAppDocumentPrintingInvoiceCopies',
                    [
                        'label' => 'Versand - Gedruckte Exemplare je Rechnung',
                        'description' => 'Wählen Sie hier, wie viele Exemplare je Rechnung gedruckt werden sollen, sobald eine Bestellung in der Versand App abgeschlossen wird.',
                        'value' => 1,
                        'minValue' => 1,
                    ]
                );
                // Add a config element for choosing to always send the invoice via email when completing picking
                $form->setElement(
                    'checkbox',
                    'pickingAppSendInvoiceViaEmail',
                    [
                        'label' => 'Versand - Rechnung per E-Mail versenden',
                        'description' => 'Aktivieren Sie diese Feld um Rechnungen, die beim Kommissionieren in der Versand App erstellt werden, immer per E-Mail an den Kunden zu senden. Dies ist unabhängig davon, ob Rechnung auch gedruckt werden sollen (siehe "Versand - Dokumentendruck").',
                        'value' => false,
                    ]
                );
                // Add a config element for the printing an additional invoice if the order will
                // contain export documents
                $form->setElement(
                    'checkbox',
                    'pickingAppPrintAdditionalInvoiceForExportDocuments',
                    [
                        'label' => 'Versand - Zusätzliche Rechnung bei Exportdokument',
                        'description' => 'Wenn Sie diese Option aktivieren, wird in der Versand App ein zusätzliches Exemplar der Rechnung gedruckt, wenn die Bestellung einer Versandart mit Exportdokument zugeordnet ist.',
                        'value' => false,
                    ]
                );
                // Add a config element for choosing payment methods for which a second delivery note shall be
                // printed when completing picking (dynamic field type)
                $form->setElement(
                    'select',
                    'pickingAppPrintAdditionalDeliveryNoteForPaymentMethodIds',
                    [
                        'label' => 'Versand - Zusätzlicher Lieferschein für ausgewählte Zahlungsarten',
                        'description' => 'Wählen Sie hier Zahlungsarten aus, für die beim Kommissionieren entsprechender Bestellungen zwei Lieferscheine gedruckt werden sollen.',
                        'value' => [],
                        'store' => 'Shopware.apps.Base.store.Payment',
                        'displayField' => 'description',
                        'multiSelect' => true,
                        'editable' => false,
                    ]
                );
                // Add a config element for the selection of dispatch methods that should be associated
                // with the third party shipping provider (dynamic field type)
                $form->setElement(
                    'select',
                    'pickingAppThirdPartyDispatchMethodIds',
                    [
                        'label' => 'Versand - Fremde Versandarten',
                        'description' => 'Wählen Sie hier zusätzliche Versandarten aus, deren entsprechende Bestellungen in der Versand App angezeigt werden sollen, obwohl für diese keine Versandetiketten generiert werden können.',
                        'value' => [],
                        'store' => 'Shopware.apps.Base.store.Dispatch',
                        'multiSelect' => true,
                        'editable' => false,
                    ]
                );
                // Add a config element for the selection of dispatch methods that should be privileged
                // while Picking (dynamic field type). Will be removed when migrating to v5.0.0.
                $form->setElement(
                    'select',
                    'pickingAppPrivilegedDispatchMethodIds',
                    [
                        'label' => 'Versand - Bevorzugte Versandarten',
                        'description' => 'Wählen Sie hier die Versandarten aus, die von der Versand App bevorzugt behandelt, das heißt immer am Anfang der Liste der Bestellungen geführt werden sollen.',
                        'value' => [],
                        'store' => 'Shopware.apps.Base.store.Dispatch',
                        'multiSelect' => true,
                        'editable' => false,
                    ]
                );
                // Add a config element for the selection of payment methods that should be privileged
                // while Picking (dynamic field type). Will be removed when migrating to v5.0.0.
                $form->setElement(
                    'select',
                    'pickingAppPrivilegedPaymentMethodIds',
                    [
                        'label' => 'Versand - Bevorzugte Zahlungsarten',
                        'description' => 'Wählen Sie hier die Zahlungsarten aus, die von der Versand App bevorzugt behandelt, das heißt immer am Anfang der Liste der Bestellungen geführt werden sollen.',
                        'value' => [],
                        'store' => 'Shopware.apps.Base.store.Payment',
                        'displayField' => 'description',
                        'multiSelect' => true,
                        'editable' => false,
                    ]
                );
                // Add a config element for the selection of dispatch methods, for which no dispatch
                // status emails shall be sent (dynamic field type)
                $form->setElement(
                    'select',
                    'pickingAppNoStatusMailDispatchMethodIds',
                    [
                        'label' => 'Versand - Versandarten ohne autom. E-Mail',
                        'description' => 'Wählen Sie hier die Versandarten aus, für die keine automatischen Versand E-Mails verschickt werden sollen.',
                        'value' => [],
                        'store' => 'Shopware.apps.Base.store.Dispatch',
                        'multiSelect' => true,
                        'editable' => false,
                    ]
                );
                // Add a config element for defining the maximum number of orders displayed in the Picking app
                $form->setElement(
                    'number',
                    'pickingAppMaxNumberOfOrdersInAPIResults',
                    [
                        'label' => 'Versand - Max. in App angezeigte Bestellungen',
                        'description' => 'Wählen Sie hier wie viele Bestellungen maximal gleichzeitig in der Lager App angezeigt werden sollen. Bestellungen, die über dieser Anzahl liegen, werden erst angezeigt, sobald andere Bestellungen komplett versandt wurden.',
                        'value' => 1000,
                        'minValue' => 1,
                    ]
                );
                // Add a config element for the number of printed copies per generated invoice
                $form->setElement(
                    'checkbox',
                    'pickingAppCreateShippingLabelUponCompletion',
                    [
                        'label' => 'Versand - Versandetikett direkt nach dem Picken erstellen',
                        'description' => 'Wenn Sie diese Option aktivieren, öffnet die Versand App automatisch den Assistenten zum Erstellen eines Versandetiketts, nachdem die Bestellung gepickt wurde.',
                        'value' => false,
                    ]
                );
                // Add config element for the selection of subshops, whose orders shall be displayed in
                // the Picking app (dynamic field type). Will be removed when migrating to v5.0.0.
                $form->setElement(
                    'select',
                    'pickingOrderFilterRelevantSubshopIds',
                    [
                        'label' => 'Versand - Relevante Shops',
                        'description' => 'Wählen Sie hier die Subshops aus, deren Bestellungen in der Versand App angezeigt werden sollen. Lassen Sie dieses Feld leer, um die Bestellungen aller Subshops anzuzeigen.',
                        'value' => [1],
                        'store' => 'Shopware.apps.Base.store.ShopLanguage',
                        'multiSelect' => true,
                        'editable' => false,
                    ]
                );
                // Add a config element for the number of days an order, which contains only preordered items,
                // is returned in advance, when using the 'pickware' filter. Will be removed when migrating to v5.0.0.
                $form->setElement(
                    'number',
                    'pickingOrderFilterNumDaysForPreOrderedItems',
                    [
                        'label' => 'Versand - Vorlauf Bestellungen',
                        'description' => 'Wählen Sie hier wie viele Tage eine Bestellung, welche ausschließlich vorbestellbare Artikel enthält, vor Erscheinen des ersten Artikels in der Versand App angezeigt werden soll.',
                        'value' => 3,
                        'minValue' => 0,
                    ]
                );
                // Add a config element for filtering picking orders based on available items. Will be removed when
                // migrating to v5.0.0.
                $form->setElement(
                    'select',
                    'pickingOrderFilterStockBaseFiltering',
                    [
                        'label' => 'Versand - Bestellfilter nach Bestand',
                        'description' => 'Wählen Sie ob Bestellungen basierend auf dem Warenbestand der enthaltenen Positionen in der Versand App angezeigt werden sollen.',
                        'value' => StockBasedFilterConfiguration::FILTER_MODE_OFF,
                        'store' => [
                            [
                                StockBasedFilterConfiguration::FILTER_MODE_OFF,
                                'Bestellungen unabhängig von Lagerbestand anzeigen',
                            ],
                            [
                                StockBasedFilterConfiguration::FILTER_MODE_AT_LEAST_ONE_ITEM_HAS_SOME_STOCK,
                                'Mind. ein Artikel muss teilweise auf Lager sein',
                            ],
                            [
                                StockBasedFilterConfiguration::FILTER_MODE_AT_LEAST_ONE_ITEM_HAS_SUFFICIENT_STOCK,
                                'Mind. ein Artikel muss komplett auf Lager sein',
                            ],
                            [
                                StockBasedFilterConfiguration::FILTER_MODE_ALL_RELEASED_ITEMS_HAVE_ALL_REQUIRED_STOCK,
                                'Alle erschienenen Artikel müssen komplett auf Lager sein',
                            ],
                            [
                                StockBasedFilterConfiguration::FILTER_MODE_ALL_ITEMS_HAVE_ALL_REQUIRED_STOCK,
                                'Alle Artikel müssen komplett auf Lager sein',
                            ],
                        ],
                    ]
                );
                // Add a config element for the selection of dispatch methods, for which the stock
                // based order filter is modified to let them pass the filter even if only one item
                // has a stock greater zero (dynamic field type). Will be removed when migrating to v5.0.0.
                $form->setElement(
                    'select',
                    'pickingOrderFilterStockBaseFilteringExceptedDispatchMethodIds',
                    [
                        'label' => 'Versand - Ausnahmen vom Bestellfilter nach Bestand',
                        'description' => 'Wählen Sie hier die Versandarten aus, für die entsprechende Bestellungen, auch bei aktiviertem Bestandsfilter, unabhängig vom Bestand angezeigt werden sollen.',
                        'value' => [],
                        'store' => 'Shopware.apps.Base.store.Dispatch',
                        'multiSelect' => true,
                        'editable' => false,
                    ]
                );// Add a config element for choosing payment stati, whose respective open orders will always pass the
                // OrderFilter, if no other conditions fail. Will be removed when migrating to v5.0.0.
                $form->setElement(
                    'select',
                    'pickingOrderFilterRelevantPaymentStatusIds',
                    [
                        'label' => 'Versand - Relevante Bezahlstatus',
                        'description' => 'Wählen Sie hier Bezahlstatus aus, für die entsprechende, offene Bestellungen in der Versand App angezeigt werden sollen.',
                        'value' => [
                            12,
                            20,
                        ],
                        'store' => 'Shopware.apps.Base.store.PaymentStatus',
                        'displayField' => 'description',
                        'multiSelect' => true,
                        'editable' => false,
                    ]
                );
                // Add a config element for choosing payment methods, whose respective open orders will only pass the
                // OrderFilter, if they have one of the payment stati chosen in
                // 'pickingOrderFilterPaymentMethodConstrainedPaymentStatusIds' and no other conditions fail. Will be
                // removed when migrating to v5.0.0.
                $form->setElement(
                    'select',
                    'pickingOrderFilterPaymentStatusConstrainedPaymentMethodIds',
                    [
                        'label' => 'Versand - Relevante Zahlungsarten mit bestimmtem Bezahlstatus',
                        'description' => 'Wählen Sie hier Zahlungsarten aus, für die entsprechende, offene Bestellungen nur dann in der Versand App angezeigt werden sollen, wenn die Bestellungen einen Bezahlstatus aus "Versand - Bezahlstatus für relevante Zahlungsarten" haben.',
                        'value' => [
                            3,
                            4,
                        ],
                        'store' => 'Shopware.apps.Base.store.Payment',
                        'displayField' => 'description',
                        'multiSelect' => true,
                        'editable' => false,
                    ]
                );
                // Add a config element for choosing payment stati, whose respective open orders will only pass the
                // OrderFilter, if they have one of the payment methods chosen in
                // 'pickingOrderFilterPaymentStatusConstrainedPaymentMethodIds' and no other conditions fail. Will be
                // removed when migrating to v5.0.0.
                $form->setElement(
                    'select',
                    'pickingOrderFilterPaymentMethodConstrainedPaymentStatusIds',
                    [
                        'label' => 'Versand - Bezahlstatus für relevante Zahlungsarten',
                        'description' => 'Wählen Sie hier Bezahlstatus aus, für die entsprechende Bestellungen nur dann in der Versand App angezeigt werden sollen, wenn die Bestellungen eine Zahlungsart aus "Versand - Relevante Zahlungsarten mit bestimmtem Bezahlstatus" haben.',
                        'value' => [17],
                        'store' => 'Shopware.apps.Base.store.PaymentStatus',
                        'displayField' => 'description',
                        'multiSelect' => true,
                        'editable' => false,
                    ]
                );
                // Add a config element for the selection of the field by which the items in the
                // REST API orders index result are sorted
                $form->setElement(
                    'select',
                    'apiOrderItemsSortField',
                    [
                        'label' => 'Versand/Lager - Artikel sortieren nach',
                        'description' => 'Wählen Sie das Feld, nach dem die Artikel einer Bestellung sortiert werden sollen. Sind zwei Werte gleich, so wird als zweites Kriterium immer die Artikelnummer verglichen, da diese eindeutig ist.',
                        'value' => 'defaultBinLocationCode',
                        'store' => [
                            [
                                'defaultBinLocationCode',
                                'Lagerplatz',
                            ],
                            [
                                'article.name',
                                'Artikelname',
                            ],
                            [
                                'number',
                                'Artikelnummer',
                            ],
                            [
                                'supplierNumber',
                                'Herstellernummer',
                            ],
                        ],
                    ]
                );
                // Add a config element for the selection of the comparison type used when sorting items
                // or the REST API orders index result
                $form->setElement(
                    'select',
                    'apiOrderItemsSortComparisonType',
                    [
                        'label' => 'Versand/Lager - Art der Sortierung',
                        'description' => 'Wählen Sie, ob die Felder der Artikel alphanumerisch oder numerisch verglichen werden sollen.',
                        'value' => 0,
                        'store' => [
                            [
                                0,
                                'Alphanumerisch',
                            ],
                            [
                                1,
                                'Numerisch',
                            ],
                        ],
                    ]
                );
                // Add a config element for displaying/hiding the about window
                $form->setElement(
                    'checkbox',
                    'displayAboutPickwareMobileWindow',
                    [
                        'label' => 'Shopware WMS powered by Pickware Info-Dialog anzeigen',
                        'description' => 'Bestimmt, ob der Shopware WMS powered by Pickware Info-Dialog beim Laden des Backends angezeigt werden soll.',
                        'value' => true,
                    ]
                );
                // Add a config element for selecting extra documents that will be created/printed alongside invoices
                $form->setElement(
                    'text',
                    'pickingAppDocumentGenerationExtraDocumentsWithInvoice',
                    [
                        'label' => 'Versand - Zusätzlich Dokumente bei Rechnungserstellung',
                        'description' => 'Tragen Sie hier die IDs der Dokumente ein, die immer dann erstellt und gedruckt werden sollen, wenn die Versand App eine Rechnung erstellt. Mehrere IDs durch Kommata trennen.',
                        'value' => '',
                    ]
                );
                // Add a config element for selecting extra documents that will be created/printed alongside dispatch notes
                $form->setElement(
                    'text',
                    'pickingAppDocumentGenerationExtraDocumentsWithDeliveryNote',
                    [
                        'label' => 'Versand - Zusätzlich Dokumente bei Lieferscheinerstellung',
                        'description' => 'Tragen Sie hier die IDs der Dokumente ein, die immer dann erstellt und gedruckt werden sollen, wenn die Versand App einen Lieferschein erstellt. Mehrere IDs durch Kommata trennen.',
                        'value' => '',
                    ]
                );
                // Add a config element for enabling/disabling automatic 'return shipment received' emails
                $form->setElement(
                    'checkbox',
                    'stockingAppSendReshipmentReceivedMails',
                    [
                        'label' => 'Lager - E-Mail an Kunden bei eingegangener Retoure',
                        'description' => 'Aktivieren Sie dieses Feld, um automatisch eine E-Mail an den Kunden zu schicken, sobald dessen Retoure über die Lager App erfasst wurde.',
                        'value' => false,
                    ]
                );

                /* Attribute fields */
                $attributeColumnInstallationHelper->addAttributeColumnsIfNotExist([
                    // Articles: Add a column to store the 'not relevant for picking' flag
                    new AttributeColumnDescription(
                        's_articles_attributes',
                        'viison_not_relevant_for_picking',
                        'tinyint(1)',
                        0,
                        false
                    ),
                    // Order: Add column to store the tracking codes, which still have to be dispatched
                    new AttributeColumnDescription(
                        's_order_attributes',
                        'viison_undispatched_tracking_codes',
                        'varchar(255)'
                    ),
                    // Order: Add column to store a batch picking box ID
                    new AttributeColumnDescription(
                        's_order_attributes',
                        'viison_batch_picking_box_id',
                        'varchar(255)'
                    ),
                    // Order: Add column to store a batch picking transaction ID
                    new AttributeColumnDescription(
                        's_order_attributes',
                        'viison_batch_picking_transaction_id',
                        'varchar(255)'
                    ),
                    // Order: Add column to store the ID of the warehouse in which the order is being processed/picked
                    new AttributeColumnDescription(
                        's_order_attributes',
                        'viison_processing_warehouse_id',
                        'int(11)'
                    ),
                    // Add an order attribute column to store the combined reshipment status
                    new AttributeColumnDescription(
                        's_order_attributes',
                        'viison_reshipment_status_id',
                        'int(11)'
                    ),
                    // Order details: Add column to store the already picked quantity
                    new AttributeColumnDescription(
                        's_order_details_attributes',
                        'viison_picked_quantity',
                        'int(11)',
                        0,
                        false
                    ),
                ]);

                /* Attribute configurations */

                // Add a checkbox to the article details for manually marking an article as not relevant for picking
                $attributeConfigurationInstallationHelper->createAttributeConfigurationUnlessExists(
                    's_articles_attributes',
                    'viison_not_relevant_for_picking',
                    'boolean',
                    'In der Versand App ausblenden',
                    'Aktivieren Sie dieses Feld, um den Artikel in der Versand App auszublenden. Enthält eine Bestellung nur Artikel, bei denen diese Checkbox aktiviert ist, wird die Bestellung nicht in der Versand App angezeigt.',
                    55
                );

                /* Picked quantity tables */

                // Create the table for Shopware\CustomModels\ViisonPickwareMobile\PickedQuantity\PickedQuantity
                $database->query(
                    'CREATE TABLE IF NOT EXISTS `s_plugin_pickware_mobile_picked_quantities` (
                        `id` int(11) NOT NULL AUTO_INCREMENT,
                        `orderDetailId` int(11) NOT NULL,
                        `binLocationId` int(11) DEFAULT NULL,
                        `quantity` int(11) NOT NULL DEFAULT 0,
                        PRIMARY KEY (`id`),
                        UNIQUE KEY `orderDetailId_binLocationId` (`orderDetailId`, `binLocationId`),
                        KEY `IDX_A44406B0CB2FF2E0` (`orderDetailId`),
                        KEY `IDX_A44406B0EC8281FB` (`binLocationId`),
                        CONSTRAINT `FK_A44406B0CB2FF2E0` FOREIGN KEY (`orderDetailId`) REFERENCES `s_order_details` (`id`) ON DELETE CASCADE,
                        CONSTRAINT `FK_A44406B0EC8281FB` FOREIGN KEY (`binLocationId`) REFERENCES `pickware_erp_bin_locations` (`id`) ON DELETE SET NULL
                    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;'
                );
                // Create the table for Shopware\CustomModels\ViisonPickwareMobile\PickedQuantity\StockLedgerEntryMapping
                $database->query(
                    'CREATE TABLE IF NOT EXISTS `s_plugin_pickware_mobile_picked_quantity_stock_entry_mappings` (
                        `id` int(11) NOT NULL AUTO_INCREMENT,
                        `pickedQuantityId` int(11) NOT NULL,
                        `stockEntryId` int(11) NOT NULL,
                        PRIMARY KEY (`id`),
                        UNIQUE KEY `pickedQuantityId_stockEntryId` (`pickedQuantityId`, `stockEntryId`),
                        KEY `IDX_CEC39A8AB54908EE` (`pickedQuantityId`),
                        KEY `IDX_CEC39A8A7794F255` (`stockEntryId`),
                        CONSTRAINT `FK_CEC39A8AB54908EE` FOREIGN KEY (`pickedQuantityId`) REFERENCES `s_plugin_pickware_mobile_picked_quantities` (`id`) ON DELETE CASCADE,
                        CONSTRAINT `FK_CEC39A8A7794F255` FOREIGN KEY (`stockEntryId`) REFERENCES `pickware_erp_stock_ledger_entries` (`id`) ON DELETE CASCADE
                    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;'
                );

                /* Custom media albums */

                // Add a new media album for pictures/documents related to reshipments
                $mediaAlbumInstallationHelper->createMediaAlbumUnlessExists(
                    'Pickware Rücksendungen',
                    'sprite-box--arrow',
                    30
                );

                /* Other */

                // Install the extended order state email templates for 'partly shipped' and 'completely shipped'
                $this->createEmailTemplates();
                $this->addLocalizedGrowlMessage('bootstrap/viison_pickware_mobile/main', 'update_hint/email_templates_modified');

                $pickwareCommonInstaller->installViisonPickwareCommon();

            /* Incremental Updates */

            case self::MIN_SUPPORTED_UPDATE_VERSION:
                // Nothing to do
            case '4.2.1':
                // Nothing to do
            case '4.2.2':
                // Nothing to do
            case '4.2.3':
                // Nothing to do
            case '4.3.0':
                // Install reworked barcode label printing feature
                $pickwareCommonInstaller->installViisonPickwareCommon();
            case '4.4.0':
                // Nothing to do
            case '4.4.1':
                // Nothing to do
            case '4.5.0':
                // Nothing to do
            case '4.5.1':
                // Nothing to do
            case '4.5.2':
                // Nothing to do
            case '4.6.0':
                // Nothing to do
            case '4.6.1':
                // Nothing to do
            case '4.6.2':
                // Save the form to make sure, that the new config elements are persisted
                $modelManager->persist($form);

                // Add new option 'Only print shipping labels' to 'pickingAppDocumentPrintingMode'
                $pickingAppDocumentPrintingModeElement = $form->getElement('pickingAppDocumentPrintingMode');
                if ($pickingAppDocumentPrintingModeElement) {
                    $options = $pickingAppDocumentPrintingModeElement->getOptions();
                    $options['store'][] = [
                        2,
                        'Only print shipping labels',
                    ];
                    $pickingAppDocumentPrintingModeElement->setOptions($options);
                    $modelManager->flush($pickingAppDocumentPrintingModeElement);
                }
            case '4.7.0':
                // Nothing to do
            case '4.8.0':
                // Nothing to do
            case '4.9.0':
                // Nothing to do
            case '4.9.1':
                // Nothing to do (original migration moved to step '4.10.3')
            case '4.9.2':
                // Nothing to do
            case '4.10.0':
                // Nothing to do
            case '4.10.1':
                // Nothing to do
            case '4.10.2':
                // Fix backend sessions
                $backendSessionMigration = new BackendSessionLocaleClassMigration(
                    $this->get('models'),
                    $this->get('db'),
                    $this->get('pluginlogger'),
                    $this->get('application')->Container()
                );
                $backendSessionMigration->fixSessions();
            case '4.10.3':
                // Add a checkpoint to ensure none of the migrations above this version are executed after the tables
                // are renamed
                $installationCheckpointWriter->commitCheckpointAtVersion('4.10.3');

                // Fix all cached reserved stocks (originally executed in step '4.9.1')
                $database->query(
                    'UPDATE `pickware_erp_article_detail_bin_location_mappings` AS `binLocationMappings`
                    LEFT JOIN (
                        # Sum the quantities of all stock reservations
                        SELECT
                            `articleDetailBinLocationMappingId`,
                            SUM(`quantity`) AS `quantity`
                        FROM `pickware_erp_order_stock_reservations`
                        GROUP BY `articleDetailBinLocationMappingId`
                    ) AS `reservedStock`
                        ON `reservedStock`.`articleDetailBinLocationMappingId` = `binLocationMappings`.`id`
                    SET `binLocationMappings`.`reservedStock` = IFNULL(`reservedStock`.`quantity`, 0)'
                );
                $database->query(
                    'UPDATE `pickware_erp_warehouse_article_detail_stock_counts` AS `stockCounts`
                    LEFT JOIN (
                        # Sum the reserved stocks of all bin location mappings
                        SELECT
                            `binLocationMappings`.`articleDetailId` AS `articleDetailId`,
                            `binLocation`.`warehouseId` AS `warehouseId`,
                            SUM(`binLocationMappings`.`reservedStock`) AS `quantity`
                        FROM `pickware_erp_article_detail_bin_location_mappings` AS `binLocationMappings`
                        INNER JOIN `pickware_erp_bin_locations` AS `binLocation`
                            ON `binLocation`.id = `binLocationMappings`.`binLocationId`
                        GROUP BY
                            `binLocationMappings`.`articleDetailId`,
                            `binLocation`.`warehouseId`
                    ) AS `reservedStock`
                        ON `reservedStock`.`articleDetailId` = `stockCounts`.`articleDetailId`
                        AND `reservedStock`.`warehouseId` = `stockCounts`.`warehouseId`
                    SET `stockCounts`.`reservedStock` = IFNULL(`reservedStock`.`quantity`, 0)'
                );

                // Rename tables for Pickware 5
                // Drop all old constraints
                $sqlHelper->dropForeignKeyConstraintIfExists(
                    's_plugin_pickware_mobile_picked_quantities',
                    'orderDetailId',
                    's_order_details',
                    'id'
                );
                $sqlHelper->dropForeignKeyConstraintIfExists(
                    's_plugin_pickware_mobile_picked_quantities',
                    'binLocationId',
                    'pickware_erp_bin_locations',
                    'id'
                );
                $sqlHelper->dropForeignKeyConstraintIfExists(
                    's_plugin_pickware_mobile_picked_quantity_stock_entry_mappings',
                    'pickedQuantityId',
                    's_plugin_pickware_mobile_picked_quantities',
                    'id'
                );
                $sqlHelper->dropForeignKeyConstraintIfExists(
                    's_plugin_pickware_mobile_picked_quantity_stock_entry_mappings',
                    'stockEntryId',
                    'pickware_erp_stock_ledger_entries',
                    'id'
                );
                // Actually rename the tables
                $sqlHelper->ensureTableIsRenamed(
                    's_plugin_pickware_mobile_picked_quantities',
                    'pickware_wms_picked_quantities'
                );
                $sqlHelper->ensureTableIsRenamed(
                    's_plugin_pickware_mobile_picked_quantity_stock_entry_mappings',
                    'pickware_wms_picked_quantity_stock_ledger_entry_mappings'
                );
                // Rename 'pickware_wms_picked_quantity_stock_ledger_entry_mappings.stockEntryId'
                // to 'stockLedgerEntryId'
                $sqlHelper->ensureColumnIsRenamed(
                    'pickware_wms_picked_quantity_stock_ledger_entry_mappings',
                    'stockEntryId',
                    'stockLedgerEntryId',
                    'int(11) NOT NULL'
                );
                // Add all constraints again
                // Add a cascading foreign key constraint from picked quantities to order details
                $sqlHelper->cleanUpAndEnsureCascadingForeignKeyConstraint(
                    'pickware_wms_picked_quantities',
                    'orderDetailId',
                    's_order_details',
                    'id'
                );
                // Add a nulling foreign key constraint from picked quantities to bin locations
                $sqlHelper->cleanUpAndEnsureNullingForeignKeyConstraint(
                    'pickware_wms_picked_quantities',
                    'binLocationId',
                    'pickware_erp_bin_locations',
                    'id'
                );
                // Add a cascading foreign key constraint from stock ledger entry mappings to picked quantities
                $sqlHelper->cleanUpAndEnsureCascadingForeignKeyConstraint(
                    'pickware_wms_picked_quantity_stock_ledger_entry_mappings',
                    'pickedQuantityId',
                    'pickware_wms_picked_quantities',
                    'id'
                );
                // Add a cascading foreign key constraint from stock ledger entry mappings to stock ledger entries
                $sqlHelper->cleanUpAndEnsureCascadingForeignKeyConstraint(
                    'pickware_wms_picked_quantity_stock_ledger_entry_mappings',
                    'stockLedgerEntryId',
                    'pickware_erp_stock_ledger_entries',
                    'id'
                );

                // Rename attribute columns for Pickware 5
                $sqlHelper->ensureColumnIsRenamed(
                    's_articles_attributes',
                    'viison_not_relevant_for_picking',
                    'pickware_not_relevant_for_picking',
                    'tinyint(1) NOT NULL DEFAULT \'0\''
                );
                $sqlHelper->ensureColumnIsRenamed(
                    's_order_attributes',
                    'viison_batch_picking_box_id',
                    'pickware_batch_picking_box_id',
                    'varchar(255) NULL'
                );
                $sqlHelper->ensureColumnIsRenamed(
                    's_order_attributes',
                    'viison_batch_picking_transaction_id',
                    'pickware_batch_picking_transaction_id',
                    'varchar(255) NULL'
                );
                $sqlHelper->ensureColumnIsRenamed(
                    's_order_attributes',
                    'viison_processing_warehouse_id',
                    'pickware_processing_warehouse_id',
                    'int(11) NULL'
                );
                $sqlHelper->ensureColumnIsRenamed(
                    's_order_attributes',
                    'viison_reshipment_status_id',
                    'pickware_reshipment_status_id',
                    'int(11) NULL'
                );
                $sqlHelper->ensureColumnIsRenamed(
                    's_order_details_attributes',
                    'viison_picked_quantity',
                    'pickware_picked_quantity',
                    'int(11) NOT NULL DEFAULT \'0\''
                );
                $modelManager->generateAttributeModels([
                    's_articles_attributes',
                    's_order_details_attributes',
                    's_order_attributes',
                ]);
                // Remove / recreate any attribute configurations for the renamed columns
                $attributeConfigurationUninstallationHelper->removeAttributeConfigurationsIfExist([
                    's_articles_attributes' => [
                        'viison_not_relevant_for_picking',
                    ],
                ]);
                $attributeConfigurationInstallationHelper->createAttributeConfigurationUnlessExists(
                    's_articles_attributes',
                    'pickware_not_relevant_for_picking',
                    'boolean',
                    'In der Versand App ausblenden',
                    'Aktivieren Sie dieses Feld, um den Artikel in der Versand App auszublenden. Enthält eine Bestellung nur Artikel, bei denen diese Checkbox aktiviert ist, wird die Bestellung nicht in der Versand App angezeigt.',
                    55
                );

                // Re-calculate the total picked quantity for all order details, since we might have cleaned up some
                // stale picked quantity entries
                $database->query(
                    'UPDATE `s_order_details_attributes` AS `attributes`
                    LEFT JOIN (
                        SELECT
                            `orderDetailId`,
                            SUM(`quantity`) AS `pickedQuantity`
                        FROM `pickware_wms_picked_quantities`
                        GROUP BY `orderDetailId`
                    ) AS `tmpResult`
                        ON `tmpResult`.`orderDetailId` = `attributes`.`detailID`
                    SET `attributes`.`pickware_picked_quantity` = IFNULL(`tmpResult`.`pickedQuantity`, 0)'
                );
            case '5.0.0':
                // Nothing to do
            case '5.0.1':
                // Nothing to do
            case '5.0.2':
                // Nothing to do
            case '5.0.3':
                // Save the form to make sure, that the new config elements are persisted
                $modelManager->persist($form);

                // Add a new table for storing pick profiles
                $database->query(
                    'CREATE TABLE IF NOT EXISTS `pickware_wms_pick_profiles` (
                        `id` int(11) NOT NULL AUTO_INCREMENT,
                        `name` varchar(255) NOT NULL DEFAULT \'\',
                        `stockBasedOrderFilterMode` int(11) NOT NULL DEFAULT \'0\',
                        `advanceDaysForPreOrderedItems` int(11) NOT NULL DEFAULT \'0\',
                        `encodedOrderFilterQueryConditions` text,
                        PRIMARY KEY (`id`)
                    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;'
                );
                // Add a new table for storing pick profiles' prioritized dispatch methods
                $database->query(
                    'CREATE TABLE IF NOT EXISTS `pickware_wms_pick_profile_prioritized_dispatch_methods` (
                        `id` int(11) NOT NULL AUTO_INCREMENT,
                        `pickProfileId` int(11) NOT NULL,
                        `dispatchMethodId` int(11) NOT NULL,
                        PRIMARY KEY (`id`),
                        UNIQUE KEY `pickProfileId_dispatchMethodId` (`pickProfileId`, `dispatchMethodId`),
                        KEY `IDX_CE95EC11F9461B0` (`pickProfileId`),
                        KEY `IDX_CE95EC12C02BD9B` (`dispatchMethodId`),
                        CONSTRAINT `FK_CE95EC11F9461B0` FOREIGN KEY (`pickProfileId`) REFERENCES `pickware_wms_pick_profiles` (`id`) ON DELETE CASCADE,
                        CONSTRAINT `FK_CE95EC12C02BD9B` FOREIGN KEY (`dispatchMethodId`) REFERENCES `s_premium_dispatch` (`id`) ON DELETE CASCADE
                    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;'
                );
                // Add a new table for storing pick profiles' prioritized payment methods
                $database->query(
                    'CREATE TABLE IF NOT EXISTS `pickware_wms_pick_profile_prioritized_payment_methods` (
                        `id` int(11) NOT NULL AUTO_INCREMENT,
                        `pickProfileId` int(11) NOT NULL,
                        `paymentMethodId` int(11) NOT NULL,
                        PRIMARY KEY (`id`),
                        UNIQUE KEY `pickProfileId_paymentMethodId` (`pickProfileId`, `paymentMethodId`),
                        KEY `IDX_33F4DA451F9461B0` (`pickProfileId`),
                        KEY `IDX_33F4DA451D3B227F` (`paymentMethodId`),
                        CONSTRAINT `FK_33F4DA451F9461B0` FOREIGN KEY (`pickProfileId`) REFERENCES `pickware_wms_pick_profiles` (`id`) ON DELETE CASCADE,
                        CONSTRAINT `FK_33F4DA451D3B227F` FOREIGN KEY (`paymentMethodId`) REFERENCES `s_core_paymentmeans` (`id`) ON DELETE CASCADE
                    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;'
                );
                // Add a new table for storing dispatch methods that are exempt from pick profiles' stock based
                // order filter
                $database->query(
                    'CREATE TABLE IF NOT EXISTS `pickware_wms_pick_profile_stock_filter_exempt_dispatch_methods` (
                        `id` int(11) NOT NULL AUTO_INCREMENT,
                        `pickProfileId` int(11) NOT NULL,
                        `dispatchMethodId` int(11) NOT NULL,
                        PRIMARY KEY (`id`),
                        UNIQUE KEY `pickProfileId_dispatchMethodId` (`pickProfileId`, `dispatchMethodId`),
                        KEY `IDX_16AA38CF1F9461B0` (`pickProfileId`),
                        KEY `IDX_16AA38CF2C02BD9B` (`dispatchMethodId`),
                        CONSTRAINT `FK_16AA38CF1F9461B0` FOREIGN KEY (`pickProfileId`) REFERENCES `pickware_wms_pick_profiles` (`id`) ON DELETE CASCADE,
                        CONSTRAINT `FK_16AA38CF2C02BD9B` FOREIGN KEY (`dispatchMethodId`) REFERENCES `s_premium_dispatch` (`id`) ON DELETE CASCADE
                    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;'
                );

                // Create a default pick profile, if none exists
                $allPickProfiles = $modelManager->getRepository(PickProfile::class)->findAll();
                if (count($allPickProfiles) === 0) {
                    // Save the values of all obsolete plugin config elements that will be removed later
                    $pluginConfig = $this->Config()->toArray();
                    $isInstallation = $oldInstallVersion === 'install';
                    if (is_array($pluginConfig['pickingAppPrivilegedDispatchMethodIds'])) {
                        $prioritizedDispatchMethodIds = $pluginConfig['pickingAppPrivilegedDispatchMethodIds'];
                    } else {
                        $prioritizedDispatchMethodIds = [];
                    }
                    if (is_array($pluginConfig['pickingAppPrivilegedPaymentMethodIds'])) {
                        $prioritizedPaymentMethodIds = $pluginConfig['pickingAppPrivilegedPaymentMethodIds'];
                    } else {
                        $prioritizedPaymentMethodIds = [];
                    }
                    if (is_int($pluginConfig['pickingOrderFilterNumDaysForPreOrderedItems'])) {
                        $advanceDaysForPreOrderedItems = $pluginConfig['pickingOrderFilterNumDaysForPreOrderedItems'];
                    } else {
                        $advanceDaysForPreOrderedItems = 3;
                    }
                    if (is_int($pluginConfig['pickingOrderFilterStockBaseFiltering'])) {
                        $stockBasedOrderFilterMode = $pluginConfig['pickingOrderFilterStockBaseFiltering'];
                    } else {
                        $stockBasedOrderFilterMode = StockBasedFilterConfiguration::FILTER_MODE_OFF;
                    }
                    if (is_array($pluginConfig['pickingOrderFilterStockBaseFilteringExceptedDispatchMethodIds'])) {
                        $stockBasedOrderFilterExemptDispatchMethodIds = $pluginConfig['pickingOrderFilterStockBaseFilteringExceptedDispatchMethodIds'];
                    } else {
                        $stockBasedOrderFilterExemptDispatchMethodIds = [];
                    }
                    if (is_array($pluginConfig['pickingOrderFilterRelevantSubshopIds'])) {
                        // Filter the configured subshop IDs to keep only those that correspond to a top-level shop
                        // (i.e. no language subshop)
                        $subshops = $modelManager->getRepository(Shop::class)->findBy([
                            'id' => $pluginConfig['pickingOrderFilterRelevantSubshopIds'],
                            'main' => null,
                        ]);
                        $validSubshopIds = array_map(
                            function (Shop $subshop) {
                                return $subshop->getId();
                            },
                            $subshops
                        );
                    } elseif ($isInstallation) {
                        $defaultShop = $modelManager->getRepository(Shop::class)->getDefault();
                        $validSubshopIds = ($defaultShop) ? [$defaultShop->getId()] : [];
                    } else {
                        $validSubshopIds = [];
                    }
                    if (is_array($pluginConfig['pickingOrderFilterRelevantPaymentStatusIds'])) {
                        $paymentStatus = $modelManager->getRepository(OrderStatus::class)->findBy([
                            'id' => $pluginConfig['pickingOrderFilterRelevantPaymentStatusIds'],
                            'group' => OrderStatus::GROUP_PAYMENT,
                        ]);
                        $validPaymentStatusIds = array_map(
                            function (OrderStatus $paymentStatus) {
                                return $paymentStatus->getId();
                            },
                            $paymentStatus
                        );
                    } elseif ($isInstallation) {
                        $validPaymentStatusIds = [
                            OrderStatus::PAYMENT_STATE_COMPLETELY_PAID,
                            OrderStatus::PAYMENT_STATE_RE_CREDITING,
                        ];
                    } else {
                        $validPaymentStatusIds = [];
                    }
                    if (is_array($pluginConfig['pickingOrderFilterPaymentStatusConstrainedPaymentMethodIds'])) {
                        $paymentMethods = $modelManager->getRepository(Payment::class)->findBy([
                            'id' => $pluginConfig['pickingOrderFilterPaymentStatusConstrainedPaymentMethodIds'],
                        ]);
                        $paymentMethodIdsConstrainedByPaymentStatus = array_map(
                            function (Payment $paymentMethod) {
                                return $paymentMethod->getId();
                            },
                            $paymentMethods
                        );
                    } elseif ($isInstallation) {
                        $paymentMethodIdsConstrainedByPaymentStatus = [
                            3, // 'cash'
                            4, // 'invoice'
                        ];
                    } else {
                        $paymentMethodIdsConstrainedByPaymentStatus = [];
                    }
                    if (is_array($pluginConfig['pickingOrderFilterPaymentMethodConstrainedPaymentStatusIds'])) {
                        $paymentStatus = $modelManager->getRepository(OrderStatus::class)->findBy([
                            'id' => $pluginConfig['pickingOrderFilterPaymentMethodConstrainedPaymentStatusIds'],
                            'group' => OrderStatus::GROUP_PAYMENT,
                        ]);
                        $paymentMethodConstrainingPaymentStatusIds = array_map(
                            function (OrderStatus $paymentStatus) {
                                return $paymentStatus->getId();
                            },
                            $paymentStatus
                        );
                    } elseif ($isInstallation) {
                        $paymentMethodConstrainingPaymentStatusIds = [OrderStatus::PAYMENT_STATE_OPEN];
                    } else {
                        $paymentMethodConstrainingPaymentStatusIds = [];
                    }

                    // Build a order filter condition based on the configured values
                    $orderFilterCondition = BooleanCompositionQueryComponent::createConjunction();
                    if (count($validSubshopIds) > 0) {
                        $orderFilterCondition->addCompositionComponents(new ComparisonQueryComponent(
                            new FieldDescriptorQueryComponent('s_order', 'subshopID'),
                            'IN',
                            new ScalarValueQueryComponent($validSubshopIds)
                        ));
                    }
                    $paymentStatusConditionComponent = BooleanCompositionQueryComponent::createDisjunction();
                    if (count($validPaymentStatusIds) > 0) {
                        $paymentStatusConditionComponent->addCompositionComponents(new ComparisonQueryComponent(
                            new FieldDescriptorQueryComponent('s_order', 'cleared'),
                            'IN',
                            new ScalarValueQueryComponent($validPaymentStatusIds)
                        ));
                    }
                    if (count($paymentMethodIdsConstrainedByPaymentStatus) > 0 && count($paymentMethodConstrainingPaymentStatusIds) > 0) {
                        $paymentStatusConditionComponent->addCompositionComponents(
                            BooleanCompositionQueryComponent::createConjunction(
                                new ComparisonQueryComponent(
                                    new FieldDescriptorQueryComponent('s_order', 'cleared'),
                                    'IN',
                                    new ScalarValueQueryComponent($paymentMethodConstrainingPaymentStatusIds)
                                ),
                                new ComparisonQueryComponent(
                                    new FieldDescriptorQueryComponent('s_order', 'paymentID'),
                                    'IN',
                                    new ScalarValueQueryComponent($paymentMethodIdsConstrainedByPaymentStatus)
                                )
                            )
                        );
                    }
                    // Reduce nesting of payment status condition
                    if (count($paymentStatusConditionComponent->getCompositionComponents()) > 1) {
                        $orderFilterCondition->addCompositionComponents($paymentStatusConditionComponent);
                    } elseif (count($paymentStatusConditionComponent->getCompositionComponents()) === 1) {
                        $orderFilterCondition->addCompositionComponents($paymentStatusConditionComponent->getCompositionComponents()[0]);
                    }
                    // Encode the constructed filter condition, if it is not empty, and reduce its nesting as possible
                    try {
                        if (count($orderFilterCondition->getCompositionComponents()) > 1) {
                            $encodedOrderFilterCondition = $queryComponentArrayCodingService->encode(
                                $orderFilterCondition
                            );
                        } elseif (count($orderFilterCondition->getCompositionComponents()) === 1) {
                            $encodedOrderFilterCondition = $queryComponentArrayCodingService->encode(
                                $orderFilterCondition->getCompositionComponents()[0]
                            );
                        } else {
                            $encodedOrderFilterCondition = [];
                        }
                    } catch (\Exception $e) {
                        $encodedOrderFilterCondition = [];
                    }

                    // Create a default pick profile using the config values
                    $defaultPickProfile = $pickProfilesService->createPickProfile(
                        $this->getSnippetNamespace()->get('default_pick_profile/name', 'Default'),
                        $encodedOrderFilterCondition
                    );
                    $defaultPickProfile->setAdvanceDaysForPreOrderedItems($advanceDaysForPreOrderedItems);
                    try {
                        $defaultPickProfile->setStockBasedOrderFilterMode($stockBasedOrderFilterMode);
                    } catch (\InvalidArgumentException $exception) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement -- ignore this exception, because the profile uses a valid default value (mode "off")
                    }
                    if (count($prioritizedDispatchMethodIds) > 0) {
                        $database->query(
                            'INSERT IGNORE INTO pickware_wms_pick_profile_prioritized_dispatch_methods (
                                pickProfileId,
                                dispatchMethodId
                            )
                            SELECT
                                :pickProfileId,
                                dispatchMethod.id
                            FROM s_premium_dispatch AS dispatchMethod
                            WHERE dispatchMethod.id IN (' . implode(',', $prioritizedDispatchMethodIds) . ')',
                            [
                                'pickProfileId' => $defaultPickProfile->getId(),
                            ]
                        );
                    }
                    if (count($prioritizedPaymentMethodIds) > 0) {
                        $database->query(
                            'INSERT IGNORE INTO pickware_wms_pick_profile_prioritized_payment_methods (
                                pickProfileId,
                                paymentMethodId
                            )
                            SELECT
                                :pickProfileId,
                                paymentMethod.id
                            FROM s_core_paymentmeans AS paymentMethod
                            WHERE paymentMethod.id IN (' . implode(',', $prioritizedPaymentMethodIds) . ')',
                            [
                                'pickProfileId' => $defaultPickProfile->getId(),
                            ]
                        );
                    }
                    if (count($stockBasedOrderFilterExemptDispatchMethodIds) > 0) {
                        $database->query(
                            'INSERT IGNORE INTO pickware_wms_pick_profile_stock_filter_exempt_dispatch_methods (
                                pickProfileId,
                                dispatchMethodId
                            )
                            SELECT
                                :pickProfileId,
                                dispatchMethod.id
                            FROM s_premium_dispatch AS dispatchMethod
                            WHERE dispatchMethod.id IN (' . implode(',', $stockBasedOrderFilterExemptDispatchMethodIds) . ')',
                            [
                                'pickProfileId' => $defaultPickProfile->getId(),
                            ]
                        );
                    }
                }

                // Remove obsolete plugin config fields
                $obsoleteFormElementNames = [
                    'pickingAppPrivilegedDispatchMethodIds',
                    'pickingAppPrivilegedPaymentMethodIds',
                    'pickingOrderFilterNumDaysForPreOrderedItems',
                    'pickingOrderFilterPaymentMethodConstrainedPaymentStatusIds',
                    'pickingOrderFilterPaymentStatusConstrainedPaymentMethodIds',
                    'pickingOrderFilterRelevantPaymentStatusIds',
                    'pickingOrderFilterRelevantSubshopIds',
                    'pickingOrderFilterStockBaseFiltering',
                    'pickingOrderFilterStockBaseFilteringExceptedDispatchMethodIds',
                ];
                $removedElements = [];
                foreach ($obsoleteFormElementNames as $elementName) {
                    $element = $form->getElement($elementName);
                    if ($element) {
                        $form->removeElement($element);
                        $modelManager->remove($element);
                        $removedElements[] = $element;
                    }
                }
                $modelManager->flush($removedElements);

                // Create menu item for the pick profiles module
                $menuInstallationHelper->ensureMenuItemInDatabaseIs([
                    'parent' => $this->Menu()->findOneBy(['controller' => 'ConfigurationMenu']),
                    'controller' => 'ViisonPickwareMobilePickProfiles',
                    'action' => 'Index',
                    'label' => 'Pickprofile',
                    'class' => 'c-sprite-picking-visible',
                    'active' => 1,
                    'position' => 40,
                ]);
                // Remove the shop configuration element for handling the backend about window and use hidden config
                // field instead
                $backendAboutPopUpConfigElementValue = true;
                $backendAboutPopUpConfigElement = $form->getElement('displayAboutPickwareMobileWindow');
                if ($backendAboutPopUpConfigElement) {
                    $backendAboutPopUpConfigElementValue = $backendAboutPopUpConfigElement->getValue();
                    $values = $backendAboutPopUpConfigElement->getValues();
                    if ($values !== null && count($values) > 0) {
                        // Since the HiddenConfigStorageService uses the default value of the config element, look for
                        // subshop configurations (config element value) and override the config element default value
                        $backendAboutPopUpConfigElementValue = $values[0]->getValue();
                    }
                    $form->removeElement($backendAboutPopUpConfigElement);
                    $modelManager->remove($backendAboutPopUpConfigElement);
                    $modelManager->flush([
                        $form,
                        $backendAboutPopUpConfigElement,
                    ]);
                }
                $this->get('viison_common.hidden_config_storage')->setConfigValue(
                    'pickwareWmsDisplayAboutWindow',
                    'boolean',
                    $backendAboutPopUpConfigElementValue
                );
            case '5.1.0':
                // Nothing to do
            case '5.1.1':
                // Nothing to do
            case '5.1.2':
                // Nothing to do
            case '5.1.3':
                // Nothing to do
            case '5.1.4':
                $attributeColumnInstallationHelper->addAttributeColumnsIfNotExist([
                    // Orders: Add a column to store the shipment GUID
                    new AttributeColumnDescription(
                        's_order_attributes',
                        'pickware_wms_shipment_guid',
                        'varchar(255)'
                    ),
                    // Order documents: Add a column to store the order shipment GUID
                    new AttributeColumnDescription(
                        's_order_documents_attributes',
                        'pickware_wms_shipment_guid',
                        'varchar(255)'
                    ),
                ]);
                $form->setElement(
                    'checkbox',
                    'showCurrentStockWhenStocktaking',
                    [
                        'label' => 'Erwarteten Lagerbestand bei Inventur anzeigen',
                        'description' => (
                            'Bei der Einstellung "Ja" wird während einer Inventur der erwartete (aktuelle) Bestand für '
                            . 'einen Artikel auf einem Lagerplatz angezeigt. Stellen Sie diese Option auf "Nein" um '
                            . 'den erwarteten Bestand nicht anzuzeigen (Blindinventur).'
                        ),
                        'value' => false,
                    ]
                );
            case '5.2.0':
                // Nothing to do
            case '5.2.1':
                // Nothing to do
            case '5.2.2':
                // Nothing to do
            case '5.2.3':
                // Nothing to do
            case '5.3.0':
                // Nothing to do
            case '5.3.1':
                // Add configuration field to show remaining positions in partial delivery notes. This configuration is
                // false for updates/reinstallations to reflect Shopware's default behavior but set it true for new
                // installations.
                $form->setElement(
                    'checkbox',
                    'showRemainingPositionsOnPartialDeliveryNote',
                    [
                        'label' => 'Ausstehende Positionen auf Teillieferschein anzeigen',
                        'description' => (
                            'Bei der Einstellung "Ja" wird einem Teillieferschein eine Seite angehängt, auf der die '
                            . 'noch ausstehenden Positionen aufgelistet werden.'
                        ),
                        'value' => $isNewInstallation,
                    ]
                );
            case '5.4.0':
                // Nothing to do
            case '5.5.0':
                // Remove comparisons against the order status from existing pick profiles
                $pickProfiles = $database->fetchAll(
                    'SELECT id, encodedOrderFilterQueryConditions
                    FROM pickware_wms_pick_profiles'
                );
                $queryComponentFilter = new QueryComponentFilter();
                $operandToRemove = [
                    'type' => FieldDescriptorQueryComponentArrayCoder::CODABLE_TYPE,
                    'tableName' => 's_order',
                    'fieldName' => 'status',
                ];
                $filterFunction = function (array $comparisonComponent) use ($operandToRemove) {
                    return $comparisonComponent['leftOperand'] != $operandToRemove && $comparisonComponent['rightOperand'] != $operandToRemove;
                };
                foreach ($pickProfiles as $pickProfile) {
                    $encodedOrderFilterConditions = json_decode($pickProfile['encodedOrderFilterQueryConditions'], true);
                    $filteredEncodedOrderFilterConditions = $queryComponentFilter->filterComparisons(
                        $encodedOrderFilterConditions,
                        $filterFunction
                    );
                    if (isset($filteredEncodedOrderFilterConditions['type'])
                        && $filteredEncodedOrderFilterConditions['type'] === ComparisonQueryComponentArrayCoder::CODABLE_TYPE
                    ) {
                        $filteredEncodedOrderFilterConditions = [
                            'type' => BooleanCompositionQueryComponentArrayCoder::CODABLE_TYPE,
                            'booleanOperator' => BooleanCompositionQueryComponent::BOOLEAN_OPERATOR_AND,
                            'compositionComponents' => [
                                $filteredEncodedOrderFilterConditions
                            ],
                        ];
                    }
                    $pickProfile['encodedOrderFilterQueryConditions'] = json_encode($filteredEncodedOrderFilterConditions);

                    $database->query(
                        'UPDATE pickware_wms_pick_profiles
                        SET encodedOrderFilterQueryConditions = :encodedOrderFilterQueryConditions
                        WHERE id = :id',
                        $pickProfile
                    );
                }
            case '5.6.0':
                // Nothing to do
            case '5.6.1':
                // Nothing to do
            case '5.6.2':
                // Update the apiOrderItemsSortField config element by updating the store values and migrating existing
                // values to the new mapping.
                /** @var ConfigElement $orderItemSortFieldConfigElement */
                $orderItemSortFieldConfigElement = $form->getElement('apiOrderItemsSortField');
                $options = $orderItemSortFieldConfigElement->getOptions();
                $options['store'] = [
                    [
                        'binLocation',
                        'Lagerplatz',
                    ],
                    [
                        'articleName',
                        'Artikelname',
                    ],
                    [
                        'articleNumber',
                        'Artikelnummer',
                    ],
                    [
                        'supplierNumber',
                        'Herstellernummer',
                    ],
                ];
                $orderItemSortFieldConfigElement->setOptions($options);

                $orderItemSortValueMigrationMapping = [
                    'article.name' => 'articleName',
                    'defaultBinLocationCode' => 'binLocation',
                    'number' => 'articleNumber',
                ];

                // Migrate the default value
                $changedEntities = [];
                if (array_key_exists($orderItemSortFieldConfigElement->getValue(), $orderItemSortValueMigrationMapping)) {
                    $orderItemSortFieldConfigElement->setValue(
                        $orderItemSortValueMigrationMapping[$orderItemSortFieldConfigElement->getValue()]
                    );
                    $changedEntities = [$orderItemSortFieldConfigElement];
                }

                // Migrate existing custom values
                foreach ($orderItemSortFieldConfigElement->getValues() as $configValue) {
                    if (array_key_exists($configValue->getValue(), $orderItemSortValueMigrationMapping)) {
                        $configValue->setValue(
                            $orderItemSortValueMigrationMapping[$configValue->getValue()]
                        );
                        $changedEntities = [$configValue];
                    }
                }
                if (count($changedEntities) > 0) {
                    $modelManager->flush($changedEntities);
                }

                $attributeColumnInstallationHelper->addAttributeColumnsIfNotExist([
                    // Article: Add column to store internal picking instructions
                    new AttributeColumnDescription(
                        's_articles_attributes',
                        'pickware_wms_internal_picking_instructions',
                        'TEXT'
                    ),
                    // Order: Add column to store internal picking instructions
                    new AttributeColumnDescription(
                        's_order_attributes',
                        'pickware_wms_internal_picking_instructions',
                        'TEXT'
                    ),
                ]);
                $attributeConfigurationInstallationHelper->createAttributeConfigurationUnlessExists(
                    's_articles_attributes',
                    'pickware_wms_internal_picking_instructions',
                    'text',
                    'Interne Pick-Anweisungen',
                    'Dieses Field dient nur der internen Kommunikation mit dem Lager und wird ausschließlich in der Versand-App angezeigt. Das Feld wird zu keiner Zeit in der Storefront oder für den Kunden sichtbar ausgegeben.'
                );
            case '5.7.0':
                $attributeColumnUninstallationHelper->removeAttributeColumnsIfExist([
                    // This attribute has been migrated to PickwareERP. It is named
                    // "pickware_return_shipment_status_id" there, so the column can simply be dropped here.
                    new AttributeColumnDescription(
                        's_order_attributes',
                        'pickware_reshipment_status_id'
                    ),
                ]);
                // Update the foreign key constraint of picked quantities and bin locations to prevent the deletion of
                // bin locations as long as picked quantities reference them
                $sqlHelper->cleanUpAndEnsureRestrictingForeignKeyConstraint(
                    'pickware_wms_picked_quantities',
                    'binLocationId',
                    'pickware_erp_bin_locations',
                    'id'
                );
            case '5.8.0':
                // Nothing to do
            case '5.8.1':
                // Add a config element for the selection of dispatch methods, for which return labels should
                // automatically be created and printed after picking
                $form->setElement(
                    'select',
                    'automaticReturnLabelCreationDispatchMethodIds',
                    [
                        'label' => 'Versand - Versandarten mit autom. Druck von Retourenlabel',
                        'description' => 'Wählen Sie hier die Versandarten aus, für die nach (teilweisem) Abschluss der Kommissionierung automatisch ein Retourenlabel erstellt und gedruck werden soll. Hinweis: Aktuell werden nur Versandarten unterstützt, welche Produkten von DHL, PDP oder PostNL zugeordnet sind.',
                        'value' => [],
                        'store' => 'Shopware.apps.Base.store.Dispatch',
                        'multiSelect' => true,
                        'editable' => false,
                    ]
                );
            case '5.9.0':
                $attributeColumnInstallationHelper->addAttributeColumnsIfNotExist([
                    new AttributeColumnDescription(
                        's_article_configurator_templates_attributes',
                        'pickware_not_relevant_for_picking',
                        'boolean'
                    ),
                ]);
                $attributeConfigurationInstallationHelper->createAttributeConfigurationUnlessExists(
                    's_article_configurator_templates_attributes',
                    'pickware_not_relevant_for_picking',
                    'boolean',
                    'In der Versand-App ausblenden',
                    'Aktivieren Sie dieses Feld, um die Variante in der Versand-App auszublenden. Enthält eine Bestellung nur Varianten, bei denen diese Checkbox aktiviert ist, wird die Bestellung nicht in der Versand-App angezeigt.',
                    55
                );
            case '5.10.0':
                // Remove the stocktaking module which is now part of PickwareERP. To avoid a situation where you could
                // end up with both plugins updated an no menu item left, we removed the creation of this menu item in
                // the installation step of this plugin.
                $stockTakingMenuItem = $modelManager->getRepository('Shopware\\Models\\Menu\\Menu')->findOneBy([
                    'controller' => 'ViisonPickwareMobileStockTakeExport',
                ]);
                if ($stockTakingMenuItem) {
                    $modelManager->remove($stockTakingMenuItem);
                    $modelManager->flush($stockTakingMenuItem);
                }
                $stockTakingConfigElement = $form->getElement('showCurrentStockWhenStocktaking');
                if ($stockTakingConfigElement) {
                    // Persist the config element. If this is a new installation, the config element has to be persisted
                    // before this step can remove it.
                    $modelManager->persist($stockTakingConfigElement);
                    $modelManager->flush($stockTakingConfigElement);
                    $form->removeElement($stockTakingConfigElement);
                    $modelManager->remove($stockTakingConfigElement);
                    $modelManager->flush([
                        $form,
                        $stockTakingConfigElement,
                    ]);
                }
            case '5.10.1':
                $database->query(
                    'UPDATE s_article_configurator_templates_attributes
                    SET pickware_not_relevant_for_picking = 0
                    WHERE pickware_not_relevant_for_picking IS NULL'
                );
                $database->query(
                    'ALTER TABLE s_article_configurator_templates_attributes
                    MODIFY pickware_not_relevant_for_picking
                    boolean NOT NULL DEFAULT 0'
                );
            case '5.10.2':
                // Nothing to do
            case '5.11.0':
                $aclResourceCreator->replaceResource('pickware_wms_stocking', [
                    'edit_item_details',
                    'override_suggested_bin_locations',
                    'view_purchase_prices',
                ]);
                $aclResourceCreator->replaceResource('pickware_wms_picking', [
                    'select_any_order_for_picking',
                    'take_over_locked_order_for_picking',
                ]);

                // Don't trigger migration if the plugin is reinstalled, so the user settings are not overridden
                $isPluginUpdate = $oldInstallVersion !== 'install';
                if ($isNewInstallation || $isPluginUpdate) {
                    $database->query(
                        'INSERT IGNORE INTO s_core_acl_roles (
                            roleID,
                            resourceID,
                            privilegeID
                        )
                        SELECT
                            aclRoles.id,
                            aclResources.id,
                            aclPrivileges.id
                        FROM
                            s_core_auth_roles AS aclRoles,
                            s_core_acl_resources AS aclResources
                        JOIN s_core_acl_privileges AS aclPrivileges
                            ON aclResources.id = aclPrivileges.resourceID
                        WHERE aclResources.name IN (:picking, :stocking)',
                        [
                            'picking' => Subscribers\PickwareMobileAppConfigSubscriber::ACL_RESOURCE_NAME_PICKING,
                            'stocking' => Subscribers\PickwareMobileAppConfigSubscriber::ACL_RESOURCE_NAME_STOCKING,
                        ]
                    );
                }
            case '5.12.0':
                $apiOrderItemsSortComparisonTypeElement = $form->getElement('apiOrderItemsSortComparisonType');
                if ($apiOrderItemsSortComparisonTypeElement) {
                    $form->removeElement($apiOrderItemsSortComparisonTypeElement);
                    $modelManager->remove($apiOrderItemsSortComparisonTypeElement);
                    $modelManager->flush([
                        $form,
                        $apiOrderItemsSortComparisonTypeElement,
                    ]);
                }
            case '5.13.0':
                // Nothing to do
            case '5.13.1':
                // Nothing to do
            case '5.13.2':
                // Nothing to do
            case '5.13.3':
                // Nothing to do
            case '5.14.0':
                // Nothing to do
            case '5.14.1':
                // Nothing to do
            case '5.14.2':
                // Nothing to do
            case '5.14.3':
                // Nothing to do
            case '5.15.0':
                // Nothing to do
            case '5.15.1':
                // Nothing to do
            case '5.16.0':
                // Nothing to do
            case '5.17.0':
                // Nothing to do
            case '5.17.1':
                // Nothing to do
            case '5.18.0':
                // Nothing to do
            case '5.18.1':
                // Nothing to do
            case '5.18.2':
                // Nothing to do
            case '5.19.0':
                // Nothing to do
            case '5.20.0':
                // Nothing to do
            case '5.20.1':
                // Nothing to do
            case '5.20.2':
                // Next release

                // *** *** *** *** ***
                // NEVER REMOVE THE FOLLOWING BREAK! All updates must be added above this comment block!
                // *** *** *** *** ***
                break;
            default:
                throw InstallationException::updateFromVersionNotSupported(
                    $this,
                    $oldVersion,
                    $this->getVersion(),
                    self::MIN_SUPPORTED_UPDATE_VERSION
                );
        }

        /* Shopware version/feature dependent migrations */

        // Migrate the picking app extra document config elements to multi-select boxes
        $this->convertTextConfigElementToDocTypeComboboxElement('pickingAppDocumentGenerationExtraDocumentsWithInvoice');
        $this->convertTextConfigElementToDocTypeComboboxElement('pickingAppDocumentGenerationExtraDocumentsWithDeliveryNote');

        /* Other steps */

        $this->sortConfigElements();
        $this->updateConfigFormTranslations();

        // Always run PickwareCommon installation (it is idempotent)
        $pickwareCommonInstaller->installViisonPickwareCommon();

        // Update ViisonCommon and PickwareCommon snippets
        $this->importViisonCommonSnippetsIntoDb();
        $pickwareCommonInstaller->updateViisonPickwareCommonSnippets();

        // Clean up plugin directory
        $this->removeObsoletePluginFiles();

        // Make sure the last safe uninstall version is deleted to allow installing the plugin again later
        $this->get('viison_common.hidden_config_storage')->removeConfigValue(
            self::CONFIG_ELEMENT_NAME_LAST_SAFE_UNINSTALL_VERSION
        );
    }

    /**
     * @inheritdoc
     */
    protected function runUninstall($deleteData)
    {
        if (!$deleteData) {
            // Just save the currently installed plugin version to be able to detect 'safe' uninstalls of
            // incompatible versions
            $this->get('viison_common.hidden_config_storage')->setConfigValue(
                self::CONFIG_ELEMENT_NAME_LAST_SAFE_UNINSTALL_VERSION,
                'text',
                $this->getPlugin()->getVersion()
            );

            return;
        }

        /** @var HiddenConfigStorageService $hiddenConfigStorageService */
        $hiddenConfigStorageService = $this->get('viison_common.hidden_config_storage');
        // Make sure the last safe uninstall version is deleted to allow installing the plugin again later
        $hiddenConfigStorageService->removeConfigValue(self::CONFIG_ELEMENT_NAME_LAST_SAFE_UNINSTALL_VERSION);

        /** @var \Shopware\Components\Model\ModelManager $modelManager */
        $modelManager = $this->get('models');
        $sqlHelper = new SQLHelper($this->get('db'));
        $attributeColumnUninstallationHelper = new AttributeColumnUninstaller($modelManager, $sqlHelper);

        // Remove custom attributes
        $attributeColumnUninstallationHelper->removeAttributeColumnsIfExist([
            new AttributeColumnDescription(
                's_articles_attributes',
                'pickware_not_relevant_for_picking'
            ),
            new AttributeColumnDescription(
                's_articles_attributes',
                'pickware_wms_internal_picking_instructions'
            ),
            new AttributeColumnDescription(
                's_articles_attributes',
                'viison_not_relevant_for_picking'
            ),
            new AttributeColumnDescription(
                's_order_attributes',
                'viison_undispatched_tracking_codes'
            ),
            new AttributeColumnDescription(
                's_order_attributes',
                'pickware_batch_picking_box_id'
            ),
            new AttributeColumnDescription(
                's_order_attributes',
                'viison_batch_picking_box_id'
            ),
            new AttributeColumnDescription(
                's_order_attributes',
                'pickware_batch_picking_transaction_id'
            ),
            new AttributeColumnDescription(
                's_order_attributes',
                'viison_batch_picking_transaction_id'
            ),
            new AttributeColumnDescription(
                's_order_attributes',
                'pickware_processing_warehouse_id'
            ),
            new AttributeColumnDescription(
                's_order_attributes',
                'viison_processing_warehouse_id'
            ),
            new AttributeColumnDescription(
                's_order_attributes',
                'pickware_reshipment_status_id'
            ),
            new AttributeColumnDescription(
                's_order_attributes',
                'viison_reshipment_status_id'
            ),
            new AttributeColumnDescription(
                's_order_details_attributes',
                'pickware_picked_quantity'
            ),
            new AttributeColumnDescription(
                's_order_details_attributes',
                'viison_picked_quantity'
            ),
            new AttributeColumnDescription(
                's_order_attributes',
                'pickware_wms_shipment_guid'
            ),
            new AttributeColumnDescription(
                's_order_attributes',
                'pickware_wms_internal_picking_instructions'
            ),
            new AttributeColumnDescription(
                's_order_documents_attributes',
                'pickware_wms_shipment_guid'
            ),
        ]);

        // Remove custom tables
        $this->get('db')->query(
            'SET FOREIGN_KEY_CHECKS = 0;
            -- Old table names (< 5.0.0)
            DROP TABLE IF EXISTS s_plugin_pickware_mobile_picked_quantities;
            DROP TABLE IF EXISTS s_plugin_pickware_mobile_picked_quantity_stock_entry_mappings;
            -- New table names (>= 5.0.0)
            DROP TABLE IF EXISTS pickware_wms_pick_profile_prioritized_dispatch_methods;
            DROP TABLE IF EXISTS pickware_wms_pick_profile_prioritized_payment_methods;
            DROP TABLE IF EXISTS pickware_wms_pick_profile_stock_filter_exempt_dispatch_methods;
            DROP TABLE IF EXISTS pickware_wms_pick_profiles;
            DROP TABLE IF EXISTS pickware_wms_picked_quantities;
            DROP TABLE IF EXISTS pickware_wms_picked_quantity_stock_ledger_entry_mappings;
            SET FOREIGN_KEY_CHECKS = 1;'
        );

        // Remove custom attribute configurations
        $attributeConfigurationUninstallationHelper = new AttributeConfiguration\UninstallationHelper($modelManager);
        $attributeConfigurationUninstallationHelper->removeAttributeConfigurationsIfExist([
            's_articles_attributes' => [
                'pickware_not_relevant_for_picking',
                'pickware_wms_internal_picking_instructions',
                'viison_not_relevant_for_picking',
            ],
        ]);

        // Remove custom media albums
        $mediaAlbumUninstallationHelper = new MediaAlbumUninstallationHelper($modelManager);
        $mediaAlbumUninstallationHelper->removeMediaAlbumsIfExist([
            'Pickware Rücksendungen'
        ]);

        // Make sure to remove other hidden config fields since they are not removed with the plugin form
        $hiddenConfigStorageService->removeConfigValue('pickwareWmsDisplayAboutWindow');

        // Register subscribers to ensure that uninstall is idempotent
        $viisonPickwareCommonSubscriberRegistrator = new Shopware\Plugins\ViisonPickwareCommon\Classes\SubscriberRegistrator($this);
        $viisonPickwareCommonSubscriberRegistrator->registerSubscribers();

        // Remove custom resources
        $aclResourceHelper = new AclResource\AclResourceHelper($modelManager);
        $aclResourceRemover = new AclResource\AclResourceRemover($aclResourceHelper, $this->get('acl'), $modelManager);
        $aclResourceRemover->deleteResource(Subscribers\PickwareMobileAppConfigSubscriber::ACL_RESOURCE_NAME_PICKING);
        $aclResourceRemover->deleteResource(Subscribers\PickwareMobileAppConfigSubscriber::ACL_RESOURCE_NAME_STOCKING);

        // Uninstall PickwareCommon if necessary
        $pickwareCommonInstaller = new PickwareCommonInstaller($this);
        $pickwareCommonInstaller->uninstallViisonPickwareCommon();
    }

    /**
     * @inheritdoc
     */
    protected function runActivation()
    {
        // Make sure ViisonPickwareERP is installed and active in a compatible version and not an expired test version
        if (!self::isViisonPickwareErpActiveInSupportedVersion() || $this->get('plugins')->get('Core')->get('ViisonPickwareERP')->pluginTestExpired()) {
            $this->throwViisonPickwareErpVersionConstraintsException();
        }
    }

    /**
     * @inheritdoc
     */
    protected function runDeactivation()
    {
        // Nothing to do
    }

    /**
     * Registers the plugin's namespaces.
     */
    public function afterInit()
    {
        $this->loadDependencies();
        $this->loadPlugin();
    }

    /* Events & Hooks */

    /**
     * This callback function is triggered at the very beginning of the dispatch process and allows
     * us to register additional events on the fly.
     *
     * @param \Enlight_Event_EventArgs $args
     */
    public function onStartDispatch(\Enlight_Event_EventArgs $args)
    {
        // Nothing to do here, since the dynamic subscribers were already registered in 'afterInit()'
    }

    /**
     * This callback function is triggered when executing any of the shopware CLI commands and allows
     * us to register additional events on the fly.
     *
     * @param \Enlight_Event_EventArgs $args
     */
    public function onAddConsoleCommand(\Enlight_Event_EventArgs $args)
    {
        // Nothing to do here, since the dynamic subscribers were already registered in 'afterInit()'
    }

    /* Other */

    /**
     * Uses the dependency loader to load the namespaces and susbcribers of all required,
     * shared dependencies.
     */
    private function loadDependencies()
    {
        // Require all shared dependencies
        $loader = VIISON\ShopwarePluginDependencyLoader\Loader::getInstance();
        $loader->requireDependencies($this->Path(), [
            'ViisonCommon',
            'ViisonPickwareCommon',
        ]);
        Shopware\Plugins\ViisonCommon\Classes\PickwareAutoloadDependencyLoader::ensureDependenciesLoaded();

        // Add the subscribers of ViisonCommon
        $viisonCommonSubscriberRegistrator = new Shopware\Plugins\ViisonCommon\Classes\SubscriberRegistrator($this);
        $viisonCommonSubscriberRegistrator->registerSubscribers();

        // Load the Shopware polyfill
        require_once __DIR__ . '/ViisonCommon/Polyfill/Loader.php';

        // Make sure this plugin is installed, ViisonPickwareERP is installed in a compatible version and is not an
        // expired test version
        if (!$this->isInstalledAndActive() || !self::isViisonPickwareErpActiveInSupportedVersion() || $this->get('plugins')->get('Core')->get('ViisonPickwareERP')->pluginTestExpired()) {
            return;
        }

        // Add the subscribers of ViisonPickwareCommon
        $viisonPickwareCommonSubscriberRegistrator = new Shopware\Plugins\ViisonPickwareCommon\Classes\SubscriberRegistrator($this);
        $viisonPickwareCommonSubscriberRegistrator->registerSubscribers();
    }

    /**
     * First checks whether the plugin has a valid license, is installed and is active.
     * If all this is true, first the namespaces of this plugin are registered with the
     * class loader, before all subscribers are instanciated and added to the event manager.
     */
    private function loadPlugin()
    {
        if (!$this->checkLicenseInternal(false)) {
            return;
        }

        // Make sure this plugin is installed, ViisonPickwareERP is installed in a compatible version and is not an
        // expired test version
        if (!$this->isInstalledAndActive() || !self::isViisonPickwareErpActiveInSupportedVersion() || $this->get('plugins')->get('Core')->get('ViisonPickwareERP')->pluginTestExpired()) {
            return;
        }

        // Create all plugin subscribers
        $subscribers = [
            new RestApiRequestCompatibilityLayerSubscriber($this, 'Classes/ApiRequestCompatibility/', 'Shopware\\Plugins\\ViisonPickwareMobile\\Classes\\ApiRequestCompatibility'),
            new Subscribers\Api\ArticlesSubscriber($this),
            new Subscribers\Api\BinLocationsSubscriber($this),
            new Subscribers\Api\MediaSubscriber($this),
            new Subscribers\Api\OrdersSubscriber($this),
            new Subscribers\Api\VariantsSubscriber($this),
            new Subscribers\RestApiRequestLogFilterSubscriber($this),
            new Subscribers\RestApiRouterSubscriber($this),
            new Subscribers\Backend\IndexSubscriber($this),
            new Subscribers\Backend\OrderSubscriber($this),
            new Subscribers\Backend\VariantGenerationSubscriber($this),
            new Subscribers\Backend\ViisonPickwareERPWarehouseManagementSubscriber($this),
            new Subscribers\BarcodeLabel\PickingBoxBarcodeLabelTypeSubscriber($this),
            new Subscribers\CompatibilityCheckSubscriber($this),
            new Subscribers\Components\DocumentSubscriber($this),
            new Subscribers\Components\OrderDetailQuantityCalculatorServiceSubscriber($this),
            new Subscribers\Components\OrderDetailQuantityValidatorSubscriber($this),
            new Subscribers\ControllersSubscriber($this),
            new Subscribers\Models\OrderSubscriber($this),
            new Subscribers\PickwareMobileAppConfigSubscriber($this),
            new Subscribers\ServiceDecorationSubscriber($this),
            new Subscribers\ServicesSubscriber($this),
            new Subscribers\ShippingProviderSubscriber($this),
            new Subscribers\SubApplicationRegistrationSubscriber($this),
            new ViisonCommonSubscribers\IndexPopupWindowQueue($this, 'ViisonPickwareMobileIndexAboutPopup', 100),
            new ViisonCommonSubscribers\ViewLoading($this, 'ViisonPickwareMobile'),
        ];

        // Make sure that the subscribers are only added once
        // The second subscriber is used to check whether the subscribers are already registered. This is done because the
        // method isSubscriberRegistered only check whether the CLASS not the INSTANCE has been registered already. But
        // PickwareApiCompatibilityLayerSubscriber is used in ShopwarePickwareMobile and ShopwarePickwarePOS. The check
        // can only be done with subscribers that only exist for the plugin.
        if (!$this->isSubscriberRegistered($subscribers[1])) {
            $eventManager = $this->get('events');
            foreach ($subscribers as $subscriber) {
                $eventManager->addSubscriber($subscriber);
            }
        }
    }

    /* Install helpers */

    /**
     * Helper method that registers only the services subscriber for this plugin. This is required in order to use
     * the plugin's services during installation or activation, if the plugin is not active at the moment (i.e. fresh
     * installation or activation).
     */
    private function loadServicesForInstallation()
    {
        $servicesSubscriber = new Subscribers\ServicesSubscriber($this);
        if (!$this->isSubscriberRegistered($servicesSubscriber)) {
            $this->get('events')->addSubscriber($servicesSubscriber);
        }
    }

    /**
     * Creates the custom email templates contained in this plugin, including the following:
     *  * sORDERSTATEMAIL6 - The email for order status 'partly shipped'
     *  * sORDERSTATEMAIL7 - The email for order status 'completely shipped'
     *
     * This method should only be used during the initial installation and not in update steps
     * since users will lose their custom changes to any of these emails otherwise.
     */
    private function createEmailTemplates()
    {
        $modelManager = $this->get('models');
        // Add/update the email templates for the completely/partly dispatched status emails
        $mails = [
            'sORDERSTATEMAIL6' => 'order_state_mail_partly_shipped',
            'sORDERSTATEMAIL7' => 'order_state_mail_completely_shipped',
        ];

        $mailEntitiesToFlush = [];
        foreach ($mails as $templateName => $fileName) {
            // Get the mail
            /** @var Shopware\Models\Mail\Mail $mail */
            $mail = $modelManager->getRepository(Mail::class)->findOneBy([
                'name' => $templateName,
            ]);
            if (!$mail) {
                continue;
            }

            // Backup the original email template as <Template_Name>_old to prevent data loss
            $now = new \DateTime();
            $backupTemplateName = $templateName . '_backup_' . $now->format('Y-m-d_H:i:s');
            $backupMail = $modelManager->getRepository(Mail::class)->findOneBy([
                'name' => $backupTemplateName,
            ]);
            if (!$backupMail) {
                $backupMail = clone $mail;
                $backupMail->setName($backupTemplateName);
                $backupMail->setStatus(null); // The status column has a unique key so we must not use the same status as the real template
                $backupMail->setMailtype(1); // Switch to 'User defined email template' type so that the template is shown
                $modelManager->persist($backupMail);
                $mailEntitiesToFlush[] = $backupMail;
            }

            // Add the plain email content
            $content = file_get_contents($this->Path() . 'Views/mails/' . $fileName . '.tpl');
            if ($content !== false) {
                $mail->setSubject('Versandbenachrichtigung zu Ihrer Bestellung bei {config name=shopName}');
                $mail->setContent($content);
            }

            // Add the HTML email content
            $content = file_get_contents($this->Path() . 'Views/mails/' . $fileName . '__html.tpl');
            if ($content !== false) {
                $mail->setSubject('Versandbenachrichtigung zu Ihrer Bestellung bei {config name=shopName}');
                $mail->setContentHtml($content);
                $mail->setIsHtml(true);
            }

            // Save the mail
            $modelManager->persist($mail);
            $mailEntitiesToFlush[] = $mail;
        }
        $modelManager->flush($mailEntitiesToFlush);
    }

    /**
     * Checks whether the backend Base controller has the 'getDocTypesAction' method and, if it does, converts the
     * config element with the given $name to a multi select combobox that is backend by the document type store.
     *
     * @param string $name
     */
    private function convertTextConfigElementToDocTypeComboboxElement($name)
    {
        // Make sure the document type combobox is available
        $reflectionClass = new \ReflectionClass('Shopware_Controllers_Backend_Base');
        if (!$reflectionClass->hasMethod('getDocTypesAction')) {
            return;
        }

        // Try to find the element
        $form = $this->Form();
        $this->get('models')->persist($form);
        $element = $form->getElement($name);
        if (!$element || $element->getType() === 'select') {
            return;
        }

        $element->setType('select');

        // Migrate default and current value
        $element->setValue([]);
        if ($element->getValues()) {
            foreach ($element->getValues() as $value) {
                $newValue = ViisonCommonUtil::safeExplode(',', $value->getValue());
                $newValue = array_map('intval', $newValue);
                $newValue = array_values(array_unique($newValue));
                $value->setValue($newValue);
                $this->get('models')->flush($value);
            }
        }

        // Add select/store options
        $options = $element->getOptions();
        $options['store'] = 'Shopware.apps.Base.store.DocType';
        $options['displayField'] = 'name';
        $options['multiSelect'] = true;
        $options['editable'] = false;
        $element->setOptions($options);

        $this->get('models')->flush($element);
    }

    /**
     * Sorts the config elements by charging their 'position' according to the specified list.
     */
    private function sortConfigElements()
    {
        // Sort the config elements
        $configElementOrder = [
            'pickingAppDocumentGenerationMode',
            'pickingAppDocumentGenerationExtraDocumentsWithInvoice',
            'pickingAppDocumentGenerationExtraDocumentsWithDeliveryNote',
            'pickingAppPaymentMethodIdsWithDispatchNoteInsteadOfInvoice',
            'pickingAppDocumentPrintingMode',
            'pickingAppDocumentPrintingInvoiceCopies',
            'pickingAppSendInvoiceViaEmail',
            'pickingAppPrintAdditionalInvoiceForExportDocuments',
            'pickingAppPrintAdditionalDeliveryNoteForPaymentMethodIds',
            'automaticReturnLabelCreationDispatchMethodIds',
            'pickingAppThirdPartyDispatchMethodIds',
            'pickingAppNoStatusMailDispatchMethodIds',
            'pickingAppMaxNumberOfOrdersInAPIResults',
            'pickingAppCreateShippingLabelUponCompletion',
            'showRemainingPositionsOnPartialDeliveryNote',
            'stockingAppSendReshipmentReceivedMails',
            'apiOrderItemsSortField',
        ];
        foreach ($this->Form()->getElements() as $element) {
            $newPos = array_search($element->getName(), $configElementOrder);
            if ($newPos === false) {
                // Move the element to the end of the list
                $newPos = 100;
            }
            $element->setPosition($newPos);
            $this->get('models')->persist($element);
        }
    }

    /* Other */

    /**
     * @return boolean True, iff the plugin 'ViisonPickwareERP' is installed and active in a version that satisfies the
     *         version constraints.
     */
    private static function isViisonPickwareErpActiveInSupportedVersion()
    {
        return ViisonCommonUtil::isPluginActiveAndSatisfiesVersionConstraints(
            'ViisonPickwareERP',
            self::VIISON_PICKWARE_ERP_MIN_SUPPORTED_VERSION,
            self::VIISON_PICKWARE_ERP_EXCLUSIVE_MAX_SUPPORTED_VERSION
        );
    }

    /**
     * @throws InstallationException
     */
    private function throwViisonPickwareErpVersionConstraintsException()
    {
        throw InstallationException::requiredPluginMissing(
            $this,
            $this->getSnippetNamespace()->get('exception/param/pickware_erp_plugin_name'),
            self::VIISON_PICKWARE_ERP_MIN_SUPPORTED_VERSION,
            self::VIISON_PICKWARE_ERP_EXCLUSIVE_MAX_SUPPORTED_VERSION
        );
    }

    /**
     * @return \Enlight_Components_Snippet_Namespace
     */
    private function getSnippetNamespace()
    {
        return $this->getBootstrapSnippetManager()->getNamespace('bootstrap/viison_pickware_mobile/main');
    }

    // Removed license checking method
    // Removed license checking method
    // Removed license checking method
    // Removed license checking method
    // Removed license checking method
    // Removed license checking method

    /**
     * Checks whether this shop has a license to run this plugin.
     *
     * @param bool $throwException
     * @throws Exception
     * @return bool True, if the plugin is licensed. Otherwise false.
     */
    public function checkLicenseInternal($throwException = true)
    {
        $ionCubeNotLoaded = !extension_loaded('ionCube Loader');
        $fileNotEncoded = function_exists('ioncube_file_is_encoded') && !ioncube_file_is_encoded();
        if ($ionCubeNotLoaded || $fileNotEncoded) {
            return true;
        }

        if (!Shopware()->Container()->has('license')) {
            if ($throwException) {
                throw new \Exception('The license manager has to be installed and active');
            } else {
                return false;
            }
        }

        try {
            static $r;
            static $module = 'ViisonPickwareMobile';
            if (!isset($r)) {
                $s = base64_decode('g1h5kvUdYhh8Wo83KAwA79xCH2c=');
                $c = base64_decode('oCUyGMk/IGF0hCdGao9pfszTu4I=');
                $r = sha1(uniqid('', true), true);
                /** @var $l Shopware_Components_License */
                $l = $this->get('license');
                $i = $l->getLicense($module, $r);
                $t = $l->getCoreLicense();
                // Using mb_strlen() here changes the semantics and breaks the license check for some customers
                // phpcs:ignore Generic.PHP.ForbiddenFunctions
                $u = strlen($t) === 20 ? sha1($t . $s . $t, true) : 0;
                $r = $i === sha1($c. $u . $r, true);
            }
            if (!$r && $throwException) {
                throw new \Exception('License check for module "' . $module . '" has failed.');
            }

            return $r;
        } catch (\Exception $e) {
            if ($throwException) {
                throw new \Exception('License check for module "' . $module . '" has failed.');
            } else {
                return false;
            }
        }
    }

    /**
     * Returns device limit encoded in this plugin's license key.
     *
     * @return int
     */
    public function getPickwareDeviceLimit()
    {
        if (!$this->checkLicenseInternal(false) || !Shopware()->Container()->has('license')) {
            return 0;
        }

        // Get the device limit encoded in the plugin license (allow up to ten device when using a test license)
        $license = $this->get('license')->getLicenseInfo('ViisonPickwareMobile', $this->get('license')->getHost());
        $limit = ($license['type'] === 3) ? 10 : intval($license['notation']);

        return $limit;
    }

    /**
     * Returns an array containing the names of all apps, this plugin provides support for.
     *
     * @return string[]
     */
    public function getNamesOfSupportedPickwareApps()
    {
        return [
            'Picking',
            'Stocking',
        ];
    }
}
