diff --git a/core/lib/Thelia/Config/Resources/coupon.xml b/core/lib/Thelia/Config/Resources/coupon.xml
index f08746d22..530a36d56 100644
--- a/core/lib/Thelia/Config/Resources/coupon.xml
+++ b/core/lib/Thelia/Config/Resources/coupon.xml
@@ -51,6 +51,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/lib/Thelia/Coupon/Type/AbstractRemoveOnAttributeValues.php b/core/lib/Thelia/Coupon/Type/AbstractRemoveOnAttributeValues.php
new file mode 100644
index 000000000..eee1db95c
--- /dev/null
+++ b/core/lib/Thelia/Coupon/Type/AbstractRemoveOnAttributeValues.php
@@ -0,0 +1,174 @@
+
+ */
+abstract class AbstractRemoveOnAttributeValues extends CouponAbstract
+{
+ const ATTRIBUTES_AV_LIST = 'attribute_avs';
+ const ATTRIBUTE = 'attribute_id';
+
+ public $attributeAvList = array();
+ public $attribute = 0;
+
+ /**
+ * Set the value of specific coupon fields.
+ * @param Array $effects the Coupon effects params
+ */
+ protected abstract function setFieldsValue($effects);
+
+ /**
+ * Get the discount for a specific cart item.
+ *
+ * @param CartItem $cartItem the cart item
+ * @return float the discount value
+ */
+ protected abstract function getCartItemDiscount($cartItem);
+
+ /**
+ * @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->attributeAvList = isset($effects[self::ATTRIBUTES_AV_LIST]) ? $effects[self::ATTRIBUTES_AV_LIST] : array();
+
+ if (! is_array($this->attributeAvList)) $this->attributeAvList = array($this->attributeAvList);
+
+ $this->attribute = isset($effects[self::ATTRIBUTE]) ? $effects[self::ATTRIBUTE] : 0;
+
+ $this->setFieldsValue($effects);
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function exec()
+ {
+ // This coupon subtracts the specified amount from the order total
+ // for each product which uses the selected attributes
+ $discount = 0;
+
+ $cartItems = $this->facade->getCart()->getCartItems();
+
+ /** @var CartItem $cartItem */
+ foreach ($cartItems as $cartItem) {
+
+ if (! $cartItem->getPromo() || $this->isAvailableOnSpecialOffers()) {
+ $productSaleElements = $cartItem->getProductSaleElements();
+
+ $combinations = $productSaleElements->getAttributeCombinations();
+
+ /** @var AttributeCombination $combination */
+ foreach ($combinations as $combination) {
+
+ $attrValue = $combination->getAttributeAvId();
+
+ if (in_array($attrValue, $this->attributeAvList)) {
+ $discount += $this->getCartItemDiscount($cartItem);
+
+ break;
+ }
+ }
+ }
+ }
+
+ return $discount;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function drawBaseBackOfficeInputs($templateName, $otherFields)
+ {
+ return $this->facade->getParser()->render($templateName, array_merge($otherFields, [
+
+ // The attributes list field
+ 'attribute_field_name' => $this->makeCouponFieldName(self::ATTRIBUTE),
+ 'attribute_value' => $this->attribute,
+
+ // The attributes list field
+ 'attribute_av_field_name' => $this->makeCouponFieldName(self::ATTRIBUTES_AV_LIST),
+ 'attribute_av_values' => $this->attributeAvList
+ ]));
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getBaseFieldList($otherFields)
+ {
+ return array_merge($otherFields, [self::ATTRIBUTE, self::ATTRIBUTES_AV_LIST]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function checkBaseCouponFieldValue($fieldName, $fieldValue)
+ {
+ if ($fieldName === self::ATTRIBUTE) {
+ if (empty($fieldValue)) {
+ throw new \InvalidArgumentException(
+ Translator::getInstance()->trans(
+ 'Please select an attribute'
+ )
+ );
+ }
+ } elseif ($fieldName === self::ATTRIBUTES_AV_LIST) {
+ if (empty($fieldValue)) {
+ throw new \InvalidArgumentException(
+ Translator::getInstance()->trans(
+ 'Please select at least one attribute value'
+ )
+ );
+ }
+ }
+
+ return $fieldValue;
+ }
+}
diff --git a/core/lib/Thelia/Coupon/Type/RemoveAmountOnAttributeValues.php b/core/lib/Thelia/Coupon/Type/RemoveAmountOnAttributeValues.php
new file mode 100644
index 000000000..523365d08
--- /dev/null
+++ b/core/lib/Thelia/Coupon/Type/RemoveAmountOnAttributeValues.php
@@ -0,0 +1,108 @@
+
+ */
+class RemoveAmountOnAttributeValues extends AbstractRemoveOnAttributeValues
+{
+ /** @var string Service Id */
+ protected $serviceId = 'thelia.coupon.type.remove_amount_on_attribute_av';
+
+ /**
+ * @inheritdoc
+ */
+ protected function setFieldsValue($effects) {
+ // Nothing to do, the amount is processed by CouponAbstract.
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getCartItemDiscount($cartItem) {
+ return $cartItem->getQuantity() * $this->amount;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getName()
+ {
+ return $this->facade
+ ->getTranslator()
+ ->trans('Fixed amount discount for selected attribute values', array(), 'coupon');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getToolTip()
+ {
+ $toolTip = $this->facade
+ ->getTranslator()
+ ->trans(
+ 'This coupon subtracts the specified amount from the order total for each product which uses the selected attribute values. If the discount is greater than the total order, the customer will only pay the shipping, or nothing if the coupon also provides free shipping.',
+ array(),
+ 'coupon'
+ );
+
+ return $toolTip;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function drawBackOfficeInputs()
+ {
+ return $this->drawBaseBackOfficeInputs('coupon/type-fragments/remove-amount-on-attributes.html', [
+ 'amount_field_name' => $this->makeCouponFieldName(self::AMOUNT_FIELD_NAME),
+ 'amount_value' => $this->amount
+ ]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getFieldList()
+ {
+ return $this->getBaseFieldList([self::AMOUNT_FIELD_NAME]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function checkCouponFieldValue($fieldName, $fieldValue)
+ {
+ $this->checkBaseCouponFieldValue($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]
+ )
+ );
+ }
+ }
+
+ return $fieldValue;
+ }
+}
diff --git a/core/lib/Thelia/Coupon/Type/RemovePercentageOnAttributeValues.php b/core/lib/Thelia/Coupon/Type/RemovePercentageOnAttributeValues.php
new file mode 100644
index 000000000..26c55022c
--- /dev/null
+++ b/core/lib/Thelia/Coupon/Type/RemovePercentageOnAttributeValues.php
@@ -0,0 +1,114 @@
+
+ */
+class RemovePercentageOnAttributeValues extends AbstractRemoveOnAttributeValues
+{
+ const PERCENTAGE = 'percentage';
+
+ public $percentage = 0;
+
+ /** @var string Service Id */
+ protected $serviceId = 'thelia.coupon.type.remove_percentage_on_attribute_av';
+
+ /**
+ * @inheritdoc
+ */
+ protected function setFieldsValue($effects) {
+ $this->percentage = $effects[self::PERCENTAGE];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getCartItemDiscount($cartItem) {
+ return $cartItem->getQuantity() * $cartItem->getPrice() * $this->percentage;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getName()
+ {
+ return $this->facade
+ ->getTranslator()
+ ->trans('Percentage discount for selected attribute values', array(), 'coupon');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getToolTip()
+ {
+ $toolTip = $this->facade
+ ->getTranslator()
+ ->trans(
+ 'This coupon subtracts from the order total the specified percentage of each product price which uses the selected attribute values. If the discount is greater than the total order, the customer will only pay the shipping, or nothing if the coupon also provides free shipping.',
+ array(),
+ 'coupon'
+ );
+
+ return $toolTip;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function drawBackOfficeInputs()
+ {
+ return $this->drawBaseBackOfficeInputs('coupon/type-fragments/remove-percentage-on-attributes.html', [
+ 'percentage_field_name' => $this->makeCouponFieldName(self::PERCENTAGE),
+ 'percentage_value' => $this->percentage,
+ ]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getFieldList()
+ {
+ return $this->getBaseFieldList([self::PERCENTAGE]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function checkCouponFieldValue($fieldName, $fieldValue)
+ {
+ $this->checkBaseCouponFieldValue($fieldName, $fieldValue);
+
+ if ($fieldName === self::PERCENTAGE) {
+
+ $pcent = floatval($fieldValue);
+
+ if ($pcent <= 0 || $pcent > 100) {
+ 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;
+ }
+}
diff --git a/templates/backOffice/default/coupon/type-fragments/base-remove-on-attributes.html b/templates/backOffice/default/coupon/type-fragments/base-remove-on-attributes.html
new file mode 100644
index 000000000..073cdedbf
--- /dev/null
+++ b/templates/backOffice/default/coupon/type-fragments/base-remove-on-attributes.html
@@ -0,0 +1,63 @@
+{block name="discount-field"}{/block}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {intl l='Use Ctrl+click to select (or deselect) more that one attribute value'}
+
+
+
diff --git a/templates/backOffice/default/coupon/type-fragments/remove-amount-on-attributes.html b/templates/backOffice/default/coupon/type-fragments/remove-amount-on-attributes.html
new file mode 100644
index 000000000..17f68cc28
--- /dev/null
+++ b/templates/backOffice/default/coupon/type-fragments/remove-amount-on-attributes.html
@@ -0,0 +1,16 @@
+{extends file="coupon/type-fragments/base-remove-on-attributes.html"}
+
+{block name="discount-field"}
+
+{/block}
\ No newline at end of file
diff --git a/templates/backOffice/default/coupon/type-fragments/remove-percentage-on-attributes.html b/templates/backOffice/default/coupon/type-fragments/remove-percentage-on-attributes.html
new file mode 100644
index 000000000..262c10d97
--- /dev/null
+++ b/templates/backOffice/default/coupon/type-fragments/remove-percentage-on-attributes.html
@@ -0,0 +1,14 @@
+{extends file="coupon/type-fragments/base-remove-on-attributes.html"}
+
+{block name="discount-field"}
+
+{/block}
\ No newline at end of file