EC-CUBE4で日付毎に1番目から注文番号を採番する方法

EC-CUBE Advent Calendar 2022 8日目の記事です。

EC-CUBE4から注文番号が自由に採番できるようになりました。 こちらはドキュメントにも詳しく載っておらず意外と知らない方が多いのですが、app/config/eccube/packages/eccube.yaml にある、

    # 注文番号のフォーマット. 以下のフォーマットが利用可能です. フォーマットを空にした場合, dtb_order.idを出力します.
    # {yyyy} : 西暦(4桁)
    # {yy}: 西暦(2桁)
    # {mm}: 月(09)
    # {dd}: 日(01)
    # {id,桁数}: dtb_order.idの桁数分0埋め(桁数を超えたらそのまま表示)
    # {random,桁数}: ランダムな数値を桁数分作成
    # {random_alnum,桁数} : ランダムな半角英数大文字を桁数分作成
    eccube_order_no_format: ''

で設定可能となります。

設定方法は記載されている通りで例えば、

    eccube_order_no_format: '{yyyy}{mm}{dd}-{id,3}'

と設定すると、注文番号は20221208-001と採番されます。桁数が3と指定されていますが、1000を超えると20221208-1000というようになります。

時々要望として上がるのが、日付度ごとに注文番号を1から採番してほしいというのがあります。今回はそのカスタマイズ方法を説明します。 まずeccube.yaml

    # 注文番号のフォーマット. 以下のフォーマットが利用可能です. フォーマットを空にした場合, dtb_order.idを出力します.
    # {yyyy} : 西暦(4桁)
    # {yy}: 西暦(2桁)
    # {mm}: 月(09)
    # {dd}: 日(01)
    # {id,桁数}: dtb_order.idの桁数分0埋め(桁数を超えたらそのまま表示)
    # {random,桁数}: ランダムな数値を桁数分作成
    # {random_alnum,桁数} : ランダムな半角英数大文字を桁数分作成
    # {dd_no,桁数} : 日が変わると新たな連番を桁数分作成
    eccube_order_no_format: '{yyyy}{mm}{dd}-{dd_no,3}'

というように新たに{dd_no,桁数}という定義を行い、eccube_order_no_formatにそれを利用するように設定します。

次に、OrderNoProcessor.phpクラスを以下のように修正します。

  • src/Eccube/Service/PurchaseFlow/Processor/OrderNoProcessor.php
<?php

/*
 * This file is part of EC-CUBE
 *
 * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
 *
 * http://www.ec-cube.co.jp/
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Eccube\Service\PurchaseFlow\Processor;

use Eccube\Common\EccubeConfig;
use Eccube\Entity\ItemHolderInterface;
use Eccube\Entity\Order;
use Eccube\Repository\OrderRepository;
use Eccube\Service\PurchaseFlow\ItemHolderPreprocessor;
use Eccube\Service\PurchaseFlow\PurchaseContext;
use Eccube\Util\StringUtil;

class OrderNoProcessor implements ItemHolderPreprocessor
{
    /**
     * @var EccubeConfig
     */
    private $eccubeConfig;

    /**
     * @var OrderRepository
     */
    private $orderRepository;

    /**
     * OrderNoProcessor constructor.
     *
     * @param EccubeConfig $eccubeConfig
     * @param OrderRepository $orderRepository
     */
    public function __construct(EccubeConfig $eccubeConfig, OrderRepository $orderRepository)
    {
        $this->eccubeConfig = $eccubeConfig;
        $this->orderRepository = $orderRepository;
    }

    /**
     * {@inheritdoc}
     */
    public function process(ItemHolderInterface $itemHolder, PurchaseContext $context)
    {
        $Order = $itemHolder;

        if ($Order instanceof Order) {
            if ($Order->getOrderNo()) {
                return;
            }

            if (null === $Order->getId()) {
                return;
            }

            $format = $this->eccubeConfig['eccube_order_no_format'];
            if (empty($format)) {
                // フォーマットが設定されていなければ受注IDが設定される
                $Order->setOrderNo($Order->getId());
            } else {
                do {
                    $orderNo = preg_replace_callback('/\{(.*)}/U', function($matches) use ($Order) {
                        if (count($matches) === 2) {
                            $dateTime = new \DateTime('now', new \DateTimeZone($this->eccubeConfig->get('timezone')));
                            switch ($matches[1]) {
                                case 'yyyy':
                                    return $dateTime->format('Y');
                                case 'yy':
                                    return $dateTime->format('y');
                                case 'mm':
                                    return $dateTime->format('m');
                                case 'dd':
                                    return $dateTime->format('d');
                                default:
                                    $res = explode(',', str_replace(' ', '', $matches[1]));
                                    if (count($res) === 2 && $res[0] == 'dd_no') {
                                        $count = $this->orderRepository->getTodayOrderCount();
                                        if (is_numeric($res[1])) {
                                            return sprintf("%0{$res[1]}d", $count);
                                        } else {
                                            return $count;
                                        }
                                    } else {
                                        if (count($res) === 2 && is_numeric($res[1])) {
                                            if ($res[0] === 'id') {
                                                return sprintf("%0{$res[1]}d", $Order->getId());
                                            } elseif ($res[0] === 'random') {
                                                $random = random_int(1, (int)str_repeat('9', $res[1]));

                                                return sprintf("%0{$res[1]}d", $random);
                                            } elseif ($res[0] === 'random_alnum') {
                                                return strtoupper(StringUtil::random($res[1]));
                                            }
                                        }
                                    }

                                    return $Order->getId();
                            }
                        }

                        return $Order->getId();
                    }, $format);

                    $tempOrder = $this->orderRepository->findOneBy([
                        'order_no' => $orderNo,
                    ]);
                } while ($tempOrder);

                $Order->setOrderNo($orderNo);
            }
        }
    }
}

84行目から91行目が新たに追加した内容です。今回は日毎という事なので、dtb_orderを日付ごとにカウントする関数をOrderRepository.phpクラスに追加します。

以下関数のみを抜粋します。

  • src/Eccube/Repository/OrderRepository.php
<?php
・
・
・

    /**
     * 当日の注文件数を取得
     *
     * @return float|int|mixed|string
     * @throws NoResultException
     * @throws \Doctrine\ORM\NonUniqueResultException
     */
    public function getTodayOrderCount()
    {
        $today = Carbon::today();

        $qb = $this->createQueryBuilder('o')
            ->select('COALESCE(COUNT(o.id) + 1, 1)')
            ->andWhere('o.create_date >= :create_date_start')
            ->andWhere('o.create_date < :create_date_end')
            ->andWhere('o.order_no is not null')
            ->setParameter('create_date_start', $today)
            ->setParameter('create_date_end', $today->copy()->addDay());

        $count = $qb
            ->getQuery()
            ->getSingleScalarResult();

        return $count;
    }

本来であればorder_dateで比較したいところですが、一部の画面遷移で正常にカウントした値がとれず、無限ループが発生してしまうため、create_dateを利用しています。

こちらを設定すると注文番号は、

20221208-001
20221208-002
20221208-003
20221209-001
20221209-002
20221209-003
20221210-001
20221210-002

というように日毎に新たに1から採番されます。

以上で日付が変われば1から新たに採番される方法となります。 利用しない方には特に不要なカスタマイズとなりますが、時々要望として言われる方はぜひご活用ください。