Improved and simplified coupon form management

This commit is contained in:
Franck Allimant
2014-05-30 19:58:02 +02:00
parent a018102572
commit 0683d20d2c
15 changed files with 317 additions and 142 deletions

View File

@@ -30,12 +30,12 @@
<argument type="service" id="thelia.facade" />
<tag name="thelia.coupon.addCoupon"/>
</service>
<!--
<service id="thelia.coupon.type.remove_amount_on_categories" class="Thelia\Coupon\Type\RemoveAmountOnCategories">
<argument type="service" id="thelia.facade" />
<tag name="thelia.coupon.addCoupon"/>
</service>
-->
<!-- Condition module -->
<service id="thelia.condition.factory" class="Thelia\Condition\ConditionFactory">

View File

@@ -25,9 +25,7 @@ use Thelia\Core\Security\AccessManager;
use Thelia\Coupon\CouponFactory;
use Thelia\Coupon\CouponManager;
use Thelia\Condition\ConditionCollection;
use Thelia\Coupon\Type\CouponAbstract;
use Thelia\Coupon\Type\CouponInterface;
use Thelia\Coupon\Type\RemoveXPercent;
use Thelia\Form\CouponCreationForm;
use Thelia\Form\Exception\FormValidationException;
use Thelia\Log\Tlog;
@@ -577,7 +575,7 @@ class CouponController extends BaseAdminController
$condition['serviceId'] = $availableCoupon->getServiceId();
$condition['name'] = $availableCoupon->getName();
$condition['toolTip'] = $availableCoupon->getToolTip();
// $condition['inputName'] = $availableCoupon->getInputName();
$cleanedCoupons[] = $condition;
}
@@ -676,35 +674,6 @@ class CouponController extends BaseAdminController
}
/**
* Add percentage logic if found in the Coupon post data
*
* @param array $effects Effect parameters to populate
* @param array $extendedInputNames Extended Inputs to manage
*
* @return array Populated effect with percentage
*/
protected function addExtendedLogic(array $effects, array $extendedInputNames)
{
/** @var Request $request */
$request = $this->container->get('request');
$postData = $request->request;
// Validate quantity input
if ($postData->has(RemoveXPercent::INPUT_EXTENDED__NAME)) {
$extentedPostData = $postData->get(RemoveXPercent::INPUT_EXTENDED__NAME);
foreach ($extendedInputNames as $extendedInputName) {
if (isset($extentedPostData[$extendedInputName])) {
$inputValue = $extentedPostData[$extendedInputName];
$effects[$extendedInputName] = $inputValue;
}
}
}
return $effects;
}
/**
* Feed the Coupon Create or Update event with the User inputs
*
@@ -718,16 +687,15 @@ class CouponController extends BaseAdminController
// Get the form field values
$data = $form->getData();
$serviceId = $data['type'];
/** @var CouponInterface $couponManager */
$couponManager = $this->container->get($serviceId);
$effects = [CouponAbstract::INPUT_AMOUNT_NAME => $data[CouponAbstract::INPUT_AMOUNT_NAME]];
$effects = $this->addExtendedLogic($effects, $couponManager->getExtendedInputs());
/** @var CouponInterface $coupon */
$coupon = $this->container->get($serviceId);
$couponEvent = new CouponCreateOrUpdateEvent(
$data['code'],
$serviceId,
$data['title'],
$effects,
$coupon->getEffects($data),
$data['shortDescription'],
$data['description'],
$data['isEnabled'],

View File

@@ -327,10 +327,9 @@ class CouponCreateOrUpdateEvent extends ActionEvent
*/
public function setEffects(array $effects)
{
if (null === $effects['amount']) {
throw new InvalidArgumentException('Missing key \'amount\' in Coupon effect ready to be serialized array');
}
$this->amount = $effects['amount'];
// Amount is now optionnal.
$this->amount = isset($effects['amount']) ? $effects['amount'] : 0;
$this->effects = $effects;
}

View File

@@ -17,6 +17,7 @@ use Thelia\Condition\ConditionEvaluator;
use Thelia\Condition\ConditionOrganizerInterface;
use Thelia\Core\Translation\Translator;
use Thelia\Coupon\FacadeInterface;
use Thelia\Form\CouponCreationForm;
use Thelia\Model\CouponCountry;
use Thelia\Model\CouponModule;
@@ -29,12 +30,21 @@ use Thelia\Model\CouponModule;
*/
abstract class CouponAbstract implements CouponInterface
{
const INPUT_EXTENDED__NAME = 'thelia_coupon_creation_extended';
/**
* The dataset name for all coupon specific input fields, that do not appear in the CouPonCreationForm form.
*
* In the input form, these fields have to be created like:
*
* thelia_coupon_specific[my_field, thelia_coupon_creation_extended[my_other_field]
*
* use the makeCouponField() method to do that safely.
*/
const COUPON_DATASET_NAME = 'coupon_specific';
const INPUT_AMOUNT_NAME = 'amount';
/** @var array Extended Inputs to manage */
protected $extendedInputs = array();
/**
* A standard 'amount' filed name, thant can be used in coupons which extends this class
*/
const AMOUNT_FIELD_NAME = 'amount';
/** @var FacadeInterface Provide necessary value from Thelia */
protected $facade = null;
@@ -161,7 +171,8 @@ abstract class CouponAbstract implements CouponInterface
$this->facade = $facade;
$this->effects = $effects;
$this->amount = $effects[self::INPUT_AMOUNT_NAME];
// Amount is now optional.
$this->amount = isset($effects[self::AMOUNT_FIELD_NAME]) ? $effects[self::AMOUNT_FIELD_NAME] : 0;
$this->freeShippingForCountries = $freeShippingForCountries;
$this->freeShippingForModules = $freeShippingForModules;
@@ -383,6 +394,18 @@ abstract class CouponAbstract implements CouponInterface
return $this->conditionEvaluator->isMatching($this->conditions);
}
/**
* This is the field label than will be displayed in the form.
* This method should be overridden to be useful.
*
* For backward compatibility only.
*
* @return string
*/
public function getInputName() {
return "Please override getInputName() method";
}
/**
* Draw the input displayed in the BackOffice
* allowing Admin to set its Coupon effect
@@ -394,19 +417,86 @@ abstract class CouponAbstract implements CouponInterface
{
return $this->facade->getParser()->render('coupon/type-fragments/remove-x.html', [
'label' => $this->getInputName(),
'fieldName' => self::INPUT_AMOUNT_NAME,
'fieldId' => self::AMOUNT_FIELD_NAME,
'fieldName' => $this->makeCouponFieldName(self::AMOUNT_FIELD_NAME),
'value' => $this->amount
]);
}
/**
* Get all extended inputs name to manage
* This methods checks a field value. If the field has a correct value, this value is returned
* Otherwise, an InvalidArgumentException describing the problem should be thrown.
*
* This method should be ovveriden to be useful.
*
* @param $fieldName
* @param $fieldValue
* @return mixed
* @throws \InvalidArgumentException if the field valiue is not valid.
*/
public function getExtendedInputs()
{
return $this->extendedInputs;
protected function checkCouponFieldValue($fieldName, $fieldValue) {
return $fieldValue;
}
/**
* A helper to get the value of a standard field name
*
* @param string $fieldName the field name
* @param array $data the input form data (e.g. $form->getData())
* @param mixed $defaultValue the default value if the field is not found.
*
* @return mixed the input value, or the default one
*
* @throws \InvalidArgumentException if the field is not found, and no default value has been defined.
*/
protected function getCouponFieldValue($fieldName, $data, $defaultValue = null) {
if (isset($data[self::COUPON_DATASET_NAME][$fieldName])) {
return $this->checkCouponFieldValue(
$fieldName,
$data[self::COUPON_DATASET_NAME][$fieldName]
);
}
else if (null !== $defaultValue) {
return $defaultValue;
}
else {
throw new \InvalidArgumentException(sprintf("The coupon field name %s was not found in the coupon form", $fieldName));
}
}
/**
* A helper to create an standard field name that will be used in the coupon form
*
* @param string $fieldName the field name
* @return string the complete name, ready to be used in a form.
*/
protected function makeCouponFieldName($fieldName) {
return sprintf("%s[%s][%s]", CouponCreationForm::COUPON_CREATION_FORM_NAME, self::COUPON_DATASET_NAME, $fieldName);
}
/**
* Return a list of the fields name for this coupon.
*
* @return array
*/
protected function getFieldList() {
return [self::AMOUNT_FIELD_NAME];
}
/**
* Create the effect array from the list of fields
*
* @param array $data the input form data (e.g. $form->getData())
* @return array a filedName => fieldValue array
*/
public function getEffects($data) {
$effects = [];
foreach($this->getFieldList() as $fieldName) {
$effects[$fieldName] = $this->getCouponFieldValue($fieldName, $data);
}
return $effects;
}
}

View File

@@ -15,6 +15,7 @@ namespace Thelia\Coupon\Type;
use Propel\Runtime\Collection\ObjectCollection;
use Thelia\Condition\ConditionCollection;
use Thelia\Coupon\FacadeInterface;
use Thelia\Form\CouponCreationForm;
/**
* Represents a Coupon ready to be processed in a Checkout process
@@ -220,13 +221,6 @@ interface CouponInterface
*/
public function drawBackOfficeInputs();
/**
* Get all extended inputs name to manage
*
* @return mixed
*/
public function getExtendedInputs();
/**
* @return ObjectCollection list of country IDs for which shipping is free. All if empty
*/
@@ -236,4 +230,14 @@ interface CouponInterface
* @return ObjectCollection list of module IDs for which shipping is free. All if empty
*/
public function getFreeShippingForModules();
/**
* Create the effect array from the list of fields
*
* @param array $data the input form data (e.g. $form->getData())
*
* @return array a filedName => fieldValue array
*/
public function getEffects($data);
}

View File

@@ -12,6 +12,9 @@
namespace Thelia\Coupon\Type;
use Thelia\Core\Translation\Translator;
use Thelia\Coupon\FacadeInterface;
/**
* Allow to remove an amount from the checkout total
*
@@ -26,6 +29,44 @@ class RemoveAmountOnCategories extends CouponAbstract
/** @var string Service Id */
protected $serviceId = 'thelia.coupon.type.remove_amount_on_categories';
var $category_list = array();
/**
* @inheritdoc
*/
public function set(
FacadeInterface $facade,
$code,
$title,
$shortDescription,
$description,
array $effects,
$isCumulative,
$isRemovingPostage,
$isAvailableOnSpecialOffers,
$isEnabled,
$maxUsage,
\DateTime $expirationDate,
$freeShippingForCountries,
$freeShippingForModules,
$perCustomerUsageCount
)
{
parent::set(
$facade, $code, $title, $shortDescription, $description, $effects,
$isCumulative, $isRemovingPostage, $isAvailableOnSpecialOffers, $isEnabled, $maxUsage, $expirationDate,
$freeShippingForCountries,
$freeShippingForModules,
$perCustomerUsageCount
);
$this->category_list = isset($effects[self::CATEGORIES_LIST]) ? $effects[self::CATEGORIES_LIST] : array();
if (! is_array($this->category_list)) $this->category_list = array($this->category_list);
return $this;
}
/**
* Get I18n name
*
@@ -64,18 +105,64 @@ class RemoveAmountOnCategories extends CouponAbstract
*/
public function exec()
{
// TODO !!!
return $this->amount;
}
public function drawBackOfficeInputs()
{
return $this->facade->getParser()->render('coupon/type-fragments/remove-amount-on-categories.html', [
'amount_field_name' => self::INPUT_AMOUNT_NAME,
'amount_value' => $this->amount,
'categories_field_name' => self::CATEGORIES_LIST,
'categories_values' => isset($this->values[self::CATEGORIES_LIST]) ? $this->values[self::CATEGORIES_LIST] : array()
// The standard "Amount" field
'amount_field_name' => $this->makeCouponFieldName(self::AMOUNT_FIELD_NAME),
'amount_value' => $this->amount,
]);
// The categories list field
'categories_field_id' => self::CATEGORIES_LIST,
'categories_field_name' => $this->makeCouponFieldName(self::CATEGORIES_LIST),
// The selected categories
'categories_values' => $this->category_list
]);
}
/**
* Return a list of the fields name for this coupon.
*
* @return array
*/
protected function getFieldList() {
return [self::AMOUNT_FIELD_NAME, self::CATEGORIES_LIST];
}
/**
* @inheritdoc
*/
protected function checkCouponFieldValue($fieldName, $fieldValue)
{
if ($fieldName === self::AMOUNT_FIELD_NAME) {
if (floatval($fieldValue) < 0) {
throw new \InvalidArgumentException(
Translator::getInstance()->trans(
'Value %val for Discount Amount is invalid. Please enter a positive value.',
[ '%val' => $fieldValue]
)
);
}
}
else if ($fieldName === self::CATEGORIES_LIST) {
if (empty($fieldValue)) {
throw new \InvalidArgumentException(
Translator::getInstance()->trans(
'Please select at least one category'
)
);
}
}
return $fieldValue;
}
}

View File

@@ -12,6 +12,8 @@
namespace Thelia\Coupon\Type;
use Thelia\Core\Translation\Translator;
/**
* Allow to remove an amount from the checkout total
*
@@ -36,18 +38,6 @@ class RemoveXAmount extends CouponAbstract
->trans('Fixed Amount Discount', array(), 'coupon');
}
/**
* Get I18n amount input name
*
* @return string
*/
public function getInputName()
{
return $this->facade
->getTranslator()
->trans('Discount amount', array(), 'coupon');
}
/**
* Get I18n tooltip
*
@@ -69,9 +59,38 @@ class RemoveXAmount extends CouponAbstract
public function drawBackOfficeInputs()
{
return $this->facade->getParser()->render('coupon/type-fragments/remove-x-amount.html', [
'label' => $this->getInputName(),
'fieldName' => self::INPUT_AMOUNT_NAME,
'fieldName' => $this->makeCouponFieldName(self::AMOUNT_FIELD_NAME),
'value' => $this->amount
]);
}
}
/**
* Return a list of the fields name for this coupon.
*
* @return array
*/
protected function getFieldList() {
return [self::AMOUNT_FIELD_NAME];
}
/**
* @inheritdoc
*/
protected function checkCouponFieldValue($fieldName, $fieldValue)
{
if ($fieldName === self::AMOUNT_FIELD_NAME) {
if (floatval($fieldValue) < 0) {
throw new \InvalidArgumentException(
Translator::getInstance()->trans(
'Value %val for Disount Amount is invalid. Please enter a positive value.',
[ '%val' => $fieldValue]
)
);
}
}
return $fieldValue;
}
}

View File

@@ -12,6 +12,7 @@
namespace Thelia\Coupon\Type;
use Thelia\Core\Translation\Translator;
use Thelia\Coupon\FacadeInterface;
/**
@@ -29,11 +30,6 @@ class RemoveXPercent extends CouponAbstract
/** @var float Percentage removed from the Cart */
protected $percentage = 0;
/** @var array Extended Inputs to manage */
protected $extendedInputs = array(
self::INPUT_PERCENTAGE_NAME
);
/**
* @inheritdoc
*/
@@ -87,6 +83,26 @@ class RemoveXPercent extends CouponAbstract
return round($this->facade->getCartTotalTaxPrice() * $this->percentage/100, 2);
}
/**
* @inheritdoc
*/
protected function checkCouponFieldValue($fieldName, $fieldValue)
{
if ($fieldName === self::INPUT_PERCENTAGE_NAME) {
if (floatval($fieldValue) <= 0) {
throw new \InvalidArgumentException(
Translator::getInstance()->trans(
'Value %val for Percent Discount is invalid. Please enter a positive value between 1 and 100.',
[ '%val' => $fieldValue]
)
);
}
}
return $fieldValue;
}
/**
* Get I18n name
*
@@ -99,18 +115,6 @@ class RemoveXPercent extends CouponAbstract
->trans('Remove X percent to total cart', array(), 'coupon');
}
/**
* Get I18n amount input name
*
* @return string
*/
public function getInputName()
{
return $this->facade
->getTranslator()
->trans('Percent Discount', array(), 'coupon');
}
/**
* Get I18n tooltip
*
@@ -138,11 +142,17 @@ class RemoveXPercent extends CouponAbstract
public function drawBackOfficeInputs()
{
return $this->facade->getParser()->render('coupon/type-fragments/remove-x-percent.html', [
'label' => $this->getInputName(),
'typeKey' => self::INPUT_AMOUNT_NAME,
'fieldId' => self::INPUT_PERCENTAGE_NAME,
'fieldName' => self::INPUT_EXTENDED__NAME,
'fieldName' => $this->makeCouponFieldName(self::INPUT_PERCENTAGE_NAME),
'value' => $this->percentage
]);
}
/**
* Return a list of the fields name for this coupon.
*
* @return array
*/
protected function getFieldList() {
return [self::INPUT_PERCENTAGE_NAME];
}
}

View File

@@ -33,6 +33,8 @@ use Thelia\Module\BaseModule;
*/
class CouponCreationForm extends BaseForm
{
const COUPON_CREATION_FORM_NAME = 'thelia_coupon_creation';
/**
* Build Coupon form
*
@@ -110,14 +112,6 @@ class CouponCreationForm extends BaseForm
)
)
)
->add(
'amount',
'money',
array(
'constraints' => array(
new NotBlank()
))
)
->add(
'isEnabled',
'text',
@@ -198,7 +192,12 @@ class CouponCreationForm extends BaseForm
new NotBlank()
)
)
);
)
->add('coupon_specific', 'collection', array(
'allow_add' => true,
'allow_delete' => true,
))
;
}
/**
@@ -226,6 +225,6 @@ class CouponCreationForm extends BaseForm
*/
public function getName()
{
return 'thelia_coupon_creation';
return self::COUPON_CREATION_FORM_NAME;
}
}

View File

@@ -184,7 +184,8 @@ class Coupon extends BaseCoupon
*/
public function getAmount()
{
$amount = $this->getEffects()['amount'];
// Amount is now optional
$amount = isset($this->getEffects()['amount']) ? $this->getEffects()['amount'] : 0;
return floatval($amount);
}
@@ -199,10 +200,6 @@ class Coupon extends BaseCoupon
{
$effects = $this->unserializeEffects($this->getSerializedEffects());
if (null === $effects['amount']) {
throw new InvalidArgumentException('Missing key \'amount\' in Coupon effect coming from database');
}
return $effects;
}
@@ -210,18 +207,12 @@ class Coupon extends BaseCoupon
* Get the Coupon effects
*
* @param array $effects Effect ready to be serialized
* Needs at least the key 'amount'
* with the amount removed from the cart
*
* @throws Exception\InvalidArgumentException
* @return $this
*/
public function setEffects(array $effects)
{
if (null === $effects['amount']) {
throw new InvalidArgumentException('Missing key \'amount\' in Coupon effect ready to be serialized array');
}
$this->setSerializedEffects($this->serializeEffects($effects));
return $this;

View File

@@ -181,9 +181,7 @@
{/form_field}
<div class="inputs">
{form_field form=$form field='amount'}
{$couponInputsHtml nofilter}
{/form_field}
</div>
</div>
</div>

View File

@@ -1,7 +1,11 @@
<div class="form-group input-{$fieldName}">
<label for="{$amount_field_name}" class="control-label">{intl l="Discount amount"}</label>
<div class="form-group input-coupon-amount">
<label for="coupon-amount" class="control-label">{intl l="Discount amount"}</label>
<div class="input-group">
<input id="{$amount_field_name}" type="money" class="form-control" name="thelia_coupon_creation[{$amount_field_name}]" value="{$amount_value}" placeholder="14.50">
<input id="coupon-amount" type="money" class="form-control" name="{$amount_field_name}" value="{$amount_value}" placeholder="14.50">
{loop type="currency" name="get-symbol" default_only="true"}
<div class="input-group-addon">{$SYMBOL}</div>
{/loop}
@@ -9,11 +13,14 @@
</div>
<div class="form-group">
<label for="{$categories_field_name}-value">{intl l="Applies to products in categories :"}</label>
<select required multiple size="5" class="form-control" id="{$categories_field_name}-value" name="{$categories_field_name}[value][]">
<label for="coupon-categories-id">{intl l="Applies to products in categories :"}</label>
<select required multiple size="10" class="form-control" id="coupon-categories-id" name="{$categories_field_name}[]">
{loop type="category-tree" category=0 name="list-of-categories" backend_context="1"}
<option style="padding-left: {$LEVEL * 20}px" value="{$ID}" {if in_array($ID, $categories_values)}selected="selected"{/if}>{$TITLE}</option>
<option style="padding-left: {$LEVEL * 10}px" value="{$ID}" {if in_array($ID, $categories_values)}selected="selected"{/if}>{$TITLE}</option>
{/loop}
</select>
<span class="label-help-block">{intl l='Use Ctrl+click to select (or deselect) more that one category'}</span>
</div>

View File

@@ -1,9 +1,11 @@
<div class="form-group input-{$fieldName}">
<label for="{$fieldName}" class="control-label">{$label}</label>
<div class="form-group input-coupon-amount">
<label for="coupon-amount" class="control-label">{intl l='Discount amount'}</label>
<div class="input-group">
<input id="{$fieldName}" type="money" class="form-control" name="thelia_coupon_creation[{$fieldName}]" value="{$value}" placeholder="14.50">
<input id="coupon-amount" type="money" class="form-control" name="{$fieldName}" value="{$value}" placeholder="{intl l="Amount, e.g. 12.50"}">
{loop type="currency" name="get-symbol" default_only="true"}
<div class="input-group-addon">{$SYMBOL}</div>
<div class="input-group-addon">{$SYMBOL}</div>
{/loop}
</div>
</div>

View File

@@ -1,8 +1,9 @@
<input type="hidden" name="thelia_coupon_creation[{$typeKey}]" value="0"/>
<div class="form-group input-{$fieldId}">
<label for="{$fieldId}" class="control-label">{$label}</label>
<div class="form-group input-coupon-percent">
<label for="coupon-percent" class="control-label">{intl l='Percent Discount'}</label>
<div class="input-group">
<input id="{$fieldId}" class="form-control" name="{$fieldName}[{$fieldId}]" type="text" value="{$value}"/>
<input id="coupon-percent" class="form-control" name="{$fieldName}" type="text" value="{$value}"/>
<div class="input-group-addon">%</div>
</div>
</div>

View File

@@ -1,4 +1,4 @@
<div class="form-group input-{$fieldName}">
<label for="{$fieldName}" class="control-label">{$label}</label>
<input id="{$fieldName}" type="money" class="form-control" name="thelia_coupon_creation[{$fieldName}]" value="{$value}">
<div class="form-group input-{$fieldId}">
<label for="{$fieldId}" class="control-label">{$label}</label>
<input id="{$fieldId}" type="money" class="form-control" name="{$fieldName}" value="{$value}">
</div>