* @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ namespace PrestaShopBundle\Controller\Admin\Sell\Order; use Currency; use Exception; use InvalidArgumentException; use PrestaShop\PrestaShop\Adapter\Shop\Context as ShopContext; use PrestaShop\PrestaShop\Core\Action\ActionsBarButtonsCollection; use PrestaShop\PrestaShop\Core\Domain\Cart\Query\GetCartForOrderCreation; use PrestaShop\PrestaShop\Core\Domain\CartRule\Exception\InvalidCartRuleDiscountValueException; use PrestaShop\PrestaShop\Core\Domain\CustomerMessage\Command\AddOrderCustomerMessageCommand; use PrestaShop\PrestaShop\Core\Domain\CustomerMessage\Exception\CannotSendEmailException; use PrestaShop\PrestaShop\Core\Domain\CustomerMessage\Exception\CustomerMessageConstraintException; use PrestaShop\PrestaShop\Core\Domain\Order\Command\AddCartRuleToOrderCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Command\BulkChangeOrderStatusCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Command\ChangeOrderCurrencyCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Command\ChangeOrderDeliveryAddressCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Command\ChangeOrderInvoiceAddressCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Command\DeleteCartRuleFromOrderCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Command\DuplicateOrderCartCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Command\ResendOrderEmailCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Command\SendProcessOrderEmailCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Command\SetInternalOrderNoteCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Command\UpdateOrderShippingDetailsCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Command\UpdateOrderStatusCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\CannotEditDeliveredOrderProductException; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\CannotFindProductInOrderException; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\ChangeOrderStatusException; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\DuplicateProductInOrderException; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\DuplicateProductInOrderInvoiceException; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\InvalidAmountException; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\InvalidCancelProductException; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\InvalidOrderStateException; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\InvalidProductQuantityException; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\NegativePaymentAmountException; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\OrderConstraintException; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\OrderEmailSendException; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\OrderException; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\OrderNotFoundException; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\TransistEmailSendingException; use PrestaShop\PrestaShop\Core\Domain\Order\Invoice\Command\GenerateInvoiceCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Invoice\Command\UpdateInvoiceNoteCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Invoice\Exception\InvoiceException; use PrestaShop\PrestaShop\Core\Domain\Order\OrderConstraints; use PrestaShop\PrestaShop\Core\Domain\Order\Payment\Command\AddPaymentCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Product\Command\AddProductToOrderCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Product\Command\DeleteProductFromOrderCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Product\Command\UpdateProductInOrderCommand; use PrestaShop\PrestaShop\Core\Domain\Order\Query\GetOrderForViewing; use PrestaShop\PrestaShop\Core\Domain\Order\Query\GetOrderPreview; use PrestaShop\PrestaShop\Core\Domain\Order\QueryResult\OrderForViewing; use PrestaShop\PrestaShop\Core\Domain\Order\QueryResult\OrderPreview; use PrestaShop\PrestaShop\Core\Domain\Order\QueryResult\OrderProductForViewing; use PrestaShop\PrestaShop\Core\Domain\Order\ValueObject\OrderId; use PrestaShop\PrestaShop\Core\Domain\Product\Exception\ProductOutOfStockException; use PrestaShop\PrestaShop\Core\Domain\Product\Exception\ProductSearchEmptyPhraseException; use PrestaShop\PrestaShop\Core\Domain\Product\Query\SearchProducts; use PrestaShop\PrestaShop\Core\Domain\Product\QueryResult\FoundProduct; use PrestaShop\PrestaShop\Core\Domain\ValueObject\QuerySorting; use PrestaShop\PrestaShop\Core\Form\ConfigurableFormChoiceProviderInterface; use PrestaShop\PrestaShop\Core\Grid\Definition\Factory\OrderGridDefinitionFactory; use PrestaShop\PrestaShop\Core\Order\OrderSiblingProviderInterface; use PrestaShop\PrestaShop\Core\Search\Filters\OrderFilters; use PrestaShopBundle\Component\CsvResponse; use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController; use PrestaShopBundle\Exception\InvalidModuleException; use PrestaShopBundle\Form\Admin\Sell\Customer\PrivateNoteType; use PrestaShopBundle\Form\Admin\Sell\Order\AddOrderCartRuleType; use PrestaShopBundle\Form\Admin\Sell\Order\AddProductRowType; use PrestaShopBundle\Form\Admin\Sell\Order\CartSummaryType; use PrestaShopBundle\Form\Admin\Sell\Order\ChangeOrderAddressType; use PrestaShopBundle\Form\Admin\Sell\Order\ChangeOrderCurrencyType; use PrestaShopBundle\Form\Admin\Sell\Order\ChangeOrdersStatusType; use PrestaShopBundle\Form\Admin\Sell\Order\EditProductRowType; use PrestaShopBundle\Form\Admin\Sell\Order\InternalNoteType; use PrestaShopBundle\Form\Admin\Sell\Order\OrderMessageType; use PrestaShopBundle\Form\Admin\Sell\Order\OrderPaymentType; use PrestaShopBundle\Form\Admin\Sell\Order\UpdateOrderShippingType; use PrestaShopBundle\Form\Admin\Sell\Order\UpdateOrderStatusType; use PrestaShopBundle\Security\Annotation\AdminSecurity; use PrestaShopBundle\Security\Annotation\DemoRestricted; use PrestaShopBundle\Service\Grid\ResponseBuilder; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; /** * Manages "Sell > Orders" page */ class OrderController extends FrameworkBundleAdminController { /** * Default number of products per page (in case invalid value is used) */ public const DEFAULT_PRODUCTS_NUMBER = 8; /** * Options used for the number of products per page */ public const PRODUCTS_PAGINATION_OPTIONS = [8, 20, 50, 100]; /** * Shows list of orders * * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))") * * @param Request $request * @param OrderFilters $filters * * @return Response */ public function indexAction(Request $request, OrderFilters $filters) { $orderKpiFactory = $this->get('prestashop.core.kpi_row.factory.orders'); $orderGrid = $this->get('prestashop.core.grid.factory.order')->getGrid($filters); $changeOrderStatusesForm = $this->createForm(ChangeOrdersStatusType::class); return $this->render( '@PrestaShop/Admin/Sell/Order/Order/index.html.twig', [ 'orderGrid' => $this->presentGrid($orderGrid), 'help_link' => $this->generateSidebarLink($request->attributes->get('_legacy_controller')), 'enableSidebar' => true, 'changeOrderStatusesForm' => $changeOrderStatusesForm->createView(), 'orderKpi' => $orderKpiFactory->build(), 'layoutHeaderToolbarBtn' => $this->getOrderToolbarButtons(), ] ); } /** * @return array */ private function getOrderToolbarButtons(): array { $toolbarButtons = []; $isSingleShopContext = $this->get('prestashop.adapter.shop.context')->isSingleShopContext(); $toolbarButtons['add'] = [ 'href' => $this->generateUrl('admin_orders_create'), 'desc' => $this->trans('Add new order', 'Admin.Orderscustomers.Feature'), 'icon' => 'add_circle_outline', 'disabled' => !$isSingleShopContext, ]; if (!$isSingleShopContext) { $toolbarButtons['add']['help'] = $this->trans( 'You can use this feature in a single-store context only. Switch contexts to enable it.', 'Admin.Orderscustomers.Feature' ); $toolbarButtons['add']['href'] = '#'; } return $toolbarButtons; } /** * Places an order from BO * * @AdminSecurity("is_granted('create', request.get('_legacy_controller'))") * * @param Request $request * * @return RedirectResponse */ public function placeAction(Request $request) { $summaryForm = $this->createForm(CartSummaryType::class); $summaryForm->handleRequest($request); $formHandler = $this->get('prestashop.core.form.identifiable_object.handler.cart_summary_form_handler'); try { $result = $formHandler->handle($summaryForm); if ($result->getIdentifiableObjectId() instanceof OrderId) { /** @var OrderId $orderId */ $orderId = $result->getIdentifiableObjectId(); return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId->getValue(), ]); } } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); } return $this->redirectToRoute('admin_orders_create'); } /** * Renders create order page. * Whole page dynamics are on javascript side. * To load specific cart pass cartId to url query params (handled by javascript) * * @AdminSecurity("is_granted('create', request.get('_legacy_controller'))") * * @param Request $request * * @return Response */ public function createAction(Request $request) { /** @var ShopContext $shopContextChecker */ $shopContextChecker = $this->container->get('prestashop.adapter.shop.context'); if (!$shopContextChecker->isSingleShopContext()) { $this->addFlash('error', $this->trans( 'You have to select a shop before creating new orders.', 'Admin.Orderscustomers.Notification' )); return $this->redirectToRoute('admin_orders_index'); } $summaryForm = $this->createForm(CartSummaryType::class); $languages = $this->get('prestashop.core.form.choice_provider.language_by_id')->getChoices( [ 'shop_id' => $shopContextChecker->getContextShopID(), ] ); $currencies = $this->get('prestashop.core.form.choice_provider.currency_by_id')->getChoices(); $configuration = $this->getConfiguration(); return $this->render('@PrestaShop/Admin/Sell/Order/Order/create.html.twig', [ 'currencies' => $currencies, 'languages' => $languages, 'summaryForm' => $summaryForm->createView(), 'help_link' => $this->generateSidebarLink($request->attributes->get('_legacy_controller')), 'enableSidebar' => true, 'recycledPackagingEnabled' => (bool) $configuration->get('PS_RECYCLABLE_PACK'), 'giftSettingsEnabled' => (bool) $configuration->get('PS_GIFT_WRAPPING'), 'stockManagementEnabled' => (bool) $configuration->get('PS_STOCK_MANAGEMENT'), 'isB2BEnabled' => (bool) $configuration->get('PS_B2B_ENABLE'), ]); } /** * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @param Request $request * * @return RedirectResponse */ public function searchAction(Request $request) { /** @var ResponseBuilder $responseBuilder */ $responseBuilder = $this->get('prestashop.bundle.grid.response_builder'); return $responseBuilder->buildSearchResponse( $this->get('prestashop.core.grid.definition.factory.order'), $request, OrderGridDefinitionFactory::GRID_ID, 'admin_orders_index' ); } /** * Generates invoice PDF for given order * * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @param int $orderId */ public function generateInvoicePdfAction($orderId) { $this->get('prestashop.adapter.pdf.order_invoice_pdf_generator')->generatePDF([$orderId]); // When using legacy generator, // we want to be sure that displaying PDF is the last thing this controller will do die(); } /** * Generates delivery slip PDF for given order * * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @param int $orderId */ public function generateDeliverySlipPdfAction($orderId) { $this->get('prestashop.adapter.pdf.delivery_slip_pdf_generator')->generatePDF([$orderId]); // When using legacy generator, // we want to be sure that displaying PDF is the last thing this controller will do die(); } /** * @param Request $request * * @AdminSecurity("is_granted('update', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @return RedirectResponse */ public function changeOrdersStatusAction(Request $request) { $changeOrdersStatusForm = $this->createForm(ChangeOrdersStatusType::class); $changeOrdersStatusForm->handleRequest($request); $data = $changeOrdersStatusForm->getData(); try { $this->getCommandBus()->handle( new BulkChangeOrderStatusCommand($data['order_ids'], (int) $data['new_order_status_id']) ); $this->addFlash('success', $this->trans('Successful update', 'Admin.Notifications.Success')); } catch (ChangeOrderStatusException $e) { $this->handleChangeOrderStatusException($e); } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); } return $this->redirectToRoute('admin_orders_index'); } /** * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @param OrderFilters $filters * * @return CsvResponse */ public function exportAction(OrderFilters $filters) { $isB2bEnabled = $this->getConfiguration()->get('PS_B2B_ENABLE'); $filters = new OrderFilters(['limit' => null] + $filters->all()); $orderGrid = $this->get('prestashop.core.grid.factory.order')->getGrid($filters); $headers = [ 'id_order' => $this->trans('ID', 'Admin.Global'), 'reference' => $this->trans('Reference', 'Admin.Global'), 'new' => $this->trans('New client', 'Admin.Orderscustomers.Feature'), 'country_name' => $this->trans('Delivery', 'Admin.Global'), 'customer' => $this->trans('Customer', 'Admin.Global'), 'total_paid_tax_incl' => $this->trans('Total', 'Admin.Global'), 'payment' => $this->trans('Payment', 'Admin.Global'), 'osname' => $this->trans('Status', 'Admin.Global'), 'date_add' => $this->trans('Date', 'Admin.Global'), ]; if ($isB2bEnabled) { $headers['company'] = $this->trans('Company', 'Admin.Global'); } $data = []; foreach ($orderGrid->getData()->getRecords()->all() as $record) { $item = [ 'id_order' => $record['id_order'], 'reference' => $record['reference'], 'new' => $record['new'], 'country_name' => $record['country_name'], 'customer' => $record['customer'], 'total_paid_tax_incl' => $record['total_paid_tax_incl'], 'payment' => $record['payment'], 'osname' => $record['osname'], 'date_add' => $record['date_add'], ]; if ($isB2bEnabled) { $item['company'] = $record['company']; } $data[] = $item; } return (new CsvResponse()) ->setData($data) ->setHeadersData($headers) ->setFileName('order_' . date('Y-m-d_His') . '.csv'); } /** * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))") * * @param int $orderId * @param Request $request * * @return Response */ public function viewAction(int $orderId, Request $request): Response { try { /** @var OrderForViewing $orderForViewing */ $orderForViewing = $this->getQueryBus()->handle(new GetOrderForViewing($orderId, QuerySorting::DESC)); } catch (OrderException $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); return $this->redirectToRoute('admin_orders_index'); } $formFactory = $this->get('form.factory'); $updateOrderStatusForm = $formFactory->createNamed( 'update_order_status', UpdateOrderStatusType::class, [ 'new_order_status_id' => $orderForViewing->getHistory()->getCurrentOrderStatusId(), ] ); $updateOrderStatusActionBarForm = $formFactory->createNamed( 'update_order_status_action_bar', UpdateOrderStatusType::class, [ 'new_order_status_id' => $orderForViewing->getHistory()->getCurrentOrderStatusId(), ] ); $addOrderCartRuleForm = $this->createForm(AddOrderCartRuleType::class, [], [ 'order_id' => $orderId, ]); $addOrderPaymentForm = $this->createForm(OrderPaymentType::class, [ 'id_currency' => $orderForViewing->getCurrencyId(), ], [ 'id_order' => $orderId, ]); $orderMessageForm = $this->createForm(OrderMessageType::class, [ 'lang_id' => $orderForViewing->getCustomer()->getLanguageId(), ], [ 'action' => $this->generateUrl('admin_orders_send_message', ['orderId' => $orderId]), ]); $orderMessageForm->handleRequest($request); $changeOrderCurrencyForm = $this->createForm(ChangeOrderCurrencyType::class, [], [ 'current_currency_id' => $orderForViewing->getCurrencyId(), ]); $changeOrderAddressForm = null; $privateNoteForm = null; if (null !== $orderForViewing->getCustomer() && $orderForViewing->getCustomer()->getId() !== 0) { $changeOrderAddressForm = $this->createForm(ChangeOrderAddressType::class, [], [ 'customer_id' => $orderForViewing->getCustomer()->getId(), ]); $privateNoteForm = $this->createForm(PrivateNoteType::class, [ 'note' => $orderForViewing->getCustomer()->getPrivateNote(), ]); } $updateOrderShippingForm = $this->createForm(UpdateOrderShippingType::class, [ 'new_carrier_id' => $orderForViewing->getCarrierId(), ], [ 'order_id' => $orderId, ]); $currencyDataProvider = $this->container->get('prestashop.adapter.data_provider.currency'); //@todo: Fix me. Should not rely on legacy object model - Currency $orderCurrency = $currencyDataProvider->getCurrencyById($orderForViewing->getCurrencyId()); $addProductRowForm = $this->createForm(AddProductRowType::class, [], [ 'order_id' => $orderId, 'currency_id' => $orderForViewing->getCurrencyId(), 'symbol' => $orderCurrency->symbol, ]); $editProductRowForm = $this->createForm(EditProductRowType::class, [], [ 'order_id' => $orderId, 'symbol' => $orderCurrency->symbol, ]); $internalNoteForm = $this->createForm(InternalNoteType::class, [ 'note' => $orderForViewing->getNote(), ]); $formBuilder = $this->get('prestashop.core.form.identifiable_object.builder.cancel_product_form_builder'); $backOfficeOrderButtons = new ActionsBarButtonsCollection(); try { $this->dispatchHook( 'actionGetAdminOrderButtons', [ 'controller' => $this, 'id_order' => $orderId, 'actions_bar_buttons_collection' => $backOfficeOrderButtons, ] ); $cancelProductForm = $formBuilder->getFormFor($orderId); } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); return $this->redirectToRoute('admin_orders_index'); } $this->handleOutOfStockProduct($orderForViewing); $merchandiseReturnEnabled = (bool) $this->getConfiguration()->get('PS_ORDER_RETURN'); /** @var OrderSiblingProviderInterface $orderSiblingProvider */ $orderSiblingProvider = $this->get('prestashop.adapter.order.order_sibling_provider'); $paginationNum = (int) $this->getConfiguration()->get('PS_ORDER_PRODUCTS_NB_PER_PAGE', self::DEFAULT_PRODUCTS_NUMBER); $paginationNumOptions = self::PRODUCTS_PAGINATION_OPTIONS; if (!in_array($paginationNum, $paginationNumOptions)) { $paginationNumOptions[] = $paginationNum; } sort($paginationNumOptions); $metatitle = sprintf( '%s %s %s', $this->trans('Orders', 'Admin.Orderscustomers.Feature'), $this->getConfiguration()->get('PS_NAVIGATION_PIPE', '>'), $this->trans( 'Order %reference% from %firstname% %lastname%', 'Admin.Orderscustomers.Feature', [ '%reference%' => $orderForViewing->getReference(), '%firstname%' => $orderForViewing->getCustomer()->getFirstName(), '%lastname%' => $orderForViewing->getCustomer()->getLastName(), ] ) ); return $this->render('@PrestaShop/Admin/Sell/Order/Order/view.html.twig', [ 'showContentHeader' => true, 'enableSidebar' => true, 'orderCurrency' => $orderCurrency, 'meta_title' => $metatitle, 'help_link' => $this->generateSidebarLink($request->attributes->get('_legacy_controller')), 'orderForViewing' => $orderForViewing, 'addOrderCartRuleForm' => $addOrderCartRuleForm->createView(), 'updateOrderStatusForm' => $updateOrderStatusForm->createView(), 'updateOrderStatusActionBarForm' => $updateOrderStatusActionBarForm->createView(), 'addOrderPaymentForm' => $addOrderPaymentForm->createView(), 'changeOrderCurrencyForm' => $changeOrderCurrencyForm->createView(), 'privateNoteForm' => $privateNoteForm ? $privateNoteForm->createView() : null, 'updateOrderShippingForm' => $updateOrderShippingForm->createView(), 'cancelProductForm' => $cancelProductForm->createView(), 'invoiceManagementIsEnabled' => $orderForViewing->isInvoiceManagementIsEnabled(), 'changeOrderAddressForm' => $changeOrderAddressForm ? $changeOrderAddressForm->createView() : null, 'orderMessageForm' => $orderMessageForm->createView(), 'addProductRowForm' => $addProductRowForm->createView(), 'editProductRowForm' => $editProductRowForm->createView(), 'backOfficeOrderButtons' => $backOfficeOrderButtons, 'merchandiseReturnEnabled' => $merchandiseReturnEnabled, 'priceSpecification' => $this->getContextLocale()->getPriceSpecification($orderCurrency->iso_code)->toArray(), 'previousOrderId' => $orderSiblingProvider->getPreviousOrderId($orderId), 'nextOrderId' => $orderSiblingProvider->getNextOrderId($orderId), 'paginationNum' => $paginationNum, 'paginationNumOptions' => $paginationNumOptions, 'isAvailableQuantityDisplayed' => $this->getConfiguration()->getBoolean('PS_STOCK_MANAGEMENT'), 'internalNoteForm' => $internalNoteForm->createView(), ]); } /** * @AdminSecurity( * "is_granted('update', request.get('_legacy_controller')) && is_granted('delete', request.get('_legacy_controller'))", * redirectRoute="admin_orders_view", * redirectQueryParamsToKeep={"orderId"}, * message="You do not have permission to edit this." * ) * * @param int $orderId * @param Request $request * * @return RedirectResponse */ public function partialRefundAction(int $orderId, Request $request) { $formBuilder = $this->get('prestashop.core.form.identifiable_object.builder.cancel_product_form_builder'); $formHandler = $this->get('prestashop.core.form.identifiable_object.partial_refund_form_handler'); $form = $formBuilder->getFormFor($orderId); try { $form->handleRequest($request); $result = $formHandler->handleFor($orderId, $form); if ($result->isSubmitted()) { if ($result->isValid()) { $this->addFlash('success', $this->trans('A partial refund was successfully created.', 'Admin.Orderscustomers.Notification')); } else { $this->addFlashFormErrors($form); } } } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * @AdminSecurity( * "is_granted('update', request.get('_legacy_controller')) && is_granted('delete', request.get('_legacy_controller'))" * ) * * @param int $orderId * @param Request $request * * @return RedirectResponse */ public function standardRefundAction(int $orderId, Request $request) { $formBuilder = $this->get('prestashop.core.form.identifiable_object.builder.cancel_product_form_builder'); $formHandler = $this->get('prestashop.core.form.identifiable_object.standard_refund_form_handler'); $form = $formBuilder->getFormFor($orderId); try { $form->handleRequest($request); $result = $formHandler->handleFor($orderId, $form); if ($result->isSubmitted()) { if ($result->isValid()) { $this->addFlash('success', $this->trans('A standard refund was successfully created.', 'Admin.Orderscustomers.Notification')); } else { $this->addFlashFormErrors($form); } } } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * @AdminSecurity( * "is_granted('update', request.get('_legacy_controller')) && is_granted('delete', request.get('_legacy_controller'))" * ) * * @param int $orderId * @param Request $request * * @return RedirectResponse */ public function returnProductAction(int $orderId, Request $request) { $formBuilder = $this->get('prestashop.core.form.identifiable_object.builder.cancel_product_form_builder'); $formHandler = $this->get('prestashop.core.form.identifiable_object.return_product_form_handler'); $form = $formBuilder->getFormFor($orderId); try { $form->handleRequest($request); $result = $formHandler->handleFor($orderId, $form); if ($result->isSubmitted()) { if ($result->isValid()) { $this->addFlash('success', $this->trans('The product was successfully returned.', 'Admin.Orderscustomers.Notification')); } else { $this->addFlashFormErrors($form); } } } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * @param OrderForViewing $orderForViewing */ private function handleOutOfStockProduct(OrderForViewing $orderForViewing) { $isStockManagementEnabled = $this->getConfiguration()->getBoolean('PS_STOCK_MANAGEMENT'); if (!$isStockManagementEnabled || $orderForViewing->isDelivered() || $orderForViewing->isShipped()) { return; } foreach ($orderForViewing->getProducts()->getProducts() as $product) { if ($product->getAvailableQuantity() <= 0) { $this->addFlash( 'warning', $this->trans('This product is out of stock:', 'Admin.Orderscustomers.Notification') . ' ' . $product->getName() ); } } } /** * @AdminSecurity("is_granted('create', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @param int $orderId * @param Request $request * * @return Response */ public function addProductAction(int $orderId, Request $request): Response { /** @var OrderForViewing $orderForViewing */ $orderForViewing = $this->getQueryBus()->handle(new GetOrderForViewing($orderId, QuerySorting::DESC)); $previousProducts = []; foreach ($orderForViewing->getProducts()->getProducts() as $orderProductForViewing) { $previousProducts[$orderProductForViewing->getOrderDetailId()] = $orderProductForViewing; } $invoiceId = (int) $request->get('invoice_id'); try { if ($invoiceId > 0) { $addProductCommand = AddProductToOrderCommand::toExistingInvoice( $orderId, $invoiceId, (int) $request->get('product_id'), (int) $request->get('combination_id'), $request->get('price_tax_incl'), $request->get('price_tax_excl'), (int) $request->get('quantity') ); } else { $hasFreeShipping = null; if ($request->request->has('free_shipping')) { $hasFreeShipping = (bool) filter_var($request->get('free_shipping'), FILTER_VALIDATE_BOOLEAN); } $addProductCommand = AddProductToOrderCommand::withNewInvoice( $orderId, (int) $request->get('product_id'), (int) $request->get('combination_id'), $request->get('price_tax_incl'), $request->get('price_tax_excl'), (int) $request->get('quantity'), $hasFreeShipping ); } $this->getCommandBus()->handle($addProductCommand); } catch (Exception $e) { return $this->json( ['message' => $this->getErrorMessageForException($e, $this->getErrorMessages($e))], Response::HTTP_BAD_REQUEST ); } /** * Returning the products list view is not required since we reload the whole list * We keep it for now to avoid Breaking Change */ /** @var OrderForViewing $orderForViewing */ $orderForViewing = $this->getQueryBus()->handle(new GetOrderForViewing($orderId, QuerySorting::DESC)); $updatedProducts = []; foreach ($orderForViewing->getProducts()->getProducts() as $orderProductForViewing) { $updatedProducts[$orderProductForViewing->getOrderDetailId()] = $orderProductForViewing; } $newProducts = array_diff_key($updatedProducts, $previousProducts); $formBuilder = $this->get('prestashop.core.form.identifiable_object.builder.cancel_product_form_builder'); $cancelProductForm = $formBuilder->getFormFor($orderId); $currencyDataProvider = $this->container->get('prestashop.adapter.data_provider.currency'); $orderCurrency = $currencyDataProvider->getCurrencyById($orderForViewing->getCurrencyId()); $addedGridRows = ''; foreach ($newProducts as $newProduct) { $addedGridRows .= $this->renderView('@PrestaShop/Admin/Sell/Order/Order/Blocks/View/product.html.twig', [ 'orderForViewing' => $orderForViewing, 'product' => $newProduct, 'isColumnLocationDisplayed' => $newProduct->getLocation() !== '', 'isColumnRefundedDisplayed' => $newProduct->getQuantityRefunded() > 0, 'isAvailableQuantityDisplayed' => $this->getConfiguration()->getBoolean('PS_STOCK_MANAGEMENT'), 'cancelProductForm' => $cancelProductForm->createView(), 'orderCurrency' => $orderCurrency, ]); } return new Response($addedGridRows); } /** * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @param int $orderId * * @return Response */ public function getProductPricesAction(int $orderId): Response { try { $orderForViewing = $this->getQueryBus()->handle(new GetOrderForViewing($orderId)); $productsForViewing = $orderForViewing->getProducts(); $productList = $productsForViewing->getProducts(); $result = []; foreach ($productList as $product) { $result[] = [ 'orderDetailId' => $product->getOrderDetailId(), 'unitPrice' => $product->getUnitPrice(), 'unitPriceTaxExclRaw' => $product->getUnitPriceTaxExclRaw(), 'unitPriceTaxInclRaw' => $product->getUnitPriceTaxInclRaw(), 'quantity' => $product->getQuantity(), 'availableQuantity' => $product->getAvailableQuantity(), 'totalPrice' => $product->getTotalPrice(), ]; } } catch (Exception $e) { return $this->json( ['message' => $this->getErrorMessageForException($e, $this->getErrorMessages($e))], Response::HTTP_INTERNAL_SERVER_ERROR ); } return $this->json($result); } /** * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @param int $orderId */ public function getInvoicesAction(int $orderId) { /** @var ConfigurableFormChoiceProviderInterface $choiceProvider */ $choiceProvider = $this->get('prestashop.adapter.form.choice_provider.order_invoice_by_id'); $choices = $choiceProvider->getChoices([ 'id_order' => $orderId, 'id_lang' => $this->getContextLangId(), 'display_total' => true, ]); return $this->json([ 'invoices' => $choices, ]); } /** * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @param int $orderId */ public function getDocumentsAction(int $orderId) { /** @var OrderForViewing $orderForViewing */ $orderForViewing = $this->getQueryBus()->handle(new GetOrderForViewing($orderId)); return $this->json([ 'total' => count($orderForViewing->getDocuments()->getDocuments()), 'html' => $this->render('@PrestaShop/Admin/Sell/Order/Order/Blocks/View/documents.html.twig', [ 'orderForViewing' => $orderForViewing, ])->getContent(), ]); } /** * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @param int $orderId */ public function getShippingAction(int $orderId) { /** @var OrderForViewing $orderForViewing */ $orderForViewing = $this->getQueryBus()->handle(new GetOrderForViewing($orderId)); return $this->json([ 'total' => count($orderForViewing->getShipping()->getCarriers()), 'html' => $this->render('@PrestaShop/Admin/Sell/Order/Order/Blocks/View/shipping.html.twig', [ 'orderForViewing' => $orderForViewing, ])->getContent(), ]); } /** * @AdminSecurity( * "is_granted('update', request.get('_legacy_controller'))", * redirectRoute="admin_orders_view", * redirectQueryParamsToKeep={"orderId"}, * message="You do not have permission to edit this." * ) * * @param int $orderId * @param Request $request * * @return RedirectResponse */ public function updateShippingAction(int $orderId, Request $request): RedirectResponse { $form = $this->createForm(UpdateOrderShippingType::class, [], [ 'order_id' => $orderId, ]); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $data = $form->getData(); try { $this->getCommandBus()->handle( new UpdateOrderShippingDetailsCommand( $orderId, (int) $data['current_order_carrier_id'], (int) $data['new_carrier_id'], $data['tracking_number'] ) ); $this->addFlash('success', $this->trans('Successful update', 'Admin.Notifications.Success')); } catch (TransistEmailSendingException $e) { $this->addFlash( 'error', $this->trans( 'An error occurred while sending an email to the customer.', 'Admin.Orderscustomers.Notification' ) ); } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); } } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * @AdminSecurity( * "is_granted('update', 'AdminOrders')", * redirectRoute="admin_orders_view", * redirectQueryParamsToKeep={"orderId"}, * message="You do not have permission to edit this." * ) * * @param int $orderId * @param int $orderCartRuleId * * @return RedirectResponse */ public function removeCartRuleAction(int $orderId, int $orderCartRuleId): RedirectResponse { $this->getCommandBus()->handle( new DeleteCartRuleFromOrderCommand($orderId, $orderCartRuleId) ); $this->addFlash('success', $this->trans('Successful update', 'Admin.Notifications.Success')); return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * @AdminSecurity( * "is_granted('update', 'AdminOrders')", * redirectRoute="admin_orders_view", * redirectQueryParamsToKeep={"orderId"}, * message="You do not have permission to edit this." * ) * * @param int $orderId * @param int $orderInvoiceId * @param Request $request * * @return RedirectResponse */ public function updateInvoiceNoteAction(int $orderId, int $orderInvoiceId, Request $request): RedirectResponse { try { $this->getCommandBus()->handle(new UpdateInvoiceNoteCommand( $orderInvoiceId, $request->request->get('invoice_note') )); $this->addFlash('success', $this->trans('Update successful', 'Admin.Notifications.Success')); } catch (InvoiceException $e) { $this->addFlash( 'error', $this->getErrorMessageForException($e, $this->getErrorMessages($e)) ); } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * @AdminSecurity("is_granted('update', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @param int $orderId * @param int $orderDetailId * @param Request $request * * @return Response */ public function updateProductAction(int $orderId, int $orderDetailId, Request $request): Response { try { $this->getCommandBus()->handle( new UpdateProductInOrderCommand( $orderId, $orderDetailId, $request->get('price_tax_incl'), $request->get('price_tax_excl'), (int) $request->get('quantity'), (int) $request->get('invoice') ) ); } catch (Exception $e) { return $this->json( ['message' => $this->getErrorMessageForException($e, $this->getErrorMessages($e))], Response::HTTP_BAD_REQUEST ); } /** @var OrderForViewing $orderForViewing */ $orderForViewing = $this->getQueryBus()->handle(new GetOrderForViewing($orderId, QuerySorting::DESC)); $products = $orderForViewing->getProducts()->getProducts(); $product = array_reduce($products, function ($result, OrderProductForViewing $item) use ($orderDetailId) { return $item->getOrderDetailId() == $orderDetailId ? $item : $result; }); // The whole product row has been removed so we return an empty response if (null === $product) { return new Response(''); } $formBuilder = $this->get('prestashop.core.form.identifiable_object.builder.cancel_product_form_builder'); $cancelProductForm = $formBuilder->getFormFor($orderId); $currencyDataProvider = $this->container->get('prestashop.adapter.data_provider.currency'); $orderCurrency = $currencyDataProvider->getCurrencyById($orderForViewing->getCurrencyId()); return $this->render('@PrestaShop/Admin/Sell/Order/Order/Blocks/View/product.html.twig', [ 'cancelProductForm' => $cancelProductForm->createView(), 'isColumnLocationDisplayed' => $product->getLocation() !== '', 'isColumnRefundedDisplayed' => $product->getQuantityRefunded() > 0, 'isAvailableQuantityDisplayed' => $this->getConfiguration()->getBoolean('PS_STOCK_MANAGEMENT'), 'orderCurrency' => $orderCurrency, 'orderForViewing' => $orderForViewing, 'product' => $product, ]); } /** * @AdminSecurity( * "is_granted('update', 'AdminOrders')", * redirectRoute="admin_orders_view", * redirectQueryParamsToKeep={"orderId"}, * message="You do not have permission to edit this." * ) * * @param int $orderId * @param Request $request * * @return RedirectResponse */ public function addCartRuleAction(int $orderId, Request $request): RedirectResponse { $addOrderCartRuleForm = $this->createForm(AddOrderCartRuleType::class, [], [ 'order_id' => $orderId, ]); $addOrderCartRuleForm->handleRequest($request); if ($addOrderCartRuleForm->isSubmitted()) { if ($addOrderCartRuleForm->isValid()) { $data = $addOrderCartRuleForm->getData(); try { $this->getCommandBus()->handle( new AddCartRuleToOrderCommand( $orderId, $data['name'], $data['type'], $data['value'] ?? null, empty($data['invoice_id']) ? null : (int) $data['invoice_id'] ) ); $this->addFlash('success', $this->trans('Successful update', 'Admin.Notifications.Success')); } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); } } else { foreach ($addOrderCartRuleForm->getErrors(true) as $error) { $this->addFlash('error', $error->getMessage()); } } } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * @param int $orderId * @param Request $request * * @AdminSecurity("is_granted('update', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @return RedirectResponse */ public function updateStatusAction(int $orderId, Request $request): RedirectResponse { $formFactory = $this->get('form.factory'); $form = $formFactory->createNamed( 'update_order_status', UpdateOrderStatusType::class ); $form->handleRequest($request); if (!$form->isSubmitted() || !$form->isValid()) { // Check if the form is submit from the action bar $form = $formFactory->createNamed( 'update_order_status_action_bar', UpdateOrderStatusType::class ); $form->handleRequest($request); } if ($form->isSubmitted() && $form->isValid()) { $this->handleOrderStatusUpdate($orderId, (int) $form->getData()['new_order_status_id']); } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * Updates order status directly from list page. * * @param int $orderId * @param Request $request * * @AdminSecurity("is_granted('update', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @return RedirectResponse */ public function updateStatusFromListAction(int $orderId, Request $request): RedirectResponse { $this->handleOrderStatusUpdate($orderId, $request->request->getInt('value')); return $this->redirectToRoute('admin_orders_index'); } /** * @AdminSecurity( * "is_granted('update', 'AdminOrders')", * redirectRoute="admin_orders_view", * redirectQueryParamsToKeep={"orderId"}, * message="You do not have permission to edit this." * ) * * @param int $orderId * @param Request $request * * @return RedirectResponse */ public function addPaymentAction(int $orderId, Request $request): RedirectResponse { $form = $this->createForm(OrderPaymentType::class, [], [ 'id_order' => $orderId, ]); $form->handleRequest($request); if ($form->isSubmitted()) { if ($form->isValid()) { $data = $form->getData(); try { $this->getCommandBus()->handle( new AddPaymentCommand( $orderId, $data['date'], $data['payment_method'], $data['amount'], $data['id_currency'], (int) $this->getContext()->employee->id, $data['id_invoice'], $data['transaction_id'] ) ); $this->addFlash('success', $this->trans('Successful update', 'Admin.Notifications.Success')); } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getPaymentErrorMessages($e))); } } else { foreach ($form->getErrors(true) as $error) { $this->addFlash('error', $error->getMessage()); } } } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))") * * @param int $orderId * * @return JsonResponse */ public function previewAction(int $orderId): JsonResponse { try { /** @var OrderPreview $orderPreview */ $orderPreview = $this->getQueryBus()->handle(new GetOrderPreview($orderId)); return $this->json([ 'preview' => $this->renderView('@PrestaShop/Admin/Sell/Order/Order/preview.html.twig', [ 'orderPreview' => $orderPreview, 'productsPreviewLimit' => OrderConstraints::PRODUCTS_PREVIEW_LIMIT, 'orderId' => $orderId, ]), ]); } catch (Exception $e) { return $this->json( ['message' => $this->getErrorMessageForException($e, $this->getErrorMessages($e))], Response::HTTP_BAD_REQUEST ); } } /** * Duplicates cart from specified order * * @AdminSecurity("is_granted('update', request.get('_legacy_controller')) || is_granted('create', 'AdminOrders')") * * @param int $orderId * * @return JsonResponse */ public function duplicateOrderCartAction(int $orderId) { $cartId = $this->getCommandBus()->handle(new DuplicateOrderCartCommand($orderId))->getValue(); return $this->json( $this->getQueryBus()->handle( (new GetCartForOrderCreation($cartId)) ->setHideDiscounts(true) ) ); } /** * @AdminSecurity( * "is_granted('update', request.get('_legacy_controller'))", * redirectRoute="admin_orders_view", * redirectQueryParamsToKeep={"orderId"}, * message="You do not have permission to edit this." * ) * @DemoRestricted( * redirectRoute="admin_orders_view", * redirectQueryParamsToKeep={"orderId"} * ) * * @param Request $request * @param int $orderId * * @return Response */ public function sendMessageAction(Request $request, int $orderId): Response { $orderMessageForm = $this->createForm(OrderMessageType::class); $orderMessageForm->handleRequest($request); if ($orderMessageForm->isSubmitted() && $orderMessageForm->isValid()) { $data = $orderMessageForm->getData(); try { $this->getCommandBus()->handle(new AddOrderCustomerMessageCommand( $orderId, $data['message'], !$data['is_displayed_to_customer'] )); $this->addFlash( 'success', $this->trans('Comment successfully added.', 'Admin.Notifications.Success') ); } catch (CannotSendEmailException $exception) { $this->addFlash( 'success', $this->trans('Comment successfully added.', 'Admin.Notifications.Success') ); $this->addFlash( 'error', $this->trans( 'An error occurred while sending an email to the customer.', 'Admin.Orderscustomers.Notification' ) ); } catch (Exception $exception) { $this->addFlash( 'error', $this->getErrorMessageForException($exception, $this->getCustomerMessageErrorMapping($exception)) ); } } $routesCollection = $this->get('router')->getRouteCollection(); if (null !== $routesCollection && !$orderMessageForm->isValid() && $viewRoute = $routesCollection->get('admin_orders_view') ) { $attributes = $viewRoute->getDefaults(); $attributes['orderId'] = $orderId; return $this->forward( $viewRoute->getDefault('_controller'), $attributes ); } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * @AdminSecurity( * "is_granted('update', 'AdminOrders')", * redirectRoute="admin_orders_view", * redirectQueryParamsToKeep={"orderId"}, * message="You do not have permission to edit this." * ) * * @param Request $request * * @return RedirectResponse */ public function changeCustomerAddressAction(Request $request): RedirectResponse { $orderId = $request->query->get('orderId'); if (!$orderId) { return $this->redirectToRoute('admin_orders_index'); } $customerId = $request->query->get('customerId'); if (!$customerId) { return $this->redirectToRoute('admin_orders_index'); } $changeOrderAddressForm = $this->createForm(ChangeOrderAddressType::class, [], [ 'customer_id' => (int) $request->query->get('customerId'), ]); $changeOrderAddressForm->handleRequest($request); if (!$changeOrderAddressForm->isSubmitted() || !$changeOrderAddressForm->isValid()) { return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } $data = $changeOrderAddressForm->getData(); try { if ($data['address_type'] === ChangeOrderAddressType::SHIPPING_TYPE) { $command = new ChangeOrderDeliveryAddressCommand((int) $orderId, (int) $data['new_address_id']); } else { $command = new ChangeOrderInvoiceAddressCommand((int) $orderId, (int) $data['new_address_id']); } $this->getCommandBus()->handle($command); $this->addFlash('success', $this->trans('Successful update', 'Admin.Notifications.Success')); } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * @AdminSecurity( * "is_granted('update', request.get('_legacy_controller'))", * redirectRoute="admin_orders_view", * redirectQueryParamsToKeep={"orderId"}, * message="You do not have permission to edit this." * ) * * @param int $orderId * @param Request $request * * @return RedirectResponse */ public function changeCurrencyAction(int $orderId, Request $request): RedirectResponse { $changeOrderCurrencyForm = $this->createForm(ChangeOrderCurrencyType::class); $changeOrderCurrencyForm->handleRequest($request); if (!$changeOrderCurrencyForm->isSubmitted() || !$changeOrderCurrencyForm->isValid()) { return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } $data = $changeOrderCurrencyForm->getData(); try { $this->getCommandBus()->handle( new ChangeOrderCurrencyCommand($orderId, (int) $data['new_currency_id']) ); $this->addFlash('success', $this->trans('Successful update', 'Admin.Notifications.Success')); } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * @AdminSecurity( * "is_granted('update', 'AdminOrders')", * redirectRoute="admin_orders_view", * redirectQueryParamsToKeep={"orderId"}, * message="You do not have permission to edit this." * ) * * @param int $orderId * @param int $orderStatusId * @param int $orderHistoryId * * @return RedirectResponse */ public function resendEmailAction(int $orderId, int $orderStatusId, int $orderHistoryId): RedirectResponse { try { $this->getCommandBus()->handle( new ResendOrderEmailCommand($orderId, $orderStatusId, $orderHistoryId) ); $this->addFlash( 'success', $this->trans('The message was successfully sent to the customer.', 'Admin.Orderscustomers.Notification') ); } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * @AdminSecurity("is_granted('delete', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @param int $orderId * @param int $orderDetailId * * @return JsonResponse */ public function deleteProductAction(int $orderId, int $orderDetailId): JsonResponse { try { $this->getCommandBus()->handle( new DeleteProductFromOrderCommand($orderId, $orderDetailId) ); return $this->json(null, Response::HTTP_NO_CONTENT); } catch (Exception $e) { return $this->json( ['message' => $this->getErrorMessageForException($e, $this->getErrorMessages($e))], Response::HTTP_BAD_REQUEST ); } } /** * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @param int $orderId * * @return Response */ public function getDiscountsAction(int $orderId): Response { /** @var OrderForViewing $orderForViewing */ $orderForViewing = $this->getQueryBus()->handle(new GetOrderForViewing($orderId)); return $this->render('@PrestaShop/Admin/Sell/Order/Order/Blocks/View/discount_list.html.twig', [ 'discounts' => $orderForViewing->getDiscounts()->getDiscounts(), 'orderId' => $orderId, ]); } /** * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @param int $orderId * * @return JsonResponse */ public function getPricesAction(int $orderId): JsonResponse { /** @var OrderForViewing $orderForViewing */ $orderForViewing = $this->getQueryBus()->handle(new GetOrderForViewing($orderId)); $orderForViewingPrices = $orderForViewing->getPrices(); return $this->json([ 'orderTotalFormatted' => $orderForViewingPrices->getTotalAmountFormatted(), 'discountsAmountFormatted' => $orderForViewingPrices->getDiscountsAmountFormatted(), 'discountsAmountDisplayed' => $orderForViewingPrices->getDiscountsAmountRaw()->isGreaterThanZero(), 'productsTotalFormatted' => $orderForViewingPrices->getProductsPriceFormatted(), 'shippingTotalFormatted' => $orderForViewingPrices->getShippingPriceFormatted(), 'shippingTotalDisplayed' => $orderForViewingPrices->getShippingPriceRaw()->isGreaterThanZero(), 'taxesTotalFormatted' => $orderForViewingPrices->getTaxesAmountFormatted(), ]); } /** * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @param int $orderId * * @return Response */ public function getPaymentsAction(int $orderId): Response { try { /** @var OrderForViewing $orderForViewing */ $orderForViewing = $this->getQueryBus()->handle(new GetOrderForViewing($orderId)); return $this->render('@PrestaShop/Admin/Sell/Order/Order/Blocks/View/payments_alert.html.twig', [ 'payments' => $orderForViewing->getPayments(), 'linkedOrders' => $orderForViewing->getLinkedOrders(), ]); } catch (Exception $e) { return $this->json( ['message' => $this->getErrorMessageForException($e, $this->getErrorMessages($e))], Response::HTTP_BAD_REQUEST ); } } /** * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))", redirectRoute="admin_orders_index") * * @param int $orderId * * @return Response */ public function getProductsListAction(int $orderId): Response { /** @var OrderForViewing $orderForViewing */ $orderForViewing = $this->getQueryBus()->handle(new GetOrderForViewing($orderId, QuerySorting::DESC)); $currencyDataProvider = $this->container->get('prestashop.adapter.data_provider.currency'); $orderCurrency = $currencyDataProvider->getCurrencyById($orderForViewing->getCurrencyId()); $formBuilder = $this->get('prestashop.core.form.identifiable_object.builder.cancel_product_form_builder'); $cancelProductForm = $formBuilder->getFormFor($orderId); $paginationNum = $this->getConfiguration()->getInt('PS_ORDER_PRODUCTS_NB_PER_PAGE', self::DEFAULT_PRODUCTS_NUMBER); $paginationNumOptions = self::PRODUCTS_PAGINATION_OPTIONS; if (!in_array($paginationNum, $paginationNumOptions)) { $paginationNumOptions[] = $paginationNum; } sort($paginationNumOptions); $isColumnLocationDisplayed = false; $isColumnRefundedDisplayed = false; foreach (array_slice($orderForViewing->getProducts()->getProducts(), $paginationNum) as $product) { if (!empty($product->getLocation())) { $isColumnLocationDisplayed = true; } if ($product->getQuantityRefunded() > 0) { $isColumnRefundedDisplayed = true; } } return $this->render('@PrestaShop/Admin/Sell/Order/Order/Blocks/View/product_list.html.twig', [ 'orderForViewing' => $orderForViewing, 'cancelProductForm' => $cancelProductForm->createView(), 'orderCurrency' => $orderCurrency, 'paginationNum' => $paginationNum, 'isColumnLocationDisplayed' => $isColumnLocationDisplayed, 'isColumnRefundedDisplayed' => $isColumnRefundedDisplayed, 'isAvailableQuantityDisplayed' => $this->getConfiguration()->getBoolean('PS_STOCK_MANAGEMENT'), ]); } /** * @AdminSecurity( * "is_granted('update', request.get('_legacy_controller'))", * message="You do not have permission to generate this." * ) * * Generates invoice for given order * * @param int $orderId * * @return RedirectResponse */ public function generateInvoiceAction(int $orderId): RedirectResponse { try { $this->getCommandBus()->handle(new GenerateInvoiceCommand($orderId)); $this->addFlash('success', $this->trans('Successful update', 'Admin.Notifications.Success')); } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * Sends email with process order link to customer * * @AdminSecurity("is_granted('update', request.get('_legacy_controller')) || is_granted('create', 'AdminOrders')") * * @param Request $request * * @return JsonResponse */ public function sendProcessOrderEmailAction(Request $request): JsonResponse { try { $this->getCommandBus()->handle(new SendProcessOrderEmailCommand($request->request->getInt('cartId'))); return $this->json([ 'message' => $this->trans('The email was sent to your customer.', 'Admin.Orderscustomers.Notification'), ]); } catch (Exception $e) { return $this->json( ['message' => $this->getErrorMessageForException($e, $this->getErrorMessages($e))], Response::HTTP_INTERNAL_SERVER_ERROR ); } } /** * @AdminSecurity( * "is_granted('update', request.get('_legacy_controller')) && is_granted('delete', request.get('_legacy_controller'))", * redirectRoute="admin_orders_view", * redirectQueryParamsToKeep={"orderId"}, * message="You do not have permission to edit this." * ) * * @param int $orderId * @param Request $request * * @return RedirectResponse */ public function cancellationAction(int $orderId, Request $request) { $formBuilder = $this->get('prestashop.core.form.identifiable_object.builder.cancel_product_form_builder'); $formHandler = $this->get('prestashop.core.form.identifiable_object.cancellation_form_handler'); $form = $formBuilder->getFormFor($orderId); try { $form->handleRequest($request); $result = $formHandler->handleFor($orderId, $form); if ($result->isSubmitted()) { if ($result->isValid()) { $this->addFlash('success', $this->trans('Selected products were successfully canceled.', 'Admin.Catalog.Notification')); } else { $this->addFlashFormErrors($form); } } } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * @AdminSecurity("is_granted('update', request.get('_legacy_controller'))") * * @param Request $request * * @return JsonResponse */ public function configureProductPaginationAction(Request $request): JsonResponse { $numPerPage = (int) $request->request->get('numPerPage'); if ($numPerPage < 1) { $numPerPage = self::DEFAULT_PRODUCTS_NUMBER; } try { $this->getConfiguration()->set('PS_ORDER_PRODUCTS_NB_PER_PAGE', $numPerPage); } catch (Exception $e) { return $this->json( ['message' => $this->getErrorMessageForException($e, $this->getErrorMessages($e))], Response::HTTP_BAD_REQUEST ); } return $this->json(null, Response::HTTP_NO_CONTENT); } /** * Method for downloading customization picture * * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))") * * @param int $orderId * @param string $value * * @return BinaryFileResponse|RedirectResponse */ public function displayCustomizationImageAction(int $orderId, string $value) { $uploadDir = $this->get('prestashop.adapter.legacy.context')->getUploadDirectory(); $filePath = $uploadDir . $value; $filesystem = new Filesystem(); try { if (!$filesystem->exists($filePath)) { $this->addFlash('error', $this->trans('The product customization picture could not be found.', 'Admin.Notifications.Error')); return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } $imageFile = new File($filePath); $fileName = sprintf('%s-customization-%s.%s', $orderId, $value, $imageFile->guessExtension() ?? 'jpg'); return $this->file($filePath, $fileName); } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * Set order internal note. * * @AdminSecurity( * "is_granted('update', request.get('_legacy_controller')) && is_granted('create', request.get('_legacy_controller'))", * redirectRoute="admin_orders_index" * ) * * @param mixed $orderId * @param Request $request * * @return Response */ public function setInternalNoteAction($orderId, Request $request) { $internalNoteForm = $this->createForm(InternalNoteType::class); $internalNoteForm->handleRequest($request); if ($internalNoteForm->isSubmitted()) { $data = $internalNoteForm->getData(); try { $this->getCommandBus()->handle(new SetInternalOrderNoteCommand( (int) $orderId, $data['note'] )); if ($request->isXmlHttpRequest()) { return $this->json([ 'success' => true, 'message' => $this->trans('Update successful', 'Admin.Notifications.Success'), ]); } $this->addFlash('success', $this->trans('Update successful', 'Admin.Notifications.Success')); } catch (OrderException $e) { $this->addFlash( 'error', $this->getErrorMessageForException($e, $this->getErrorMessages($e)) ); } } return $this->redirectToRoute('admin_orders_view', [ 'orderId' => $orderId, ]); } /** * @AdminSecurity( * "is_granted('create', request.get('_legacy_controller')) && is_granted('update', request.get('_legacy_controller'))", * message="You do not have permission to perform this search." * ) * * @param Request $request * * @return JsonResponse */ public function searchProductsAction(Request $request): JsonResponse { try { $defaultCurrencyId = (int) $this->getConfiguration()->get('PS_CURRENCY_DEFAULT'); $searchPhrase = $request->query->get('search_phrase'); $currencyId = $request->query->get('currency_id'); $currencyIsoCode = $currencyId !== null ? Currency::getIsoCodeById((int) $currencyId) : Currency::getIsoCodeById($defaultCurrencyId); $orderId = null; if ($request->query->has('order_id')) { $orderId = (int) $request->query->get('order_id'); } /** @var FoundProduct[] $foundProducts */ $foundProducts = $this->getQueryBus()->handle(new SearchProducts($searchPhrase, 10, $currencyIsoCode, $orderId)); return $this->json([ 'products' => $foundProducts, ]); } catch (ProductSearchEmptyPhraseException $e) { return $this->json( ['message' => $this->getErrorMessageForException($e, $this->getErrorMessages($e))], Response::HTTP_BAD_REQUEST ); } catch (Exception $e) { return $this->json( ['message' => $this->getErrorMessageForException($e, [])], Response::HTTP_INTERNAL_SERVER_ERROR ); } } /** * Initializes order status update * * @param int $orderId * @param int $orderStatusId */ private function handleOrderStatusUpdate(int $orderId, int $orderStatusId): void { try { $this->getCommandBus()->handle( new UpdateOrderStatusCommand( $orderId, $orderStatusId ) ); $this->addFlash('success', $this->trans('Successful update', 'Admin.Notifications.Success')); } catch (ChangeOrderStatusException $e) { $this->handleChangeOrderStatusException($e); } catch (Exception $e) { $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); } } /** * @param Exception $e * * @return array */ private function getErrorMessages(Exception $e) { $refundableQuantity = 0; if ($e instanceof InvalidCancelProductException) { $refundableQuantity = $e->getRefundableQuantity(); } $orderInvoiceNumber = '#unknown'; if ($e instanceof DuplicateProductInOrderInvoiceException) { $orderInvoiceNumber = $e->getOrderInvoiceNumber(); } return [ ProductSearchEmptyPhraseException::class => $this->trans( 'Product search phrase must not be an empty string.', 'Admin.Orderscustomers.Notification' ), CannotEditDeliveredOrderProductException::class => $this->trans( 'You cannot edit the cart once the order delivered.', 'Admin.Orderscustomers.Notification' ), OrderNotFoundException::class => $e instanceof OrderNotFoundException ? $this->trans( 'Order #%d cannot be loaded.', 'Admin.Orderscustomers.Notification', ['#%d' => $e->getOrderId()->getValue()] ) : '', OrderEmailSendException::class => $this->trans( 'An error occurred while sending the e-mail to the customer.', 'Admin.Orderscustomers.Notification' ), OrderException::class => $this->trans( $e->getMessage(), 'Admin.Orderscustomers.Notification' ), InvoiceException::class => $this->trans( $e->getMessage(), 'Admin.Orderscustomers.Notification' ), InvalidAmountException::class => $this->trans( 'Only numbers and decimal points (".") are allowed in the amount fields, e.g. 10.50 or 1050.', 'Admin.Orderscustomers.Notification' ), InvalidCartRuleDiscountValueException::class => [ InvalidCartRuleDiscountValueException::INVALID_MIN_PERCENT => $this->trans( 'Percent value must be greater than 0.', 'Admin.Orderscustomers.Notification' ), InvalidCartRuleDiscountValueException::INVALID_MAX_PERCENT => $this->trans( 'Percent value cannot exceed 100.', 'Admin.Orderscustomers.Notification' ), InvalidCartRuleDiscountValueException::INVALID_MIN_AMOUNT => $this->trans( 'Amount value must be greater than 0.', 'Admin.Orderscustomers.Notification' ), InvalidCartRuleDiscountValueException::INVALID_MAX_AMOUNT => $this->trans( 'Discount value cannot exceed the total price of this order.', 'Admin.Orderscustomers.Notification' ), InvalidCartRuleDiscountValueException::INVALID_FREE_SHIPPING => $this->trans( 'Shipping discount value cannot exceed the total price of this order.', 'Admin.Orderscustomers.Notification' ), ], InvalidCancelProductException::class => [ InvalidCancelProductException::INVALID_QUANTITY => $this->trans( 'Positive product quantity is required.', 'Admin.Notifications.Error' ), InvalidCancelProductException::QUANTITY_TOO_HIGH => $this->trans( 'Please enter a maximum quantity of [1].', 'Admin.Orderscustomers.Notification', ['[1]' => $refundableQuantity] ), InvalidCancelProductException::NO_REFUNDS => $this->trans( 'Please select at least one product.', 'Admin.Orderscustomers.Notification' ), InvalidCancelProductException::INVALID_AMOUNT => $this->trans( 'Please enter a positive amount.', 'Admin.Orderscustomers.Notification' ), InvalidCancelProductException::NO_GENERATION => $this->trans( 'Please generate at least one credit slip or voucher.', 'Admin.Orderscustomers.Notification' ), ], InvalidModuleException::class => $this->trans( 'You must choose a payment module to create the order.', 'Admin.Orderscustomers.Notification' ), ProductOutOfStockException::class => $this->trans( 'There are not enough products in stock.', 'Admin.Catalog.Notification' ), NegativePaymentAmountException::class => $this->trans( 'Invalid value: the payment must be a positive amount.', 'Admin.Notifications.Error' ), InvalidOrderStateException::class => [ InvalidOrderStateException::ALREADY_PAID => $this->trans( 'Invalid action: this order has already been paid.', 'Admin.Notifications.Error' ), InvalidOrderStateException::DELIVERY_NOT_FOUND => $this->trans( 'Invalid action: this order has not been delivered.', 'Admin.Notifications.Error' ), InvalidOrderStateException::UNEXPECTED_DELIVERY => $this->trans( 'Invalid action: this order has already been delivered.', 'Admin.Notifications.Error' ), InvalidOrderStateException::NOT_PAID => $this->trans( 'Invalid action: this order has not been paid.', 'Admin.Notifications.Error' ), InvalidOrderStateException::INVALID_ID => $this->trans( 'You must choose an order status to create the order.', 'Admin.Orderscustomers.Notification' ), ], OrderConstraintException::class => [ OrderConstraintException::INVALID_CUSTOMER_MESSAGE => $this->trans( 'The order message given is invalid.', 'Admin.Orderscustomers.Notification' ), ], InvalidProductQuantityException::class => $this->trans( 'Positive product quantity is required.', 'Admin.Notifications.Error' ), DuplicateProductInOrderException::class => $this->trans( 'This product is already in your order, please edit the quantity instead.', 'Admin.Notifications.Error' ), DuplicateProductInOrderInvoiceException::class => $this->trans( 'This product is already in the invoice [1], please edit the quantity instead.', 'Admin.Notifications.Error', ['[1]' => $orderInvoiceNumber] ), CannotFindProductInOrderException::class => $this->trans( 'You cannot edit the price of a product that no longer exists in your catalog.', 'Admin.Notifications.Error' ), ]; } private function getPaymentErrorMessages(Exception $e) { return array_merge($this->getErrorMessages($e), [ InvalidArgumentException::class => $this->trans( 'Only numbers and decimal points (".") are allowed in the amount fields of the payment block, e.g. 10.50 or 1050.', 'Admin.Orderscustomers.Notification' ), OrderConstraintException::class => [ OrderConstraintException::INVALID_PAYMENT_METHOD => sprintf( '%s %s %s', $this->trans( 'The selected payment method is invalid.', 'Admin.Orderscustomers.Notification' ), $this->trans( 'Invalid characters:', 'Admin.Notifications.Info' ), AddPaymentCommand::INVALID_CHARACTERS_NAME ), ], ]); } /** * @param ChangeOrderStatusException $e */ private function handleChangeOrderStatusException(ChangeOrderStatusException $e) { $orderIds = array_merge( $e->getOrdersWithFailedToUpdateStatus(), $e->getOrdersWithFailedToSendEmail() ); /** @var OrderId $orderId */ foreach ($orderIds as $orderId) { $this->addFlash( 'error', $this->trans( 'An error occurred while changing the status for order #%d, or we were unable to send an email to the customer.', 'Admin.Orderscustomers.Notification', ['#%d' => $orderId->getValue()] ) ); } foreach ($e->getOrdersWithAssignedStatus() as $orderId) { $this->addFlash( 'error', $this->trans( 'Order #%d has already been assigned this status.', 'Admin.Orderscustomers.Notification', ['#%d' => $orderId->getValue()] ) ); } } private function getCustomerMessageErrorMapping(Exception $exception): array { return [ OrderNotFoundException::class => $exception instanceof OrderNotFoundException ? $this->trans( 'Order #%d cannot be loaded.', 'Admin.Orderscustomers.Notification', ['#%d' => $exception->getOrderId()->getValue()] ) : '', CustomerMessageConstraintException::class => [ CustomerMessageConstraintException::MISSING_MESSAGE => $this->trans( 'The %s field is not valid', 'Admin.Notifications.Error', [ sprintf('"%s"', $this->trans('Message', 'Admin.Global')), ] ), CustomerMessageConstraintException::INVALID_MESSAGE => $this->trans( 'The %s field is not valid', 'Admin.Notifications.Error', [ sprintf('"%s"', $this->trans('Message', 'Admin.Global')), ] ), ], ]; } }