Skip to content

Commit 2fd5fd7

Browse files
SpartyDanbojanz
SpartyDan
authored andcommitted
Issue #2862657 by niko-, SpartyDan, bojanz: Implement usage limiting for coupons
1 parent d13fd1b commit 2fd5fd7

File tree

5 files changed

+231
-7
lines changed

5 files changed

+231
-7
lines changed

modules/promotion/commerce_promotion.install

+17
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,20 @@ function commerce_promotion_update_8202() {
9898
]);
9999
$entity_definition_update->installFieldStorageDefinition('weight', 'commerce_promotion', 'commerce_promotion', $storage_definition);
100100
}
101+
102+
/**
103+
* Add the usage_limit field to coupons.
104+
*/
105+
function commerce_promotion_update_8203() {
106+
$entity_definition_update = \Drupal::entityDefinitionUpdateManager();
107+
108+
$storage_definition = BaseFieldDefinition::create('integer')
109+
->setLabel(t('Usage limit'))
110+
->setDescription(t('The maximum number of times the coupon can be used. 0 for unlimited.'))
111+
->setDefaultValue(0)
112+
->setDisplayOptions('form', [
113+
'type' => 'commerce_usage_limit',
114+
'weight' => 4,
115+
]);
116+
$entity_definition_update->installFieldStorageDefinition('usage_limit', 'commerce_promotion_coupon', 'commerce_promotion', $storage_definition);
117+
}

modules/promotion/src/Entity/Coupon.php

+31
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,21 @@ public function setCode($code) {
6969
return $this;
7070
}
7171

72+
/**
73+
* {@inheritdoc}
74+
*/
75+
public function getUsageLimit() {
76+
return $this->get('usage_limit')->value;
77+
}
78+
79+
/**
80+
* {@inheritdoc}
81+
*/
82+
public function setUsageLimit($usage_limit) {
83+
$this->set('usage_limit', $usage_limit);
84+
return $this;
85+
}
86+
7287
/**
7388
* {@inheritdoc}
7489
*/
@@ -94,6 +109,13 @@ public function available(OrderInterface $order) {
94109
if (!$this->getPromotion()->available($order)) {
95110
return FALSE;
96111
}
112+
if ($usage_limit = $this->getUsageLimit()) {
113+
/** @var \Drupal\commerce_promotion\PromotionUsageInterface $usage */
114+
$usage = \Drupal::service('commerce_promotion.usage');
115+
if ($usage_limit <= $usage->getUsage($this->getPromotion(), $this)) {
116+
return FALSE;
117+
}
118+
}
97119

98120
return TRUE;
99121
}
@@ -133,6 +155,15 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
133155
->setDisplayConfigurable('form', TRUE)
134156
->setDisplayConfigurable('view', TRUE);
135157

158+
$fields['usage_limit'] = BaseFieldDefinition::create('integer')
159+
->setLabel(t('Usage limit'))
160+
->setDescription(t('The maximum number of times the coupon can be used. 0 for unlimited.'))
161+
->setDefaultValue(0)
162+
->setDisplayOptions('form', [
163+
'type' => 'commerce_usage_limit',
164+
'weight' => 4,
165+
]);
166+
136167
$fields['status'] = BaseFieldDefinition::create('boolean')
137168
->setLabel(t('Status'))
138169
->setDescription(t('Whether the coupon is enabled.'))

modules/promotion/src/Entity/CouponInterface.php

+21
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,27 @@ public function getCode();
4444
*/
4545
public function setCode($code);
4646

47+
/**
48+
* Gets the coupon usage limit.
49+
*
50+
* Represents the maximum number of times the coupon can be used.
51+
* 0 for unlimited.
52+
*
53+
* @return int
54+
* The coupon usage limit.
55+
*/
56+
public function getUsageLimit();
57+
58+
/**
59+
* Sets the coupon usage limit.
60+
*
61+
* @param int $usage_limit
62+
* The coupon usage limit.
63+
*
64+
* @return $this
65+
*/
66+
public function setUsageLimit($usage_limit);
67+
4768
/**
4869
* Gets whether the coupon is enabled.
4970
*

modules/promotion/src/Plugin/Field/FieldWidget/UsageLimitWidget.php

+17-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Drupal\commerce_promotion\Plugin\Field\FieldWidget;
44

5+
use Drupal\commerce_promotion\Entity\CouponInterface;
6+
use Drupal\commerce_promotion\Entity\PromotionInterface;
57
use Drupal\commerce_promotion\PromotionUsageInterface;
68
use Drupal\Core\Field\FieldDefinitionInterface;
79
use Drupal\Core\Field\FieldItemListInterface;
@@ -70,11 +72,19 @@ public static function create(ContainerInterface $container, array $configuratio
7072
* {@inheritdoc}
7173
*/
7274
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
73-
$field_name = $this->fieldDefinition->getName();
74-
/** @var \Drupal\commerce_promotion\Entity\PromotionInterface $promotion */
75-
$promotion = $items[$delta]->getEntity();
7675
$value = isset($items[$delta]->value) ? $items[$delta]->value : NULL;
77-
$usage = $this->formatPlural($this->usage->getUsage($promotion), '1 use', '@count uses');
76+
$entity = $items[$delta]->getEntity();
77+
$usage = 0;
78+
if ($entity instanceof PromotionInterface) {
79+
$usage = $this->usage->getUsage($entity);
80+
}
81+
elseif ($entity instanceof CouponInterface) {
82+
$usage = $this->usage->getUsage($entity->getPromotion(), $entity);
83+
}
84+
$formatted_usage = $this->formatPlural($usage, '1 use', '@count uses');
85+
$radio_parents = array_merge($form['#parents'], [$this->fieldDefinition->getName(), 0, 'limit']);
86+
$radio_path = array_shift($radio_parents);
87+
$radio_path .= '[' . implode('][', $radio_parents) . ']';
7888

7989
$element['limit'] = [
8090
'#type' => 'radios',
@@ -90,10 +100,10 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
90100
'#title' => $this->t('Number of uses'),
91101
'#title_display' => 'invisible',
92102
'#default_value' => $value ?: 10,
93-
'#description' => $this->t('Current usage: @usage.', ['@usage' => $usage]),
103+
'#description' => $this->t('Current usage: @usage.', ['@usage' => $formatted_usage]),
94104
'#states' => [
95105
'invisible' => [
96-
':input[name="' . $field_name . '[0][limit]"]' => ['value' => 0],
106+
':input[name="' . $radio_path . '"]' => ['value' => 0],
97107
],
98108
],
99109
];
@@ -121,7 +131,7 @@ public function massageFormValues(array $values, array $form, FormStateInterface
121131
public static function isApplicable(FieldDefinitionInterface $field_definition) {
122132
$entity_type = $field_definition->getTargetEntityTypeId();
123133
$field_name = $field_definition->getName();
124-
return $entity_type == 'commerce_promotion' && $field_name == 'usage_limit';
134+
return in_array($entity_type, ['commerce_promotion', 'commerce_promotion_coupon']) && $field_name == 'usage_limit';
125135
}
126136

127137
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
namespace Drupal\Tests\commerce_promotion\Kernel\Entity;
4+
5+
use Drupal\commerce_order\Entity\Order;
6+
use Drupal\commerce_order\Entity\OrderItem;
7+
use Drupal\commerce_order\Entity\OrderItemType;
8+
use Drupal\commerce_price\Price;
9+
use Drupal\commerce_promotion\Entity\Coupon;
10+
use Drupal\commerce_promotion\Entity\Promotion;
11+
use Drupal\Tests\commerce\Kernel\CommerceKernelTestBase;
12+
13+
/**
14+
* Tests the Coupon entity.
15+
*
16+
* @coversDefaultClass \Drupal\commerce_promotion\Entity\Coupon
17+
*
18+
* @group commerce
19+
*/
20+
class CouponTest extends CommerceKernelTestBase {
21+
22+
/**
23+
* Modules to enable.
24+
*
25+
* @var array
26+
*/
27+
public static $modules = [
28+
'entity_reference_revisions',
29+
'profile',
30+
'state_machine',
31+
'commerce_order',
32+
'commerce_product',
33+
'commerce_promotion',
34+
];
35+
36+
/**
37+
* {@inheritdoc}
38+
*/
39+
protected function setUp() {
40+
parent::setUp();
41+
42+
$this->installEntitySchema('profile');
43+
$this->installEntitySchema('commerce_order');
44+
$this->installEntitySchema('commerce_order_item');
45+
$this->installEntitySchema('commerce_promotion');
46+
$this->installEntitySchema('commerce_promotion_coupon');
47+
$this->installSchema('commerce_promotion', ['commerce_promotion_usage']);
48+
$this->installConfig([
49+
'profile',
50+
'commerce_order',
51+
'commerce_promotion',
52+
]);
53+
54+
OrderItemType::create([
55+
'id' => 'test',
56+
'label' => 'Test',
57+
'orderType' => 'default',
58+
])->save();
59+
}
60+
61+
/**
62+
* @covers ::getPromotion
63+
* @covers ::getPromotionId
64+
* @covers ::getCode
65+
* @covers ::setCode
66+
* @covers ::getUsageLimit
67+
* @covers ::setUsageLimit
68+
* @covers ::isEnabled
69+
* @covers ::setEnabled
70+
*/
71+
public function testCoupon() {
72+
$promotion = Promotion::create([
73+
'status' => FALSE,
74+
]);
75+
$promotion->save();
76+
$promotion = $this->reloadEntity($promotion);
77+
78+
$coupon = Coupon::create([
79+
'status' => FALSE,
80+
'promotion_id' => $promotion->id(),
81+
]);
82+
83+
$this->assertEquals($promotion, $coupon->getPromotion());
84+
$this->assertEquals($promotion->id(), $coupon->getPromotionId());
85+
86+
$coupon->setCode('test_code');
87+
$this->assertEquals('test_code', $coupon->getCode());
88+
89+
$coupon->setUsageLimit(10);
90+
$this->assertEquals(10, $coupon->getUsageLimit());
91+
92+
$coupon->setEnabled(TRUE);
93+
$this->assertEquals(TRUE, $coupon->isEnabled());
94+
}
95+
96+
/**
97+
* @covers ::available
98+
*/
99+
public function testAvailability() {
100+
$order_item = OrderItem::create([
101+
'type' => 'test',
102+
'quantity' => 1,
103+
'unit_price' => new Price('12.00', 'USD'),
104+
]);
105+
$order_item->save();
106+
$order = Order::create([
107+
'type' => 'default',
108+
'state' => 'draft',
109+
'mail' => '[email protected]',
110+
'ip_address' => '127.0.0.1',
111+
'order_number' => '6',
112+
'store_id' => $this->store,
113+
'uid' => $this->createUser(),
114+
'order_items' => [$order_item],
115+
]);
116+
$order->setRefreshState(Order::REFRESH_SKIP);
117+
$order->save();
118+
119+
$promotion = Promotion::create([
120+
'order_types' => ['default'],
121+
'stores' => [$this->store->id()],
122+
'usage_limit' => 1,
123+
'start_date' => '2017-01-01',
124+
'status' => TRUE,
125+
]);
126+
$promotion->save();
127+
128+
$coupon = Coupon::create([
129+
'promotion_id' => $promotion->id(),
130+
'code' => 'coupon_code',
131+
'usage_limit' => 1,
132+
'status' => TRUE,
133+
]);
134+
$coupon->save();
135+
$this->assertTrue($coupon->available($order));
136+
137+
$coupon->setEnabled(FALSE);
138+
$this->assertFalse($coupon->available($order));
139+
$coupon->setEnabled(TRUE);
140+
141+
\Drupal::service('commerce_promotion.usage')->addUsage($order, $promotion, $coupon);
142+
$this->assertFalse($coupon->available($order));
143+
}
144+
145+
}

0 commit comments

Comments
 (0)