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

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