Skip to content

Instantly share code, notes, and snippets.

@nanasess
Last active August 10, 2017 08:32
Show Gist options
  • Save nanasess/a677f949e408506122bfc8d859b76d22 to your computer and use it in GitHub Desktop.
Save nanasess/a677f949e408506122bfc8d859b76d22 to your computer and use it in GitHub Desktop.

のし・ラッピングオプションのサンプル

商品情報、注文情報に「のし・ラッピングオプション」を追加するサンプルです。 3.n にて追加された拡張機能を使用して実装します。 以下のカスタマイズを実施します。

  • 商品情報に「のし・ラッピング対応」フラグ追加
  • 商品詳細に「のし・ラッピング対応」オプション表示
  • のし・ラッピング種別マスタ追加(名称・追加料金)
  • 注文画面に、のし・ラッピング選択・記載内容フォーム表示
  • 注文情報に、のし・ラッピング明細追加

商品に属性追加

商品情報に、のし・ラッピング対応フラグを追加します。 Entity拡張機構(#2267) を使用します。

<?php

namespace Acme\Entity;

use Doctrine\ORM\Mapping as ORM;
use Eccube\Annotation\EntityExtension;

/**
 * @EntityExtension("Eccube\Entity\Product")
 */
trait WrappingOptionTrait
{
    /**
     * @ORM\Column(name="wrapping_option_flg", type="smallint", options={"unsigned":true,"default":0})
     */
    public $wrapping_option_flg;
}
<?php

namespace Acme\Entity;

use Doctrine\ORM\Mapping as ORM;
use Eccube\Annotation as Eccube;

/**
 * @Eccube\EntityExtension("Eccube\Entity\Order")
 */
trait OrderWrappingOptionTrait
{
    /**
     * @var string|null
     *
     * @ORM\Column(
     *     name="wrapping_comment", type="string", length=4000, nullable=true
     * )
     * @Eccube\FormAppend(
     *     auto_render=true,
     *     type="\Symfony\Component\Form\Extension\Core\Type\TextAreaType",
     *     options={
     *          "required": false,
     *          "label": "のし・ラッピングの宛名等ご記入ください"
     *     })
     */
    public $wrapping_comment;

    /**
     * @var \Acme\Entity\Wrapping
     *
     * @ORM\ManyToOne(targetEntity="Acme\Entity\Wrapping")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="wrapping_id", referencedColumnName="wrapping_id")
     * })
     * @Eccube\FormAppend(
     *     auto_render=true,
     *     options={
     *         "label": "のし・ラッピング種別",
     *         "placeholder": "のし・ラッピング種別を選択してください"
     *     }
     * )
     */
    public $Wrapping;

    public function isWrappingSupport()
    {
        foreach ($this->getItems() as $Item) {
            if ($Item->getProduct()->wrapping_option_flg == 1) {
                return true;
            }
        }
        return false;
    }
}

以下のコマンドを実行し、 Trait を反映します。

app/console generate-proxies

ATTENTION 古い Proxy クラスが残っていると誤作動する場合がある. 再生成時にきちんと消した方が良い

のし・ラッピング種別マスタ追加

のし・ラッピング種別マスタを追加します。名称とオプション金額のフィールドを用意します。

<?php

namespace Acme\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Eccube\Common\Constant;
use Doctrine\ORM\Mapping as ORM;

/**
 * Wrapping
 *
 * @ORM\Table(name="dtb_wrapping")
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="discriminator_type", type="string", length=255)
 * @ORM\HasLifecycleCallbacks()
 * @ORM\Entity(repositoryClass="Acme\Repository\WrappingRepository")
 */
class Wrapping extends \Eccube\Entity\AbstractEntity
{
    /**
     * @var integer
     *
     * @ORM\Column(name="wrapping_id", type="integer", options={"unsigned":true})
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    public $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    public $name;

    /**
     * @var string
     *
     * @ORM\Column(name="option_price", type="decimal", precision=10, scale=0, options={"unsigned":true})
     */
    public $option_price;

    /**
     * @var int
     *
     * @ORM\Column(name="del_flg", type="smallint", options={"unsigned":true,"default":0})
     */
    public $del_flg = 0;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="create_date", type="datetimetz")
     */
    public $create_date;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="update_date", type="datetimetz")
     */
    public $update_date;
}

以下のコマンドを実行し、 Entity と Repository を生成します。

vendor/bin/doctrine orm:schema-tool:update --force
vendor/bin/doctrine orm:generate-repositories --filter=Wrapping app

フォーム追加

注文画面に、のし選択・記載内容フォーム表示を表示します。 エンティティからフォームを生成する機構(#2268, #2321) を使用します。

Shopping/index.twig に以下の対応が入っていれば、自動的に表示します。

*** src/Eccube/Resource/template/default/Shopping/index.twig.HEAD	2017-07-24 10:39:49.000000000 +0900
--- src/Eccube/Resource/template/default/Shopping/index.twig	2017-07-24 10:39:49.000000000 +0900
***************
*** 340,345 ****
--- 340,356 ----
                              {% endif %}
                          {% endfor %}
                      </div>
+                     {# エンティティ拡張の自動出力 #}
+                     {% for f in form %}
+                         {# auto_render有効時に出力 #}
+                         {% if f.vars.eccube_form_options.auto_render %}
+                             {# form_themeが設定されている場合にthemeを適用 #}
+                             {% if f.vars.eccube_form_options.form_theme %}
+                                 {% form_theme f f.vars.eccube_form_options.form_theme %}
+                             {% endif %}
+                             {{ form_row(f) }}
+                         {% endif %}
+                     {% endfor %}
                  </div><!-- /.col -->
  
                  <div id="confirm_side" class="col-sm-4">

商品検索条件追加

のし・オプション対応商品で絞り込みできるようにします。 QueryBuilderの拡張(#2285, #2298) を使用します。

ここでは簡易的に、 wrapping=1 という URLパラメータで絞り込みできるようにしています。

<?php


namespace Acme\Entity;


use Eccube\Annotation\QueryExtension;
use Eccube\Doctrine\Query\WhereClause;
use Eccube\Doctrine\Query\WhereCustomizer;
use Eccube\Repository\QueryKey;

/**
 * @QueryExtension(QueryKey::PRODUCT_SEARCH)
 */
class ProductListCustomizer extends WhereCustomizer
{
    /**
     * ラッピング対応で絞り込む
     *
     * @param array $params
     * @param string $queryKey
     * @return WhereByClause[]
     */
    protected function createStatements($params, $queryKey)
    {
        if (isset($_REQUEST['wrapping'])) { // TODO
            return [WhereClause::eq('p.wrapping_option_flg', ':wrapping_option_flg', $_REQUEST['wrapping'])];
        }
        return [];
    }
}

ServiceProvider に以下を追加

$app['eccube.queries']->addCustomizer(new \Acme\Entity\ProductListCustomizer());

ATTENTION 検索パラメータも簡単に渡せるようにしたい

twig関数追加

商品詳細にのし・ラッピングオプションを表示するカスタムファンクションを作成します。 Twig のユーザー定義関数(#2263) を使用します。

render_block_twig に以下を定義

{% block wrapping %}
    {% if Product.wrapping_option_flg == 1 %}
        のし・ラッピング対応
    {% endif %}
{% endblock %}

detail.twig で以下のタグが使用可能となる

{{ eccube_block_wrapping({'Product': Product}) }}

ルーティング

  • のし説明ページ追加

SensioFrameworkExtraBundle(#2127)

注文時の処理

のし・ラッピングオプション付きの場合は, のし明細追加

のし・ラッピングオプション付きの場合のみ、選択フォームを表示させる場合は、{% if Order->isSupportWrapping() %}{% endif %} で囲む必要があります。 このサンプルでは、エンティティからフォームを生成する機構を使用するため割愛します。

のし・ラッピング明細追加

注文情報に、のし・ラッピング明細を追加します。 以下のような条件で、明細を追加します。

  • のし種別に応じて, 追加料金
  • 10000円以上は追加料金無料

eccube.purchase.flow.shoppingWrappingProcessor を追加します。 単価集計、支払処理拡張(#2127) を使用します。

        $app['eccube.purchase.flow.shopping'] = function () use ($app) {
            $flow = new PurchaseFlow();
            $flow->addItemProcessor(new StockValidator());
            $flow->addItemProcessor(new DisplayStatusValidator());
            $flow->addItemHolderProcessor(new PaymentTotalLimitValidator($app['config']['max_total_fee']));
            $flow->addItemHolderProcessor(new DeliveryFeeProcessor($app));
            $flow->addItemHolderProcessor(new PaymentTotalNegativeValidator());
            $flow->addItemHolderProcessor(new \Acme\Service\PurchaseFlow\Processor\WrappingProcessor($app)); // 追加
            return $flow;
        };
<?php

namespace Acme\Service\PurchaseFlow\Processor;

use Eccube\Entity\ItemHolderInterface;
use Eccube\Entity\Master\OrderItemType;
use Eccube\Entity\Master\TaxDisplayType;
use Eccube\Entity\Master\TaxType;
use Eccube\Entity\Order;
use Acme\Entity\Wrapping;
use Eccube\Entity\ShipmentItem;
use Eccube\Entity\Shipping;
use Eccube\Service\PurchaseFlow\ItemHolderProcessor;
use Eccube\Service\PurchaseFlow\ProcessResult;
use Eccube\Service\PurchaseFlow\PurchaseContext;

/**
 * ラッピング明細追加.
 */
class WrappingProcessor implements ItemHolderProcessor
{
    private $app;

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

    /**
     * @param ItemHolderInterface $itemHolder
     * @param PurchaseContext     $context
     *
     * @return ProcessResult
     */
    public function process(ItemHolderInterface $itemHolder, PurchaseContext $context)
    {
        if ($this->containsWrappingItem($itemHolder) == false) {
            $this->addWrappingItem($itemHolder);
        }

        return ProcessResult::success();
    }

    protected function addWrappingItem(ItemHolderInterface $itemHolder)
    {
        // TODO
        $TaxInclude = $this->app['orm.em']->getRepository(TaxDisplayType::class)->find(TaxDisplayType::INCLUDED);
        $Taxion = $this->app['orm.em']->getRepository(TaxType::class)->find(TaxType::TAXATION);

        /** @var Order $Order */
        $Order = $itemHolder;
        /** @var Wrapping $Wrapping */
        $Wrapping = $Order->Wrapping;
        if (is_object($Wrapping)) {
            if ($Order->getSubtotal() >= 10000) {
                $option_price = 0;
            } else {
                $option_price = $Wrapping->option_price;
                // TODO 暫定対応. 明細追加と集計のロジックは分ける
                $Order->setCharge($Order->getCharge() + $option_price);
                $Order->setTotal($Order->getTotal() + $option_price);
                $Order->setPaymentTotal($Order->getPaymentTotal() + $option_price);
            }

            $ShipmentItem = new ShipmentItem();
            $ShipmentItem->setProductName('のし・ラッピングオプション')
                ->setPrice($option_price)
                ->setPriceIncTax($option_price)
                ->setTaxRate(8)
                ->setQuantity(1)
                ->setOrder($Order)
                ->setTaxDisplayType($TaxInclude)
                ->setTaxType($Taxion);

            $itemHolder->addItem($ShipmentItem);
        }
    }

    protected function containsWrappingItem(ItemHolderInterface $itemHolder)
    {
        foreach ($itemHolder->getItems() as $item) {
            if (strpos('のし・ラッピングオプション', $item->getProductName()) !== false) {
                return true;
            }
            return false;
        }
    }
}
@izayoi256
Copy link

環境

  • リビジョン: EC-CUBE/ec-cube@d6f9d6a
  • PHP: 7.1.2 (Core, date, libxml, openssl, pcre, sqlite3, zlib, ctype, curl, dom, fileinfo, filter, ftp, hash, iconv, json, mbstring, SPL, PDO, session, posix, Reflection, standard, SimpleXML, pdo_sqlite, Phar, tokenizer, xml, xmlreader, xmlwriter, mysqlnd, apache2handler, apcu, gd, intl, mcrypt, pdo_mysql, pdo_pgsql, soap, zip, xdebug)
  • サーバー: Apache/2.4.10 (Debian)
  • DB:
    • MySQL 10.1.21-MariaDB

報告

  • /app/Acme/Entity/Wrapping.php__toString()が必要です。

  • /src/Eccube/Resource/template/default/Shopping/index.twig の記述を下記のように変更する。

- {% if f.vars.eccube_form_options.form_theme is defined %}
+ {% if f.vars.eccube_form_options.form_theme is defined and f.vars.eccube_form_options.form_theme is not empty %}
  • dtb_wrapping.discriminator_type列に'wrapping'を登録する必要がある。

    • Doctrine経由なら問題ないが、直接INSERTするケース等で注意。
  • OrderWrappingOptionTrait::$wrapping_comment@Eccube\FormAppendのtypeに"\Symfony\Component\Form\Extension\Core\Type\TextAreaType"を指定すると、フロント画面側の会員登録画面でエラーが発生。

    • 管理画面側の会員登録画面だとエラーが発生しない。
    • "\Symfony\Component\Form\Extension\Core\Type\TextAreaType"だとフロント画面・管理画面共にエラーが発生しない。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment