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

namespace Shopware\Plugins\ViisonPickwareMobile\Subscribers\Api;

use Enlight_Hook_HookArgs;
use Enlight_Hook_Proxy;
use Shopware\Components\Api\Exception as ApiException;
use Shopware\CustomModels\ViisonPickwareERP\StockLedger\OrderStockReservation;
use Shopware\CustomModels\ViisonPickwareERP\Warehouse\Warehouse;
use Shopware\CustomModels\ViisonPickwareMobile\PickedQuantity\PickedQuantity;
use Shopware\CustomModels\ViisonPickwareMobile\PickProfile\PickProfile;
use Shopware\Models\Document\Document as DocumentType;
use Shopware\Models\Order\DetailStatus;
use Shopware\Models\Order\Order;
use Shopware\Models\Order\Status;
use Shopware\Plugins\ViisonCommon\Classes\Subscribers\Base;
use Shopware\Plugins\ViisonCommon\Classes\Util\Document as DocumentUtil;
use Shopware\Plugins\ViisonPickwareCommon\Classes\Util as PickwareUtil;
use Shopware\Plugins\ViisonPickwareERP\Subscribers\RestApi\RestApiOrdersSubscriber as ViisonPickwareErpApiOrderSubscriber;
use Shopware\Plugins\ViisonPickwareMobile\Classes\ShippingProvider\ShippingDocumentTypeHandling;
use Shopware\Plugins\ViisonPickwareMobile\Interfaces\ShippingProvider\ReturnLabelCreation;
use Shopware_Controllers_Api_Orders;
use sOrder;
use ViisonPickwareMobile_Interfaces_ShippingProvider_ShippingDocument as ShippingDocument;
use ViisonPickwareMobile_Interfaces_ShippingProvider_ShippingProvider as ShippingProvider;

class OrdersSubscriber extends Base
{
    use ShippingDocumentTypeHandling;

    const REQUEST_ATTR_PICK_PROFILE = 'ViisonPickwareMobile_Orders_PickProfile';
    const REQUEST_ATTR_WAREHOUSE = 'ViisonPickwareMobile_Orders_Warehouse';
    const REQUEST_ATTR_ORDER_BACKUP = 'ViisonPickwareMobile_Orders_OrderBackup';

    /**
     * @see \Shopware\Plugins\ViisonCommon\Classes\Subscribers\Base::getSubscribedEvents()
     */
    public static function getSubscribedEvents()
    {
        return [
            'Shopware_Controllers_Api_Orders::indexAction::before' => 'onBeforeIndexAction',
            'Shopware_Plugins_ViisonPickwareCommon_API_Orders_FilterOrdersResult' => 'onFilterOrdersResult',
            'Shopware_Controllers_Api_Orders::putAction::before' => 'onBeforePutAction',
            'Shopware_Controllers_Api_Orders::putAction::after' => [
                'onAfterPutAction',
                // Ensure the "put order" subscriber is executed BEFORE the one from ViisonPickwareERP. This is done to
                // ensure that picked quantities are cleared before an order is marked as completely shipped or
                // cancelled. If the order of the subscribers would be arbitrarily, a cancellation or
                // "complete delivery" of an order that has picked quantities may lead to an order that still has items
                // remaining to ship.
                //
                // We cannot assume that the position of a subscriber is exactly the value provided during registration
                // we take a  bigger step (50) to ensure that the subscriber is definitely executed before the ERP
                // subscriber.
                // See https://github.com/shopware/shopware/blob/4496e0a25f74d33f681d7b785f3fc3e8f02ecf47/engine/Library/Enlight/Event/EventManager.php#L115
                defined(ViisonPickwareErpApiOrderSubscriber::class . '::AFTER_PUT_ACTION_SUBSCRIBER_POSITION') ? (ViisonPickwareErpApiOrderSubscriber::AFTER_PUT_ACTION_SUBSCRIBER_POSITION - 50) : 0,
            ],
            'Shopware_Controllers_Api_ViisonPickwareCommonOrders::createDocument::before' => 'onBeforeCreateDocument',
        ];
    }

    /**
     * Note: Remove this method once ViisonPickwareMobile_Interfaces_ShippingProvider_ShippingDocument::getDocumentTypeId()
     *       is declared mandatory and apply makeShippingDocumentTypeIdGloballyUnique() directly on the IDs instead.
     *
     * @param ShippingDocument $document
     * @param ShippingProvider $provider
     * @return string
     */
    public static function createShippingDocumentType(ShippingDocument $document, ShippingProvider $provider)
    {
        // Determine the document type ID
        if (method_exists($document, 'getDocumentTypeId')) {
            $typeId = $document->getDocumentTypeId();
        } else {
            $typeId = intval(mb_substr(sha1($provider->getIdentifier() . '::shipping_document'), 0, 8), 16);
        }

        return self::makeShippingDocumentTypeIdGloballyUnique($typeId, $provider);
    }

    /**
     * Checks the request for a 'filter' parameter with 'pickware' as the value. If the parameter is found, all order
     * IDs that are relevant for picking are loaded. Finally the filter parameter is replaced by a new filter which is
     * equivalent to an SQL query like 'id IN (<relevant_order_ids>)'.
     *
     * @param Enlight_Hook_HookArgs $args
     * @throws ApiException\CustomValidationException
     */
    public function onBeforeIndexAction(Enlight_Hook_HookArgs $args)
    {
        // Check for the 'pickware' filter
        $request = $args->getSubject()->Request();
        $filter = $request->getParam('filter', []);

        // Check for a valid pickProfileId
        $pickProfileId = $request->getParam('pickProfileId', 0);
        $pickProfile = $this->get('models')->find(PickProfile::class, $pickProfileId);
        $request->setAttribute(self::REQUEST_ATTR_PICK_PROFILE, $pickProfile);

        // Check for a valid warehouseId
        $warehouseId = $request->getParam('warehouseId', 0);
        $warehouse = $this->get('models')->find(Warehouse::class, $warehouseId);
        $request->setAttribute(self::REQUEST_ATTR_WAREHOUSE, $warehouse);

        if ($pickProfile) {
            // Make sure that a valid warehouse was provided
            if (!$warehouse) {
                throw new ApiException\CustomValidationException(sprintf(
                    'You must pass the ID of a valid warehouse, %d given',
                    $warehouseId
                ));
            }

            // Evaluate the filters configured in the pick profile
            $validOrderIds = $this->get('viison_pickware_mobile.pick_profile_order_filter_service')->getIdsOfOrdersPassingFilterOfPickProfile(
                $pickProfile,
                $warehouse
            );
            // Remark: Set the 'operator' field to null, since it is interpreted differently in SW pre 5.2.8.
            // With the field set to null, the id condition is concatenated correctly with an logical AND to any
            // other filter components, no matter which Shopware version is used.
            $filter[] = [
                'property' => 'id',
                'value' => (count($validOrderIds) > 0) ? $validOrderIds : 0,
                'operator' => null,
            ];

            // Set the results limit to the configured value
            $limit = $this->getPluginConfig()->get('pickingAppMaxNumberOfOrdersInAPIResults', 1000);
            $request->setParam('limit', $limit);
        }

        if (is_array($filter)) {
            // Check for a 'return label tracking code' filter
            $returnLabelTrackingCodeFilterIndex = -1;
            foreach ($filter as $index => $filterElement) {
                if (is_array($filterElement) && $filterElement['property'] === 'pickwareReturnLabelTrackingCode') {
                    $returnLabelTrackingCodeFilterIndex = $index;
                }
            }

            if ($returnLabelTrackingCodeFilterIndex > -1) {
                $filterElement = $filter[$returnLabelTrackingCodeFilterIndex];
                unset($filter[$returnLabelTrackingCodeFilterIndex]);
                $trackingCode = $filterElement['value'];

                // Decide on the type of matching that is required (exact vs. partial) based on the filter value
                $matchingMethodName = 'getOrderIdForTrackingCode';
                if (preg_match('/^%.+%$/', $trackingCode) === 1) {
                    // Partial matching
                    $trackingCode = trim($trackingCode, '%');
                    $matchingMethodName = 'getOrdersIdsForTrackingCodeFragment';
                }

                // Use the available shipping providers to determine the IDs of orders that
                // match the return label tracking code
                $orderIds = [];
                $providers = $this->get('viison_pickware_mobile.shipping_provider_repository')->getProviders();
                foreach ($providers as $provider) {
                    $providerResult = $provider->$matchingMethodName($trackingCode);
                    if (is_array($providerResult)) {
                        $orderIds = array_merge($orderIds, $providerResult);
                    } elseif ($providerResult) {
                        $orderIds[] = $providerResult;
                    }
                }

                $filterIsOperator = isset($filterElement['operator']) && $filterElement['operator'] == 1;
                if (count($orderIds) > 0) {
                    // Remark: Set the 'operator' field to null if it is not set before, since it is interpreted
                    // differently in SW pre 5.2.8 (see comment above)
                    $filter[] = [
                        'property' => 'id',
                        'value' => $orderIds,
                        'operator' => $filterIsOperator ?: null,
                    ];
                } elseif (!$filterIsOperator) {
                    // Remark: Set the 'operator' field to null if it is not set before, since it is interpreted
                    // differently in SW pre 5.2.8. (See comment above)
                    $filter[] = [
                        'property' => 'id',
                        'value' => 0,
                        'operator' => $filterIsOperator ?: null,
                    ];
                }
            }
        }

        // Set the update filter in the request
        $request->setParam('filter', $filter);
    }

    /**
     * Adds some information specific to Pickware Mobile to the result of /api/orders after it has been augmented by the
     * subscriber \Shopware\Plugins\ViisonPickwareCommon\Subscribers\Api\Orders.
     *
     * @param \Enlight_Event_EventArgs $args
     * @return array
     */
    public function onFilterOrdersResult(\Enlight_Event_EventArgs $args)
    {
        // Get orders
        $orders = $args->getReturn();
        /** @var \Enlight_Controller_Request_Request $request */
        $request = $args->get('request');
        $pickProfile = $request->getAttribute(self::REQUEST_ATTR_PICK_PROFILE);
        $warehouse = $request->getAttribute(self::REQUEST_ATTR_WAREHOUSE);

        // Gather all order ids
        $orderIds = array_map(function ($order) {
            return $order['id'];
        }, $orders);
        $orderIdString = implode(',', $orderIds);

        $ordersById = [];
        $orderDetailIds = [];
        foreach ($orders as &$order) {
            // Use reference semantics here so any changes made to the order in $ordersById also change the original
            // order in $orders, so we don't need to flatten and possibly sort $ordersById afterwards.
            $ordersById[$order['id']] = &$order;
            $orderDetailIds = array_merge($orderDetailIds, array_map(
                function ($orderDetail) {
                    return $orderDetail['id'];
                },
                $order['details']
            ));
        }

        // Fetch Pickware Mobile-specific dispatch information for all orders
        $orderDispatch = $this->get('db')->fetchAll(
            'SELECT
                o.id AS orderId,
                oa.viison_undispatched_tracking_codes AS undispatchedTrackingCodes,
                oa.pickware_batch_picking_box_id AS batchPickingBoxId,
                oa.pickware_batch_picking_transaction_id AS batchPickingTransactionId,
                oa.pickware_processing_warehouse_id AS processingWarehouseId
            FROM s_order o
            INNER JOIN s_order_attributes oa
                ON o.id = oa.orderID
            WHERE o.id IN (' . $orderIdString . ')'
        );
        foreach ($orderDispatch as $row) {
            if (!isset($ordersById[$row['orderId']])) {
                continue;
            }
            $ordersById[$row['orderId']]['undispatchedTrackingCodes'] = $row['undispatchedTrackingCodes'];
            $ordersById[$row['orderId']]['batchPickingBoxId'] = $row['batchPickingBoxId'];
            $ordersById[$row['orderId']]['batchPickingTransactionId'] = $row['batchPickingTransactionId'];
            $ordersById[$row['orderId']]['processingWarehouseId'] = ($row['processingWarehouseId'] !== null) ? intval($row['processingWarehouseId']) : null;
        }

        // Determine the relevance of all items contained in the loaded orders
        $orderItemRelevanceService = $this->get('viison_pickware_mobile.order_item_relevance_service');
        if (PickwareUtil::getRequestingAppName() === 'Picking') {
            $orderItemRelevance = $orderItemRelevanceService->computeRelevanceForPickingApp($orderIds);
        } else {
            $orderItemRelevance = $orderItemRelevanceService->computeRelevanceForStockingApp($orderIds);
        }

        // Get all picked quantities of all order details
        $pickedQuantities = $this->get('models')->getRepository(PickedQuantity::class)->getPickedQuantityArrays(
            $orderDetailIds
        );

        // Get Pickware Mobile-specific article information for each order item in all orders and merge them into
        // $orders.
        $orderItems = $this->get('db')->fetchAll(
            'SELECT
                od.orderID AS orderId,
                od.id AS detailId,
                oda.pickware_picked_quantity AS pickwarePickedQuantity,
                saa.pickware_wms_internal_picking_instructions AS pickwareWmsInternalPickingInstructions
            FROM s_order_details od
            LEFT JOIN s_order_details_attributes oda
                ON od.id = oda.detailID
            LEFT JOIN s_articles_details sad
                ON sad.ordernumber = od.articleordernumber
            LEFT JOIN s_articles_attributes saa
                ON sad.id = saa.articledetailsID
            WHERE od.orderID IN (' . $orderIdString . ')'
        );

        foreach ($orderItems as $row) {
            foreach ($ordersById[$row['orderId']]['details'] as &$orderDetailData) {
                if ($orderDetailData['id'] == $row['detailId']) {
                    $orderDetailData['pickwarePickedQuantities'] = $pickedQuantities[$row['detailId']];
                    $orderDetailData['relevant'] = $orderItemRelevance->isOrderDetailIdRelevant($row['detailId']);
                    $orderDetailData['attribute']['pickwarePickedQuantity'] = intval($row['pickwarePickedQuantity']);

                    // Add article detail attributes if not present
                    if (!array_key_exists('attribute', $orderDetailData['articleDetail'])) {
                        $orderDetailData['articleDetail']['attribute'] = [];
                    }
                    $orderDetailData['articleDetail']['attribute']['pickwareWmsInternalPickingInstructions'] = $row['pickwareWmsInternalPickingInstructions'];

                    break;
                }
            }
        }

        $configService = $this->get('viison_pickware_mobile.plugin_config');
        $printAdditionalInvoice = $this->getPluginConfig()->get('pickingAppPrintAdditionalInvoiceForExportDocuments', false);
        $paymentIdsWithoutInvoice = $this->getPluginConfig()->toArray()['pickingAppPaymentMethodIdsWithDispatchNoteInsteadOfInvoice'];
        $paymentIdsWithAdditionalDeliveryNote = $this->getPluginConfig()->toArray()['pickingAppPrintAdditionalDeliveryNoteForPaymentMethodIds'];
        $automaticReturnLabelCreationDispatchMethodIds = $configService->getAutomaticReturnLabelCreationDispatchMethodIds();
        $allShippingProviders = $this->get('viison_pickware_mobile.shipping_provider_repository')->getProviders();
        foreach ($orders as &$order) {
            $pickedItems = [];
            foreach ($order['details'] as $item) {
                if ($item['attribute']['pickwarePickedQuantity'] > 0) {
                    $pickedItems[$item['id']] = $item['attribute']['pickwarePickedQuantity'];
                }
            }

            // Add assigned shipping product
            $order['shippingProduct'] = null;
            $shippingProvider = $this->get('viison_pickware_mobile.shipping_provider_repository')->findProviderForDispatchMethodWithId($order['dispatchId']);
            if ($shippingProvider) {
                $shippingProductIdentifier = $shippingProvider->shippingProductIdentifierForOrder($order['id']);
                if ($shippingProductIdentifier !== null) {
                    // Add information about the product and the provider
                    $order['shippingProduct'] = [
                        'provider' => get_class($shippingProvider),
                        'productId' => $shippingProductIdentifier,
                        'options' => $shippingProvider->shippingProductOptionsForOrder($order['id']),
                    ];
                }
                if ($order['orderStatusId'] === Status::ORDER_STATE_READY_FOR_DELIVERY) {
                    // Let the shipping provider calculate the exact shipment weight
                    $order['shipmentWeight'] = $shippingProvider->determineShipmentWeight($order['id'], $pickedItems);
                }
            }

            // Determine whether automatic return label creation is configured (and supported)
            $order['pickwareAutomaticReturnLabelPrintingEnabled'] = (
                $shippingProvider !== null
                && in_array($order['dispatchId'], $automaticReturnLabelCreationDispatchMethodIds)
                && is_subclass_of($shippingProvider, ReturnLabelCreation::class)
            );

            // Add previously generated shipping documents, if the order is ready for shipping
            $order['shippingDocuments'] = [];
            if ($order['orderStatusId'] === Status::ORDER_STATE_READY_FOR_DELIVERY) {
                foreach ($allShippingProviders as $shippingProvider) {
                    $providerDocuments = $shippingProvider->getAllDocumentDescriptions($order['id'], false);
                    $order['shippingDocuments'] = array_merge(
                        $order['shippingDocuments'],
                        array_map(function ($document) use ($shippingProvider) {
                            // Convert the document's to JSON-encodeable arrays
                            return [
                                'identifier' => $document->getIdentifier(),
                                'typeId' => self::createShippingDocumentType($document, $shippingProvider),
                                'pageSize' => $document->getPageSize(),
                                'trackingCode' => $document->getTrackingCode(),
                            ];
                        }, $providerDocuments)
                    );
                }
            }

            if ($pickProfile) {
                // Determine whether the order's dispatch and/or payment methods are privileged
                $order['prioritizedDispatchMethod'] = $pickProfile->prioritizesDispatchMethodWithId(
                    $order['dispatchId']
                );
                if (isset($order['payment']['id'])) {
                    $order['prioritizedPaymentMethod'] = $pickProfile->prioritizesPaymentMethodWithId(
                        $order['payment']['id']
                    );
                }
            }

            // Determine special printing settings
            $order['printAdditionalInvoice'] = $printAdditionalInvoice && $order['shippingProduct']['options']['createExportDocument'];
            $order['printDispatchNoteInsteadOfInvoice'] = in_array($order['payment']['id'], $paymentIdsWithoutInvoice);
            $order['printAdditionalDeliveryNote'] = in_array($order['payment']['id'], $paymentIdsWithAdditionalDeliveryNote);
        }
        unset($order);

        return $orders;
    }

    /**
     * Checks if the orderStatusId is contained in the POST body of the request and,
     * if it is present, saves the current order status in a new field of the request
     * for later use.
     *
     * @param Enlight_Hook_HookArgs $args
     */
    public function onBeforePutAction(Enlight_Hook_HookArgs $args)
    {
        // Try to find the order
        $request = $args->getSubject()->Request();
        $orderId = $request->getParam('id', 0);
        $order = $this->get('models')->find(Order::class, $orderId);
        if (!$order) {
            return;
        }

        // Back up some order data in the request
        $orderBackup = [
            'orderStatusId' => $order->getOrderStatus()->getId(),
            'details' => [],
            'attribute' => [],
        ];
        if ($order->getAttribute()) {
            $orderBackup['attribute']['viisonUndispatchedTrackingCodes'] = $order->getAttribute()->getViisonUndispatchedTrackingCodes();
        }
        $request->setAttribute(self::REQUEST_ATTR_ORDER_BACKUP, $orderBackup);
    }

    /**
     * Checks for a details array containing picked quantities. Additionally the given order status id
     * is evaluated and, if it equals 'ready for shipping', a new shipment weight is calculated and
     * add to the response.
     *
     * @param Enlight_Hook_HookArgs $args
     */
    public function onAfterPutAction(Enlight_Hook_HookArgs $args)
    {
        /** @var Shopware_Controllers_Api_Orders & Enlight_Hook_Proxy $subject */
        $subject = $args->getSubject();
        // Check status of previous computation
        if ($subject->View()->success !== true) {
            return;
        }

        // Try to find the order
        $request = $subject->Request();
        $orderId = $request->getParam('id', 0);
        /** @var Order $order */
        $order = $this->get('models')->find(Order::class, $orderId);
        if (!$order) {
            return;
        }

        // Determine its shipping provider
        $provider = $this->get('viison_pickware_mobile.shipping_provider_repository')->findProviderForOrderWithId($orderId);

        // Check if the new order status is 'ready for shipping'
        $params = $subject->Request()->getPost();
        if ($params['orderStatusId'] === Status::ORDER_STATE_READY_FOR_DELIVERY) {
            // Collect all items, which have a total picked quantity greater 0 and thus are part
            // of the next dispatch
            $pickedItems = [];
            foreach ($order->getDetails() as $item) {
                if ($item->getAttribute() !== null && $item->getAttribute()->getPickwarePickedQuantity() > 0) {
                    $pickedItems[$item->getId()] = $item->getAttribute()->getPickwarePickedQuantity();
                }
            }

            // Calculate a shipment weight based on the currently picked items
            $shipmentWeight = ($provider !== null) ? $provider->determineShipmentWeight($orderId, $pickedItems) : null;

            // Add the weight to the response
            $data = ($subject->View()->data) ?: [];
            $data['shipmentWeight'] = $shipmentWeight;
            $subject->View()->data = $data;
        }

        // Check if the order has a shipping provider, its status has changed to (partly) dispatched
        // and if for its dispatch shall be sent mails. In case sending the email fails and thus an
        // exception is thrown, it is caught here but then only thrown after the article details were processed.
        $orderBackup = $request->getAttribute(self::REQUEST_ATTR_ORDER_BACKUP);
        $newOrderStatusId = $order->getOrderStatus()->getId();
        $orderStatusChanged = $newOrderStatusId !== $orderBackup['orderStatusId'];
        $orderStatusChangedToAnyShipped = (
            $orderStatusChanged
            && ($newOrderStatusId === Status::ORDER_STATE_PARTIALLY_DELIVERED || $newOrderStatusId === Status::ORDER_STATE_COMPLETELY_DELIVERED)
        );
        $noStatusMailDispatchIds = $this->getPluginConfig()->toArray()['pickingAppNoStatusMailDispatchMethodIds'];
        /**
         * Remark: Check if there are any order details in the API request to prevent an
         * order-state-update email to be sent without actual order detail changes (last condition)
         */
        if (!in_array($order->getDispatch()->getId(), $noStatusMailDispatchIds)
            && $orderStatusChangedToAnyShipped
            && $this->containsPickedDetails($order)
        ) {
            // Set the old undispatched tracking codes, to make them available in the email template
            $order->getAttribute()->setViisonUndispatchedTrackingCodes($orderBackup['attribute']['viisonUndispatchedTrackingCodes']);
            $this->get('models')->flush($order->getAttribute());
            try {
                // Send a dispatch email
                /** @var sOrder $orderModule */
                $orderModule = $this->get('modules')->getModule('Order');
                $mail = $orderModule->createStatusMail($orderId, $newOrderStatusId);
                $orderModule->sendStatusMail($mail);
            } catch (\Exception $mailException) {
                // Only log the error to not break any other subscribers following this one
                $this->get('pluginlogger')->error($mailException->getMessage(), ['exception' => $mailException]);
            }
            if (isset($params['attribute']) && isset($params['attribute']['viisonUndispatchedTrackingCodes'])) {
                // Set the new undispatched tracking codes again
                $order->getAttribute()->setViisonUndispatchedTrackingCodes($params['attribute']['viisonUndispatchedTrackingCodes']);
                $this->get('models')->flush($order->getAttribute());
            }
        }

        if ($orderStatusChangedToAnyShipped) {
            // Increase the shipped quantity of all order details and reset their picked quantities
            $pickedQuantityUpdater = $this->get('viison_pickware_mobile.picked_quantity_updater');
            $originalShippedValues = $request->getAttribute(ViisonPickwareErpApiOrderSubscriber::REQUEST_ATTRIBUTE_ORIGINAL_SHIPPED_VALUES);
            foreach ($order->getDetails() as $orderDetail) {
                // Increase 'shipped' by the picked quantity
                $totalPickedQuantity = $orderDetail->getAttribute()->getPickwarePickedQuantity();
                $orderDetail->setShipped($orderDetail->getShipped() + $totalPickedQuantity);
                // Increase the original shipped values back upped by ERP to avoid duplicate stock entries. If this
                // was not done, ERP would recognize the change to the shipped quantity of the orders details as a
                // change done by the API and would write stock entries for it.
                $originalShippedValues[$orderDetail->getId()] += $totalPickedQuantity;

                if ($orderDetail->getShipped() > 0 && $totalPickedQuantity > 0 && $orderDetail->getStatus()->getId() !== 2) {
                    // Update the status accordingly
                    $newOrderDetailStatusId = ($orderDetail->getShipped() >= $orderDetail->getQuantity()) ? 3 : 1;
                    $newOrderDetailStatus = $this->get('models')->find(DetailStatus::class, $newOrderDetailStatusId);
                    $orderDetail->setStatus($newOrderDetailStatus);
                }

                // Reset picked quantities
                $pickedQuantityUpdater->clearPickedQuantities($orderDetail);
            }
            $request->setAttribute(ViisonPickwareErpApiOrderSubscriber::REQUEST_ATTRIBUTE_ORIGINAL_SHIPPED_VALUES, $originalShippedValues);

            // Save changes
            $this->get('models')->flush($order->getDetails()->toArray());

            // Clear any batch picking box ID, if not explicitly changed by the request. This prevents orders from
            // blocking batch picking boxes, if they were not shipped via the Picking app (see
            // https://github.com/pickware/ShopwarePickwareMobile/issues/416).
            if (!$params['pickwareBatchPickingBoxId'] && $order->getAttribute()->getPickwareBatchPickingBoxId() !== null) {
                $order->getAttribute()->setPickwareBatchPickingBoxId(null);
                $this->get('models')->flush($order->getAttribute());
            }
        }

        // Free all reserved stock, if the new order status is any other than 'in process'
        if ($orderStatusChanged && $newOrderStatusId != Status::ORDER_STATE_IN_PROCESS) {
            $stockReservations = $this->get('models')->getRepository(OrderStockReservation::class)->findBy([
                'orderDetail' => $order->getDetails()->toArray(),
            ]);
            $stockReservationService = $this->get('pickware.erp.stock_reservation_service');
            foreach ($stockReservations as $stockReservation) {
                $stockReservationService->clearStockReservation($stockReservation);
            }
        }
    }

    /**
     * Checks a given order if there are details that have been picked.
     * This check is necessary to ensure that if you update an order and send out an email,
     * be sure that actual details have been changed.
     *
     * @param Order $order
     * @return bool
     */
    private function containsPickedDetails(Order $order)
    {
        foreach ($order->getDetails() as $detail) {
            if ($detail->getAttribute() && $detail->getAttribute()->getPickwarePickedQuantity() > 0) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param Enlight_Hook_HookArgs $args
     */
    public function onBeforeCreateDocument(Enlight_Hook_HookArgs $args)
    {
        /** @var DocumentType $type */
        $type = $args->get('type');
        if ($type->getId() !== DocumentUtil::DOCUMENT_TYPE_ID_DELIVERY_NOTE) {
            return;
        }

        // Filter the document positions to only show the ones that have been picked
        $pickedItemQuantities = $this->get('viison_pickware_mobile.order_picking_service')->getPickedItemQuantitiesOfOrder(
            $args->get('order')
        );
        $this->get('viison_common.document_component_listener_service')->filterPositionsOnNextDocument(
            function (\Shopware_Models_Document_Order $documentModel, array $positions) use ($pickedItemQuantities) {
                $filteredPositions = [];
                foreach ($positions as $position) {
                    if (!isset($pickedItemQuantities[$position['id']])) {
                        continue;
                    }

                    $position['quantity'] = $pickedItemQuantities[$position['id']];
                    $filteredPositions[] = $position;
                }

                return $filteredPositions;
            }
        );
        $this->get('viison_pickware_mobile.document_component_listener_service')->addRemainingPositionsOnNextDocument();
    }
}
