会員グループ管理::会員ランク管理アドオンで先月の注文合計金額が設定された条件を超えた場合、会員に会員ランクを付与する方法

はじめに

会員グループ管理プラグイン for EC-CUBE4会員グループ管理::会員ランク管理アドオン for EC-CUBE4 を利用すると、EC-CUBE4 で会員ランク管理ができるようになります。

会員ランク管理アドオンは、作成した会員グループに購入金額と購入回数を登録しておくと会員の注文合計金額または注文回数をもとに会員ランクが自動で決定されるのですが、独自の会員ランク決定処理を実装したいといった要件にも対応可能です。

実装方法は簡単でRankInterfaceを実装したクラスを作成してそのクラスを登録するだけです。

以下は、先月の注文合計金額(税別)が条件注文金額を超えた場合、会員ランクを付与する実装方法です。

注文合計金額の集計方法

今回、以下の条件で注文合計金額を集計しています。

  • 出荷済み注文のみ
  • 商品金額は税別
  • 送料、手数料、割引は注文合計金額の集計に含まれていません

デフォルトの集計方法と異なります

EC-CUBE では 商品金額(税込)、送料、手数料、割引 で計算された注文金額をもとに会員の注文合計金額を集計してます。

会員ランク管理アドオンはEC-CUBE本体で集計された会員の注文合計金額をもとに会員ランクの決定を行っていますので、今回実装した集計方法では会員ランク決定の結果が異なりますのでご注意ください

先月の注文合計金額を集計してランクを決定する処理

app/Customize/Service/Rank/LastMonthRank.php を作成して以下のコードを追加してください。

<?php

namespace Customize\Service\Rank;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use Eccube\Entity\Customer;
use Eccube\Entity\Master\OrderItemType;
use Eccube\Entity\Master\OrderStatus;
use Eccube\Entity\Order;
use Eccube\Entity\OrderItem;
use Eccube\Entity\Shipping;
use Plugin\CustomerGroup\Entity\Group;
use Plugin\CustomerGroupRank\Service\Rank\RankInterface;

class LastMonthRank implements RankInterface
{
    /**
     * @var EntityManagerInterface
     */
    protected $entityManager;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    /**
     * 優先度が最上位のグループを会員に設定する
     *
     * @param Customer $customer
     * @return void
     */
    public function decide(Customer $customer): void
    {
        // 会員グループをクリアする
        $customer->getGroups()->clear();

        // 対象の会員ブループが見つかったら登録
        $groups = $this->getGroups($customer);
        if ($groups->count() > 0) {
            /** @var Group $group */
            $group = $groups->first();
            $customer->addGroup($group);
        }
    }

    protected function getGroups(Customer $customer): ArrayCollection
    {
        $last_month_first = (new \DateTimeImmutable('first day of previous month'))->setTime(0, 0, 0);
        $last_month_last = (new \DateTimeImmutable('last day of previous month'))->setTime(23, 59, 59);

        $orderStatus = $this->entityManager->find(OrderStatus::class, OrderStatus::DELIVERED);
        $orderItemType = $this->entityManager->find(OrderItemType::class, OrderItemType::PRODUCT);

        $qb = $this->entityManager->createQueryBuilder();
        $qb
            ->select('SUM(oi.price * oi.quantity)')
            ->from(OrderItem::class, 'oi')
            ->innerJoin(Order::class, 'o', 'WITH', 'oi.Order = o.id')
            ->innerJoin(Shipping::class, 's', 'WITH', 'o.id = s.Order')
            ->where('oi.OrderItemType = :OrderItemType')
            ->andWhere('o.Customer = :Customer')
            ->andWhere('o.OrderStatus = :OrderStatus')
            ->andWhere('s.shipping_date BETWEEN :last_month_first AND :last_month_last')
            ->setParameter('Customer', $customer)
            ->setParameter('OrderStatus', $orderStatus)
            ->setParameter('OrderItemType', $orderItemType)
            ->setParameter('last_month_first', $last_month_first)
            ->setParameter('last_month_last', $last_month_last);

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

        // 注文回数は条件に入れないので0指定
        $searchData = [
            'buyTimes' => 0,
            'buyTotal' => $buyTotal ?? 0
        ];

        $groups = $this->entityManager->getRepository(Group::class)->getQueryBuilderBySearchData($searchData)
            ->getQuery()
            ->getResult();

        return new ArrayCollection($groups);
    }
}

上記の会員ランク決定クラスを登録

上記の会員ランク決定クラスを登録します。また、既存の会員ランク決定クラスの無効化も行います。

app/Customize/Resource/config/services.yaml を作成して以下を追加してください。

services:
  Plugin\CustomerGroupRank\Service\Rank\Rank:
  Customize\Service\Rank\LastMonthRank:
    tags:
      - { name: 'plugin.customer.group.rank', priority: 100 }
    arguments:
      - '@doctrine.orm.default_entity_manager'

以上で完成です。

会員グループに条件を登録

会員グループに購入金額を登録してください。

今回、購入回数は利用しないので1を登録してください。

設定例

  • プラチナ会員グループ:購入金額1000000円
  • ゴールド会員グループ:購入金額100000円
  • シルバー会員グループ:購入金額10000円

配送料、手数料、割引は含まない前月の注文合計金額(税別)に応じて会員がログインしたときに振り分けられます。

例えば、先月の注文合計金額が2000000円の場合は購入金額が1000000円を超えているのでログイン時にプラチナ会員グループに振り分けられ、先月の注文合計金額が50000円の場合は購入金額が10000円を超えているのが100000円を超えていないのでシルバー会員グループに振り分けられます。注文合計金額が10000円未満だったらどのグループにも属されません。

とりあえずテストも用意

以下、テストのサンプルです。

テストを用意しておくとあとでいろいろ助かります。

app/Tests/Service/Rank/LastMonthRankTest.php を作成して以下を追加してください。

<?php

namespace Customize\Tests\Service\Rank;

use Eccube\Entity\Customer;
use Eccube\Entity\Master\OrderStatus;
use Eccube\Tests\EccubeTestCase;
use Plugin\CustomerGroup\Tests\TestCaseTrait;
use Plugin\CustomerGroupRank\Service\Rank\Context;

class LastMonthRankTest extends EccubeTestCase
{
    use TestCaseTrait;

    /**
     * @var Context
     */
    protected $context;

    public function setUp()
    {
        parent::setUp();

        $this->context = self::$container->get(Context::class);
    }

    /**
     * @param $buy_total
     * @param $price
     * @param $quantity
     * @param $expected
     *
     * @dataProvider decideProvider
     */
    public function testDecide($buy_total, $price, $quantity, $datetime, $hour, $minute, $second, $expected)
    {
        $group = $this->createGroup();
        $group
            ->setBuyTimes(1)
            ->setBuyTotal($buy_total);

        $customer = $this->createCustomer();

        $shipping_date= (new \DateTimeImmutable($datetime))->setTime($hour, $minute, $second);

        $orderStatus = $this->entityManager->find(OrderStatus::class, OrderStatus::DELIVERED);

        for($i = 0; $i < 2; $i++) {
            $order = $this->createOrder($customer);
            foreach ($order->getOrderItems() as $orderItem) {
                if ($orderItem->isProduct()) {
                    $shipping = $orderItem->getShipping();
                    $shipping->setShippingDate($shipping_date);
                    $orderItem
                        ->setShipping($shipping)
                        ->setPrice($price)
                        ->setQuantity($quantity);

                    $order->setOrderStatus($orderStatus);
                    $this->entityManager->persist($order);
                }
            }
        }

        $this->entityManager->flush();

        $this->context->decide($customer);

        $groups = $this->entityManager->find(Customer::class, $customer->getId())->getGroups();

        self::assertCount($expected, $groups);
    }

    public function decideProvider()
    {
        return [
            [1000, 1000, 1, 'first day of previous month', 0, 0, 0, 1],
            [1000, 100, 1, 'first day of previous month', 0, 0, 0, 0],
            [1000, 1000, 1, 'last day of previous month', 23, 59, 59, 1],
            [1000, 100, 1, 'last day of previous month', 23, 59, 59, 0],
            [1000, 1000, 1, 'previous month', 0, 0, 0, 1],
            [1000, 500, 1, 'previous month', 0, 0, 0, 1],
            [1000, 499, 1, 'previous month', 0, 0, 0, 0],
        ];
    }
}

お気軽にコメントをどうぞ