src/Eccube/Controller/Admin/Order/OrderController.php line 228

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of EC-CUBE
  4.  *
  5.  * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
  6.  *
  7.  * http://www.ec-cube.co.jp/
  8.  *
  9.  * For the full copyright and license information, please view the LICENSE
  10.  * file that was distributed with this source code.
  11.  */
  12. namespace Eccube\Controller\Admin\Order;
  13. use Eccube\Common\Constant;
  14. use Eccube\Controller\AbstractController;
  15. use Eccube\Entity\ExportCsvRow;
  16. use Eccube\Entity\Master\CsvType;
  17. use Eccube\Entity\Master\OrderStatus;
  18. use Eccube\Entity\OrderPdf;
  19. use Eccube\Entity\Shipping;
  20. use Eccube\Event\EccubeEvents;
  21. use Eccube\Event\EventArgs;
  22. use Eccube\Form\Type\Admin\OrderPdfType;
  23. use Eccube\Form\Type\Admin\SearchOrderType;
  24. use Eccube\Repository\CustomerRepository;
  25. use Eccube\Repository\Master\OrderStatusRepository;
  26. use Eccube\Repository\Master\PageMaxRepository;
  27. use Eccube\Repository\Master\ProductStatusRepository;
  28. use Eccube\Repository\Master\SexRepository;
  29. use Eccube\Repository\OrderPdfRepository;
  30. use Eccube\Repository\OrderRepository;
  31. use Eccube\Repository\PaymentRepository;
  32. use Eccube\Repository\ProductStockRepository;
  33. use Eccube\Service\CsvExportService;
  34. use Eccube\Service\MailService;
  35. use Eccube\Service\OrderPdfService;
  36. use Eccube\Service\OrderStateMachine;
  37. use Eccube\Service\PurchaseFlow\PurchaseFlow;
  38. use Eccube\Util\FormUtil;
  39. use Knp\Component\Pager\PaginatorInterface;
  40. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
  41. use Symfony\Component\Form\FormBuilder;
  42. use Symfony\Component\HttpFoundation\RedirectResponse;
  43. use Symfony\Component\HttpFoundation\Request;
  44. use Symfony\Component\HttpFoundation\Response;
  45. use Symfony\Component\HttpFoundation\StreamedResponse;
  46. use Symfony\Component\Routing\Annotation\Route;
  47. use Symfony\Component\Validator\Constraints as Assert;
  48. use Symfony\Component\Validator\Validator\ValidatorInterface;
  49. use Plugin\EccubePaymentLite42\Entity\PaymentStatus;
  50. use Plugin\EccubePaymentLite42\Repository\RegularOrderRepository;
  51. use Plugin\EccubePaymentLite42\Entity\RegularStatus;
  52. //プラン移行メール送信サービス追加
  53. use Plugin\EccubePaymentLite42\Service\Mail\MigrationPlanNoticeMailService;
  54. //----------
  55. //代理店名の取得はカスタムリポジトリを使用
  56. //----------
  57. use Customize\Repository\CustomizeCustomerRepository;
  58. class OrderController extends AbstractController
  59. {
  60.     /**
  61.      * @var PurchaseFlow
  62.      */
  63.     protected $purchaseFlow;
  64.     /**
  65.      * @var CsvExportService
  66.      */
  67.     protected $csvExportService;
  68.     /**
  69.      * @var CustomerRepository
  70.      */
  71.     protected $customerRepository;
  72.     /**
  73.      * @var PaymentRepository
  74.      */
  75.     protected $paymentRepository;
  76.     /**
  77.      * @var SexRepository
  78.      */
  79.     protected $sexRepository;
  80.     /**
  81.      * @var OrderStatusRepository
  82.      */
  83.     protected $orderStatusRepository;
  84.     /**
  85.      * @var PageMaxRepository
  86.      */
  87.     protected $pageMaxRepository;
  88.     /**
  89.      * @var ProductStatusRepository
  90.      */
  91.     protected $productStatusRepository;
  92.     /**
  93.      * @var OrderRepository
  94.      */
  95.     protected $orderRepository;
  96.     /** @var OrderPdfRepository */
  97.     protected $orderPdfRepository;
  98.     /**
  99.      * @var ProductStockRepository
  100.      */
  101.     protected $productStockRepository;
  102.     /** @var OrderPdfService */
  103.     protected $orderPdfService;
  104.     /**
  105.      * @var ValidatorInterface
  106.      */
  107.     protected $validator;
  108.     /**
  109.      * @var OrderStateMachine
  110.      */
  111.     protected $orderStateMachine;
  112.     /**
  113.      * @var MailService
  114.      */
  115.     protected $mailService;
  116.     /**
  117.      * @var MigrationPlanNoticeMailService
  118.      */
  119.     private $migrationPlanNoticeMailService;
  120.     //----代理店カスタマイズ------
  121.     //代理店名の取得はカスタムリポジトリを使用
  122.     //----------
  123.     private $customizeCustomerRepository;
  124.     /**
  125.      * @var RegularOrderRepository
  126.      */
  127.     protected $regularOrderRepository;
  128.     /**
  129.      * OrderController constructor.
  130.      *
  131.      * @param PurchaseFlow $orderPurchaseFlow
  132.      * @param CsvExportService $csvExportService
  133.      * @param CustomerRepository $customerRepository
  134.      * @param PaymentRepository $paymentRepository
  135.      * @param SexRepository $sexRepository
  136.      * @param OrderStatusRepository $orderStatusRepository
  137.      * @param PageMaxRepository $pageMaxRepository
  138.      * @param ProductStatusRepository $productStatusRepository
  139.      * @param ProductStockRepository $productStockRepository
  140.      * @param OrderRepository $orderRepository
  141.      * @param OrderPdfRepository $orderPdfRepository
  142.      * @param ValidatorInterface $validator
  143.      * @param OrderStateMachine $orderStateMachine ;
  144.      */
  145.     public function __construct(
  146.         PurchaseFlow $orderPurchaseFlow,
  147.         CsvExportService $csvExportService,
  148.         CustomerRepository $customerRepository,
  149.         PaymentRepository $paymentRepository,
  150.         SexRepository $sexRepository,
  151.         OrderStatusRepository $orderStatusRepository,
  152.         PageMaxRepository $pageMaxRepository,
  153.         ProductStatusRepository $productStatusRepository,
  154.         ProductStockRepository $productStockRepository,
  155.         OrderRepository $orderRepository,
  156.         OrderPdfRepository $orderPdfRepository,
  157.         ValidatorInterface $validator,
  158.         OrderStateMachine $orderStateMachine,
  159.         MailService $mailService,
  160.         MigrationPlanNoticeMailService $migrationPlanNoticeMailService,
  161.         CustomizeCustomerRepository $customizeCustomerRepository,
  162.         RegularOrderRepository $regularOrderRepository
  163.     ) {
  164.         $this->purchaseFlow $orderPurchaseFlow;
  165.         $this->csvExportService $csvExportService;
  166.         $this->customerRepository $customerRepository;
  167.         $this->paymentRepository $paymentRepository;
  168.         $this->sexRepository $sexRepository;
  169.         $this->orderStatusRepository $orderStatusRepository;
  170.         $this->pageMaxRepository $pageMaxRepository;
  171.         $this->productStatusRepository $productStatusRepository;
  172.         $this->productStockRepository $productStockRepository;
  173.         $this->orderRepository $orderRepository;
  174.         $this->orderPdfRepository $orderPdfRepository;
  175.         $this->validator $validator;
  176.         $this->orderStateMachine $orderStateMachine;
  177.         $this->mailService $mailService;
  178.         $this->migrationPlanNoticeMailService $migrationPlanNoticeMailService;
  179.         $this->customizeCustomerRepository $customizeCustomerRepository;
  180.         $this->regularOrderRepository $regularOrderRepository;
  181.     }
  182.     /**
  183.      * 受注一覧画面.
  184.      *
  185.      * - 検索条件, ページ番号, 表示件数はセッションに保持されます.
  186.      * - クエリパラメータでresume=1が指定された場合、検索条件, ページ番号, 表示件数をセッションから復旧します.
  187.      * - 各データの, セッションに保持するアクションは以下の通りです.
  188.      *   - 検索ボタン押下時
  189.      *      - 検索条件をセッションに保存します
  190.      *      - ページ番号は1で初期化し、セッションに保存します。
  191.      *   - 表示件数変更時
  192.      *      - クエリパラメータpage_countをセッションに保存します。
  193.      *      - ただし, mtb_page_maxと一致しない場合, eccube_default_page_countが保存されます.
  194.      *   - ページング時
  195.      *      - URLパラメータpage_noをセッションに保存します.
  196.      *   - 初期表示
  197.      *      - 検索条件は空配列, ページ番号は1で初期化し, セッションに保存します.
  198.      *
  199.      * @Route("/%eccube_admin_route%/order", name="admin_order", methods={"GET", "POST"})
  200.      * @Route("/%eccube_admin_route%/order/page/{page_no}", requirements={"page_no" = "\d+"}, name="admin_order_page", methods={"GET", "POST"})
  201.      * @Template("@admin/Order/index.twig")
  202.      */
  203.     public function index(Request $requestPaginatorInterface $paginator$page_no null)
  204.     {
  205.         $builder $this->formFactory
  206.             ->createBuilder(SearchOrderType::class);
  207.         $event = new EventArgs(
  208.             [
  209.                 'builder' => $builder,
  210.             ],
  211.             $request
  212.         );
  213.         $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_ORDER_INDEX_INITIALIZE);
  214.         $searchForm $builder->getForm();
  215.         /**
  216.          * ページの表示件数は, 以下の順に優先される.
  217.          * - リクエストパラメータ
  218.          * - セッション
  219.          * - デフォルト値
  220.          * また, セッションに保存する際は mtb_page_maxと照合し, 一致した場合のみ保存する.
  221.          **/
  222.         $page_count $this->session->get(
  223.             'eccube.admin.order.search.page_count',
  224.             $this->eccubeConfig->get('eccube_default_page_count')
  225.         );
  226.         $page_count_param = (int) $request->get('page_count');
  227.         $pageMaxis $this->pageMaxRepository->findAll();
  228.         if ($page_count_param) {
  229.             foreach ($pageMaxis as $pageMax) {
  230.                 if ($page_count_param == $pageMax->getName()) {
  231.                     $page_count $pageMax->getName();
  232.                     $this->session->set('eccube.admin.order.search.page_count'$page_count);
  233.                     break;
  234.                 }
  235.             }
  236.         }
  237.         if ('POST' === $request->getMethod()) {
  238.             $searchForm->handleRequest($request);
  239.             if ($searchForm->isValid()) {
  240.                 /**
  241.                  * 検索が実行された場合は, セッションに検索条件を保存する.
  242.                  * ページ番号は最初のページ番号に初期化する.
  243.                  */
  244.                 $page_no 1;
  245.                 $searchData $searchForm->getData();
  246.                 // 検索条件, ページ番号をセッションに保持.
  247.                 $this->session->set('eccube.admin.order.search'FormUtil::getViewData($searchForm));
  248.                 $this->session->set('eccube.admin.order.search.page_no'$page_no);
  249.             } else {
  250.                 // 検索エラーの際は, 詳細検索枠を開いてエラー表示する.
  251.                 return [
  252.                     'searchForm' => $searchForm->createView(),
  253.                     'pagination' => [],
  254.                     'pageMaxis' => $pageMaxis,
  255.                     'page_no' => $page_no,
  256.                     'page_count' => $page_count,
  257.                     'has_errors' => true,
  258.                 ];
  259.             }
  260.         } else {
  261.             if (null !== $page_no || $request->get('resume')) {
  262.                 /*
  263.                  * ページ送りの場合または、他画面から戻ってきた場合は, セッションから検索条件を復旧する.
  264.                  */
  265.                 if ($page_no) {
  266.                     // ページ送りで遷移した場合.
  267.                     $this->session->set('eccube.admin.order.search.page_no', (int) $page_no);
  268.                 } else {
  269.                     // 他画面から遷移した場合.
  270.                     $page_no $this->session->get('eccube.admin.order.search.page_no'1);
  271.                 }
  272.                 $viewData $this->session->get('eccube.admin.order.search', []);
  273.                 $searchData FormUtil::submitAndGetData($searchForm$viewData);
  274.             } else {
  275.                 /**
  276.                  * 初期表示の場合.
  277.                  */
  278.                 $page_no 1;
  279.                 $viewData = [];
  280.                 if ($statusId = (int) $request->get('order_status_id')) {
  281.                     $viewData = ['status' => [$statusId]];
  282.                 }
  283.                 $searchData FormUtil::submitAndGetData($searchForm$viewData);
  284.                 // セッション中の検索条件, ページ番号を初期化.
  285.                 $this->session->set('eccube.admin.order.search'$viewData);
  286.                 $this->session->set('eccube.admin.order.search.page_no'$page_no);
  287.             }
  288.         }
  289.         $qb $this->orderRepository->getQueryBuilderBySearchDataForAdmin($searchData);
  290.         $event = new EventArgs(
  291.             [
  292.                 'qb' => $qb,
  293.                 'searchData' => $searchData,
  294.             ],
  295.             $request
  296.         );
  297.         $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_ORDER_INDEX_SEARCH);
  298.         $sortKey $searchData['sortkey'];
  299.         if (empty($this->orderRepository::COLUMNS[$sortKey]) || $sortKey == 'order_status') {
  300.             $pagination $paginator->paginate(
  301.                 $qb,
  302.                 $page_no,
  303.                 $page_count
  304.             );
  305.         } else {
  306.             $pagination $paginator->paginate(
  307.                 $qb,
  308.                 $page_no,
  309.                 $page_count,
  310.                 ['wrap-queries' => true]
  311.             );
  312.         }
  313.         // $paginationから顧客データを取得
  314.         $Orders $pagination->getItems();
  315.         // 各顧客に代理店名を追加
  316.         foreach ($Orders as $Order) {
  317.             $customer $Order->getCustomer();
  318.             if ($customer !== null) {
  319.                 $agencyName null;
  320.                 $agencyName $this->customizeCustomerRepository->findAgencyNameByCustomerId($customer->getId());
  321.                 if ($agencyName) {
  322.                     $Order->agencyName $agencyName;
  323.                 } else {
  324.                     $Order->agencyName "";
  325.                 }
  326.                 $agencyId null;
  327.                 $agencyId $this->customizeCustomerRepository->findAgencyIdByCustomerId($customer->getId());
  328.                 if ($agencyId) {
  329.                     $Order->agencyId $agencyId;
  330.                 } else {
  331.                     $Order->agencyId "";
  332.                 }
  333.             } else {
  334.                 $Order->agencyName "";
  335.                 $Order->agencyId null;
  336.             }
  337.         }
  338.         // 定期受注からの受注生成状態を取得
  339.         $regularOrderValidation $this->validateRegularOrderGeneration();
  340.         return [
  341.             'searchForm' => $searchForm->createView(),
  342.             'pagination' => $pagination,
  343.             'pageMaxis' => $pageMaxis,
  344.             'page_no' => $page_no,
  345.             'page_count' => $page_count,
  346.             'has_errors' => false,
  347.             'OrderStatuses' => $this->orderStatusRepository->findBy([], ['sort_no' => 'ASC']),
  348.             'regularOrderValidation' => $regularOrderValidation// 定期受注検証結果を追加
  349.         ];
  350.     }
  351.     /**
  352.      * 定期受注から生成された受注の整合性を検証
  353.      * 
  354.      * @return array [
  355.      *   'status' => 'not_executed' | 'success' | 'error',
  356.      *   'message' => string,
  357.      *   'executed_at' => \DateTime|null,
  358.      *   'error_count' => int,
  359.      *   'error_details' => array
  360.      * ]
  361.      */
  362.     private function validateRegularOrderGeneration(): array
  363.     {
  364.         $today = new \DateTime('today');
  365.         $batchExecutionTime = new \DateTime('today 12:20:00');
  366.         $now = new \DateTime();
  367.     
  368.     // Configテーブルから最終実行日時を取得
  369.         /** @var \Plugin\EccubePaymentLite42\Entity\Config $Config */
  370.         $Config $this->entityManager
  371.             ->getRepository(\Plugin\EccubePaymentLite42\Entity\Config::class)
  372.             ->find(1);
  373.         if (!$Config) {
  374.             return [
  375.                 'status' => 'error',
  376.                 'message' => 'バッチ処理の設定情報が見つかりません',
  377.                 'executed_at' => null,
  378.                 'error_count' => 0,
  379.                 'error_details' => [],
  380.             ];
  381.         }
  382.         $lastExecutedAt $Config->getCreatedAt();
  383.         //本日の受注一括作成対象を含めるかどうか
  384.         $todayInclude false;
  385.         //12時20分を過ぎている場合、本日の受注一括作成対象を含める
  386.         if ($now >= $batchExecutionTime){
  387.             $todayInclude true;
  388.         }
  389.         
  390.         // ここで取得できる場合は、対象日付の定期便受注一括作成から漏れている定期便
  391.         $targetRegularOrders $this->getTargetRegularOrders($todayInclude);
  392.         
  393.         $totalCount count($targetRegularOrders);
  394.         if ($totalCount === 0) {
  395.             return [
  396.                 'status' => 'success',
  397.                 'message' => '定期便受注一括作成は正常に完了しています。',
  398.                 'executed_at' => $lastExecutedAt,
  399.                 'error_count' => 0,
  400.                 'error_details' => [],
  401.             ];
  402.         }
  403.         // 各定期便の受注生成状態をチェック
  404.         $errorCount 0;
  405.         $errorDetails = [];
  406.         foreach ($targetRegularOrders as $regularOrder) {
  407.             $validationResult $this->validateSingleRegularOrder($regularOrder$todayInclude);
  408.             if (!$validationResult['is_valid']) {
  409.                 $errorCount++;
  410.                 $errorDetails[] = $validationResult;
  411.             }
  412.         }
  413.         // 状態判定
  414.         if ($errorCount === 0) {
  415.             $status 'success';
  416.             $message '定期便受注一括作成は正常に完了しています。';
  417.         } else {
  418.             $status 'error';
  419.             $message sprintf(
  420.                 '定期便受注一括作成が実施されていない定期便があります(件数: %d件)',
  421.                 $errorCount
  422.             );
  423.         }
  424.         return [
  425.             'status' => $status,
  426.             'message' => $message,
  427.             'executed_at' => $lastExecutedAt,
  428.             'error_count' => $errorCount,
  429.             'error_details' => $errorDetails,
  430.         ];
  431.     }
  432.     /**
  433.      * 個別の定期受注の検証
  434.      * 
  435.      * @param \Plugin\EccubePaymentLite42\Entity\RegularOrder $regularOrder
  436.      * @param \DateTime $targetDate
  437.      * @return array
  438.      */
  439.     private function validateSingleRegularOrder($regularOrder$todayInclude): array
  440.     {
  441.         $regularOrderId $regularOrder->getId();
  442.         $regularStatus $regularOrder->getRegularStatus();
  443.         $regularStatusName $regularStatus->getName();
  444.         $regularStopDate $regularOrder->getRegularStopDate();
  445.         $RegularShipping $regularOrder->getRegularShippings()->first();
  446.         $regularNextDeliveryDate $RegularShipping->getNextDeliveryDate()
  447.         ? $RegularShipping->getNextDeliveryDate()->format('Y年m月d日')
  448.         : '';
  449.         // エラー判定用の配列
  450.         $errors = [];
  451.         switch ($regularStatus->getId()) {
  452.             // 継続の場合
  453.             case RegularStatus::CONTINUE:
  454.                 // 再開した場合、次回配送お届け日が8日後になるため、次回配送日が7日後のものは昨日に再開した定期便となる
  455.                 $targetDaysLater = new \DateTime('today');
  456.                 if($todayInclude){
  457.                     $targetDaysLater->modify('+ 8 day');
  458.                 }else{
  459.                     $targetDaysLater->modify('+ 7 day');
  460.                 }
  461.                 // 再開判定(停止日あり)
  462.                 if ($regularStopDate && $RegularShipping) {
  463.                     // 再開かつ5日以内 → エラー
  464.                     if ($RegularShipping->getNextDeliveryDate() < $targetDaysLater) {
  465.                         $regularStatusName "継続(再開)";
  466.                         $errors[] = '再開後の受注一括作成処理が実施されていません。(継続[再開])';
  467.                     }
  468.                     // 正常
  469.                 } else {
  470.                     // 通常の継続の場合は、受注一括作成漏れの為エラー 
  471.                     $errors[] = '受注一括作成処理が実施されていません。(継続)';
  472.                 }
  473.                 break;
  474.             // 決済エラーの場合
  475.             case RegularStatus::PAYMENT_ERROR:
  476.                 // 決済エラーの場合は、受注一括作成漏れの為エラー 
  477.                 $errors[] = '受注一括作成処理が実施されていません(決済エラー)。';
  478.                 break;
  479.             // 再決済待ちの場合
  480.             case RegularStatus::WAITING_RE_PAYMENT:
  481.                 // 再決済待ちの場合は、受注一括作成漏れのためエラー
  482.                 $errors[] = '受注一括作成処理が実施されていません(再決済待ち)。';
  483.                 break;
  484.             default:
  485.                 // それ以外のステータスはクエリ上ありえないが、例外処理としてエラーを出力
  486.                 $errors[] = sprintf(
  487.                     '定期受注ID %d のステータスが想定外です(%s)',
  488.                     $regularOrder->getId(),
  489.                     $regularStatus->getName()
  490.                 );
  491.         }
  492.         return [
  493.             'is_valid' => empty($errors),
  494.             'regular_order_id' => $regularOrderId,
  495.             'regular_order_no' => $regularOrder->getOrderNo(),
  496.             'customer_name' => $regularOrder->getName01() . ' ' $regularOrder->getName02(),
  497.             'regular_status' => $regularStatusName,
  498.             'regular_next_delivery_date' => $regularNextDeliveryDate,
  499.             'generated_orders_count' => 0// 受注一括作成漏れのため0
  500.             'errors' => $errors,
  501.         ];
  502.     }
  503.     /**
  504.      * 対象の定期便を取得
  505.      * 
  506.      * @return array
  507.      */
  508.     private function getTargetRegularOrders($todayInclude): array
  509.     {
  510.         /** @var \Plugin\EccubePaymentLite42\Entity\Config $Config */
  511.         $Config $this->entityManager
  512.             ->getRepository(\Plugin\EccubePaymentLite42\Entity\Config::class)
  513.             ->find(1);
  514.         if (!$Config) {
  515.             return [];
  516.         }
  517.         // 対象の定期便の日付を計算
  518.         $deadLineStartDate = new \DateTime('today');
  519.         $deadLineStartDate->modify('- 14 day'); //過去2週間分をチェック
  520.         //本日日付を含める場合(12:20分以降)
  521.         if($todayInclude){
  522.             $deadLineEndDate = new \DateTime('tomorrow');
  523.             $deadLineEndDate->modify('+' $Config->getRegularOrderDeadline() . ' day');
  524.         }else{
  525.         //本日日付を含めない場合(12:20分より前)
  526.             $deadLineEndDate = new \DateTime('tomorrow');
  527.             $deadLineEndDate->modify('+' $Config->getRegularOrderDeadline() - ' day');
  528.         }
  529.         
  530.         // 定期便データの受け取り
  531.         /** @var RegularOrder[] $RegularOrders */
  532.         $RegularOrders $this
  533.             ->regularOrderRepository
  534.             ->getRegularOrdersForCommand(clone $deadLineStartDate, clone $deadLineEndDate);
  535.         return $RegularOrders;
  536.     }
  537.     /**
  538.      * @Route("/%eccube_admin_route%/order/bulk_delete", name="admin_order_bulk_delete", methods={"POST"})
  539.      */
  540.     public function bulkDelete(Request $request)
  541.     {
  542.         $this->isTokenValid();
  543.         $ids $request->get('ids');
  544.         foreach ($ids as $order_id) {
  545.             $Order $this->orderRepository
  546.                 ->find($order_id);
  547.             if ($Order) {
  548.                 $this->entityManager->remove($Order);
  549.                 log_info('受注削除', [$Order->getId()]);
  550.             }
  551.         }
  552.         $this->entityManager->flush();
  553.         $this->addSuccess('admin.common.delete_complete''admin');
  554.         return $this->redirect($this->generateUrl('admin_order', ['resume' => Constant::ENABLED]));
  555.     }
  556.     /**
  557.      * 受注CSVの出力.
  558.      *
  559.      * @Route("/%eccube_admin_route%/order/export/order", name="admin_order_export_order", methods={"GET"})
  560.      *
  561.      * @param Request $request
  562.      *
  563.      * @return StreamedResponse
  564.      */
  565.     public function exportOrder(Request $request)
  566.     {
  567.         $filename 'order_' . (new \DateTime())->format('YmdHis') . '.csv';
  568.         $response $this->exportCsv($requestCsvType::CSV_TYPE_ORDER$filename);
  569.         log_info('受注CSV出力ファイル名', [$filename]);
  570.         return $response;
  571.     }
  572.     /**
  573.      * 配送CSVの出力.
  574.      *
  575.      * @Route("/%eccube_admin_route%/order/export/shipping", name="admin_order_export_shipping", methods={"GET"})
  576.      *
  577.      * @param Request $request
  578.      *
  579.      * @return StreamedResponse
  580.      */
  581.     public function exportShipping(Request $request)
  582.     {
  583.         $filename 'shipping_' . (new \DateTime())->format('YmdHis') . '.csv';
  584.         $response $this->exportCsv($requestCsvType::CSV_TYPE_SHIPPING$filename);
  585.         log_info('配送CSV出力ファイル名', [$filename]);
  586.         return $response;
  587.     }
  588.     /**
  589.      * @param Request $request
  590.      * @param $csvTypeId
  591.      * @param string $fileName
  592.      *
  593.      * @return StreamedResponse
  594.      */
  595.     protected function exportCsv(Request $request$csvTypeId$fileName)
  596.     {
  597.         // タイムアウトを無効にする.
  598.         set_time_limit(0);
  599.         // sql loggerを無効にする.
  600.         $em $this->entityManager;
  601.         $em->getConfiguration()->setSQLLogger(null);
  602.         $response = new StreamedResponse();
  603.         $response->setCallback(function () use ($request$csvTypeId) {
  604.             // CSV種別を元に初期化.
  605.             $this->csvExportService->initCsvType($csvTypeId);
  606.             // 受注データ検索用のクエリビルダを取得.
  607.             $qb $this->csvExportService
  608.                 ->getOrderQueryBuilder($request);
  609.             // ヘッダ行の出力.
  610.             $this->csvExportService->exportHeader();
  611.             // データ行の出力.
  612.             $this->csvExportService->setExportQueryBuilder($qb);
  613.             $this->csvExportService->exportData(function ($entity$csvService) use ($request) {
  614.                 $Csvs $csvService->getCsvs();
  615.                 $Order $entity;
  616.                 $OrderItems $Order->getOrderItems();
  617.                 $Shippings = [];
  618.                 foreach ($OrderItems as $OrderItem) {
  619.                     if (!$OrderItem->isProduct()) {
  620.                         continue;
  621.                     }
  622.                     $Shipping $OrderItem->getShipping();
  623.                     if ($Shipping) {
  624.                         $Shippings[$Shipping->getId()] = $Shipping;
  625.                     }
  626.                 }
  627.                 //=========================
  628.                 //CSVカスタマイズ
  629.                 //伝票毎に出力
  630.                 //商品以外は除外
  631.                 //伝票番号毎にも出力
  632.                 //-------------------------
  633.                 foreach ($Shippings as $Shipping) {
  634.                     $ExportCsvRow = new ExportCsvRow();
  635.                     foreach ($Csvs as $Csv) {
  636.                         // 受注データを検索.
  637.                         $ExportCsvRow->setData($csvService->getData($Csv$Order));
  638.                         $customer $Order->getCustomer();
  639.                         if ($customer == null) {
  640.                             continue;
  641.                         }
  642.                         if ($ExportCsvRow->isDataNull()) {
  643.                             // 受注データにない場合は, 受注明細を検索.
  644.                             $OrderItems =  $Shipping->getOrderItems();
  645.                             $itemData = [];
  646.                             $shippingFlg false;
  647.                             foreach ($OrderItems as $OrderItem) {
  648.                                 // 商品以外は除外
  649.                                 if (!$OrderItem->isProduct()) {
  650.                                     continue;
  651.                                 }
  652.                                 if ($OrderItem->getShipping()) {
  653.                                     if (str_replace('\\\\''\\'$Csv->getEntityName()) == "Eccube\Entity\OrderItem") {
  654.                                         $itemData[] = $csvService->getData($Csv$OrderItem);
  655.                                     } else if (str_replace('\\\\''\\'$Csv->getEntityName()) == "Eccube\Entity\Shipping") {
  656.                                         if ($Csv->getFieldName() == "tracking_number") {
  657.                                             // 伝票番号が複数ある場合は、伝票番号を改行して出力
  658.                                             $shippingFlg true;
  659.                                             $ShippingLists = [];
  660.                                             $trackingNumber $Shipping->getTrackingNumber();
  661.                                             if (!empty($trackingNumber)) {
  662.                                                 $ShippingLists[] = $trackingNumber;
  663.                                             }
  664.                                             $truckingNumbers $Shipping->getTruckingNumbers() ?? [];
  665.                                             foreach ($truckingNumbers as $number) {
  666.                                                 if (!empty($number)) {
  667.                                                     $ShippingLists[] = $number;
  668.                                                 }
  669.                                             }
  670.                                             $ExportCsvRow->setData(implode("\n"$ShippingLists));
  671.                                         } else if ($Csv->getFieldName() == "shipping_delivery_date") {
  672.                                             $shippingFlg true;
  673.                                             $shippingData $csvService->getData($Csv$Shipping);
  674.                                             if (empty($shippingData)) {
  675.                                                 $shippingData "最短日";
  676.                                             } else {
  677.                                                 // 日付の時間部分を削除し、年月日の形式に変換
  678.                                                 $shippingData date_format(new \DateTime($shippingData), 'Y/m/d');
  679.                                             }
  680.                                             $ExportCsvRow->setData($shippingData);
  681.                                         } else {
  682.                                             $shippingFlg true;
  683.                                             $ExportCsvRow->setData($csvService->getData($Csv$Shipping));
  684.                                         }
  685.                                     }
  686.                                 } else {
  687.                                     $itemData[] = $csvService->getData($Csv$OrderItem);
  688.                                 }
  689.                             }
  690.                             if (!$shippingFlg) {
  691.                                 $ExportCsvRow->setData(implode("\n"$itemData));
  692.                             }
  693.                         }
  694.                         //カスタマイズ代理店の追加
  695.                         if (str_replace('\\\\''\\'$Csv->getEntityName()) == "Customize\Entity\Agency") {
  696.                             // 各顧客に代理店名を追加
  697.                             $customer $Order->getCustomer();
  698.                             if ($customer !== null) {
  699.                                 $agencyName $this->customizeCustomerRepository->findAgencyNameByCustomerId($customer->getId());
  700.                                 if ($agencyName) {
  701.                                     $agencyNameText $agencyName;
  702.                                 } else {
  703.                                     $agencyNameText "";
  704.                                 }
  705.                                 $ExportCsvRow->setData($agencyNameText);
  706.                             } else {
  707.                                 $agencyNameText "";
  708.                                 $ExportCsvRow->setData($agencyNameText);
  709.                             }
  710.                         }
  711.                         // カスタマイズ工場出荷日の追加
  712.                         if (str_replace('\\\\''\\'$Csv->getEntityName()) == "Customize\Entity\CSV") {
  713.                             // 本日の日付をセット
  714.                             $today = new \DateTime();
  715.                             $formattedDate $today->format('Y/m/d');
  716.                             $ExportCsvRow->setData($formattedDate);
  717.                         }
  718.                         $event = new EventArgs(
  719.                             [
  720.                                 'csvService' => $csvService,
  721.                                 'Csv' => $Csv,
  722.                                 // 'OrderItem' => $OrderItem,
  723.                                 'ExportCsvRow' => $ExportCsvRow,
  724.                             ],
  725.                             $request
  726.                         );
  727.                         $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_ORDER_CSV_EXPORT_ORDER);
  728.                         $ExportCsvRow->pushData();
  729.                     }
  730.                     // 伝票番号が複数ある場合は、伝票番号を改行して出力
  731.                     // $ShippingLists = [];
  732.                     // $trackingNumber = $Shipping->getTrackingNumber();
  733.                     // if (!empty($trackingNumber)) {
  734.                     //     $ShippingLists[] = $trackingNumber;
  735.                     // }
  736.                     // $truckingNumbers = $Shipping->getTruckingNumbers() ?? [];
  737.                     // foreach ($truckingNumbers as $number) {
  738.                     //     if (!empty($number)) {
  739.                     //         $ShippingLists[] = $number;
  740.                     //     }
  741.                     // }
  742.                     // CSV出力項目と合致するデータを取得.
  743.                     // $row[] = number_format(memory_get_usage(true));
  744.                     // 出力.
  745.                     $csvService->fputcsv($ExportCsvRow->getRow());
  746.                 }
  747.             });
  748.         });
  749.         $response->headers->set('Content-Type''application/octet-stream');
  750.         $response->headers->set('Content-Disposition''attachment; filename=' $fileName);
  751.         return $response;
  752.     }
  753.     /**
  754.      * Update to order status
  755.      *
  756.      * @Route("/%eccube_admin_route%/shipping/{id}/order_status", requirements={"id" = "\d+"}, name="admin_shipping_update_order_status", methods={"PUT"})
  757.      *
  758.      * @param Request $request
  759.      * @param Shipping $Shipping
  760.      *
  761.      * @return \Symfony\Component\HttpFoundation\JsonResponse
  762.      */
  763.     public function updateOrderStatus(Request $requestShipping $Shipping)
  764.     {
  765.         if (!($request->isXmlHttpRequest() && $this->isTokenValid())) {
  766.             return $this->json(['status' => 'NG'], 400);
  767.         }
  768.         $Order $Shipping->getOrder();
  769.         $OrderStatus $this->entityManager->find(OrderStatus::class, $request->get('order_status'));
  770.         if (!$OrderStatus) {
  771.             return $this->json(['status' => 'NG'], 400);
  772.         }
  773.         $result = [];
  774.         // 発送済みステータスへの変更を許可する条件
  775.         if ($OrderStatus->getId() == OrderStatus::DELIVERED) {
  776.             // Null チェック
  777.             $orderStatusId $Order->getOrderStatus() ? $Order->getOrderStatus()->getId() : null;
  778.             $paymentStatusId $Order->getPaymentStatus() ? $Order->getPaymentStatus()->getId() : null;
  779.             $trackingNumber $Shipping->getTrackingNumber();
  780.             $truckingNumbers $Shipping->getTruckingNumbers() ? array_filter($Shipping->getTruckingNumbers()) : [];
  781.             $FactoryShipmentStatus $Order->getFactoryShipmentStatus();
  782.             // Check if $trackingNumber is a 12-digit number
  783.             $isValidTrackingNumber preg_match('/^\d{12}$/'$trackingNumber) === 1;
  784.             // Check if there is any 12-digit number in $truckingNumbers array
  785.             $isValidTruckingNumbers array_filter($truckingNumbers, function ($num) {
  786.                 return preg_match('/^\d{12}$/'$num) === 1;
  787.             });
  788.             if ($orderStatusId != OrderStatus::PAID) {
  789.                 $from $Order->getOrderStatus()->getName();
  790.                 $to $OrderStatus->getName();
  791.                 $result = ['message' => trans('%name%:受注ステータスが入金済でないため %to% にステータス変更できません', [
  792.                     '%name%' => $Shipping->getId(),
  793.                     '%to%' => $to,
  794.                 ])];
  795.                 return $this->json(array_merge(['status' => 'OK'], $result));
  796.             } else if ($paymentStatusId != PaymentStatus::CHARGED) {
  797.                 $from $Order->getOrderStatus()->getName();
  798.                 $to $OrderStatus->getName();
  799.                 $result = ['message' => trans('%name%:決済ステータスが課金済でないため %to% にステータス変更できません', [
  800.                     '%name%' => $Shipping->getId(),
  801.                     '%to%' => $to,
  802.                 ])];
  803.                 return $this->json(array_merge(['status' => 'OK'], $result));
  804.             } else if (!$isValidTrackingNumber && empty($isValidTruckingNumbers)) {
  805.                 //有効な伝票があるかどうか
  806.                 $from $Order->getOrderStatus()->getName();
  807.                 $to $OrderStatus->getName();
  808.                 $result = ['message' => trans('%name%:有効な伝票番号がないため %to% にステータス変更できません', [
  809.                     '%name%' => $Shipping->getId(),
  810.                     '%to%' => $to,
  811.                 ])];
  812.                 return $this->json(array_merge(['status' => 'OK'], $result));
  813.             } else if ($FactoryShipmentStatus != 2) {
  814.                 //工場出荷ステータスが登録済みではない場合
  815.                 $from $Order->getOrderStatus()->getName();
  816.                 $to $OrderStatus->getName();
  817.                 $result = ['message' => trans('%name%:工場出荷未登録のため %to% にステータス変更できません', [
  818.                     '%name%' => $Shipping->getId(),
  819.                     '%to%' => $to,
  820.                 ])];
  821.                 return $this->json(array_merge(['status' => 'OK'], $result));
  822.             }
  823.         }
  824.         try {
  825.             if ($Order->getOrderStatus()->getId() == $OrderStatus->getId()) {
  826.                 log_info('対応状況一括変更スキップ');
  827.                 $result = ['message' => trans('admin.order.skip_change_status', ['%name%' => $Shipping->getId()])];
  828.             } else {
  829.                 if ($this->orderStateMachine->can($Order$OrderStatus)) {
  830.                     if ($OrderStatus->getId() == OrderStatus::DELIVERED) {
  831.                         if (!$Shipping->isShipped()) {
  832.                             $Shipping->setShippingDate(new \DateTime());
  833.                             //=============
  834.                             //プラン移行
  835.                             //=============
  836.                             $this->MigrationPlanAndSendMail($Order);
  837.                         }
  838.                         $allShipped true;
  839.                         foreach ($Order->getShippings() as $Ship) {
  840.                             if (!$Ship->isShipped()) {
  841.                                 $allShipped false;
  842.                                 break;
  843.                             }
  844.                         }
  845.                         if ($allShipped) {
  846.                             $this->orderStateMachine->apply($Order$OrderStatus);
  847.                         }
  848.                     } else if ($OrderStatus->getId() == 10) {
  849.                         $this->mailService->sendCancelExcessMail($Order);
  850.                     } else {
  851.                         $this->orderStateMachine->apply($Order$OrderStatus);
  852.                     }
  853.                     /**カスタマイズ 出荷チェックボックスのメール送信はフラグを反転させる */
  854.                     if ($request->get('notificationMail')) { // for SimpleStatusUpdate
  855.                         if ($OrderStatus->getId() == OrderStatus::DELIVERED) {
  856.                             $result['mail'] = false;
  857.                         } else {
  858.                             $result['mail'] = true;
  859.                             $this->mailService->sendShippingNotifyMail($Shipping);
  860.                             $Shipping->setMailSendDate(new \DateTime());
  861.                         }
  862.                     } else {
  863.                         if ($OrderStatus->getId() == OrderStatus::DELIVERED) {
  864.                             $result['mail'] = true;
  865.                             $this->mailService->sendShippingNotifyMail($Shipping);
  866.                             $Shipping->setMailSendDate(new \DateTime());
  867.                         } else {
  868.                             $result['mail'] = false;
  869.                         }
  870.                     }
  871.                     // 対応中・キャンセルの更新時は商品在庫を増減させているので商品情報を更新
  872.                     if ($OrderStatus->getId() == OrderStatus::IN_PROGRESS || $OrderStatus->getId() == OrderStatus::CANCEL) {
  873.                         foreach ($Order->getOrderItems() as $OrderItem) {
  874.                             $ProductClass $OrderItem->getProductClass();
  875.                             if ($OrderItem->isProduct() && !$ProductClass->isStockUnlimited()) {
  876.                                 $this->entityManager->persist($ProductClass);
  877.                                 $this->entityManager->flush();
  878.                                 $ProductStock $this->productStockRepository->findOneBy(['ProductClass' => $ProductClass]);
  879.                                 $this->entityManager->persist($ProductStock);
  880.                                 $this->entityManager->flush();
  881.                             }
  882.                         }
  883.                     }
  884.                     $this->entityManager->persist($Order);
  885.                     $this->entityManager->persist($Shipping);
  886.                     $this->entityManager->flush();
  887.                     // 会員の場合、購入回数、購入金額などを更新
  888.                     if ($Customer $Order->getCustomer()) {
  889.                         $this->orderRepository->updateOrderSummary($Customer);
  890.                         $this->entityManager->persist($Customer);
  891.                         $this->entityManager->flush();
  892.                     }
  893.                 } else {
  894.                     $from $Order->getOrderStatus()->getName();
  895.                     $to $OrderStatus->getName();
  896.                     $result = ['message' => trans('admin.order.failed_to_change_status', [
  897.                         '%name%' => $Shipping->getId(),
  898.                         '%from%' => $from,
  899.                         '%to%' => $to,
  900.                     ])];
  901.                 }
  902.                 log_info('対応状況一括変更処理完了', [$Order->getId()]);
  903.             }
  904.         } catch (\Exception $e) {
  905.             log_error('予期しないエラーです', [$e->getMessage()]);
  906.             return $this->json(['status' => 'NG'], 500);
  907.         }
  908.         return $this->json(array_merge(['status' => 'OK'], $result));
  909.     }
  910.     /**
  911.      * カスタマイズ
  912.      * 導入プランの名称変更と通知メールの送信
  913.      * 初回フラグの除去
  914.      * @param $Order
  915.      */
  916.     private function MigrationPlanAndSendMail($Order)
  917.     {
  918.         //導入プランの商品タグ
  919.         $introductionPlanProductTagId 1;
  920.         //移行フラグ
  921.         $flgMigration false;
  922.         // 定期受注データの取得
  923.         $RegularOrder $Order->getRegularOrder();
  924.         //定期受注カウントが3回の場合
  925.         if ($RegularOrder && $RegularOrder->getRegularOrderCount() == 3) {
  926.             //定期受注の中に導入プランがあるか確認
  927.             foreach ($RegularOrder->getRegularProductOrderItems() as $RegularOrderItem) {
  928.                 // 商品オブジェクトを取得
  929.                 $Product $RegularOrderItem->getProductClass()->getProduct();
  930.                 // 商品に関連付けられたタグを取得
  931.                 $ProductTags $Product->getProductTag();
  932.                 foreach ($ProductTags as $ProductTag) {
  933.                     // Tag エンティティを取得
  934.                     $Tag $ProductTag->getTag();
  935.                     // タグのIDが4であればフラグをセット
  936.                     if ($Tag->getId() == $introductionPlanProductTagId) {
  937.                         //フラグの有効化
  938.                         $flgMigration true;
  939.                         if ($flgMigration) {
  940.                             // 商品名を変更する
  941.                             $RegularOrderItem->setProductName('【定期便】詰め替え用 BIB10L(100ppm)');
  942.                             $this->entityManager->persist($RegularOrderItem);
  943.                             //プラン移行通知メール送信
  944.                             $this
  945.                                 ->migrationPlanNoticeMailService
  946.                                 ->sendMail($RegularOrder);
  947.                             //フラグの無効化
  948.                             $flgMigration false;
  949.                         }
  950.                     }
  951.                 }
  952.             }
  953.         } else if ($RegularOrder && $RegularOrder->getRegularOrderCount() == 1) {
  954.             //初回発送後は休止ができるようにフラグを変更
  955.             $RegularOrder->setRegularFirstFlag(1);
  956.         } else {
  957.         }
  958.     }
  959.     /**
  960.      * Update to Tracking number.
  961.      *
  962.      * @Route("/%eccube_admin_route%/shipping/{id}/tracking_number", requirements={"id" = "\d+"}, name="admin_shipping_update_tracking_number", methods={"PUT"})
  963.      *
  964.      * @param Request $request
  965.      * @param Shipping $shipping
  966.      *
  967.      * @return Response
  968.      */
  969.     public function updateTrackingNumber(Request $requestShipping $shipping)
  970.     {
  971.         if (!($request->isXmlHttpRequest() && $this->isTokenValid())) {
  972.             return $this->json(['status' => 'NG'], 400);
  973.         }
  974.         $trackingNumber mb_convert_kana($request->get('tracking_number'), 'a''utf-8');
  975.         /** @var \Symfony\Component\Validator\ConstraintViolationListInterface $errors */
  976.         $errors $this->validator->validate(
  977.             $trackingNumber,
  978.             [
  979.                 new Assert\Length(['max' => $this->eccubeConfig['eccube_stext_len']]),
  980.                 new Assert\Regex(
  981.                     ['pattern' => '/^[0-9a-zA-Z-]+$/u''message' => trans('admin.order.tracking_number_error')]
  982.                 ),
  983.             ]
  984.         );
  985.         if ($errors->count() != 0) {
  986.             log_info('送り状番号入力チェックエラー');
  987.             $messages = [];
  988.             /** @var \Symfony\Component\Validator\ConstraintViolationInterface $error */
  989.             foreach ($errors as $error) {
  990.                 $messages[] = $error->getMessage();
  991.             }
  992.             return $this->json(['status' => 'NG''messages' => $messages], 400);
  993.         }
  994.         try {
  995.             $shipping->setTrackingNumber($trackingNumber);
  996.             // 値が入っているかを確認
  997.             $filledCount 0;
  998.             // 全てのトラッキング番号が12桁か確認するフラグ
  999.             $all12Digits true;
  1000.             // 現在のトラッキング番号が12桁か確認
  1001.             if (strlen($trackingNumber) === 12) {
  1002.                 $filledCount++;
  1003.             } else {
  1004.                 $all12Digits false;
  1005.             }
  1006.             // 2個目以降のトラッキング番号が配列であることを前提とします
  1007.             $trackingNumbers $shipping->getTruckingNumbers();
  1008.             if (is_array($trackingNumbers)) {
  1009.                 foreach ($trackingNumbers as $number) {
  1010.                     if (!empty($number)) {
  1011.                         if (strlen($number) === 12) {
  1012.                             $filledCount++;
  1013.                         } else {
  1014.                             $all12Digits false;
  1015.                         }
  1016.                     }
  1017.                 }
  1018.             }
  1019.             $this->entityManager->persist($shipping);
  1020.             $this->entityManager->flush();
  1021.             log_info('送り状番号変更処理完了', [$shipping->getId()]);
  1022.             $message = [
  1023.                 'status' => 'OK',
  1024.                 'shipping_id' => $shipping->getId(),
  1025.                 'tracking_number' => $trackingNumber,
  1026.                 'filled_count' => $filledCount// 有効なトラッキング番号の数
  1027.                 'all12Digits' => $all12Digits // 全てが12桁であるかどうかをフラグとして返す
  1028.             ];
  1029.             return $this->json($message);
  1030.         } catch (\Exception $e) {
  1031.             log_error('予期しないエラー', [$e->getMessage()]);
  1032.             return $this->json(['status' => 'NG'], 500);
  1033.         }
  1034.     }
  1035.     /**
  1036.      * Update to Tracking number 2.
  1037.      *
  1038.      * @Route("/%eccube_admin_route%/shipping/{id}/tracking_number_2", requirements={"id" = "\d+"}, name="admin_shipping_update_tracking_number_2", methods={"PUT"})
  1039.      *
  1040.      * @param Request $request
  1041.      * @param Shipping $shipping
  1042.      *
  1043.      * @return Response
  1044.      */
  1045.     public function updateTrackingNumber2(Request $requestShipping $shipping)
  1046.     {
  1047.         if (!($request->isXmlHttpRequest() && $this->isTokenValid())) {
  1048.             return $this->json(['status' => 'NG'], 400);
  1049.         }
  1050.         $trackingNumber mb_convert_kana($request->get('tracking_number'), 'a''utf-8');
  1051.         /** @var \Symfony\Component\Validator\ConstraintViolationListInterface $errors */
  1052.         $errors $this->validator->validate(
  1053.             $trackingNumber,
  1054.             [
  1055.                 new Assert\Length(['max' => $this->eccubeConfig['eccube_stext_len']]),
  1056.                 new Assert\Regex(
  1057.                     ['pattern' => '/^[0-9a-zA-Z-]+$/u''message' => trans('admin.order.tracking_number_error')]
  1058.                 ),
  1059.             ]
  1060.         );
  1061.         if ($errors->count() != 0) {
  1062.             log_info('送り状番号入力チェックエラー');
  1063.             $messages = [];
  1064.             /** @var \Symfony\Component\Validator\ConstraintViolationInterface $error */
  1065.             foreach ($errors as $error) {
  1066.                 $messages[] = $error->getMessage();
  1067.             }
  1068.             return $this->json(['status' => 'NG''messages' => $messages], 400);
  1069.         }
  1070.         try {
  1071.             $shipping->setTrackingNumber2($trackingNumber);
  1072.             $this->entityManager->persist($shipping);
  1073.             $this->entityManager->flush();
  1074.             log_info('送り状番号変更処理完了', [$shipping->getId()]);
  1075.             $message = ['status' => 'OK''shipping_id' => $shipping->getId(), 'tracking_number' => $trackingNumber];
  1076.             return $this->json($message);
  1077.         } catch (\Exception $e) {
  1078.             log_error('予期しないエラー', [$e->getMessage()]);
  1079.             return $this->json(['status' => 'NG'], 500);
  1080.         }
  1081.     }
  1082.     /**
  1083.      * Update to Tracking number 2.
  1084.      *
  1085.      * @Route("/%eccube_admin_route%/shipping/{id}/tracking_number_3", requirements={"id" = "\d+"}, name="admin_shipping_update_tracking_number_3", methods={"PUT"})
  1086.      *
  1087.      * @param Request $request
  1088.      * @param Shipping $shipping
  1089.      *
  1090.      * @return Response
  1091.      */
  1092.     public function updateTrackingNumber3(Request $requestShipping $shipping)
  1093.     {
  1094.         if (!($request->isXmlHttpRequest() && $this->isTokenValid())) {
  1095.             return $this->json(['status' => 'NG'], 400);
  1096.         }
  1097.         $trackingNumber mb_convert_kana($request->get('tracking_number'), 'a''utf-8');
  1098.         /** @var \Symfony\Component\Validator\ConstraintViolationListInterface $errors */
  1099.         $errors $this->validator->validate(
  1100.             $trackingNumber,
  1101.             [
  1102.                 new Assert\Length(['max' => $this->eccubeConfig['eccube_stext_len']]),
  1103.                 new Assert\Regex(
  1104.                     ['pattern' => '/^[0-9a-zA-Z-]+$/u''message' => trans('admin.order.tracking_number_error')]
  1105.                 ),
  1106.             ]
  1107.         );
  1108.         if ($errors->count() != 0) {
  1109.             log_info('送り状番号入力チェックエラー');
  1110.             $messages = [];
  1111.             /** @var \Symfony\Component\Validator\ConstraintViolationInterface $error */
  1112.             foreach ($errors as $error) {
  1113.                 $messages[] = $error->getMessage();
  1114.             }
  1115.             return $this->json(['status' => 'NG''messages' => $messages], 400);
  1116.         }
  1117.         try {
  1118.             $shipping->setTrackingNumber3($trackingNumber);
  1119.             $this->entityManager->persist($shipping);
  1120.             $this->entityManager->flush();
  1121.             log_info('送り状番号変更処理完了', [$shipping->getId()]);
  1122.             $message = ['status' => 'OK''shipping_id' => $shipping->getId(), 'tracking_number' => $trackingNumber];
  1123.             return $this->json($message);
  1124.         } catch (\Exception $e) {
  1125.             log_error('予期しないエラー', [$e->getMessage()]);
  1126.             return $this->json(['status' => 'NG'], 500);
  1127.         }
  1128.     }
  1129.     /**
  1130.      * Update to Tracking number 4.
  1131.      *
  1132.      * @Route("/%eccube_admin_route%/shipping/{id}/tracking_number_4", requirements={"id" = "\d+"}, name="admin_shipping_update_tracking_number_4", methods={"PUT"})
  1133.      *
  1134.      * @param Request $request
  1135.      * @param Shipping $shipping
  1136.      *
  1137.      * @return Response
  1138.      */
  1139.     public function updateTrackingNumber4(Request $requestShipping $shipping)
  1140.     {
  1141.         if (!($request->isXmlHttpRequest() && $this->isTokenValid())) {
  1142.             return $this->json(['status' => 'NG'], 400);
  1143.         }
  1144.         $trackingNumber mb_convert_kana($request->get('tracking_number'), 'a''utf-8');
  1145.         /** @var \Symfony\Component\Validator\ConstraintViolationListInterface $errors */
  1146.         $errors $this->validator->validate(
  1147.             $trackingNumber,
  1148.             [
  1149.                 new Assert\Length(['max' => $this->eccubeConfig['eccube_stext_len']]),
  1150.                 new Assert\Regex(
  1151.                     ['pattern' => '/^[0-9a-zA-Z-]+$/u''message' => trans('admin.order.tracking_number_error')]
  1152.                 ),
  1153.             ]
  1154.         );
  1155.         if ($errors->count() != 0) {
  1156.             log_info('送り状番号入力チェックエラー');
  1157.             $messages = [];
  1158.             /** @var \Symfony\Component\Validator\ConstraintViolationInterface $error */
  1159.             foreach ($errors as $error) {
  1160.                 $messages[] = $error->getMessage();
  1161.             }
  1162.             return $this->json(['status' => 'NG''messages' => $messages], 400);
  1163.         }
  1164.         try {
  1165.             $shipping->setTrackingNumber4($trackingNumber);
  1166.             $this->entityManager->persist($shipping);
  1167.             $this->entityManager->flush();
  1168.             log_info('送り状番号変更処理完了', [$shipping->getId()]);
  1169.             $message = ['status' => 'OK''shipping_id' => $shipping->getId(), 'tracking_number' => $trackingNumber];
  1170.             return $this->json($message);
  1171.         } catch (\Exception $e) {
  1172.             log_error('予期しないエラー', [$e->getMessage()]);
  1173.             return $this->json(['status' => 'NG'], 500);
  1174.         }
  1175.     }
  1176.     /**
  1177.      * Update to Tracking number 5.
  1178.      *
  1179.      * @Route("/%eccube_admin_route%/shipping/{id}/tracking_number_5", requirements={"id" = "\d+"}, name="admin_shipping_update_tracking_number_5", methods={"PUT"})
  1180.      *
  1181.      * @param Request $request
  1182.      * @param Shipping $shipping
  1183.      *
  1184.      * @return Response
  1185.      */
  1186.     public function updateTrackingNumber5(Request $requestShipping $shipping)
  1187.     {
  1188.         if (!($request->isXmlHttpRequest() && $this->isTokenValid())) {
  1189.             return $this->json(['status' => 'NG'], 400);
  1190.         }
  1191.         $trackingNumber mb_convert_kana($request->get('tracking_number'), 'a''utf-8');
  1192.         /** @var \Symfony\Component\Validator\ConstraintViolationListInterface $errors */
  1193.         $errors $this->validator->validate(
  1194.             $trackingNumber,
  1195.             [
  1196.                 new Assert\Length(['max' => $this->eccubeConfig['eccube_stext_len']]),
  1197.                 new Assert\Regex(
  1198.                     ['pattern' => '/^[0-9a-zA-Z-]+$/u''message' => trans('admin.order.tracking_number_error')]
  1199.                 ),
  1200.             ]
  1201.         );
  1202.         if ($errors->count() != 0) {
  1203.             log_info('送り状番号入力チェックエラー');
  1204.             $messages = [];
  1205.             /** @var \Symfony\Component\Validator\ConstraintViolationInterface $error */
  1206.             foreach ($errors as $error) {
  1207.                 $messages[] = $error->getMessage();
  1208.             }
  1209.             return $this->json(['status' => 'NG''messages' => $messages], 400);
  1210.         }
  1211.         try {
  1212.             $shipping->setTrackingNumber5($trackingNumber);
  1213.             $this->entityManager->persist($shipping);
  1214.             $this->entityManager->flush();
  1215.             log_info('送り状番号変更処理完了', [$shipping->getId()]);
  1216.             $message = ['status' => 'OK''shipping_id' => $shipping->getId(), 'tracking_number' => $trackingNumber];
  1217.             return $this->json($message);
  1218.         } catch (\Exception $e) {
  1219.             log_error('予期しないエラー', [$e->getMessage()]);
  1220.             return $this->json(['status' => 'NG'], 500);
  1221.         }
  1222.     }
  1223.     /**
  1224.      * 複数のトラッキングナンバーを配列として保存
  1225.      * Update to Tracking numbers.
  1226.      *
  1227.      * @Route("/%eccube_admin_route%/shipping/{id}/tracking_numbers", requirements={"id" = "\d+"}, name="admin_shipping_update_tracking_numbers", methods={"PUT"})
  1228.      *
  1229.      * @param Request $request
  1230.      * @param Shipping $shipping
  1231.      *
  1232.      * @return Response
  1233.      */
  1234.     public function updateTrackingNumbers(Request $requestShipping $shipping)
  1235.     {
  1236.         if (!($request->isXmlHttpRequest() && $this->isTokenValid())) {
  1237.             return $this->json(['status' => 'NG'], 400);
  1238.         }
  1239.         //2個目以降の複数
  1240.         $trackingNumbers $request->get('tracking_numbers');
  1241.         //一個目
  1242.         $trackingDefaultNumber mb_convert_kana($request->get('trackingDefaultNumber'), 'a''utf-8');
  1243.         //==============================
  1244.         //1個目のトラッキング番号の更新
  1245.         //------------------------------
  1246.         // 1個目のトラッキング番号のバリデーション
  1247.         $errors $this->validator->validate(
  1248.             $trackingDefaultNumber,
  1249.             [
  1250.                 new Assert\Length(['max' => $this->eccubeConfig['eccube_stext_len']]),
  1251.                 new Assert\Regex(
  1252.                     ['pattern' => '/^[0-9a-zA-Z-]+$/u''message' => trans('admin.order.tracking_number_error')]
  1253.                 ),
  1254.             ]
  1255.         );
  1256.         if ($errors->count() != 0) {
  1257.             log_info('送り状番号入力チェックエラー');
  1258.             $messages = [];
  1259.             foreach ($errors as $error) {
  1260.                 $messages[] = $error->getMessage();
  1261.             }
  1262.             return $this->json(['status' => 'NG''messages' => $messages], 400);
  1263.         }
  1264.         // 複数のトラッキング番号のバリデーション
  1265.         foreach ($trackingNumbers as $trackingNumber) {
  1266.             $trackingNumber mb_convert_kana($trackingNumber'a''utf-8');
  1267.             $errors $this->validator->validate(
  1268.                 $trackingNumber,
  1269.                 [
  1270.                     new Assert\Length(['max' => $this->eccubeConfig['eccube_stext_len']]),
  1271.                     new Assert\Regex(
  1272.                         ['pattern' => '/^[0-9a-zA-Z-]+$/u''message' => trans('admin.order.tracking_number_error')]
  1273.                     ),
  1274.                 ]
  1275.             );
  1276.             if ($errors->count() != 0) {
  1277.                 log_info('送り状番号入力チェックエラー');
  1278.                 $messages = [];
  1279.                 foreach ($errors as $error) {
  1280.                     $messages[] = $error->getMessage();
  1281.                 }
  1282.                 return $this->json(['status' => 'NG''messages' => $messages], 400);
  1283.             }
  1284.         }
  1285.         try {
  1286.             // 全てのトラッキング番号が12桁か確認するフラグ
  1287.             $all12Digits true;
  1288.             // 1個目のトラッキング番号を個別に保存
  1289.             $shipping->setTrackingNumber($trackingDefaultNumber);
  1290.             // 2個目以降のトラッキング番号を配列として保存
  1291.             $shipping->setTruckingNumbers($trackingNumbers);
  1292.             $this->entityManager->persist($shipping);
  1293.             $this->entityManager->flush();
  1294.             // トラッキング番号のカウント
  1295.             $filledCount 0;
  1296.             //1個目ののトラッキング番号
  1297.             $updateTrackingDefaultNumber $shipping->getTrackingNumber();
  1298.             if (strlen($updateTrackingDefaultNumber) === 12) {
  1299.                 $filledCount++;
  1300.             } else {
  1301.                 //12桁でない場合はfalse
  1302.                 $all12Digits false;
  1303.             }
  1304.             // 2個目以降のトラッキング番号が配列であることを前提
  1305.             $updateTrackingNumbers $shipping->getTruckingNumbers();
  1306.             if (is_array($updateTrackingNumbers)) {
  1307.                 foreach ($updateTrackingNumbers as $number) {
  1308.                     if (!empty($number)) {
  1309.                         if (strlen($number) === 12) {
  1310.                             $filledCount++;
  1311.                         } else {
  1312.                             //12桁でない場合はfalse
  1313.                             $all12Digits false;
  1314.                         }
  1315.                     }
  1316.                 }
  1317.             }
  1318.             log_info('送り状番号変更処理完了', [$shipping->getId()]);
  1319.             $message = [
  1320.                 'status' => 'OK',
  1321.                 'shipping_id' => $shipping->getId(),
  1322.                 'trackingDefaultNumber' => $trackingDefaultNumber,
  1323.                 'trackingNumbers' => $trackingNumbers,
  1324.                 'filledCount' => $filledCount// 有効なトラッキング番号の数
  1325.                 'all12Digits' => $all12Digits // 全てが12桁であるかどうかをフラグとして返す
  1326.             ];
  1327.             return $this->json($message);
  1328.         } catch (\Exception $e) {
  1329.             log_error('予期しないエラー', [$e->getMessage()]);
  1330.             return $this->json(['status' => 'NG'], 500);
  1331.         }
  1332.     }
  1333.     /**
  1334.      * @Route("/%eccube_admin_route%/order/export/pdf", name="admin_order_export_pdf", methods={"GET", "POST"})
  1335.      * @Template("@admin/Order/order_pdf.twig")
  1336.      *
  1337.      * @param Request $request
  1338.      *
  1339.      * @return array|RedirectResponse
  1340.      */
  1341.     public function exportPdf(Request $request)
  1342.     {
  1343.         // requestから出荷番号IDの一覧を取得する.
  1344.         $ids $request->get('ids', []);
  1345.         if (count($ids) == 0) {
  1346.             $this->addError('admin.order.delivery_note_parameter_error''admin');
  1347.             log_info('The Order cannot found!');
  1348.             return $this->redirectToRoute('admin_order');
  1349.         }
  1350.         /** @var OrderPdf $OrderPdf */
  1351.         $OrderPdf $this->orderPdfRepository->find($this->getUser());
  1352.         if (!$OrderPdf) {
  1353.             $OrderPdf = new OrderPdf();
  1354.             $OrderPdf
  1355.                 ->setTitle(trans('admin.order.delivery_note_title__default'))
  1356.                 ->setMessage1(trans('admin.order.delivery_note_message__default1'))
  1357.                 ->setMessage2(trans('admin.order.delivery_note_message__default2'))
  1358.                 ->setMessage3(trans('admin.order.delivery_note_message__default3'));
  1359.         }
  1360.         /**
  1361.          * @var FormBuilder
  1362.          */
  1363.         $builder $this->formFactory->createBuilder(OrderPdfType::class, $OrderPdf);
  1364.         /* @var \Symfony\Component\Form\Form $form */
  1365.         $form $builder->getForm();
  1366.         // Formへの設定
  1367.         $form->get('ids')->setData(implode(','$ids));
  1368.         return [
  1369.             'form' => $form->createView(),
  1370.         ];
  1371.     }
  1372.     /**
  1373.      * @Route("/%eccube_admin_route%/order/export/pdf/download", name="admin_order_pdf_download", methods={"POST"})
  1374.      * @Template("@admin/Order/order_pdf.twig")
  1375.      *
  1376.      * @param Request $request
  1377.      *
  1378.      * @return Response
  1379.      */
  1380.     public function exportPdfDownload(Request $requestOrderPdfService $orderPdfService)
  1381.     {
  1382.         /**
  1383.          * @var FormBuilder
  1384.          */
  1385.         $builder $this->formFactory->createBuilder(OrderPdfType::class);
  1386.         /* @var \Symfony\Component\Form\Form $form */
  1387.         $form $builder->getForm();
  1388.         $form->handleRequest($request);
  1389.         // Validation
  1390.         if (!$form->isValid()) {
  1391.             log_info('The parameter is invalid!');
  1392.             return $this->render('@admin/Order/order_pdf.twig', [
  1393.                 'form' => $form->createView(),
  1394.             ]);
  1395.         }
  1396.         $arrData $form->getData();
  1397.         // 購入情報からPDFを作成する
  1398.         $status $orderPdfService->makePdf($arrData);
  1399.         // 異常終了した場合の処理
  1400.         if (!$status) {
  1401.             $this->addError('admin.order.export.pdf.download.failure''admin');
  1402.             log_info('Unable to create pdf files! Process have problems!');
  1403.             return $this->render('@admin/Order/order_pdf.twig', [
  1404.                 'form' => $form->createView(),
  1405.             ]);
  1406.         }
  1407.         // TCPDF::Outputを実行するとプロパティが初期化されるため、ファイル名を事前に取得しておく
  1408.         $pdfFileName $orderPdfService->getPdfFileName();
  1409.         // ダウンロードする
  1410.         $response = new Response(
  1411.             $orderPdfService->outputPdf(),
  1412.             200,
  1413.             ['content-type' => 'application/pdf']
  1414.         );
  1415.         $downloadKind $form->get('download_kind')->getData();
  1416.         // レスポンスヘッダーにContent-Dispositionをセットし、ファイル名を指定
  1417.         if ($downloadKind == 1) {
  1418.             $response->headers->set('Content-Disposition''attachment; filename="' $pdfFileName '"');
  1419.         } else {
  1420.             $response->headers->set('Content-Disposition''inline; filename="' $pdfFileName '"');
  1421.         }
  1422.         log_info('OrderPdf download success!', ['Order ID' => implode(','$request->get('ids', []))]);
  1423.         $isDefault = isset($arrData['default']) ? $arrData['default'] : false;
  1424.         if ($isDefault) {
  1425.             // Save input to DB
  1426.             $arrData['admin'] = $this->getUser();
  1427.             $this->orderPdfRepository->save($arrData);
  1428.         }
  1429.         return $response;
  1430.     }
  1431. }