昨日の担当者アカウントの記事で、承認フローは入れていないと書きました。担当者が発注したものはそのまま受注になります、と。

実際に運用に乗せると、ここが最初に問題になります。担当者に発注させたいけれど、金額の大きいものを勝手に確定されても困る。作ります。

作るもの

担当者の発注を「承認待ち」で止めて、管理者がマイページで承認したら店舗に流します。

マイページの承認待ちの発注画面。ナビに「承認待ち(2)」のタブがあり、注文番号8(発注者 佐藤 健)と注文番号7(発注者 鈴木 花子)が合計金額と商品つきで並び、それぞれに「承認する」「差し戻す」のボタンがある

画面は EC-CUBE 4.4 の開発環境で動かしたものです。担当者アカウントの仕組みは昨日の記事のものをそのまま使っています。

作るものは5つです。

つくるもの役割
受注ステータス「承認待ち」マスタに1件足す
購入フローの確定処理担当者の発注を承認待ちに落とす
ステートマシンの遷移承認と差し戻しの行き先を決める
マイページの承認画面一覧・承認・差し戻し
ナビの件数表示承認待ちが何件あるか

プラグインにはせず、app/Customize に置く前提です。

購入手続きは止めない

先に方針の話です。承認フローと聞くと、購入手続きの手前で止めたくなります。カートから「承認を依頼する」に進ませて、承認されたら購入手続きに入る、という形です。

やめました。在庫を押さえられないからです。

EC-CUBE で在庫を引く処理は、購入手続きの購入フローにだけ登録されています。購入手続きを通さずに承認だけ先に取ると、承認している間に在庫が売り切れます。承認したのに買えない、が一番まずい。

なので購入手続きは最後まで通します。在庫もポイントも通常どおり動いて、受注も確定します。変えるのはステータスだけです。 確定した受注を「新規受付」ではなく「承認待ち」で置いておいて、管理者が承認したら「新規受付」に上げます。

見積のときは逆でした。見積依頼の記事では在庫を引かずに金額だけ計算しています。見積は買うかどうかまだ決めていない段階なので在庫を押さえる筋合いがなく、承認待ちのほうは担当者がもう買うつもりでいるので押さえておきたい。同じ保留でも扱いが変わります。

どこで差し込むか

受注のステータスを「新規受付」にしているのは、購入フローの確定処理のひとつ、OrderUpdateProcessor です。受注日を入れているのも同じ場所です。

つまり、同じ仕組みの処理をもう1つ書いて、この後ろに並べればいい。EC-CUBE では PurchaseProcessor というインターフェイスがそれにあたります。確定処理として呼ばれるメソッドの中で、受注のステータスを承認待ちに差し替えます。

判定は2つだけです。本体が立てたステータスが「新規受付」であること。そして、その受注の会員が親アカウントを持っていること。親を持つ会員は昨日の記事で作った担当者アカウントなので、これで担当者の発注だけが止まります。管理者自身の発注は素通りします。

金額で分けたいなら、ここに受注の合計金額の条件を足します。判定を書く場所が1箇所に収まるのが、この差し込み方の利点です。

登録は属性を付けるだけ

EC-CUBE 4.4 では、書いたクラスに #[ShoppingFlow] という属性を付けるだけで購入フローに入ります。設定ファイルの purchaseflow.yaml を触る必要はありません。

なぜ順番が合うのか

ここが少し不思議なところです。本体の処理は purchaseflow.yaml に優先度つきで定義されていて、OrderUpdateProcessor の優先度は 600 です。属性で登録したクラスに優先度は書けません。それでも本体の後ろに来ます。

処理を組み立てている PurchaseFlowPass が2段構えになっているからです。

  1. purchaseflow.yaml に書かれたもの(フローの種別を持つもの)を、優先度の高い順に並べる
  2. そのあとで、属性が付いているものを足す

①を全部並べ終えてから②を足すので、属性で登録した処理は必ず設定ファイル側の後ろに来ます。 PurchaseProcessor を実装したクラスにはコンテナの自動設定でタグが付きますが、フローの種別を持たないため①では素通りします。

順番を細かく制御したいなら設定ファイルに書くことになります。「本体の処理が終わったあとに何かする」だけなら属性のほうが早い。

ステータスを足す

「承認待ち」を追加します。IDは本体とぶつからない 111 にしました。

入れる先何の名前か入れる値
mtb_order_status管理画面での名称承認待ち
mtb_order_status_color受注一覧のバッジの色#EEB128
mtb_customer_order_status会員に見せる名称承認待ち

dtb_order の同じ列が3つのマスタから参照されているので、3つとも同じIDの行を入れます。 入れ忘れたものだけが空のまま残ります。このあたりの事情は見積依頼の記事に詳しく書きました。

会員向けの名称も「承認待ち」にしています。担当者に見えるのはこの名前です。

担当者(鈴木 花子)のマイページの注文履歴。2件のうち、注文番号7のご注文状況が「承認待ち」、注文番号4が「注文受付」になっている

差し戻しに本体の注文取消しを使う

ステータスを足しただけでは動きません。ステートマシンに遷移を定義します。承認と差し戻しの2つです。

遷移どこからどこへ在庫とポイント
差し戻し(本体の cancel承認待ち注文取消し戻る
承認(独自の approve_order承認待ち新規受付動かない

差し戻しは、本体にもとからある注文取消しの遷移に「承認待ち」を追加するだけです。見積の記事では、取り消しに本体のものを使わず独自の遷移を切りました。今回は逆に、本体に乗ります。

OrderStateMachine が遷移の名前でイベントを購読しているからです。注文取消しの遷移を通ると、在庫を戻す処理と、使ったポイントを戻す処理が走ります。見積は在庫を引いていないので戻されると困り、承認待ちは在庫を引いているので戻してほしい。だから片方は避けて、片方は乗ります。

承認のほうは在庫を触ってほしくないので、本体にない名前を付けました。ここで入金や出荷といった本体の遷移名を使うと、入金日が入ったり在庫が二重に動いたりします。遷移の名前は、そのまま在庫やポイントの処理と結びついています。

手元では、担当者2人が5個ずつ発注して在庫が 85 から 75 に減り、1件を差し戻すと 80 に戻りました。承認したほうは引かれたままです。

承認の画面

マイページに一覧を置いて、行ごとに承認と差し戻しのボタンを出します。押されたときにやることは、対象の受注を引いて、ステータスを遷移させて、一覧に戻すだけです。

書くときに外せない点が3つあります。

ステータスを直接書き換えないこと。 受注に対してステータスを代入すると、表示だけ変わって在庫が戻りません。差し戻したのに在庫が減ったままになります。ステートマシンに「その遷移ができるか」を確認してから「適用する」を呼びます。在庫やポイントの戻しは、この適用の中でイベントとして走ります。

対象を自社の担当者に絞ること。 一覧に出す受注も、承認や差し戻しの対象にする受注も、ログイン中の管理者がぶら下げている担当者のもの、かつ承認待ちのものだけに限ります。ここを省くと、URLのIDを書き換えるだけで他社の受注を承認できてしまいます。

CSRFトークンの項目名を間違えないこと。 EC-CUBE のトークン検証は _token という名前のパラメータしか見ません。フォームの項目名を別のものにすると、送っても届かず 403 になります。ここで一度つまずきました。

ナビに件数を出す

「承認待ち(2)」の数字は Twig の関数として用意しました。ログイン中の会員が管理者なら、その担当者の承認待ちの件数を数えて返すだけのものです。

会員のエンティティにメソッドを生やしてもよさそうに見えますが、そうすると受注を数えるためにエンティティからリポジトリを触ることになります。表示のためだけの数え上げなので、Twig 側に置きました。

差し戻すと、担当者の履歴では注文取消しになります。

差し戻したあとの担当者の注文履歴。注文番号7のご注文状況が「注文取消し」に変わっている

割り切ったところ

決済は承認前に走ります。 購入手続きを最後まで通す作りなので当然ですが、カード決済だと承認していない注文で売上が立ちます。銀行振込や請求書払いを前提にしてください。カードで使うなら、承認まで与信だけにして承認時に売上を確定させる対応が決済モジュール側に要ります。

注文完了メールも承認前に届きます。 本体が購入手続きの最後に送るためです。担当者には「ご注文ありがとうございます」が届いてしまうので、承認制で運用するならメールの文面をステータスで出し分けることになります。

承認できるのは管理者1人だけです。 承認者を複数置く、金額で承認者を変える、といった多段の稟議には足りません。承認者の役割を会員に持たせるところから作り直しになります。

差し戻しの理由を残せません。 差し戻すと注文取消しになるだけで、なぜ止めたのかが担当者に伝わりません。受注のメモ欄に理由を書き込むようにすれば、管理画面からも読めます。

承認待ちの受注は店舗の受注一覧にも出ます。 ステータスで区別できるとはいえ、店舗の担当者が承認待ちを新規受付と読み違える余地があります。受注一覧に件数タブを出す設定を有効にしておくと気づきやすくなります。

通知がありません。 担当者が発注しても管理者にメールは飛びません。マイページのナビに件数を出しているだけです。運用に乗せるなら、承認待ちに落とすタイミングで管理者にメールを送ることになります。

EC-CUBE 4.2 / 4.3 での違い

4.4 で動かしたものです。4.2 / 4.3 では名前と書き方が変わります。

4.2 / 4.34.4
ルーティングはアノテーション属性記法
Eccube\Annotation\ShoppingFlowEccube\Attribute\ShoppingFlow
Symfony\Component\Security\Core\Security(4.2 のみ)Symfony\Bundle\SecurityBundle\Security

購入フローに差し込む仕組み自体は 4.2 からあります。ただし並び方の決まり方が違います。

4.3 は 4.4 と同じで、設定ファイルのタグに優先度を書き、設定ファイル側を並べ終えたあとにアノテーション側を足します。順番の話はそのまま通ります。

4.2 は設定ファイルが処理の並びを直に配列で持っていて、優先度がありません。書いた順がそのまま実行順で、OrderUpdateProcessor は最後に置かれています。アノテーションで足したものは、その配列を渡したあとに追加される形になるため、結局この後ろに来ます。仕組みは違いますが、置きたい位置には落ち着きます。

ステートマシンの設定ファイルの場所と、OrderStateMachine が注文取消しの遷移で在庫を戻す挙動は 4.2 から変わっていません。変更点の全体はEC-CUBE 4.4 でプラグインが動かなくなる変更点にまとめています。

会員グループ管理を入れている場合

承認フローは会員グループを見ていません。見ているのは担当者アカウントの親子関係だけです。会員グループ管理プラグインを入れていても入れていなくても、動きは同じです。

ただ、承認が要る場面は取引先の卸売です。一般消費者に承認フローは要りません。担当者アカウントの判定を会員グループで絞っておくと、卸売の会員だけが承認の対象になります。この判定は昨日の記事に書いたものがそのまま効きます。

卸価格も変わりません。承認待ちの受注はすでに金額が確定しているので、会員グループ価格管理アドオンが差し替えた卸価格のまま止まります。承認しても金額は動きません。

B2B サイト全体でどこまでが標準・プラグインで、どこからが作るものになるのかは、B2Bサイト全体の線引きにまとめました。