型番を入力しての一括発注は、EC-CUBE にもプラグインにも用意がありません。B2B ECの選び方取引先ごとの卸価格卸売サイトを立ち上げる順番と、3回続けて「ありません」と書いてきました。

必要なら作ることになります。作るとどれくらいのものになるのか、実際に動く形で置いておきます。

作るもの

型番と数量を並べて入力して、まとめてカートに入れる画面です。

型番でまとめて注文の画面。型番と数量の入力欄が10行並び、上から cube-01 に3、sand-01 に2、cube-05 に10 を入力した状態

画面は EC-CUBE 4.4 の開発環境で動かしたものです。デモデータの型番をそのまま入力しています。

取引先はカタログや前回の注文書を見ながら発注します。商品ページを1つずつ開いてカートに入れる操作は、20品目もあるとやっていられません。型番が分かっているなら、並べて入力できるほうが早い。

作るものは4つです。

つくるもの役割
1行分のフォーム型番と数量の入力欄
行をまとめるフォーム1行分を10行ぶん束ねる
コントローラ画面とカートへの追加
テンプレート入力欄を並べる

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

先に、型番の話

作り始める前に知っておきたいことがあります。EC-CUBE の型番は一意ではありません。

型番は dtb_product_classproduct_code 列です。この列に一意制約はありません。管理画面の商品登録フォームで型番に付いている検証も、文字数の上限だけです。必須でもないので、空のまま登録できます。

つまり同じ型番の商品規格が複数あっても、EC-CUBE は何も言いません。型番から商品を引く画面を作るなら、引いた結果が1件に決まらない場合をどうするかを先に決めておく必要があります。

ここでは、1件に決まらない型番は該当なしとして弾きます。適当に先頭を選ぶと、取引先が意図しない商品を注文してしまいます。一括発注を運用に乗せるなら、型番の重複が起きない運用にするのが前提です。

1行分のフォーム

項目は型番と数量の2つだけです。型番には文字数の上限、数量には1以上9999以下という範囲の検証を付けました。

エンティティに紐づけないので、フォームの対象クラスは指定しません。送られてくるのは型番と数量を持った配列です。

どちらの項目も必須にしません。10行並べたうち3行だけ埋めて送信する、という使い方をするためで、空行はコントローラ側で読み飛ばします。

行をまとめるフォーム

1行分のフォームをコレクションとしてまとめるだけです。行を追加できる設定と雛形の設定を有効にしておくと、行を増やすボタンを JavaScript で足せます。行数を固定にするなら不要です。

コントローラ

ここが本体になります。

10行分の空のデータでフォームを組み立てて、送信されたら上から1行ずつ見ていきます。型番が空なら読み飛ばし、商品が引けなければその行にエラーを付ける。1行でも失敗したらカート画面へは飛ばさず、入力内容とエラーを表示したまま画面に留まります。全部成功したときだけカート画面へ送ります。

存在しない型番 cube-99 を入力して送信した画面。その行だけ入力欄が赤くなり「該当する商品が見つかりません。」と表示され、他の行の入力内容はそのまま残っている

成功した行と失敗した行が混ざったとき、成功した分はカートに残ります。20行のうち1行だけ型番を間違えた場合に、19行を入力し直させるのは酷なので、この形にしました。上の画面でも、エラーになった行以外の2件はカートに入っています。

型番から商品規格を引く

同じ型番の商品規格を全部集めて、1件に決まらなければ扱いません。

あわせて2つ絞り込みます。表示対象になっている商品規格だけを見るのは、規格を変更したときに残る無効なものを除くためです。商品の公開ステータスも確認します。これは本体の商品詳細と同じで、非公開の商品を型番から拾えてしまうと商品ページを非公開にした意味がなくなります。

カートへ入れる

ここが一番の勘所です。

カートへ入れたあと、必ず購入フローの検証を通します。 本体の商品詳細ページからカートに入れる処理と同じ手順です。エラーが出たら、いま追加した明細を取り消して、エラーの内容をその行に表示します。

自分で書いた画面だと、カートに入れて終わりにしたくなります。ただ、在庫数の確認も、購入数量の上限も、販売制限も、実際に効いているのは購入フローの検証です。ここを飛ばすと、在庫を超える数量がカートに入り、注文手続きの途中で初めてエラーになります。エラーが出た明細を取り消しているのも本体と同じで、こうしておかないと購入できない明細がカートに残り続けます。

検証の結果には、エラーとは別に警告も入ってきます。こちらは行のエラーにせず、フラッシュメッセージに回しました。在庫に合わせて数量を減らした、といった通知がここに来ます。明細自体は残るので、行のエラーにすると話が合いません。

テンプレート

型番と数量の入力欄を10行のテーブルに並べて、送信ボタンを1つ置くだけです。行ごとのエラーは、その行の下に出します。

開くと10行の入力欄が並びます。会員だけに使わせたいなら、ルーティングをマイページ配下に置くか、コントローラでログイン済みかどうかを確認してください。

送信すると、入力した行がそのままカートの明細になります。

送信後のショッピングカート画面。入力した3行が明細になり、彩のジェラートCUBE(バニラ)が3点、チェリーアイスサンドが2点、彩のジェラートCUBE(抹茶)が10点並んでいる

型番は商品規格ごとに付くので、同じ商品でも規格が違えば別の行として入ります。上の画面の1行目と3行目は、どちらも同じ商品の別の規格です。

Excel から貼り付けたい場合

実際の発注は、Excel の注文書から型番と数量をコピーしてくることが多いと思います。テキストエリアを1つ置いて、貼り付けた内容を行に分解する形にすれば、フォームの構造は変えずに済みます。

テキストエリアの内容を改行で分解し、1行ずつタブかカンマで型番と数量に割ります。数量が空なら1として扱う。あとは行入力と同じ処理に流します。

区切り文字にタブを含めておくと、Excel の2列をそのままコピーして貼り付けられます。カンマ区切りの CSV を貼る運用にも同じやり方で対応できます。

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

卸売サイトなら会員グループ管理プラグインと組み合わせることになると思います。この実装はそのまま乗ります。

取引先に見せていない商品の型番を入力されても購入できません。 プラグインの GroupValidator が購入フローの検証に入っているためで、所属していないグループの商品は明細が落ちてエラーになります。そのエラーメッセージが、そのまま行に表示されます。購入フローを通す作りにしておく利点がここにも出ます。

卸価格もそのまま反映されます。 会員グループ価格管理アドオンは、商品規格を読み込んだ時点(Doctrine の postLoad)で価格を差し替えます。リポジトリから取り出した ProductClass はすでに卸価格になっているので、カートに入れる処理を自分で書いても価格の出し分けは効きます。

つまり一括発注の画面を足すだけで、価格と販売可否はプラグイン側に任せられます。

割り切ったところ

正直に書いておきます。

エラーになった行の取り消しは、もともとカートにあった分も消します。 CartService::removeProduct() は同じ商品規格の明細を丸ごと削除するためです。先にカートへ入れておいた商品の型番を一括発注でもう一度入力して、それがエラーになると、元の分まで消えます。本体の商品詳細からの追加も同じ挙動なので合わせましたが、気になるなら追加前の数量を控えておいて戻す処理が要ります。

販売種別が違う商品を混ぜると、カートが分かれます。 EC-CUBE のカートは販売種別ごとに分かれる仕様です。一括発注の画面では1回の送信に見えても、購入手続きは分けて行うことになります。

数量の妥当性は購入フロー任せです。 入力欄には上限9999しか付けていません。在庫や購入制限に引っかかったときのメッセージは、本体が出すものがそのまま行に表示されます。

見積機能のほうは、これより大きい話になります。見積のエンティティを持って、承認の状態を管理して、受注へ変換する流れが要ります。一括発注はカートへ入れるだけなので、この記事の分量で収まっています。

EC-CUBE 4.4 での違い

4.2 / 4.3 で動かしたものです。4.4 では2箇所変わります。

4.2 / 4.34.4
@Route / @Template のアノテーション#[Route] / #[Template] の属性記法
Sensio\Bundle\FrameworkExtraBundle\Configuration\TemplateSymfony\Bridge\Twig\Attribute\Template

4.4 では SensioFrameworkExtraBundle への依存がなくなり、RouteSymfony\Component\Routing\Attribute\Route に移ります。このあたりの変更点はEC-CUBE 4.4 でプラグインが動かなくなる変更点にまとめました。

カートへ入れる手順そのものは 4.4 でも変わりません。

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