diff --git a/modules/promotion/commerce_promotion.module b/modules/promotion/commerce_promotion.module index 9bb5fc59e3..0eec63d035 100644 --- a/modules/promotion/commerce_promotion.module +++ b/modules/promotion/commerce_promotion.module @@ -82,3 +82,34 @@ function commerce_promotion_entity_base_field_info(EntityTypeInterface $entity_t return $fields; } } + +/** + * Implements hook_cron(). + */ +function commerce_promotion_cron() { + /** @var \Drupal\commerce_promotion\PromotionStorageInterface $promotion_storage */ + $promotion_storage = \Drupal::service('entity_type.manager')->getStorage('commerce_promotion'); + + // Disable any promotions that have passed their end date. + $promotions = $promotion_storage->loadExpired(); + + if (!empty($promotions)) { + /** @var \Drupal\commerce_promotion\Entity\PromotionInterface $promotion */ + foreach ($promotions as $promotion) { + $promotion->setEnabled(FALSE); + $promotion->save(); + } + } + + // Disable any promotions that have met their max usage. + $promotions = $promotion_storage->loadMaxedUsage(); + + if (!empty($promotions)) { + /** @var \Drupal\commerce_promotion\Entity\PromotionInterface $promotion */ + foreach ($promotions as $promotion) { + $promotion->setEnabled(FALSE); + $promotion->save(); + } + } + +} diff --git a/modules/promotion/src/PromotionStorage.php b/modules/promotion/src/PromotionStorage.php index 63ec779417..11bc5b082f 100644 --- a/modules/promotion/src/PromotionStorage.php +++ b/modules/promotion/src/PromotionStorage.php @@ -118,4 +118,55 @@ public function loadAvailable(OrderTypeInterface $order_type, StoreInterface $st return $promotions; } + /** + * {@inheritdoc} + */ + public function loadExpired() { + $query = $this->getQuery(); + + $query + ->condition('end_date', gmdate('Y-m-d'), '<') + ->condition('status', TRUE); + + $result = $query->execute(); + + if (empty($result)) { + return []; + } + + return $this->loadMultiple($result); + } + + /** + * {@inheritdoc} + */ + public function loadMaxedUsage() { + $query = $this->getQuery(); + + $query + ->condition('usage_limit', 1, '>=') + ->condition('status', TRUE); + + $result = $query->execute(); + + if (empty($result)) { + return []; + } + + $promotions = $this->loadMultiple($result); + $maxed_promotions = []; + + // Get an array of each promotion's use count. + $promotion_uses = $this->usage->getUsageMultiple($promotions); + + /** @var \Drupal\commerce_promotion\Entity\PromotionInterface $promotion */ + foreach ($promotions as $promotion) { + if ($promotion_uses[$promotion->id()] >= $promotion->getUsageLimit()) { + $maxed_promotions[] = $promotion; + } + } + + return $maxed_promotions; + } + } diff --git a/modules/promotion/src/PromotionStorageInterface.php b/modules/promotion/src/PromotionStorageInterface.php index 5690751034..bfdabf1bf9 100644 --- a/modules/promotion/src/PromotionStorageInterface.php +++ b/modules/promotion/src/PromotionStorageInterface.php @@ -24,4 +24,20 @@ interface PromotionStorageInterface extends ContentEntityStorageInterface { */ public function loadAvailable(OrderTypeInterface $order_type, StoreInterface $store); + /** + * Return active promotions that have passed their end date. + * + * @return \Drupal\commerce_promotion\Entity\PromotionInterface[] + * The expired promotion entities. + */ + public function loadExpired(); + + /** + * Returns active promotions which have a met their maximum usage. + * + * @return \Drupal\commerce_promotion\Entity\PromotionInterface[] + * Promotions with maxed usage. + */ + public function loadMaxedUsage(); + } diff --git a/modules/promotion/tests/src/Kernel/PromotionStorageTest.php b/modules/promotion/tests/src/Kernel/PromotionStorageTest.php index 2310061a28..4e2b7ae9ae 100644 --- a/modules/promotion/tests/src/Kernel/PromotionStorageTest.php +++ b/modules/promotion/tests/src/Kernel/PromotionStorageTest.php @@ -2,7 +2,9 @@ namespace Drupal\Tests\commerce_promotion\Kernel; +use Drupal\commerce_order\Entity\Order; use Drupal\commerce_order\Entity\OrderType; +use Drupal\commerce_order\Entity\OrderItemType; use Drupal\commerce_promotion\Entity\Coupon; use Drupal\commerce_promotion\Entity\Promotion; use Drupal\Core\Datetime\DrupalDateTime; @@ -22,6 +24,20 @@ class PromotionStorageTest extends CommerceKernelTestBase { */ protected $promotionStorage; + /** + * The usage. + * + * @var \Drupal\commerce_promotion\PromotionUsageInterface + */ + protected $usage; + + /** + * The test order. + * + * @var \Drupal\commerce_order\Entity\OrderInterface + */ + protected $order; + /** * Modules to enable. * @@ -54,6 +70,25 @@ protected function setUp() { $this->installSchema('commerce_promotion', ['commerce_promotion_usage']); $this->promotionStorage = $this->container->get('entity_type.manager')->getStorage('commerce_promotion'); + $this->usage = $this->container->get('commerce_promotion.usage'); + + OrderItemType::create([ + 'id' => 'test', + 'label' => 'Test', + 'orderType' => 'default', + ])->save(); + + $this->order = Order::create([ + 'type' => 'default', + 'state' => 'completed', + 'mail' => 'test@example.com', + 'ip_address' => '127.0.0.1', + 'order_id' => '6', + 'order_number' => '6', + 'store_id' => $this->store, + 'uid' => $this->createUser()->id(), + 'order_items' => [], + ]); } /** @@ -213,4 +248,78 @@ public function testWeight() { $this->assertEquals($promotion1->label(), $promotion->label()); } + /** + * Tests that active promotions which have expired are loaded. + */ + public function testLoadExpired() { + $order_type = OrderType::load('default'); + + $valid_promotion = Promotion::create([ + 'name' => 'Valid Promotion', + 'order_types' => [$order_type], + 'stores' => [$this->store->id()], + 'status' => TRUE, + 'start_date' => gmdate('Y-m-d', time()), + 'end_date' => gmdate('Y-m-d', time() + 86400), + ]); + $this->assertEquals(SAVED_NEW, $valid_promotion->save()); + + $expired_promotion = Promotion::create([ + 'name' => 'Expired Promotion', + 'order_types' => [$order_type], + 'stores' => [$this->store->id()], + 'status' => TRUE, + 'start_date' => gmdate('Y-m-d', time() - 172800), + 'end_date' => gmdate('Y-m-d', time() - 86400), + ]); + $this->assertEquals(SAVED_NEW, $expired_promotion->save()); + + $promotions = $this->promotionStorage->loadExpired(); + $this->assertCount(1, $promotions); + + $promotion = reset($promotions); + $this->assertEquals($expired_promotion->label(), $promotion->label()); + } + + /** + * Tests that active promotions which have met their maximum usage are loaded. + */ + public function testLoadUsed() { + $order_type = OrderType::load('default'); + + $promotion1 = Promotion::create([ + 'name' => 'Promotion 1', + 'order_types' => [$order_type], + 'stores' => [$this->store->id()], + 'status' => TRUE, + 'usage_limit' => 1, + ]); + $this->assertEquals(SAVED_NEW, $promotion1->save()); + $this->usage->addUsage($this->order, $promotion1); + + $promotion2 = Promotion::create([ + 'name' => 'Promotion 2', + 'order_types' => [$order_type], + 'stores' => [$this->store->id()], + 'status' => TRUE, + 'usage_limit' => 2, + ]); + $this->assertEquals(SAVED_NEW, $promotion2->save()); + $this->usage->addUsage($this->order, $promotion2); + + $promotion3 = Promotion::create([ + 'name' => 'Promotion 3', + 'order_types' => [$order_type], + 'stores' => [$this->store->id()], + 'status' => TRUE, + ]); + $this->assertEquals(SAVED_NEW, $promotion3->save()); + + $promotions = $this->promotionStorage->loadMaxedUsage(); + $this->assertCount(1, $promotions); + + $promotion = reset($promotions); + $this->assertEquals($promotion1->label(), $promotion->label()); + } + } diff --git a/modules/promotion/tests/src/Kernel/UsageTest.php b/modules/promotion/tests/src/Kernel/UsageTest.php index 4a9f7068d4..f97cfd7d0d 100644 --- a/modules/promotion/tests/src/Kernel/UsageTest.php +++ b/modules/promotion/tests/src/Kernel/UsageTest.php @@ -103,6 +103,7 @@ protected function setUp() { 'state' => 'draft', 'mail' => 'test@example.com', 'ip_address' => '127.0.0.1', + 'order_id' => '6', 'order_number' => '6', 'store_id' => $this->store, 'uid' => $this->createUser(), @@ -237,4 +238,59 @@ public function testPromotionFiltering() { $this->assertEmpty($valid_promotions); } + /** + * Tests the Promotions module cron job. + */ + public function testPromotionCron() { + $order_type = OrderType::load($this->order->bundle()); + + // Date restricted promotions. + $valid_promotion = Promotion::create([ + 'name' => 'Valid Promotion', + 'order_types' => [$order_type], + 'stores' => [$this->store->id()], + 'status' => TRUE, + 'start_date' => gmdate('Y-m-d', time()), + 'end_date' => gmdate('Y-m-d', time() + 86400), + ]); + $this->assertEquals(SAVED_NEW, $valid_promotion->save()); + + $expired_promotion = Promotion::create([ + 'name' => 'Expired Promotion', + 'order_types' => [$order_type], + 'stores' => [$this->store->id()], + 'status' => TRUE, + 'start_date' => gmdate('Y-m-d', time() - 172800), + 'end_date' => gmdate('Y-m-d', time() - 86400), + ]); + $this->assertEquals(SAVED_NEW, $expired_promotion->save()); + + // Usage restricted promotions. + $promotion1 = Promotion::create([ + 'name' => 'Promotion 1', + 'order_types' => [$order_type], + 'stores' => [$this->store->id()], + 'status' => TRUE, + 'usage_limit' => 1, + ]); + $this->assertEquals(SAVED_NEW, $promotion1->save()); + $this->usage->addUsage($this->order, $promotion1); + + $promotion2 = Promotion::create([ + 'name' => 'Promotion 2', + 'order_types' => [$order_type], + 'stores' => [$this->store->id()], + 'status' => TRUE, + 'usage_limit' => 2, + ]); + $this->assertEquals(SAVED_NEW, $promotion2->save()); + $this->usage->addUsage($this->order, $promotion2); + + commerce_promotion_cron(); + + $valid_promotions = $this->promotionStorage->loadAvailable($order_type, $this->store); + $this->assertCount(2, $valid_promotions); + + } + }