src/Eccube/Form/Type/Admin/OrderType.php line 209

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\Form\Type\Admin;
  13. use Doctrine\Common\Collections\ArrayCollection;
  14. use Doctrine\ORM\EntityManagerInterface;
  15. use Eccube\Common\EccubeConfig;
  16. use Eccube\Entity\Master\OrderStatus;
  17. use Eccube\Entity\Order;
  18. use Eccube\Entity\Payment;
  19. use Eccube\Form\DataTransformer;
  20. use Eccube\Form\Type\AddressType;
  21. use Eccube\Form\Type\KanaType;
  22. use Eccube\Form\Type\NameType;
  23. use Eccube\Form\Type\PhoneNumberType;
  24. use Eccube\Form\Type\PostalType;
  25. use Eccube\Form\Type\PriceType;
  26. use Eccube\Form\Validator\Email;
  27. use Eccube\Repository\Master\OrderStatusRepository;
  28. use Eccube\Service\OrderStateMachine;
  29. use Symfony\Bridge\Doctrine\Form\Type\EntityType;
  30. use Symfony\Component\Form\AbstractType;
  31. use Symfony\Component\Form\Extension\Core\Type\CollectionType;
  32. use Symfony\Component\Form\Extension\Core\Type\EmailType;
  33. use Symfony\Component\Form\Extension\Core\Type\HiddenType;
  34. use Symfony\Component\Form\Extension\Core\Type\NumberType;
  35. use Symfony\Component\Form\Extension\Core\Type\TextareaType;
  36. use Symfony\Component\Form\Extension\Core\Type\TextType;
  37. use Symfony\Component\Form\FormBuilderInterface;
  38. use Symfony\Component\Form\FormError;
  39. use Symfony\Component\Form\FormEvent;
  40. use Symfony\Component\Form\FormEvents;
  41. use Symfony\Component\OptionsResolver\OptionsResolver;
  42. use Symfony\Component\Validator\Constraints as Assert;
  43. class OrderType extends AbstractType
  44. {
  45.     /**
  46.      * @var EntityManagerInterface
  47.      */
  48.     protected $entityManager;
  49.     /**
  50.      * @var EccubeConfig
  51.      */
  52.     protected $eccubeConfig;
  53.     /**
  54.      * @var OrderStateMachine
  55.      */
  56.     protected $orderStateMachine;
  57.     /**
  58.      * @var OrderStatusRepository
  59.      */
  60.     protected $orderStatusRepository;
  61.     /**
  62.      * OrderType constructor.
  63.      *
  64.      * @param EntityManagerInterface $entityManager
  65.      * @param EccubeConfig $eccubeConfig
  66.      * @param OrderStateMachine $orderStateMachine
  67.      */
  68.     public function __construct(
  69.         EntityManagerInterface $entityManager,
  70.         EccubeConfig $eccubeConfig,
  71.         OrderStateMachine $orderStateMachine,
  72.         OrderStatusRepository $orderStatusRepository
  73.     ) {
  74.         $this->entityManager $entityManager;
  75.         $this->eccubeConfig $eccubeConfig;
  76.         $this->orderStateMachine $orderStateMachine;
  77.         $this->orderStatusRepository $orderStatusRepository;
  78.     }
  79.     /**
  80.      * {@inheritdoc}
  81.      */
  82.     public function buildForm(FormBuilderInterface $builder, array $options)
  83.     {
  84.         $builder
  85.             ->add('name'NameType::class, [
  86.                 'required' => false,
  87.                 'options' => [
  88.                     'constraints' => [
  89.                         new Assert\NotBlank(),
  90.                     ],
  91.                 ],
  92.             ])
  93.             ->add('kana'KanaType::class, [
  94.                 'required' => false,
  95.                 'options' => [
  96.                     'constraints' => [
  97.                         new Assert\NotBlank(),
  98.                     ],
  99.                 ],
  100.             ])
  101.             ->add('company_name'TextType::class, [
  102.                 'required' => false,
  103.                 'constraints' => [
  104.                     new Assert\Length([
  105.                         'max' => $this->eccubeConfig['eccube_stext_len'],
  106.                     ]),
  107.                 ],
  108.             ])
  109.             ->add('postal_code'PostalType::class, [
  110.                 'required' => false,
  111.                 'constraints' => [
  112.                     new Assert\NotBlank(),
  113.                 ],
  114.                 'options' => [
  115.                     'attr' => ['class' => 'p-postal-code'],
  116.                 ],
  117.             ])
  118.             ->add('address'AddressType::class, [
  119.                 'required' => false,
  120.                 'pref_options' => [
  121.                     'constraints' => [
  122.                         new Assert\NotBlank(),
  123.                     ],
  124.                     'attr' => ['class' => 'p-region-id'],
  125.                 ],
  126.                 'addr01_options' => [
  127.                     'constraints' => [
  128.                         new Assert\NotBlank(),
  129.                         new Assert\Length([
  130.                             'max' => $this->eccubeConfig['eccube_mtext_len'],
  131.                         ]),
  132.                     ],
  133.                     'attr' => ['class' => 'p-locality p-street-address'],
  134.                 ],
  135.                 'addr02_options' => [
  136.                     'required' => false,
  137.                     'constraints' => [
  138.                         new Assert\NotBlank(),
  139.                         new Assert\Length([
  140.                             'max' => $this->eccubeConfig['eccube_mtext_len'],
  141.                         ]),
  142.                     ],
  143.                     'attr' => ['class' => 'p-extended-address'],
  144.                 ],
  145.             ])
  146.             ->add('email'EmailType::class, [
  147.                 'required' => false,
  148.                 'constraints' => [
  149.                     new Assert\NotBlank(),
  150.                     new Email(nullnull$this->eccubeConfig['eccube_rfc_email_check'] ? 'strict' null),
  151.                 ],
  152.             ])
  153.             ->add('phone_number'PhoneNumberType::class, [
  154.                 'required' => false,
  155.                 'constraints' => [
  156.                     new Assert\NotBlank(),
  157.                 ],
  158.             ])
  159.             ->add('message'TextareaType::class, [
  160.                 'required' => false,
  161.                 'constraints' => [
  162.                     new Assert\Length([
  163.                         'max' => $this->eccubeConfig['eccube_ltext_len'],
  164.                     ]),
  165.                 ],
  166.             ])
  167.             ->add('discount'PriceType::class, [
  168.                 'required' => false,
  169.             ])
  170.             ->add('delivery_fee_total'PriceType::class, [
  171.                 'required' => false,
  172.             ])
  173.             ->add('charge'PriceType::class, [
  174.                 'required' => false,
  175.             ])
  176.             ->add('use_point'NumberType::class, [
  177.                 'required' => true,
  178.                 'constraints' => [
  179.                     new Assert\Regex([
  180.                         'pattern' => "/^\d+$/u",
  181.                         'message' => 'form_error.numeric_only',
  182.                     ]),
  183.                     new Assert\Range([
  184.                         'min' => 0,
  185.                         'max' => $this->eccubeConfig['eccube_price_max']
  186.                     ]),
  187.                 ],
  188.             ])
  189.             ->add('note'TextareaType::class, [
  190.                 'required' => false,
  191.                 'constraints' => [
  192.                     new Assert\Length([
  193.                         'max' => $this->eccubeConfig['eccube_ltext_len'],
  194.                     ]),
  195.                 ],
  196.             ])
  197.             ->add('Payment'EntityType::class, [
  198.                 'required' => false,
  199.                 'class' => Payment::class,
  200.                 'choice_label' => function (Payment $Payment) {
  201.                     return $Payment->isVisible()
  202.                         ? $Payment->getMethod()
  203.                         : $Payment->getMethod().trans('admin.common.hidden_label');
  204.                 },
  205.                 'placeholder' => false,
  206.                 'query_builder' => function ($er) {
  207.                     return $er->createQueryBuilder('p')
  208.                         ->orderBy('p.visible''DESC')  // 非表示は下に配置
  209.                         ->addOrderBy('p.sort_no''ASC');
  210.                 },
  211.                 'constraints' => [
  212.                     new Assert\NotBlank(),
  213.                 ],
  214.             ])
  215.             ->add('OrderItems'CollectionType::class, [
  216.                 'entry_type' => OrderItemType::class,
  217.                 'allow_add' => true,
  218.                 'allow_delete' => true,
  219.                 'prototype' => true,
  220.             ])
  221.             ->add('OrderItemsErrors'TextType::class, [
  222.                 'mapped' => false,
  223.             ])
  224.             ->add('return_link'HiddenType::class, [
  225.                 'mapped' => false,
  226.             ]);
  227.         $builder
  228.             ->add($builder->create('Customer'HiddenType::class)
  229.                 ->addModelTransformer(new DataTransformer\EntityToIdTransformer(
  230.                     $this->entityManager,
  231.                     '\Eccube\Entity\Customer'
  232.                 )));
  233.         $builder->addEventListener(FormEvents::POST_SET_DATA, [$this'sortOrderItems']);
  234.         $builder->addEventListener(FormEvents::POST_SET_DATA, [$this'addOrderStatusForm']);
  235.         $builder->addEventListener(FormEvents::POST_SET_DATA, [$this'addShippingForm']);
  236.         $builder->addEventListener(FormEvents::POST_SUBMIT, [$this'copyFields']);
  237.         $builder->addEventListener(FormEvents::POST_SUBMIT, [$this'validateOrderStatus']);
  238.         $builder->addEventListener(FormEvents::POST_SUBMIT, [$this'validateOrderItems']);
  239.         $builder->addEventListener(FormEvents::POST_SUBMIT, [$this'associateOrderAndShipping']);
  240.     }
  241.     /**
  242.      * {@inheritdoc}
  243.      */
  244.     public function configureOptions(OptionsResolver $resolver)
  245.     {
  246.         $resolver->setDefaults([
  247.             'data_class' => Order::class,
  248.         ]);
  249.     }
  250.     /**
  251.      * {@inheritdoc}
  252.      */
  253.     public function getBlockPrefix()
  254.     {
  255.         return 'order';
  256.     }
  257.     /**
  258.      * 受注明細をソートする.
  259.      *
  260.      * @param FormEvent $event
  261.      */
  262.     public function sortOrderItems(FormEvent $event)
  263.     {
  264.         /** @var Order $Order */
  265.         $Order $event->getData();
  266.         if (null === $Order) {
  267.             return;
  268.         }
  269.         $OrderItems $Order->getItems();
  270.         $form $event->getForm();
  271.         $form['OrderItems']->setData($OrderItems);
  272.     }
  273.     /**
  274.      * 受注ステータスのフォームを追加する
  275.      * 新規登録の際は, ユーザ編集不可のため追加しない.
  276.      *
  277.      * ステータスのプルダウンは, ステートマシンで遷移可能なステータスのみ表示する.
  278.      *
  279.      * @param FormEvent $event
  280.      */
  281.     public function addOrderStatusForm(FormEvent $event)
  282.     {
  283.         /** @var Order $Order */
  284.         $Order $event->getData();
  285.         if (null === $Order || ($Order && !$Order->getId())) {
  286.             return;
  287.         }
  288.         /** @var ArrayCollection|OrderStatus[] $OrderStatuses */
  289.         $OrderStatuses $this->orderStatusRepository->findBy([], ['sort_no' => 'ASC']);
  290.         $OrderStatuses = new ArrayCollection($OrderStatuses);
  291.         foreach ($OrderStatuses as $Status) {
  292.             // 同一ステータスはスキップ
  293.             if ($Order->getOrderStatus()->getId() == $Status->getId()) {
  294.                 continue;
  295.             }
  296.             // 遷移できないステータスはリストから除外する.
  297.             if (!$this->orderStateMachine->can($Order$Status)) {
  298.                 $OrderStatuses->removeElement($Status);
  299.             }
  300.         }
  301.         $form $event->getForm();
  302.         $form->add('OrderStatus'EntityType::class, [
  303.             'class' => OrderStatus::class,
  304.             'choices' => $OrderStatuses,
  305.             'choice_label' => 'name',
  306.             'constraints' => [
  307.                 new Assert\NotBlank(),
  308.             ],
  309.             // 変更前後のステータスチェックが必要なのでmapped => false で定義する.
  310.             'mapped' => false,
  311.             'data' => $Order->getOrderStatus(),
  312.         ]);
  313.     }
  314.     /**
  315.      * 単一配送時に, Shippingのフォームを追加する.
  316.      * 複数配送時はShippingの編集は行わない.
  317.      *
  318.      * @param FormEvent $event
  319.      */
  320.     public function addShippingForm(FormEvent $event)
  321.     {
  322.         /** @var Order $Order */
  323.         $Order $event->getData();
  324.         // 複数配送時はShippingの編集は行わない
  325.         if ($Order && $Order->isMultiple()) {
  326.             return;
  327.         }
  328.         $data $Order $Order->getShippings()->first() : null;
  329.         $form $event->getForm();
  330.         $form->add('Shipping'ShippingType::class, [
  331.             'mapped' => false,
  332.             'data' => $data,
  333.         ]);
  334.     }
  335.     /**
  336.      * フォームからPOSTされない情報をコピーする.
  337.      *
  338.      * - 支払方法の名称
  339.      * - 会員の性別/職業/誕生日
  340.      * - 受注ステータス(新規登録時)
  341.      *
  342.      * @param FormEvent $event
  343.      */
  344.     public function copyFields(FormEvent $event)
  345.     {
  346.         /** @var Order $Order */
  347.         $Order $event->getData();
  348.         // 支払方法の名称をコピーする.
  349.         if ($Payment $Order->getPayment()) {
  350.             $Order->setPaymentMethod($Payment->getMethod());
  351.         }
  352.         // 新規登録時は, 新規受付ステータスで登録する.
  353.         if (null === $Order->getOrderStatus()) {
  354.             $Order->setOrderStatus($this->orderStatusRepository->find(OrderStatus::NEW));
  355.             // 会員受注の場合、会員の性別/職業/誕生日をエンティティにコピーする
  356.             if ($Customer $Order->getCustomer()) {
  357.                 $Order->setSex($Customer->getSex());
  358.                 $Order->setJob($Customer->getJob());
  359.                 $Order->setBirth($Customer->getBirth());
  360.             }
  361.         } else {
  362.             // 編集時は, mapped => falseで定義しているため, フォームから変更後データを取得する.
  363.             $form $event->getForm();
  364.             $Order->setOrderStatus($form['OrderStatus']->getData());
  365.         }
  366.         // 新規登録時は受注日を登録する.
  367.         if (null === $Order->getOrderDate()) {
  368.             $Order->setOrderDate(new \DateTime());
  369.         }
  370.     }
  371.     /**
  372.      * 受注ステータスのバリデーションを行う.
  373.      *
  374.      * @param FormEvent $event
  375.      */
  376.     public function validateOrderStatus(FormEvent $event)
  377.     {
  378.         /** @var Order $Order */
  379.         $Order $event->getData();
  380.         if (!$Order->getId()) {
  381.             return;
  382.         }
  383.         $form $event->getForm();
  384.         if (!$form['OrderStatus']->isValid()) {
  385.             return;
  386.         }
  387.         // mapped => falseで定義しているため, Orderのステータスは変更されない
  388.         $oldStatus $Order->getOrderStatus();
  389.         // 変更後のステータスはFormから直接取得する.
  390.         $newStatus $form['OrderStatus']->getData();
  391.         // ステータスに変更があった場合のみチェックする.
  392.         if ($oldStatus->getId() != $newStatus->getId()) {
  393.             if (!$this->orderStateMachine->can($Order$newStatus)) {
  394.                 $form['OrderStatus']->addError(
  395.                     new FormError(trans('admin.order.failed_to_change_status__short', [
  396.                         '%from%' => $oldStatus->getName(),
  397.                         '%to%' => $newStatus->getName(),
  398.                     ])));
  399.             }
  400.         }
  401.     }
  402.     /**
  403.      * 受注明細のバリデーションを行う.
  404.      * 商品明細が1件も登録されていない場合はエラーとする.
  405.      *
  406.      * @param FormEvent $event
  407.      */
  408.     public function validateOrderItems(FormEvent $event)
  409.     {
  410.         /** @var Order $Order */
  411.         $Order $event->getData();
  412.         $OrderItems $Order->getOrderItems();
  413.         $count 0;
  414.         foreach ($OrderItems as $OrderItem) {
  415.             if ($OrderItem->isProduct()) {
  416.                 $count++;
  417.             }
  418.         }
  419.         // 商品明細が1件もない場合はエラーとする.
  420.         if ($count 1) {
  421.             // 画面下部にエラーメッセージを表示させる
  422.             $form $event->getForm();
  423.             $form['OrderItemsErrors']->addError(new FormError(trans('admin.order.product_item_not_found')));
  424.         }
  425.     }
  426.     /**
  427.      * 受注明細と, Order/Shippingの紐付けを行う.
  428.      *
  429.      * @param FormEvent $event
  430.      */
  431.     public function associateOrderAndShipping(FormEvent $event)
  432.     {
  433.         /** @var Order $Order */
  434.         $Order $event->getData();
  435.         $OrderItems $Order->getOrderItems();
  436.         // 明細とOrder, Shippingを紐付ける.
  437.         // 新規の明細のみが対象, 更新時はスキップする.
  438.         foreach ($OrderItems as $OrderItem) {
  439.             // 更新時はスキップ
  440.             if ($OrderItem->getId()) {
  441.                 continue;
  442.             }
  443.             $OrderItem->setOrder($Order);
  444.             // 送料明細の紐付けを行う.
  445.             // 複数配送の場合は, 常に最初のShippingと紐付ける.
  446.             // Order::getShippingsは氏名でソートされている.
  447.             if ($OrderItem->isDeliveryFee()) {
  448.                 $OrderItem->setShipping($Order->getShippings()->first());
  449.             }
  450.             // 商品明細の紐付けを行う.
  451.             // 複数配送時は, 明細の追加は行われないためスキップする.
  452.             if ($OrderItem->isProduct() && !$Order->isMultiple()) {
  453.                 $OrderItem->setShipping($Order->getShippings()->first());
  454.             }
  455.         }
  456.     }
  457. }