src/Eccube/Controller/Admin/AdminController.php line 131

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;
  13. use Carbon\Carbon;
  14. use Doctrine\Common\Collections\Criteria;
  15. use Doctrine\ORM\NoResultException;
  16. use Doctrine\ORM\Query\ResultSetMapping;
  17. use Eccube\Controller\AbstractController;
  18. use Eccube\Entity\Master\CustomerStatus;
  19. use Eccube\Entity\Master\OrderStatus;
  20. use Eccube\Entity\ProductStock;
  21. use Eccube\Event\EccubeEvents;
  22. use Eccube\Event\EventArgs;
  23. use Eccube\Exception\PluginApiException;
  24. use Eccube\Form\Type\Admin\ChangePasswordType;
  25. use Eccube\Form\Type\Admin\LoginType;
  26. use Eccube\Repository\CustomerRepository;
  27. use Eccube\Repository\Master\OrderStatusRepository;
  28. use Eccube\Repository\MemberRepository;
  29. use Eccube\Repository\OrderRepository;
  30. use Eccube\Repository\ProductRepository;
  31. use Eccube\Service\PluginApiService;
  32. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
  33. use Symfony\Component\HttpFoundation\Request;
  34. use Symfony\Component\Routing\Annotation\Route;
  35. use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
  36. use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
  37. use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
  38. class AdminController extends AbstractController
  39. {
  40.     /**
  41.      * @var AuthorizationCheckerInterface
  42.      */
  43.     protected $authorizationChecker;
  44.     /**
  45.      * @var AuthenticationUtils
  46.      */
  47.     protected $helper;
  48.     /**
  49.      * @var MemberRepository
  50.      */
  51.     protected $memberRepository;
  52.     /**
  53.      * @var EncoderFactoryInterface
  54.      */
  55.     protected $encoderFactory;
  56.     /**
  57.      * @var OrderRepository
  58.      */
  59.     protected $orderRepository;
  60.     /**
  61.      * @var OrderStatusRepository
  62.      */
  63.     protected $orderStatusRepository;
  64.     /**
  65.      * @var CustomerRepository
  66.      */
  67.     protected $customerRepository;
  68.     /**
  69.      * @var ProductRepository
  70.      */
  71.     protected $productRepository;
  72.     /** @var PluginApiService */
  73.     protected $pluginApiService;
  74.     /**
  75.      * @var array 売り上げ状況用受注状況
  76.      */
  77.     private $excludes = [OrderStatus::CANCELOrderStatus::PENDINGOrderStatus::PROCESSINGOrderStatus::RETURNED];
  78.     /**
  79.      * AdminController constructor.
  80.      *
  81.      * @param AuthorizationCheckerInterface $authorizationChecker
  82.      * @param AuthenticationUtils $helper
  83.      * @param MemberRepository $memberRepository
  84.      * @param EncoderFactoryInterface $encoderFactory
  85.      * @param OrderRepository $orderRepository
  86.      * @param OrderStatusRepository $orderStatusRepository
  87.      * @param CustomerRepository $custmerRepository
  88.      * @param ProductRepository $productRepository
  89.      * @param PluginApiService $pluginApiService
  90.      */
  91.     public function __construct(
  92.         AuthorizationCheckerInterface $authorizationChecker,
  93.         AuthenticationUtils $helper,
  94.         MemberRepository $memberRepository,
  95.         EncoderFactoryInterface $encoderFactory,
  96.         OrderRepository $orderRepository,
  97.         OrderStatusRepository $orderStatusRepository,
  98.         CustomerRepository $custmerRepository,
  99.         ProductRepository $productRepository,
  100.         PluginApiService $pluginApiService
  101.     ) {
  102.         $this->authorizationChecker $authorizationChecker;
  103.         $this->helper $helper;
  104.         $this->memberRepository $memberRepository;
  105.         $this->encoderFactory $encoderFactory;
  106.         $this->orderRepository $orderRepository;
  107.         $this->orderStatusRepository $orderStatusRepository;
  108.         $this->customerRepository $custmerRepository;
  109.         $this->productRepository $productRepository;
  110.         $this->pluginApiService $pluginApiService;
  111.     }
  112.     /**
  113.      * @Route("/%eccube_admin_route%/login", name="admin_login", methods={"GET", "POST"})
  114.      * @Template("@admin/login.twig")
  115.      */
  116.     public function login(Request $request)
  117.     {
  118.         if ($this->authorizationChecker->isGranted('ROLE_ADMIN')) {
  119.             return $this->redirectToRoute('admin_homepage');
  120.         }
  121.         /* @var $form \Symfony\Component\Form\FormInterface */
  122.         $builder $this->formFactory->createNamedBuilder(''LoginType::class);
  123.         $event = new EventArgs(
  124.             [
  125.                 'builder' => $builder,
  126.             ],
  127.             $request
  128.         );
  129.         $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_ADMIM_LOGIN_INITIALIZE);
  130.         $form $builder->getForm();
  131.         return [
  132.             'error' => $this->helper->getLastAuthenticationError(),
  133.             'form' => $form->createView(),
  134.         ];
  135.     }
  136.     /**
  137.      * 管理画面ホーム
  138.      *
  139.      * @param Request $request
  140.      *
  141.      * @return array
  142.      *
  143.      * @throws NoResultException
  144.      * @throws \Doctrine\ORM\NonUniqueResultException
  145.      *
  146.      * @Route("/%eccube_admin_route%/", name="admin_homepage", methods={"GET"})
  147.      * @Template("@admin/index.twig")
  148.      */
  149.     public function index(Request $request)
  150.     {
  151.         $adminRoute $this->eccubeConfig['eccube_admin_route'];
  152.         $is_danger_admin_url false;
  153.         if ($adminRoute === 'admin') {
  154.             $is_danger_admin_url true;
  155.         }
  156.         /**
  157.          * 受注状況.
  158.          */
  159.         $excludes = [];
  160.         $excludes[] = OrderStatus::CANCEL;
  161.         $excludes[] = OrderStatus::DELIVERED;
  162.         $excludes[] = OrderStatus::PENDING;
  163.         $excludes[] = OrderStatus::PROCESSING;
  164.         $excludes[] = OrderStatus::RETURNED;
  165.         $event = new EventArgs(
  166.             [
  167.                 'excludes' => $excludes,
  168.             ],
  169.             $request
  170.         );
  171.         $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_ADMIM_INDEX_ORDER);
  172.         $excludes $event->getArgument('excludes');
  173.         // 受注ステータスごとの受注件数.
  174.         $Orders $this->getOrderEachStatus($excludes);
  175.         // 受注ステータスの一覧.
  176.         $Criteria = new Criteria();
  177.         $Criteria
  178.             ->where($Criteria::expr()->notIn('id'$excludes))
  179.             ->orderBy(['sort_no' => 'ASC']);
  180.         $OrderStatuses $this->orderStatusRepository->matching($Criteria);
  181.         /**
  182.          * 売り上げ状況
  183.          */
  184.         $event = new EventArgs(
  185.             [
  186.                 'excludes' => $this->excludes,
  187.             ],
  188.             $request
  189.         );
  190.         $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_ADMIM_INDEX_SALES);
  191.         $this->excludes $event->getArgument('excludes');
  192.         // 今日の売上/件数
  193.         $salesToday $this->getSalesByDay(new \DateTime());
  194.         // 昨日の売上/件数
  195.         $salesYesterday $this->getSalesByDay(new \DateTime('-1 day'));
  196.         // 今月の売上/件数
  197.         $salesThisMonth $this->getSalesByMonth(new \DateTime());
  198.         /**
  199.          * ショップ状況
  200.          */
  201.         // 在庫切れ商品数
  202.         $countNonStockProducts $this->countNonStockProducts();
  203.         // 取り扱い商品数
  204.         $countProducts $this->countProducts();
  205.         // 本会員数
  206.         $countCustomers $this->countCustomers();
  207.         $event = new EventArgs(
  208.             [
  209.                 'Orders' => $Orders,
  210.                 'OrderStatuses' => $OrderStatuses,
  211.                 'salesThisMonth' => $salesThisMonth,
  212.                 'salesToday' => $salesToday,
  213.                 'salesYesterday' => $salesYesterday,
  214.                 'countNonStockProducts' => $countNonStockProducts,
  215.                 'countProducts' => $countProducts,
  216.                 'countCustomers' => $countCustomers,
  217.             ],
  218.             $request
  219.         );
  220.         $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_ADMIM_INDEX_COMPLETE);
  221.         // 推奨プラグイン
  222.         $recommendedPlugins = [];
  223.         try {
  224.             $recommendedPlugins $this->pluginApiService->getRecommended();
  225.         } catch (PluginApiException $ignore) {
  226.         }
  227.         return [
  228.             'Orders' => $Orders,
  229.             'OrderStatuses' => $OrderStatuses,
  230.             'salesThisMonth' => $salesThisMonth,
  231.             'salesToday' => $salesToday,
  232.             'salesYesterday' => $salesYesterday,
  233.             'countNonStockProducts' => $countNonStockProducts,
  234.             'countProducts' => $countProducts,
  235.             'countCustomers' => $countCustomers,
  236.             'recommendedPlugins' => $recommendedPlugins,
  237.             'is_danger_admin_url' => $is_danger_admin_url,
  238.         ];
  239.     }
  240.     /**
  241.      * 売上状況の取得
  242.      *
  243.      * @param Request $request
  244.      *
  245.      * @Route("/%eccube_admin_route%/sale_chart", name="admin_homepage_sale", methods={"GET"})
  246.      *
  247.      * @return \Symfony\Component\HttpFoundation\JsonResponse
  248.      */
  249.     public function sale(Request $request)
  250.     {
  251.         if (!($request->isXmlHttpRequest() && $this->isTokenValid())) {
  252.             return $this->json(['status' => 'NG'], 400);
  253.         }
  254.         $event = new EventArgs(
  255.             [
  256.                 'excludes' => $this->excludes,
  257.             ],
  258.             $request
  259.         );
  260.         $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_ADMIM_INDEX_SALES);
  261.         $this->excludes $event->getArgument('excludes');
  262.         // 週間の売上金額
  263.         $toDate Carbon::now();
  264.         $fromDate Carbon::today()->subWeek();
  265.         $rawWeekly $this->getData($fromDate$toDate'Y/m/d');
  266.         // 月間の売上金額
  267.         $fromDate Carbon::now()->startOfMonth();
  268.         $rawMonthly $this->getData($fromDate$toDate'Y/m/d');
  269.         // 年間の売上金額
  270.         $fromDate Carbon::now()->subYear()->startOfMonth();
  271.         $rawYear $this->getData($fromDate$toDate'Y/m');
  272.         $datas = [$rawWeekly$rawMonthly$rawYear];
  273.         return $this->json($datas);
  274.     }
  275.     /**
  276.      * パスワード変更画面
  277.      *
  278.      * @Route("/%eccube_admin_route%/change_password", name="admin_change_password", methods={"GET", "POST"})
  279.      * @Template("@admin/change_password.twig")
  280.      *
  281.      * @param Request $request
  282.      *
  283.      * @return \Symfony\Component\HttpFoundation\RedirectResponse|array
  284.      */
  285.     public function changePassword(Request $request)
  286.     {
  287.         $builder $this->formFactory
  288.             ->createBuilder(ChangePasswordType::class);
  289.         $event = new EventArgs(
  290.             [
  291.                 'builder' => $builder,
  292.             ],
  293.             $request
  294.         );
  295.         $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_ADMIM_CHANGE_PASSWORD_INITIALIZE);
  296.         $form $builder->getForm();
  297.         $form->handleRequest($request);
  298.         if ($form->isSubmitted() && $form->isValid()) {
  299.             $Member $this->getUser();
  300.             $salt $Member->getSalt();
  301.             $password $form->get('change_password')->getData();
  302.             $encoder $this->encoderFactory->getEncoder($Member);
  303.             // 2系からのデータ移行でsaltがセットされていない場合はsaltを生成.
  304.             if (empty($salt)) {
  305.                 $salt $encoder->createSalt();
  306.             }
  307.             $password $encoder->encodePassword($password$salt);
  308.             $Member
  309.                 ->setPassword($password)
  310.                 ->setSalt($salt);
  311.             $this->memberRepository->save($Member);
  312.             $event = new EventArgs(
  313.                 [
  314.                     'form' => $form,
  315.                     'Member' => $Member,
  316.                 ],
  317.                 $request
  318.             );
  319.             $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_ADMIN_CHANGE_PASSWORD_COMPLETE);
  320.             $this->addSuccess('admin.change_password.password_changed''admin');
  321.             return $this->redirectToRoute('admin_change_password');
  322.         }
  323.         return [
  324.             'form' => $form->createView(),
  325.         ];
  326.     }
  327.     /**
  328.      * 在庫なし商品の検索結果を表示する.
  329.      *
  330.      * @Route("/%eccube_admin_route%/search_nonstock", name="admin_homepage_nonstock", methods={"GET"})
  331.      *
  332.      * @param Request $request
  333.      *
  334.      * @return \Symfony\Component\HttpFoundation\Response
  335.      */
  336.     public function searchNonStockProducts(Request $request)
  337.     {
  338.         // 在庫なし商品の検索条件をセッションに付与し, 商品マスタへリダイレクトする.
  339.         $searchData = [];
  340.         $searchData['stock'] = [ProductStock::OUT_OF_STOCK];
  341.         $session $request->getSession();
  342.         $session->set('eccube.admin.product.search'$searchData);
  343.         return $this->redirectToRoute('admin_product_page', [
  344.             'page_no' => 1,
  345.         ]);
  346.     }
  347.     /**
  348.      * 本会員の検索結果を表示する.
  349.      *
  350.      * @Route("/%eccube_admin_route%/search_customer", name="admin_homepage_customer", methods={"GET"})
  351.      *
  352.      * @param Request $request
  353.      *
  354.      * @return \Symfony\Component\HttpFoundation\Response
  355.      */
  356.     public function searchCustomer(Request $request)
  357.     {
  358.         $searchData = [];
  359.         $searchData['customer_status'] = [CustomerStatus::REGULAR];
  360.         $session $request->getSession();
  361.         $session->set('eccube.admin.customer.search'$searchData);
  362.         return $this->redirectToRoute('admin_customer_page', [
  363.             'page_no' => 1,
  364.         ]);
  365.     }
  366.     /**
  367.      * @param \Doctrine\ORM\EntityManagerInterface $em
  368.      * @param array $excludes
  369.      *
  370.      * @return Request|null
  371.      */
  372.     protected function getOrderEachStatus(array $excludes)
  373.     {
  374.         $sql 'SELECT
  375.                     t1.order_status_id as status,
  376.                     COUNT(t1.id) as count
  377.                 FROM
  378.                     dtb_order t1
  379.                 WHERE
  380.                     t1.order_status_id NOT IN (:excludes)
  381.                 GROUP BY
  382.                     t1.order_status_id
  383.                 ORDER BY
  384.                     t1.order_status_id';
  385.         $rsm = new ResultSetMapping();
  386.         $rsm->addScalarResult('status''status');
  387.         $rsm->addScalarResult('count''count');
  388.         $query $this->entityManager->createNativeQuery($sql$rsm);
  389.         $query->setParameters([':excludes' => $excludes]);
  390.         $result $query->getResult();
  391.         $orderArray = [];
  392.         foreach ($result as $row) {
  393.             $orderArray[$row['status']] = $row['count'];
  394.         }
  395.         return $orderArray;
  396.     }
  397.     /**
  398.      * @param \DateTime $dateTime
  399.      *
  400.      * @return array|mixed
  401.      *
  402.      * @throws \Doctrine\ORM\NonUniqueResultException
  403.      */
  404.     protected function getSalesByDay($dateTime)
  405.     {
  406.         $dateTimeStart = clone $dateTime;
  407.         $dateTimeStart->setTime(0000);
  408.         $dateTimeEnd = clone $dateTimeStart;
  409.         $dateTimeEnd->modify('+1 days');
  410.         $qb $this->orderRepository
  411.             ->createQueryBuilder('o')
  412.             ->select('
  413.             SUM(o.payment_total) AS order_amount,
  414.             COUNT(o) AS order_count')
  415.             ->setParameter(':excludes'$this->excludes)
  416.             ->setParameter(':targetDateStart'$dateTimeStart)
  417.             ->setParameter(':targetDateEnd'$dateTimeEnd)
  418.             ->andWhere(':targetDateStart <= o.order_date and o.order_date < :targetDateEnd')
  419.             ->andWhere('o.OrderStatus NOT IN (:excludes)');
  420.         $q $qb->getQuery();
  421.         $result = [];
  422.         try {
  423.             $result $q->getSingleResult();
  424.         } catch (NoResultException $e) {
  425.             // 結果がない場合は空の配列を返す.
  426.         }
  427.         return $result;
  428.     }
  429.     /**
  430.      * @param \DateTime $dateTime
  431.      *
  432.      * @return array|mixed
  433.      *
  434.      * @throws \Doctrine\ORM\NonUniqueResultException
  435.      */
  436.     protected function getSalesByMonth($dateTime)
  437.     {
  438.         $dateTimeStart = clone $dateTime;
  439.         $dateTimeStart->setTime(0000);
  440.         $dateTimeStart->modify('first day of this month');
  441.         $dateTimeEnd = clone $dateTime;
  442.         $dateTimeEnd->setTime(0000);
  443.         $dateTimeEnd->modify('first day of 1 month');
  444.         $qb $this->orderRepository
  445.             ->createQueryBuilder('o')
  446.             ->select('
  447.             SUM(o.payment_total) AS order_amount,
  448.             COUNT(o) AS order_count')
  449.             ->setParameter(':excludes'$this->excludes)
  450.             ->setParameter(':targetDateStart'$dateTimeStart)
  451.             ->setParameter(':targetDateEnd'$dateTimeEnd)
  452.             ->andWhere(':targetDateStart <= o.order_date and o.order_date < :targetDateEnd')
  453.             ->andWhere('o.OrderStatus NOT IN (:excludes)');
  454.         $q $qb->getQuery();
  455.         $result = [];
  456.         try {
  457.             $result $q->getSingleResult();
  458.         } catch (NoResultException $e) {
  459.             // 結果がない場合は空の配列を返す.
  460.         }
  461.         return $result;
  462.     }
  463.     /**
  464.      * 在庫切れ商品数を取得
  465.      *
  466.      * @return mixed
  467.      *
  468.      * @throws \Doctrine\ORM\NonUniqueResultException
  469.      */
  470.     protected function countNonStockProducts()
  471.     {
  472.         // 商品一覧画面の「在庫なし」検索(ProductRepository::getQueryBuilderBySearchDataForAdmin)と
  473.         // 条件を一致させるため、stock = 0ではなくstock <= 0(マイナス在庫も含む)で判定する
  474.         $qb $this->productRepository->createQueryBuilder('p')
  475.             ->select('count(DISTINCT p.id)')
  476.             ->innerJoin('p.ProductClasses''pc')
  477.             ->where('pc.stock_unlimited = :StockUnlimited AND pc.stock <= 0')
  478.             ->andWhere('pc.visible = :visible')
  479.             ->andWhere('p.del_flg = 0')
  480.             ->setParameter('StockUnlimited'false)
  481.             ->setParameter('visible'true);
  482.         return $qb->getQuery()->getSingleScalarResult();
  483.     }
  484.     /**
  485.      * 商品数を取得
  486.      *
  487.      * @return mixed
  488.      *
  489.      * @throws \Doctrine\ORM\NonUniqueResultException
  490.      */
  491.     protected function countProducts()
  492.     {
  493.         // 商品一覧画面のデフォルト検索(全ステータス対象、pc.visible=true、del_flg=0)と
  494.         // 母数を一致させるため、公開ステータスでの絞り込みは行わない
  495.         $qb $this->productRepository->createQueryBuilder('p')
  496.             ->select('count(DISTINCT p.id)')
  497.             ->innerJoin('p.ProductClasses''pc')
  498.             ->where('pc.visible = :visible')
  499.             ->andWhere('p.del_flg = 0')
  500.             ->setParameter('visible'true);
  501.         return $qb->getQuery()->getSingleScalarResult();
  502.     }
  503.     /**
  504.      * 本会員数を取得
  505.      *
  506.      * @return mixed
  507.      *
  508.      * @throws \Doctrine\ORM\NonUniqueResultException
  509.      */
  510.     protected function countCustomers()
  511.     {
  512.         $qb $this->customerRepository->createQueryBuilder('c')
  513.             ->select('count(c.id)')
  514.             ->where('c.Status = :Status')
  515.             ->setParameter('Status'CustomerStatus::REGULAR);
  516.         return $qb->getQuery()->getSingleScalarResult();
  517.     }
  518.     /**
  519.      * 期間指定のデータを取得
  520.      *
  521.      * @param Carbon $fromDate
  522.      * @param Carbon $toDate
  523.      * @param $format
  524.      *
  525.      * @return array
  526.      */
  527.     protected function getData(Carbon $fromDateCarbon $toDate$format)
  528.     {
  529.         $qb $this->orderRepository->createQueryBuilder('o')
  530.             ->andWhere('o.order_date >= :fromDate')
  531.             ->andWhere('o.order_date <= :toDate')
  532.             ->andWhere('o.OrderStatus NOT IN (:excludes)')
  533.             ->setParameter(':excludes'$this->excludes)
  534.             ->setParameter(':fromDate'$fromDate->copy())
  535.             ->setParameter(':toDate'$toDate->copy())
  536.             ->orderBy('o.order_date');
  537.         $result $qb->getQuery()->getResult();
  538.         return $this->convert($result$fromDate$toDate$format);
  539.     }
  540.     /**
  541.      * 期間毎にデータをまとめる
  542.      *
  543.      * @param $result
  544.      * @param Carbon $fromDate
  545.      * @param Carbon $toDate
  546.      * @param $format
  547.      *
  548.      * @return array
  549.      */
  550.     protected function convert($resultCarbon $fromDateCarbon $toDate$format)
  551.     {
  552.         $raw = [];
  553.         for ($date $fromDate$date <= $toDate$date $date->addDay()) {
  554.             $raw[$date->format($format)]['price'] = 0;
  555.             $raw[$date->format($format)]['count'] = 0;
  556.         }
  557.         foreach ($result as $Order) {
  558.             $raw[$Order->getOrderDate()->format($format)]['price'] += $Order->getPaymentTotal();
  559.             ++$raw[$Order->getOrderDate()->format($format)]['count'];
  560.         }
  561.         return $raw;
  562.     }
  563. }