Added free product coupon.

This commit is contained in:
Franck Allimant
2014-06-17 00:38:18 +02:00
parent 749487e967
commit c87c6e2793
2 changed files with 351 additions and 3 deletions

View File

@@ -66,9 +66,10 @@
<tag name="thelia.coupon.addCoupon"/>
</service>
<service id="thelia.coupon.type.free_product" class="Thelia\Coupon\Type\FreeProduct">
<argument type="service" id="thelia.facade" />
<tag name="thelia.coupon.addCoupon"/>
</service>
<!-- Condition module -->

View File

@@ -0,0 +1,347 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Coupon\Type;
use Thelia\Core\Event\Cart\CartEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Translation\Translator;
use Thelia\Coupon\FacadeInterface;
use Thelia\Model\CartItem;
use Thelia\Model\CartItemQuery;
use Thelia\Model\Product;
use Thelia\Model\ProductQuery;
/**
* Allow to remove an amount from the checkout total
*
* @package Coupon
* @author Franck Allimant <franck@cqfdev.fr>
*/
class FreeProduct extends AbstractRemoveOnProducts
{
const OFFERED_PRODUCT_ID = 'offered_product_id';
const OFFERED_CATEGORY_ID = 'offered_category_id';
/** @var string Service Id */
protected $serviceId = 'thelia.coupon.type.free_product';
protected $offeredProductId;
protected $offeredCategoryId;
/**
* This constant is user to mark a free product as in the process of being added to the cart,
* but the CartItem ID is not yet been defined.
*/
const ADD_TO_CART_IN_PROCESS = -1;
/**
* @inheritdoc
*/
public function setFieldsValue($effects)
{
$this->offeredProductId = $effects[self::OFFERED_PRODUCT_ID];
$this->offeredCategoryId = $effects[self::OFFERED_CATEGORY_ID];
}
/**
* @inheritdoc
*/
public function getCartItemDiscount($cartItem)
{
// This method is not used, we use our own implementation of exec();
return 0;
}
/**
* @return string The session variable where the cart item IDs for the free products are stored
*/
protected function getSessionVarName() {
return "coupon.free_product.cart_items." . $this->getCode();
}
/**
* Return the cart item id which contains the free product related to a given product
*
* @param Product $product the product in the cart which triggered the discount
*
* @return bool|int|CartItem the cart item which contains the free product, or false if the product is no longer in the cart, or ADD_TO_CART_IN_PROCESS if the adding process is not finished
*/
protected function getRelatedCartItem($product) {
$cartItemIdList = $this->facade->getRequest()->getSession()->get(
$this->getSessionVarName(),
array()
);
if (isset($cartItemIdList[$product->getId()])) {
$cartItemId = $cartItemIdList[$product->getId()];
if ($cartItemId == self::ADD_TO_CART_IN_PROCESS) {
return self::ADD_TO_CART_IN_PROCESS;
}
else if (null !== $cartItem = CartItemQuery::create()->findPk($cartItemId)) {
return $cartItem;
}
}
else {
// Maybe the product we're offering is already in the cart ? Search it.
$cartItems = $this->facade->getCart()->getCartItems();
/** @var CartItem $cartItem */
foreach ($cartItems as $cartItem) {
if ($cartItem->getProduct()->getId() == $this->offeredProductId) {
// We found the product. Store its cart item as the free product container.
$this->setRelatedCartItem($product, $cartItem->getId());
return $cartItem;
}
}
}
return false;
}
/**
* Set the cart item id which contains the free product related to a given product
*
* @param Product $product the product in the cart which triggered the discount
* @param bool|int $cartItemId the cart item ID which contains the free product, or just true if the free product is not yet added.
*/
protected function setRelatedCartItem($product, $cartItemId) {
$cartItemIdList = $this->facade->getRequest()->getSession()->get(
$this->getSessionVarName(),
array()
);
if (! is_array($cartItemIdList)) $cartItemIdList = array();
$cartItemIdList[$product->getId()] = $cartItemId;
$this->facade->getRequest()->getSession()->set(
$this->getSessionVarName(),
$cartItemIdList
);
}
/**
* Get the product id / cart item id list.
*
* @return array an array where the free product ID is the key, and the related cart item id the value.
*/
protected function getFreeProductsCartItemIds() {
return $this->facade->getRequest()->getSession()->get(
$this->getSessionVarName(),
array()
);
}
/**
* Clear the session variable.
*/
protected function clearFreeProductsCartItemIds() {
return $this->facade->getRequest()->getSession()->remove($this->getSessionVarName());
}
/**
* We overload this method here to remove the free products when the
* coupons conditions are no longer met.
*
* @inheritdoc
*/
public function isMatching() {
$match = parent::isMatching();
if (! $match) {
// Cancel coupon effect (but no not remove the product)
$this->clearFreeProductsCartItemIds();
}
return $match;
}
/**
* @inheritdoc
*/
public function exec()
{
$discount = 0;
$cartItems = $this->facade->getCart()->getCartItems();
/** @var Product $eligibleProduct */
$eligibleProduct = null;
/** @var CartItem $cartItem */
foreach ($cartItems as $cartItem) {
if (in_array($cartItem->getProduct()->getId(), $this->product_list)) {
if (! $cartItem->getPromo() || $this->isAvailableOnSpecialOffers()) {
$eligibleProduct = $cartItem;
break;
}
}
}
if ($eligibleProduct !== null) {
// Get the cart item for the eligible product
$freeProductCartItem = $this->getRelatedCartItem($eligibleProduct);
// We add the free product it only if it not yet in the cart.
if ($freeProductCartItem === false) {
if (null !== $freeProduct = ProductQuery::create()->findPk($this->offeredProductId)) {
// Store in the session that the free product is added to the cart,
// so that we don't enter the following infinite loop :
//
// 1) exec() adds a product by firing a CART_ADDITEM event,
// 2) the event is processed by Action\Coupon::updateOrderDiscount(),
// 3) Action\Coupon::updateOrderDiscount() calls CouponManager::getDiscount()
// 4) CouponManager::getDiscount() calls exec() -> Infinite loop !!
// Store a marker first, we do not have the cart item id yet.
$this->setRelatedCartItem($eligibleProduct, self::ADD_TO_CART_IN_PROCESS);
$cartEvent = new CartEvent($this->facade->getCart());
$cartEvent->setNewness(true);
$cartEvent->setAppend(false);
$cartEvent->setQuantity(1);
$cartEvent->setProductSaleElementsId($freeProduct->getDefaultSaleElements()->getId());
$cartEvent->setProduct($this->offeredProductId);
$this->facade->getDispatcher()->dispatch(TheliaEvents::CART_ADDITEM, $cartEvent);
// Store the final cart item ID.
$this->setRelatedCartItem($eligibleProduct, $cartEvent->getCartItem()->getId());
$freeProductCartItem = $cartEvent->getCartItem();
// Setting product price is dangerous, as the customer could change the ordered quantity of this product.
// We will instead add the product price to the order discount.
// $cartEvent->getCartItem()->setPrice(0)->save();
}
}
if ($freeProductCartItem instanceof CartItem) {
$taxCountry = $this->facade->getDeliveryCountry();
// The discount is the product price.
$discount = $freeProductCartItem->getPromo() ?
$freeProductCartItem->getPromoPrice() : $freeProductCartItem->getPrice();
}
}
// No eligible product was found !
else {
// Remove all free products for this coupon, but no not remove the product from the cart.
$this->clearFreeProductsCartItemIds();
}
return $discount;
}
/**
* @inheritdoc
*/
protected function getFieldList()
{
return $this->getBaseFieldList([self::OFFERED_CATEGORY_ID, self::OFFERED_PRODUCT_ID]);
}
/**
* @inheritdoc
*/
protected function checkCouponFieldValue($fieldName, $fieldValue)
{
$this->checkBaseCouponFieldValue($fieldName, $fieldValue);
if ($fieldName === self::OFFERED_PRODUCT_ID) {
if (floatval($fieldValue) < 0) {
throw new \InvalidArgumentException(
Translator::getInstance()->trans(
'Please select the offered product'
)
);
}
}
else if ($fieldName === self::OFFERED_CATEGORY_ID) {
if (empty($fieldValue)) {
throw new \InvalidArgumentException(
Translator::getInstance()->trans(
'Please select the category of the offred product'
)
);
}
}
return $fieldValue;
}
/**
* Get I18n name
*
* @return string
*/
public function getName()
{
return $this->facade
->getTranslator()
->trans('Free product when buying one or more selected products', array(), 'coupon');
}
/**
* @inheritdoc
*/
public function getToolTip()
{
$toolTip = $this->facade
->getTranslator()
->trans(
'This coupon adds a free product to the cart if one of the selected products is in the cart.',
array(),
'coupon'
);
return $toolTip;
}
/**
* @inheritdoc
*/
public function drawBackOfficeInputs()
{
return $this->drawBaseBackOfficeInputs("coupon/type-fragments/free-product.html", [
'offered_category_field_name' => $this->makeCouponFieldName(self::OFFERED_CATEGORY_ID),
'offered_category_value' => $this->offeredCategoryId,
'offered_product_field_name' => $this->makeCouponFieldName(self::OFFERED_PRODUCT_ID),
'offered_product_value' => $this->offeredProductId
]);
}
/**
* @inheritdoc
*/
public function clear()
{
// Clear the session variable when the coupon is cleared.
$this->clearFreeProductsCartItemIds();
}
}