From 72b5f7030164e1f28f937e77470e0f356a3015cc Mon Sep 17 00:00:00 2001 From: Hunor Kedves Date: Tue, 9 Jul 2024 09:53:23 +0300 Subject: [PATCH 01/32] Added Promotion and Coupon models --- composer.json | 3 +- src/Promotion/.gitattributes | 6 ++ .../.github/workflows/close-pull-request.yml | 13 +++ src/Promotion/.github/workflows/tests.yml | 28 ++++++ src/Promotion/LICENSE.md | 21 ++++ src/Promotion/Models/Coupon.php | 33 +++++++ src/Promotion/Models/Promotion.php | 43 ++++++++ .../Providers/ModuleServiceProvider.php | 11 +++ src/Promotion/Tests/AAASmokeTest.php | 33 +++++++ src/Promotion/Tests/CouponTest.php | 67 +++++++++++++ src/Promotion/Tests/PromotionTest.php | 97 +++++++++++++++++++ src/Promotion/Tests/TestCase.php | 53 ++++++++++ .../Tests/factories/PromotionFactory.php | 24 +++++ src/Promotion/composer.json | 38 ++++++++ src/Promotion/phpunit.xml | 12 +++ ...4_06_08_131453_create_promotions_table.php | 35 +++++++ ...2024_06_08_164653_create_coupons_table.php | 31 ++++++ src/Promotion/resources/manifest.php | 8 ++ 18 files changed, 555 insertions(+), 1 deletion(-) create mode 100644 src/Promotion/.gitattributes create mode 100644 src/Promotion/.github/workflows/close-pull-request.yml create mode 100644 src/Promotion/.github/workflows/tests.yml create mode 100644 src/Promotion/LICENSE.md create mode 100644 src/Promotion/Models/Coupon.php create mode 100644 src/Promotion/Models/Promotion.php create mode 100644 src/Promotion/Providers/ModuleServiceProvider.php create mode 100644 src/Promotion/Tests/AAASmokeTest.php create mode 100644 src/Promotion/Tests/CouponTest.php create mode 100644 src/Promotion/Tests/PromotionTest.php create mode 100644 src/Promotion/Tests/TestCase.php create mode 100644 src/Promotion/Tests/factories/PromotionFactory.php create mode 100644 src/Promotion/composer.json create mode 100644 src/Promotion/phpunit.xml create mode 100644 src/Promotion/resources/database/migrations/2024_06_08_131453_create_promotions_table.php create mode 100644 src/Promotion/resources/database/migrations/2024_06_08_164653_create_coupons_table.php create mode 100644 src/Promotion/resources/manifest.php diff --git a/composer.json b/composer.json index 01594ad4..a9bffae6 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,8 @@ "vanilo/properties": "self.version", "vanilo/shipment": "self.version", "vanilo/support": "self.version", - "vanilo/taxes": "self.version" + "vanilo/taxes": "self.version", + "vanilo/promotion": "self.version" }, "autoload": { "psr-4": { diff --git a/src/Promotion/.gitattributes b/src/Promotion/.gitattributes new file mode 100644 index 00000000..777b69dd --- /dev/null +++ b/src/Promotion/.gitattributes @@ -0,0 +1,6 @@ +* text=auto + +/.github export-ignore +/Tests export-ignore +.gitattributes export-ignore +phpunit.xml export-ignore diff --git a/src/Promotion/.github/workflows/close-pull-request.yml b/src/Promotion/.github/workflows/close-pull-request.yml new file mode 100644 index 00000000..113f8919 --- /dev/null +++ b/src/Promotion/.github/workflows/close-pull-request.yml @@ -0,0 +1,13 @@ +name: Close Pull Request + +on: + pull_request_target: + types: [opened] + +jobs: + run: + runs-on: ubuntu-latest + steps: + - uses: superbrothers/close-pull-request@v3 + with: + comment: "Thank you for your pull request. However, you have submitted this PR on a Vanilo sub-module which is a read-only split of `vanilo/framework`. Please submit your PR on the https://github.com/vanilophp/framework repository.

Thanks!" diff --git a/src/Promotion/.github/workflows/tests.yml b/src/Promotion/.github/workflows/tests.yml new file mode 100644 index 00000000..a8810fdf --- /dev/null +++ b/src/Promotion/.github/workflows/tests.yml @@ -0,0 +1,28 @@ +name: tests + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + matrix: + php: ['8.2', '8.3'] + laravel: ['10.43', '10.48', '11.0', '11.5'] + name: PHP ${{ matrix.php }} Laravel ${{ matrix.laravel }} + steps: + - name: Checkout + uses: actions/checkout@master + - name: Installing PHP + uses: shivammathur/setup-php@master + with: + php-version: ${{ matrix.php }} + extensions: mbstring, json, sqlite3 + tools: composer:v2 + - name: Lock Laravel Version + run: composer require "illuminate/support:${{ matrix.laravel }}.*" --no-update -v && composer require "illuminate/console:${{ matrix.laravel }}.*" --no-update -v + - name: Composer Install + run: composer install --prefer-dist --no-progress --no-interaction + - name: Run Tests + run: php vendor/bin/phpunit --testdox diff --git a/src/Promotion/LICENSE.md b/src/Promotion/LICENSE.md new file mode 100644 index 00000000..38695c2a --- /dev/null +++ b/src/Promotion/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright (c) 2019 Attila Fulop + +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. diff --git a/src/Promotion/Models/Coupon.php b/src/Promotion/Models/Coupon.php new file mode 100644 index 00000000..94c0f36e --- /dev/null +++ b/src/Promotion/Models/Coupon.php @@ -0,0 +1,33 @@ + 'datetime', + ]; + + public function promotion(): BelongsTo + { + return $this->belongsTo(Promotion::class); + } +} diff --git a/src/Promotion/Models/Promotion.php b/src/Promotion/Models/Promotion.php new file mode 100644 index 00000000..57d4f58b --- /dev/null +++ b/src/Promotion/Models/Promotion.php @@ -0,0 +1,43 @@ + 'datetime', + 'ends_at' => 'datetime', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + public function coupons(): HasMany + { + return $this->hasMany(Coupon::class); + } +} diff --git a/src/Promotion/Providers/ModuleServiceProvider.php b/src/Promotion/Providers/ModuleServiceProvider.php new file mode 100644 index 00000000..1877e3fd --- /dev/null +++ b/src/Promotion/Providers/ModuleServiceProvider.php @@ -0,0 +1,11 @@ +assertTrue(true); + } + + /** + * Test for minimum PHP version + * + * @depends smoke + * @test + */ + public function php_version_satisfies_requirements() + { + $this->assertFalse( + version_compare(PHP_VERSION, self::MIN_PHP_VERSION, '<'), + 'PHP version ' . self::MIN_PHP_VERSION . ' or greater is required but only ' + . PHP_VERSION . ' found.' + ); + } +} diff --git a/src/Promotion/Tests/CouponTest.php b/src/Promotion/Tests/CouponTest.php new file mode 100644 index 00000000..a8e2f985 --- /dev/null +++ b/src/Promotion/Tests/CouponTest.php @@ -0,0 +1,67 @@ + factory(Promotion::class)->create()->id, + 'code' => 'coupon', + 'per_customer_usage_limit' => 2, + 'usage_limit' => 4, + 'used' => 4, + 'expiresAt' => Carbon::now()->endOfDay()->toDateTimeString(), + ]); + + $this->assertEquals('coupon', $coupon->code); + $this->assertEquals(2, $coupon->per_customer_usage_limit); + $this->assertEquals(4, $coupon->usage_limit); + $this->assertEquals(4, $coupon->used); + $this->assertEquals(Carbon::now()->endOfDay()->toDateTimeString(), $coupon->expiresAt); + } + + /** @test */ + public function all_mutable_fields_can_be_set() + { + $coupon = new Coupon(); + + $coupon->code = 'coupon'; + $coupon->per_customer_usage_limit = 1; + $coupon->usage_limit = 15; + $coupon->used = 4; + $coupon->expiresAt = Carbon::now()->endOfDay()->toDateTimeString(); + + $this->assertEquals('coupon', $coupon->code); + $this->assertEquals(1, $coupon->per_customer_usage_limit); + $this->assertEquals(15, $coupon->usage_limit); + $this->assertEquals(4, $coupon->used); + $this->assertEquals(Carbon::now()->endOfDay()->toDateTimeString(), $coupon->expiresAt); + } + + /** @test */ + public function code_must_be_unique() + { + $this->expectExceptionMessageMatches('/UNIQUE constraint failed/'); + + $c1 = Coupon::create([ + 'code' => 'coupon-1', + 'promotion_id' => factory(Promotion::class)->create()->id, + ]); + + $c2 = Coupon::create([ + 'code' => 'coupon-1', + 'promotion_id' => factory(Promotion::class)->create()->id, + ]); + + $this->assertNotEquals($c1->code, $c2->code); + } +} diff --git a/src/Promotion/Tests/PromotionTest.php b/src/Promotion/Tests/PromotionTest.php new file mode 100644 index 00000000..d6fd32b3 --- /dev/null +++ b/src/Promotion/Tests/PromotionTest.php @@ -0,0 +1,97 @@ +startOfDay()->toDateTimeString(); + $nextMonth = Carbon::now()->addMonths(1)->endOfDay()->toDateTimeString(); + + $promotion = Promotion::create([ + 'code' => 'PROMO-1', + 'name' => 'Awesome promotion', + 'description' => 'The description', + 'priority' => 4, + 'exclusive' => false, + 'usage_limit' => 15, + 'used' => 1, + 'coupon_based' => false, + 'starts_at' => $now, + 'ends_at' => $nextMonth, + 'applies_to_discounted' => false, + + ]); + + $this->assertEquals('PROMO-1', $promotion->code); + $this->assertEquals('Awesome promotion', $promotion->name); + $this->assertEquals('The description', $promotion->description); + $this->assertEquals(4, $promotion->priority); + $this->assertFalse($promotion->exclusive); + $this->assertEquals(15, $promotion->usage_limit); + $this->assertEquals(1, $promotion->used); + $this->assertFalse($promotion->coupon_based); + $this->assertEquals($now, $promotion->starts_at); + $this->assertEquals($nextMonth, $promotion->ends_at); + $this->assertFalse($promotion->applies_to_discounted); + } + + /** @test */ + public function all_mutable_fields_can_be_set() + { + $now = Carbon::now()->startOfDay()->startOfDay()->toDateTimeString(); + $nextMonth = Carbon::now()->addMonth()->endOfDay()->toDateTimeString(); + + $promotion = new Promotion(); + + $promotion->code = 'PROMO-1'; + $promotion->name = 'Awesome promotion'; + $promotion->description = 'The description'; + $promotion->priority = 4; + $promotion->exclusive = false; + $promotion->usage_limit = 15; + $promotion->used = 1; + $promotion->coupon_based = false; + $promotion->applies_to_discounted = false; + $promotion->starts_at = $now; + $promotion->ends_at = $nextMonth; + + + $this->assertEquals('PROMO-1', $promotion->code); + $this->assertEquals('Awesome promotion', $promotion->name); + $this->assertEquals('The description', $promotion->description); + $this->assertEquals(4, $promotion->priority); + $this->assertFalse($promotion->exclusive); + $this->assertEquals(15, $promotion->usage_limit); + $this->assertEquals(1, $promotion->used); + $this->assertFalse($promotion->coupon_based); + $this->assertEquals($now, $promotion->starts_at->toDateTimeString()); + $this->assertEquals($nextMonth, $promotion->ends_at->toDateTimeString()); + $this->assertFalse($promotion->applies_to_discounted); + } + + /** @test */ + public function code_must_be_unique() + { + $this->expectExceptionMessageMatches('/UNIQUE constraint failed/'); + + $p1 = Promotion::create([ + 'name' => 'Promo 1', + 'code' => 'PROMO-1', + ]); + + $p2 = Promotion::create([ + 'name' => 'Promo 2', + 'code' => 'PROMO-1', + ]); + + $this->assertNotEquals($p1->code, $p2->code); + } +} diff --git a/src/Promotion/Tests/TestCase.php b/src/Promotion/Tests/TestCase.php new file mode 100644 index 00000000..5f2431e8 --- /dev/null +++ b/src/Promotion/Tests/TestCase.php @@ -0,0 +1,53 @@ +setUpDatabase($this->app); + $this->withFactories(__DIR__.'/factories'); + } + + protected function getPackageProviders($app) + { + return [ + ConcordServiceProvider::class, + ]; + } + + protected function getEnvironmentSetUp($app) + { + $app['config']->set('database.default', 'sqlite'); + $app['config']->set('database.connections.sqlite', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ]); + } + + protected function setUpDatabase($app) + { + //$this->loadMigrationsFrom(__DIR__.'/migrations'); + Artisan::call('migrate', ['--force' => true]); + } + + protected function resolveApplicationConfiguration($app) + { + parent::resolveApplicationConfiguration($app); + + $app['config']->set('concord.modules', [ + PromotionModule::class, + ]); + } +} diff --git a/src/Promotion/Tests/factories/PromotionFactory.php b/src/Promotion/Tests/factories/PromotionFactory.php new file mode 100644 index 00000000..2001891f --- /dev/null +++ b/src/Promotion/Tests/factories/PromotionFactory.php @@ -0,0 +1,24 @@ +define(\Vanilo\Promotion\Models\Promotion::class, function (Faker $faker) { + static $password; + + return [ + 'code' => $faker->word(), + 'name' => $faker->name(), + 'description' => $faker->text(), + 'priority' => 4, + 'exclusive' => false, + 'usage_limit' => 15, + 'used' => 0, + 'coupon_based' => false, + 'starts_at' => null, + 'ends_at' => null, + 'applies_to_discounted' => false, + ]; +}); + diff --git a/src/Promotion/composer.json b/src/Promotion/composer.json new file mode 100644 index 00000000..1da914cc --- /dev/null +++ b/src/Promotion/composer.json @@ -0,0 +1,38 @@ +{ + "name": "vanilo/promotion", + "description": "Vanilo Promotion Module", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": ["promotion", "ecommerce", "vanilo", "laravel"], + "support": { + "issues": "https://github.com/vanilophp/framework/issues", + "source": "https://github.com/vanilophp/promotion" + }, + "authors": [ + { + "name": "Attila Fulop", + "homepage": "https://github.com/fulopattila122" + } + ], + "require": { + "php": "^8.2", + "konekt/concord": "^1.13", + "laravel/framework": "^10.43|^11.0", + "vanilo/support": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "orchestra/testbench": "^8.0|^9.0", + "laravel/legacy-factories": "^1.0" + }, + "autoload": { + "psr-4": { "Vanilo\\Promotion\\": "" } + }, + "extra": { + "branch-alias": { + "dev-master": "dev" + } + } +} diff --git a/src/Promotion/phpunit.xml b/src/Promotion/phpunit.xml new file mode 100644 index 00000000..bfd33d1a --- /dev/null +++ b/src/Promotion/phpunit.xml @@ -0,0 +1,12 @@ + + + + + + ./Tests + + + diff --git a/src/Promotion/resources/database/migrations/2024_06_08_131453_create_promotions_table.php b/src/Promotion/resources/database/migrations/2024_06_08_131453_create_promotions_table.php new file mode 100644 index 00000000..03bc3222 --- /dev/null +++ b/src/Promotion/resources/database/migrations/2024_06_08_131453_create_promotions_table.php @@ -0,0 +1,35 @@ +id(); + + $table->string('code')->unique(); + $table->string('name'); + $table->text('description')->nullable(); + $table->integer('priority')->default(0); + $table->boolean('exclusive')->default(false); + $table->unsignedInteger('usage_limit')->nullable(); + $table->unsignedInteger('used')->default(0); + $table->boolean('coupon_based')->default(0); + $table->dateTime('starts_at')->nullable(); + $table->dateTime('ends_at')->nullable(); + $table->boolean('applies_to_discounted')->default(1); + + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::drop('promotions'); + } +}; diff --git a/src/Promotion/resources/database/migrations/2024_06_08_164653_create_coupons_table.php b/src/Promotion/resources/database/migrations/2024_06_08_164653_create_coupons_table.php new file mode 100644 index 00000000..a17affbc --- /dev/null +++ b/src/Promotion/resources/database/migrations/2024_06_08_164653_create_coupons_table.php @@ -0,0 +1,31 @@ +id(); + $table->unsignedInteger('promotion_id'); + $table->foreign('promotion_id')->references('id')->on('promotions'); + + $table->string('code')->unique(); + $table->unsignedInteger('usage_limit')->nullable(); + $table->unsignedInteger('per_customer_usage_limit')->nullable(); + $table->unsignedInteger('used')->default(0); + $table->dateTime('expires_at')->nullable(); + + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::drop('coupons'); + } +}; diff --git a/src/Promotion/resources/manifest.php b/src/Promotion/resources/manifest.php new file mode 100644 index 00000000..86e68824 --- /dev/null +++ b/src/Promotion/resources/manifest.php @@ -0,0 +1,8 @@ + 'Vanilo Promotion Module', + 'version' => 'dev' +]; From 3f8c56b5c3bffad17aaec779e56879b410ae9f52 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 9 Jul 2024 06:53:35 +0000 Subject: [PATCH 02/32] Apply fixes from StyleCI --- src/Promotion/Tests/PromotionTest.php | 2 -- src/Promotion/Tests/TestCase.php | 2 +- src/Promotion/Tests/factories/PromotionFactory.php | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Promotion/Tests/PromotionTest.php b/src/Promotion/Tests/PromotionTest.php index d6fd32b3..83b9bd36 100644 --- a/src/Promotion/Tests/PromotionTest.php +++ b/src/Promotion/Tests/PromotionTest.php @@ -27,7 +27,6 @@ public function all_mutable_fields_can_be_mass_assigned() 'starts_at' => $now, 'ends_at' => $nextMonth, 'applies_to_discounted' => false, - ]); $this->assertEquals('PROMO-1', $promotion->code); @@ -63,7 +62,6 @@ public function all_mutable_fields_can_be_set() $promotion->starts_at = $now; $promotion->ends_at = $nextMonth; - $this->assertEquals('PROMO-1', $promotion->code); $this->assertEquals('Awesome promotion', $promotion->name); $this->assertEquals('The description', $promotion->description); diff --git a/src/Promotion/Tests/TestCase.php b/src/Promotion/Tests/TestCase.php index 5f2431e8..44a82d7d 100644 --- a/src/Promotion/Tests/TestCase.php +++ b/src/Promotion/Tests/TestCase.php @@ -16,7 +16,7 @@ protected function setUp(): void parent::setUp(); $this->setUpDatabase($this->app); - $this->withFactories(__DIR__.'/factories'); + $this->withFactories(__DIR__ . '/factories'); } protected function getPackageProviders($app) diff --git a/src/Promotion/Tests/factories/PromotionFactory.php b/src/Promotion/Tests/factories/PromotionFactory.php index 2001891f..8903462d 100644 --- a/src/Promotion/Tests/factories/PromotionFactory.php +++ b/src/Promotion/Tests/factories/PromotionFactory.php @@ -21,4 +21,3 @@ 'applies_to_discounted' => false, ]; }); - From cc18de69271bf714f92fc1ba5d5b509fd645ecd5 Mon Sep 17 00:00:00 2001 From: Hunor Kedves Date: Tue, 9 Jul 2024 09:59:07 +0300 Subject: [PATCH 03/32] Class fix --- src/Promotion/Models/Coupon.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Promotion/Models/Coupon.php b/src/Promotion/Models/Coupon.php index 94c0f36e..dc65a9e7 100644 --- a/src/Promotion/Models/Coupon.php +++ b/src/Promotion/Models/Coupon.php @@ -4,7 +4,7 @@ namespace Vanilo\Promotion\Models; -use Carbon\Carbon; +use Illuminate\Support\Carbon; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; From 622d4a7f01a7b674195658c3d88eec651ad10ed8 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 9 Jul 2024 06:59:23 +0000 Subject: [PATCH 04/32] Apply fixes from StyleCI --- src/Promotion/Models/Coupon.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Promotion/Models/Coupon.php b/src/Promotion/Models/Coupon.php index dc65a9e7..5fbbe584 100644 --- a/src/Promotion/Models/Coupon.php +++ b/src/Promotion/Models/Coupon.php @@ -4,9 +4,9 @@ namespace Vanilo\Promotion\Models; -use Illuminate\Support\Carbon; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Support\Carbon; /** * @property int $id From 5f5978d44ef9f8155678141559c09f014b14828b Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:37:33 +0200 Subject: [PATCH 05/32] Added the Promotion module to the build scripts + test suites --- build-tools/release.sh | 2 +- build-tools/split.sh | 2 ++ composer.json | 4 ++-- module-status.md | 1 + phpunit.xml.dist | 3 +++ src/Promotion/README.md | 21 +++++++++++++++++++++ src/Promotion/composer.json | 9 ++++++--- src/Promotion/resources/manifest.php | 2 +- 8 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 src/Promotion/README.md diff --git a/build-tools/release.sh b/build-tools/release.sh index 6fbf6ce7..d1539d2c 100755 --- a/build-tools/release.sh +++ b/build-tools/release.sh @@ -49,7 +49,7 @@ git tag $VERSION git push origin --tags # Tag Components -for REMOTE in adjustments cart category channel checkout contracts links master-product order payment product properties shipment support taxes +for REMOTE in adjustments cart category channel checkout contracts links master-product order payment product promotion properties shipment support taxes do echo "" echo "" diff --git a/build-tools/split.sh b/build-tools/split.sh index 6c40e86e..30e47a3f 100755 --- a/build-tools/split.sh +++ b/build-tools/split.sh @@ -35,6 +35,7 @@ remote master-product remote order remote payment remote product +remote promotion remote properties remote shipment remote support @@ -51,6 +52,7 @@ split 'src/MasterProduct' master-product split 'src/Order' order split 'src/Payment' payment split 'src/Product' product +split 'src/Promotion' promotion split 'src/Properties' properties split 'src/Shipment' shipment split 'src/Support' support 8683e47dd2dbd15ac2ceac4dcfae405c4b271aff diff --git a/composer.json b/composer.json index a9bffae6..84872e00 100644 --- a/composer.json +++ b/composer.json @@ -42,11 +42,11 @@ "vanilo/order": "self.version", "vanilo/payment": "self.version", "vanilo/product": "self.version", + "vanilo/promotion": "self.version", "vanilo/properties": "self.version", "vanilo/shipment": "self.version", "vanilo/support": "self.version", - "vanilo/taxes": "self.version", - "vanilo/promotion": "self.version" + "vanilo/taxes": "self.version" }, "autoload": { "psr-4": { diff --git a/module-status.md b/module-status.md index 8c5a1d04..9ebe1868 100644 --- a/module-status.md +++ b/module-status.md @@ -17,6 +17,7 @@ | Order | [![Tests](https://img.shields.io/github/actions/workflow/status/vanilophp/order/tests.yml?branch=master&style=flat-square)](https://github.com/vanilophp/order/actions?query=workflow%3Atests) | [![Packagist version](https://img.shields.io/packagist/v/vanilo/order.svg?style=flat-square&include_prereleases)](https://packagist.org/packages/vanilo/order) | | Payment | [![Tests](https://img.shields.io/github/actions/workflow/status/vanilophp/payment/tests.yml?branch=master&style=flat-square)](https://github.com/vanilophp/payment/actions?query=workflow%3Atests) | [![Packagist version](https://img.shields.io/packagist/v/vanilo/payment.svg?style=flat-square&include_prereleases)](https://packagist.org/packages/vanilo/payment) | | Product | [![Tests](https://img.shields.io/github/actions/workflow/status/vanilophp/product/tests.yml?branch=master&style=flat-square)](https://github.com/vanilophp/product/actions?query=workflow%3Atests) | [![Packagist version](https://img.shields.io/packagist/v/vanilo/product.svg?style=flat-square&include_prereleases)](https://packagist.org/packages/vanilo/product) | +| Promotion | [![Tests](https://img.shields.io/github/actions/workflow/status/vanilophp/promotion/tests.yml?branch=master&style=flat-square)](https://github.com/vanilophp/promotion/actions?query=workflow%3Atests) | [![Packagist version](https://img.shields.io/packagist/v/vanilo/promotion.svg?style=flat-square&include_prereleases)](https://packagist.org/packages/vanilo/promotion) | | Properties | [![Tests](https://img.shields.io/github/actions/workflow/status/vanilophp/properties/tests.yml?branch=master&style=flat-square)](https://github.com/vanilophp/properties/actions?query=workflow%3Atests) | [![Packagist version](https://img.shields.io/packagist/v/vanilo/properties.svg?style=flat-square&include_prereleases)](https://packagist.org/packages/vanilo/properties) | | Shipment | [![Tests](https://img.shields.io/github/actions/workflow/status/vanilophp/shipment/tests.yml?branch=master&style=flat-square)](https://github.com/vanilophp/shipment/actions?query=workflow%3Atests) | [![Packagist version](https://img.shields.io/packagist/v/vanilo/shipment.svg?style=flat-square&include_prereleases)](https://packagist.org/packages/vanilo/shipment) | | Support | [![Tests](https://img.shields.io/github/actions/workflow/status/vanilophp/support/tests.yml?branch=master&style=flat-square)](https://github.com/vanilophp/support/actions?query=workflow%3Atests) | [![Packagist version](https://img.shields.io/packagist/v/vanilo/support.svg?style=flat-square&include_prereleases)](https://packagist.org/packages/vanilo/support) | diff --git a/phpunit.xml.dist b/phpunit.xml.dist index bc8c130b..3725d88f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,6 +16,9 @@ src/Properties/Tests + + src/Promotion/Tests + src/Category/Tests diff --git a/src/Promotion/README.md b/src/Promotion/README.md new file mode 100644 index 00000000..b03c9bd6 --- /dev/null +++ b/src/Promotion/README.md @@ -0,0 +1,21 @@ +# Vanilo Promotion Module + +[![Tests](https://img.shields.io/github/actions/workflow/status/vanilophp/promotion/tests.yml?branch=master&style=flat-square)](https://github.com/vanilophp/promotion/actions?query=workflow%3Atests) +[![Packagist version](https://img.shields.io/packagist/v/vanilo/promotion.svg?style=flat-square)](https://packagist.org/packages/vanilo/promotion) +[![Packagist downloads](https://img.shields.io/packagist/dt/vanilo/promotion.svg?style=flat-square)](https://packagist.org/packages/vanilo/promotion) +[![MIT Software License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE.md) + +This is the standalone Promotion module of the [Vanilo E-commerce Framework](https://vanilo.io). + +## Installation + +(As Standalone Component) + +1. `composer require vanilo/promotion` +2. `php artisan vendor:publish --provider="Konekt\Concord\ConcordServiceProvider"` +3. Add `Vanilo\Promotion\Providers\ModuleServiceProvider::class` to modules in `config/concord.php` +4. `php artisan migrate` + +## Usage + +See the [Vanilo Promotions Documentation](https://vanilo.io/docs/4.x/promotions) for more details. diff --git a/src/Promotion/composer.json b/src/Promotion/composer.json index 1da914cc..be30e039 100644 --- a/src/Promotion/composer.json +++ b/src/Promotion/composer.json @@ -11,6 +11,10 @@ "source": "https://github.com/vanilophp/promotion" }, "authors": [ + { + "name": "Hunor Kedves", + "homepage": "https://github.com/kedves" + }, { "name": "Attila Fulop", "homepage": "https://github.com/fulopattila122" @@ -24,15 +28,14 @@ }, "require-dev": { "phpunit/phpunit": "^10.0", - "orchestra/testbench": "^8.0|^9.0", - "laravel/legacy-factories": "^1.0" + "orchestra/testbench": "^8.0|^9.0" }, "autoload": { "psr-4": { "Vanilo\\Promotion\\": "" } }, "extra": { "branch-alias": { - "dev-master": "dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Promotion/resources/manifest.php b/src/Promotion/resources/manifest.php index 86e68824..12fecca7 100644 --- a/src/Promotion/resources/manifest.php +++ b/src/Promotion/resources/manifest.php @@ -4,5 +4,5 @@ return [ 'name' => 'Vanilo Promotion Module', - 'version' => 'dev' + 'version' => '4.2-dev' ]; From 8a0e8ee3c14cf2ad8dfd9210c3245cc4489fdcc9 Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:39:20 +0200 Subject: [PATCH 06/32] Added the missing Concord-orchestration to the Promotion + Coupon modules --- src/Promotion/Contracts/Coupon.php | 20 ++++++++++++++++++ src/Promotion/Contracts/Promotion.php | 20 ++++++++++++++++++ src/Promotion/Models/Coupon.php | 3 ++- src/Promotion/Models/CouponProxy.php | 21 +++++++++++++++++++ src/Promotion/Models/Promotion.php | 3 ++- src/Promotion/Models/PromotionProxy.php | 21 +++++++++++++++++++ .../Providers/ModuleServiceProvider.php | 6 ++++++ src/Promotion/Tests/AAASmokeTest.php | 6 ++---- src/Promotion/Tests/TestCase.php | 1 - 9 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 src/Promotion/Contracts/Coupon.php create mode 100644 src/Promotion/Contracts/Promotion.php create mode 100644 src/Promotion/Models/CouponProxy.php create mode 100644 src/Promotion/Models/PromotionProxy.php diff --git a/src/Promotion/Contracts/Coupon.php b/src/Promotion/Contracts/Coupon.php new file mode 100644 index 00000000..a492f601 --- /dev/null +++ b/src/Promotion/Contracts/Coupon.php @@ -0,0 +1,20 @@ +assertTrue(true); diff --git a/src/Promotion/Tests/TestCase.php b/src/Promotion/Tests/TestCase.php index 44a82d7d..95062572 100644 --- a/src/Promotion/Tests/TestCase.php +++ b/src/Promotion/Tests/TestCase.php @@ -38,7 +38,6 @@ protected function getEnvironmentSetUp($app) protected function setUpDatabase($app) { - //$this->loadMigrationsFrom(__DIR__.'/migrations'); Artisan::call('migrate', ['--force' => true]); } From 27bca59d3870d23772d0d13e67484748be592a19 Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:40:19 +0200 Subject: [PATCH 07/32] Removed the `code` field from the Promotion table/model - Renamed the `coupon_based` field to `is_coupon_based` --- src/Promotion/Tests/PromotionTest.php | 38 ++++++------------- ...4_06_08_131453_create_promotions_table.php | 9 ++--- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/src/Promotion/Tests/PromotionTest.php b/src/Promotion/Tests/PromotionTest.php index 83b9bd36..f97c9fd1 100644 --- a/src/Promotion/Tests/PromotionTest.php +++ b/src/Promotion/Tests/PromotionTest.php @@ -9,6 +9,14 @@ class PromotionTest extends TestCase { + /** @test */ + public function it_can_be_created_with_minimal_data() + { + $promotion = Promotion::create(['name' => 'Sample Promotion']); + $this->assertInstanceOf(Promotion::class, $promotion); + $this->assertEquals('Sample Promotion', $promotion->name); + } + /** @test */ public function all_mutable_fields_can_be_mass_assigned() { @@ -16,27 +24,25 @@ public function all_mutable_fields_can_be_mass_assigned() $nextMonth = Carbon::now()->addMonths(1)->endOfDay()->toDateTimeString(); $promotion = Promotion::create([ - 'code' => 'PROMO-1', 'name' => 'Awesome promotion', 'description' => 'The description', 'priority' => 4, 'exclusive' => false, 'usage_limit' => 15, 'used' => 1, - 'coupon_based' => false, + 'is_coupon_based' => false, 'starts_at' => $now, 'ends_at' => $nextMonth, 'applies_to_discounted' => false, ]); - $this->assertEquals('PROMO-1', $promotion->code); $this->assertEquals('Awesome promotion', $promotion->name); $this->assertEquals('The description', $promotion->description); $this->assertEquals(4, $promotion->priority); $this->assertFalse($promotion->exclusive); $this->assertEquals(15, $promotion->usage_limit); $this->assertEquals(1, $promotion->used); - $this->assertFalse($promotion->coupon_based); + $this->assertFalse($promotion->is_coupon_based); $this->assertEquals($now, $promotion->starts_at); $this->assertEquals($nextMonth, $promotion->ends_at); $this->assertFalse($promotion->applies_to_discounted); @@ -50,46 +56,26 @@ public function all_mutable_fields_can_be_set() $promotion = new Promotion(); - $promotion->code = 'PROMO-1'; $promotion->name = 'Awesome promotion'; $promotion->description = 'The description'; $promotion->priority = 4; $promotion->exclusive = false; $promotion->usage_limit = 15; $promotion->used = 1; - $promotion->coupon_based = false; + $promotion->is_coupon_based = false; $promotion->applies_to_discounted = false; $promotion->starts_at = $now; $promotion->ends_at = $nextMonth; - $this->assertEquals('PROMO-1', $promotion->code); $this->assertEquals('Awesome promotion', $promotion->name); $this->assertEquals('The description', $promotion->description); $this->assertEquals(4, $promotion->priority); $this->assertFalse($promotion->exclusive); $this->assertEquals(15, $promotion->usage_limit); $this->assertEquals(1, $promotion->used); - $this->assertFalse($promotion->coupon_based); + $this->assertFalse($promotion->is_coupon_based); $this->assertEquals($now, $promotion->starts_at->toDateTimeString()); $this->assertEquals($nextMonth, $promotion->ends_at->toDateTimeString()); $this->assertFalse($promotion->applies_to_discounted); } - - /** @test */ - public function code_must_be_unique() - { - $this->expectExceptionMessageMatches('/UNIQUE constraint failed/'); - - $p1 = Promotion::create([ - 'name' => 'Promo 1', - 'code' => 'PROMO-1', - ]); - - $p2 = Promotion::create([ - 'name' => 'Promo 2', - 'code' => 'PROMO-1', - ]); - - $this->assertNotEquals($p1->code, $p2->code); - } } diff --git a/src/Promotion/resources/database/migrations/2024_06_08_131453_create_promotions_table.php b/src/Promotion/resources/database/migrations/2024_06_08_131453_create_promotions_table.php index 03bc3222..ae15fc7b 100644 --- a/src/Promotion/resources/database/migrations/2024_06_08_131453_create_promotions_table.php +++ b/src/Promotion/resources/database/migrations/2024_06_08_131453_create_promotions_table.php @@ -11,19 +11,16 @@ public function up(): void { Schema::create('promotions', function (Blueprint $table) { $table->id(); - - $table->string('code')->unique(); $table->string('name'); $table->text('description')->nullable(); - $table->integer('priority')->default(0); + $table->integer('priority')->unsigned()->default(0); $table->boolean('exclusive')->default(false); $table->unsignedInteger('usage_limit')->nullable(); $table->unsignedInteger('used')->default(0); - $table->boolean('coupon_based')->default(0); + $table->boolean('is_coupon_based')->default(false); $table->dateTime('starts_at')->nullable(); $table->dateTime('ends_at')->nullable(); - $table->boolean('applies_to_discounted')->default(1); - + $table->boolean('applies_to_discounted')->default(true); $table->timestamps(); }); } From b2f4a8d56e64c74f1c5d499d0eb2f700ac8e551f Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:40:53 +0200 Subject: [PATCH 08/32] Removed the `code` field from the Promotion model #2 --- src/Promotion/Models/Promotion.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Promotion/Models/Promotion.php b/src/Promotion/Models/Promotion.php index 9fe92baa..3141a81d 100644 --- a/src/Promotion/Models/Promotion.php +++ b/src/Promotion/Models/Promotion.php @@ -12,7 +12,6 @@ /** * @property int $id - * @property string $code * @property string $name * @property ?string $description * @property int $priority From 06db9d01db0c46ebf4e9753f35d1956515f85aeb Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Tue, 9 Jul 2024 11:11:15 +0200 Subject: [PATCH 09/32] Renamed promo/coupon fields to match existing patterns - Improved the test cases - Converted legacy factories to new ones - Added missing type casts --- src/Promotion/Models/Coupon.php | 2 +- src/Promotion/Models/Promotion.php | 12 +++-- src/Promotion/Tests/CouponTest.php | 49 ++++++++++++++----- .../Tests/Factories/PromotionFactory.php | 40 +++++++++++++++ src/Promotion/Tests/PromotionTest.php | 42 +++++++++++++--- src/Promotion/Tests/TestCase.php | 1 - .../Tests/factories/PromotionFactory.php | 23 --------- ...4_06_08_131453_create_promotions_table.php | 4 +- ...2024_06_08_164653_create_coupons_table.php | 2 +- 9 files changed, 123 insertions(+), 52 deletions(-) create mode 100644 src/Promotion/Tests/Factories/PromotionFactory.php delete mode 100644 src/Promotion/Tests/factories/PromotionFactory.php diff --git a/src/Promotion/Models/Coupon.php b/src/Promotion/Models/Coupon.php index 9af6b17c..3620e036 100644 --- a/src/Promotion/Models/Coupon.php +++ b/src/Promotion/Models/Coupon.php @@ -14,7 +14,7 @@ * @property string $code * @property ?int $usage_limit * @property ?int $per_customer_usage_limit - * @property int $used + * @property int $usage_count * @property ?Carbon $expires_at * * @property Promotion $promotion diff --git a/src/Promotion/Models/Promotion.php b/src/Promotion/Models/Promotion.php index 3141a81d..869ea9ef 100644 --- a/src/Promotion/Models/Promotion.php +++ b/src/Promotion/Models/Promotion.php @@ -15,10 +15,10 @@ * @property string $name * @property ?string $description * @property int $priority - * @property bool $exclusive + * @property bool $is_exclusive * @property ?int $usage_limit - * @property int $used - * @property bool $coupon_based + * @property int $usage_count + * @property bool $is_coupon_based * @property bool $applies_to_discounted * @property ?Carbon $starts_at * @property ?Carbon $ends_at @@ -34,6 +34,12 @@ class Promotion extends Model implements PromotionContract 'ends_at' => 'datetime', 'created_at' => 'datetime', 'updated_at' => 'datetime', + 'priority' => 'int', + 'usage_limit' => 'int', + 'usage_count' => 'int', + 'is_exclusive' => 'bool', + 'is_coupon_based' => 'bool', + 'applies_to_discounted' => 'bool', ]; public function coupons(): HasMany diff --git a/src/Promotion/Tests/CouponTest.php b/src/Promotion/Tests/CouponTest.php index a8e2f985..c53003bc 100644 --- a/src/Promotion/Tests/CouponTest.php +++ b/src/Promotion/Tests/CouponTest.php @@ -6,27 +6,30 @@ use Carbon\Carbon; use Vanilo\Promotion\Models\Coupon; -use Vanilo\Promotion\Models\Promotion; +use Vanilo\Promotion\Tests\Factories\PromotionFactory; class CouponTest extends TestCase { /** @test */ public function all_mutable_fields_can_be_mass_assigned() { + $expiryDate = Carbon::now()->endOfDay(); $coupon = Coupon::create([ - 'promotion_id' => factory(Promotion::class)->create()->id, + 'promotion_id' => PromotionFactory::new()->create()->id, 'code' => 'coupon', 'per_customer_usage_limit' => 2, 'usage_limit' => 4, - 'used' => 4, - 'expiresAt' => Carbon::now()->endOfDay()->toDateTimeString(), + 'usage_count' => 4, + 'expires_at' => $expiryDate, ]); + $coupon = $coupon->refresh(); + $this->assertEquals('coupon', $coupon->code); $this->assertEquals(2, $coupon->per_customer_usage_limit); $this->assertEquals(4, $coupon->usage_limit); - $this->assertEquals(4, $coupon->used); - $this->assertEquals(Carbon::now()->endOfDay()->toDateTimeString(), $coupon->expiresAt); + $this->assertEquals(4, $coupon->usage_count); + $this->assertEquals($expiryDate->toDateTimeString(), $coupon->expires_at->toDateTimeString()); } /** @test */ @@ -37,14 +40,14 @@ public function all_mutable_fields_can_be_set() $coupon->code = 'coupon'; $coupon->per_customer_usage_limit = 1; $coupon->usage_limit = 15; - $coupon->used = 4; - $coupon->expiresAt = Carbon::now()->endOfDay()->toDateTimeString(); + $coupon->usage_count = 4; + $coupon->expires_at = Carbon::now()->endOfDay()->toDateTimeString(); $this->assertEquals('coupon', $coupon->code); $this->assertEquals(1, $coupon->per_customer_usage_limit); $this->assertEquals(15, $coupon->usage_limit); - $this->assertEquals(4, $coupon->used); - $this->assertEquals(Carbon::now()->endOfDay()->toDateTimeString(), $coupon->expiresAt); + $this->assertEquals(4, $coupon->usage_count); + $this->assertEquals(Carbon::now()->endOfDay()->toDateTimeString(), $coupon->expires_at); } /** @test */ @@ -54,14 +57,34 @@ public function code_must_be_unique() $c1 = Coupon::create([ 'code' => 'coupon-1', - 'promotion_id' => factory(Promotion::class)->create()->id, + 'promotion_id' => PromotionFactory::new()->create()->id, ]); $c2 = Coupon::create([ 'code' => 'coupon-1', - 'promotion_id' => factory(Promotion::class)->create()->id, + 'promotion_id' => PromotionFactory::new()->create()->id, + ]); + } + + /** @test */ + public function the_fields_are_of_proper_types() + { + $coupon = Coupon::create([ + 'promotion_id' => PromotionFactory::new()->create()->id, + 'code' => 'coupon', + 'per_customer_usage_limit' => 2, + 'usage_limit' => 8, + 'usage_count' => 7, + 'expires_at' => Carbon::parse('tomorrow'), ]); - $this->assertNotEquals($c1->code, $c2->code); + $coupon = $coupon->refresh(); + + $this->assertIsInt($coupon->per_customer_usage_limit); + $this->assertIsInt($coupon->usage_limit); + $this->assertIsInt($coupon->usage_count); + $this->assertInstanceOf(Carbon::class, $coupon->expires_at); + $this->assertInstanceOf(Carbon::class, $coupon->created_at); + $this->assertInstanceOf(Carbon::class, $coupon->updated_at); } } diff --git a/src/Promotion/Tests/Factories/PromotionFactory.php b/src/Promotion/Tests/Factories/PromotionFactory.php new file mode 100644 index 00000000..6777ce5c --- /dev/null +++ b/src/Promotion/Tests/Factories/PromotionFactory.php @@ -0,0 +1,40 @@ + $this->faker->words(mt_rand(2, 5), true), + ]; + } + + public function expired(): self + { + return $this->state(function (array $attributes) { + return [ + 'ends_at' => Carbon::now()->subDays(2), + ]; + }); + } + + public function inActivePeriod(): self + { + return $this->state(function (array $attributes) { + return [ + 'starts_at' => Carbon::now()->subDays(3), + 'ends_at' => Carbon::now()->addDays(3), + ]; + }); + } +} diff --git a/src/Promotion/Tests/PromotionTest.php b/src/Promotion/Tests/PromotionTest.php index f97c9fd1..cd72fa92 100644 --- a/src/Promotion/Tests/PromotionTest.php +++ b/src/Promotion/Tests/PromotionTest.php @@ -27,9 +27,9 @@ public function all_mutable_fields_can_be_mass_assigned() 'name' => 'Awesome promotion', 'description' => 'The description', 'priority' => 4, - 'exclusive' => false, + 'is_exclusive' => false, 'usage_limit' => 15, - 'used' => 1, + 'usage_count' => 2, 'is_coupon_based' => false, 'starts_at' => $now, 'ends_at' => $nextMonth, @@ -39,9 +39,9 @@ public function all_mutable_fields_can_be_mass_assigned() $this->assertEquals('Awesome promotion', $promotion->name); $this->assertEquals('The description', $promotion->description); $this->assertEquals(4, $promotion->priority); - $this->assertFalse($promotion->exclusive); + $this->assertFalse($promotion->is_exclusive); $this->assertEquals(15, $promotion->usage_limit); - $this->assertEquals(1, $promotion->used); + $this->assertEquals(2, $promotion->usage_count); $this->assertFalse($promotion->is_coupon_based); $this->assertEquals($now, $promotion->starts_at); $this->assertEquals($nextMonth, $promotion->ends_at); @@ -59,9 +59,9 @@ public function all_mutable_fields_can_be_set() $promotion->name = 'Awesome promotion'; $promotion->description = 'The description'; $promotion->priority = 4; - $promotion->exclusive = false; + $promotion->is_exclusive = false; $promotion->usage_limit = 15; - $promotion->used = 1; + $promotion->usage_count = 1; $promotion->is_coupon_based = false; $promotion->applies_to_discounted = false; $promotion->starts_at = $now; @@ -70,12 +70,38 @@ public function all_mutable_fields_can_be_set() $this->assertEquals('Awesome promotion', $promotion->name); $this->assertEquals('The description', $promotion->description); $this->assertEquals(4, $promotion->priority); - $this->assertFalse($promotion->exclusive); + $this->assertFalse($promotion->is_exclusive); $this->assertEquals(15, $promotion->usage_limit); - $this->assertEquals(1, $promotion->used); + $this->assertEquals(1, $promotion->usage_count); $this->assertFalse($promotion->is_coupon_based); $this->assertEquals($now, $promotion->starts_at->toDateTimeString()); $this->assertEquals($nextMonth, $promotion->ends_at->toDateTimeString()); $this->assertFalse($promotion->applies_to_discounted); } + + /** @test */ + public function the_fields_are_of_proper_types() + { + $promotion = Promotion::create([ + 'name' => 'Typed Promotion', + 'priority' => 4, + 'usage_limit' => 100, + 'usage_count' => 35, + 'starts_at' => Carbon::now(), + 'ends_at' => Carbon::parse('next month'), + ]); + + $promotion = Promotion::find($promotion->id); + + $this->assertIsInt($promotion->priority); + $this->assertIsInt($promotion->usage_limit); + $this->assertIsInt($promotion->usage_count); + $this->assertIsBool($promotion->is_exclusive); + $this->assertIsBool($promotion->is_coupon_based); + $this->assertIsBool($promotion->applies_to_discounted); + $this->assertInstanceOf(Carbon::class, $promotion->starts_at); + $this->assertInstanceOf(Carbon::class, $promotion->ends_at); + $this->assertInstanceOf(Carbon::class, $promotion->created_at); + $this->assertInstanceOf(Carbon::class, $promotion->updated_at); + } } diff --git a/src/Promotion/Tests/TestCase.php b/src/Promotion/Tests/TestCase.php index 95062572..23b3d5f5 100644 --- a/src/Promotion/Tests/TestCase.php +++ b/src/Promotion/Tests/TestCase.php @@ -16,7 +16,6 @@ protected function setUp(): void parent::setUp(); $this->setUpDatabase($this->app); - $this->withFactories(__DIR__ . '/factories'); } protected function getPackageProviders($app) diff --git a/src/Promotion/Tests/factories/PromotionFactory.php b/src/Promotion/Tests/factories/PromotionFactory.php deleted file mode 100644 index 8903462d..00000000 --- a/src/Promotion/Tests/factories/PromotionFactory.php +++ /dev/null @@ -1,23 +0,0 @@ -define(\Vanilo\Promotion\Models\Promotion::class, function (Faker $faker) { - static $password; - - return [ - 'code' => $faker->word(), - 'name' => $faker->name(), - 'description' => $faker->text(), - 'priority' => 4, - 'exclusive' => false, - 'usage_limit' => 15, - 'used' => 0, - 'coupon_based' => false, - 'starts_at' => null, - 'ends_at' => null, - 'applies_to_discounted' => false, - ]; -}); diff --git a/src/Promotion/resources/database/migrations/2024_06_08_131453_create_promotions_table.php b/src/Promotion/resources/database/migrations/2024_06_08_131453_create_promotions_table.php index ae15fc7b..b26b5929 100644 --- a/src/Promotion/resources/database/migrations/2024_06_08_131453_create_promotions_table.php +++ b/src/Promotion/resources/database/migrations/2024_06_08_131453_create_promotions_table.php @@ -14,9 +14,9 @@ public function up(): void $table->string('name'); $table->text('description')->nullable(); $table->integer('priority')->unsigned()->default(0); - $table->boolean('exclusive')->default(false); + $table->boolean('is_exclusive')->default(false); $table->unsignedInteger('usage_limit')->nullable(); - $table->unsignedInteger('used')->default(0); + $table->unsignedInteger('usage_count')->default(0); $table->boolean('is_coupon_based')->default(false); $table->dateTime('starts_at')->nullable(); $table->dateTime('ends_at')->nullable(); diff --git a/src/Promotion/resources/database/migrations/2024_06_08_164653_create_coupons_table.php b/src/Promotion/resources/database/migrations/2024_06_08_164653_create_coupons_table.php index a17affbc..566a478d 100644 --- a/src/Promotion/resources/database/migrations/2024_06_08_164653_create_coupons_table.php +++ b/src/Promotion/resources/database/migrations/2024_06_08_164653_create_coupons_table.php @@ -17,7 +17,7 @@ public function up(): void $table->string('code')->unique(); $table->unsignedInteger('usage_limit')->nullable(); $table->unsignedInteger('per_customer_usage_limit')->nullable(); - $table->unsignedInteger('used')->default(0); + $table->unsignedInteger('usage_count')->default(0); $table->dateTime('expires_at')->nullable(); $table->timestamps(); From 94c7da7e0db2a517a78f5b835abec1778a8bd0d5 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 9 Jul 2024 09:11:30 +0000 Subject: [PATCH 10/32] Apply fixes from StyleCI --- src/Promotion/Contracts/Coupon.php | 1 - src/Promotion/Contracts/Promotion.php | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Promotion/Contracts/Coupon.php b/src/Promotion/Contracts/Coupon.php index a492f601..cc61e988 100644 --- a/src/Promotion/Contracts/Coupon.php +++ b/src/Promotion/Contracts/Coupon.php @@ -16,5 +16,4 @@ interface Coupon { - } diff --git a/src/Promotion/Contracts/Promotion.php b/src/Promotion/Contracts/Promotion.php index 14fe1e38..c02a85cc 100644 --- a/src/Promotion/Contracts/Promotion.php +++ b/src/Promotion/Contracts/Promotion.php @@ -16,5 +16,4 @@ interface Promotion { - } From 676dea3ac58b59b742cd39a57b996af343696f92 Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Tue, 9 Jul 2024 12:31:18 +0200 Subject: [PATCH 11/32] Using proxies in relations --- src/Promotion/Models/Coupon.php | 2 +- src/Promotion/Models/Promotion.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Promotion/Models/Coupon.php b/src/Promotion/Models/Coupon.php index 3620e036..90d64df1 100644 --- a/src/Promotion/Models/Coupon.php +++ b/src/Promotion/Models/Coupon.php @@ -29,6 +29,6 @@ class Coupon extends Model implements CouponInterface public function promotion(): BelongsTo { - return $this->belongsTo(Promotion::class); + return $this->belongsTo(PromotionProxy::modelClass()); } } diff --git a/src/Promotion/Models/Promotion.php b/src/Promotion/Models/Promotion.php index 869ea9ef..ca96a10a 100644 --- a/src/Promotion/Models/Promotion.php +++ b/src/Promotion/Models/Promotion.php @@ -44,6 +44,6 @@ class Promotion extends Model implements PromotionContract public function coupons(): HasMany { - return $this->hasMany(Coupon::class); + return $this->hasMany(CouponProxy::modelClass()); } } From 00eb1aadca14d95f0f52ea55dde65f533331a29b Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Tue, 9 Jul 2024 12:40:47 +0200 Subject: [PATCH 12/32] Added Promo/Coupo interface candidates --- src/Promotion/Contracts/Coupon.php | 9 +++++++++ src/Promotion/Contracts/Promotion.php | 1 + 2 files changed, 10 insertions(+) diff --git a/src/Promotion/Contracts/Coupon.php b/src/Promotion/Contracts/Coupon.php index cc61e988..40971da0 100644 --- a/src/Promotion/Contracts/Coupon.php +++ b/src/Promotion/Contracts/Coupon.php @@ -16,4 +16,13 @@ interface Coupon { + public static function findByCode(string $code): ?Coupon; + + public function getPromotion(): Promotion; + + public function canBeUsed(): bool; + + public function isExpired(): bool; + + public function isDepleted(): bool; } diff --git a/src/Promotion/Contracts/Promotion.php b/src/Promotion/Contracts/Promotion.php index c02a85cc..67fae553 100644 --- a/src/Promotion/Contracts/Promotion.php +++ b/src/Promotion/Contracts/Promotion.php @@ -16,4 +16,5 @@ interface Promotion { + public function isValid(?\DateTimeInterface $at = null): bool; } From 0c41da43a358b474b7afd138e0ac3d5cbeb35ca6 Mon Sep 17 00:00:00 2001 From: Hunor Kedves Date: Tue, 9 Jul 2024 15:04:20 +0300 Subject: [PATCH 13/32] Implemented Interface methods and added test for Promotion and Coupon --- src/Promotion/Models/Coupon.php | 30 +++++++++ src/Promotion/Models/Promotion.php | 17 +++++ src/Promotion/Tests/CouponTest.php | 64 +++++++++++++++++++ .../Tests/Factories/CouponFactory.php | 21 ++++++ src/Promotion/Tests/PromotionTest.php | 35 ++++++++++ 5 files changed, 167 insertions(+) create mode 100644 src/Promotion/Tests/Factories/CouponFactory.php diff --git a/src/Promotion/Models/Coupon.php b/src/Promotion/Models/Coupon.php index 90d64df1..d33a4808 100644 --- a/src/Promotion/Models/Coupon.php +++ b/src/Promotion/Models/Coupon.php @@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Carbon; use Vanilo\Promotion\Contracts\Coupon as CouponInterface; +use Vanilo\Promotion\Contracts\Promotion; /** * @property int $id @@ -31,4 +32,33 @@ public function promotion(): BelongsTo { return $this->belongsTo(PromotionProxy::modelClass()); } + + public static function findByCode(string $code): ?CouponInterface + { + return static::where('code', $code)->first(); + } + + public function getPromotion(): Promotion + { + return $this->promotion; + } + + public function canBeUsed(): bool + { + return !$this->isDepleted() && !$this->isExpired(); + } + + public function isExpired(): bool + { + return $this->expires_at->isPast(); + } + + public function isDepleted(): bool + { + if (!$this->usage_limit) { + return false; + } + + return $this->usage_count >= $this->usage_limit; + } } diff --git a/src/Promotion/Models/Promotion.php b/src/Promotion/Models/Promotion.php index ca96a10a..bac37661 100644 --- a/src/Promotion/Models/Promotion.php +++ b/src/Promotion/Models/Promotion.php @@ -46,4 +46,21 @@ public function coupons(): HasMany { return $this->hasMany(CouponProxy::modelClass()); } + + public function isValid(?\DateTimeInterface $at = null): bool + { + if ($this->usage_count >= $this->usage_limit) { + return false; + } + + if (!$this->ends_at) { + return true; + } + + if ($at) { + return $this->ends_at->isAfter($at); + } + + return $this->ends_at->isFuture(); + } } diff --git a/src/Promotion/Tests/CouponTest.php b/src/Promotion/Tests/CouponTest.php index c53003bc..c716897a 100644 --- a/src/Promotion/Tests/CouponTest.php +++ b/src/Promotion/Tests/CouponTest.php @@ -5,6 +5,7 @@ namespace Vanilo\Promotion\Tests; use Carbon\Carbon; +use Vanilo\Promotion\Tests\Factories\CouponFactory; use Vanilo\Promotion\Models\Coupon; use Vanilo\Promotion\Tests\Factories\PromotionFactory; @@ -87,4 +88,67 @@ public function the_fields_are_of_proper_types() $this->assertInstanceOf(Carbon::class, $coupon->created_at); $this->assertInstanceOf(Carbon::class, $coupon->updated_at); } + + /** @test */ + public function can_return_coupon_by_code() + { + CouponFactory::new(['code' => 'test-code'])->create(); + + $this->assertEquals('test-code', Coupon::findByCode('test-code')->code); + } + + /** @test */ + public function cat_return_promotion() + { + $promotion = PromotionFactory::new(['name' => 'Test promo'])->create(); + $coupon = CouponFactory::new(['promotion_id' => $promotion->id])->create(); + + $this->assertEquals('Test promo', $coupon->getPromotion()->name); + } + + /** @test */ + public function determines_if_its_depleted() + { + $depleted = CouponFactory::new(['usage_limit' => 3, 'usage_count' => 3])->create(); + $notDepleted = CouponFactory::new(['usage_limit' => 3, 'usage_count' => 2])->create(); + + $this->assertTrue($depleted->isDepleted()); + $this->assertFalse($notDepleted->isDepleted()); + } + + /** @test */ + public function determines_if_its_expired() + { + $expiredCoupon = CouponFactory::new(['expires_at' => Carbon::now()->subWeek()])->create(); + $notExpired = CouponFactory::new(['expires_at' => Carbon::now()->addWeek()])->create(); + + $this->assertTrue($expiredCoupon->isExpired()); + $this->assertFalse($notExpired->isExpired()); + } + + /** @test */ + public function determines_if_can_be_used() + { + $canBeUsed = CouponFactory::new([ + 'expires_at' => Carbon::now()->addWeek(), + 'usage_limit' => 3, + 'usage_count' => 2, + ])->create(); + + $cantBeUsedA = CouponFactory::new([ + 'expires_at' => Carbon::now()->subWeek(), + 'usage_limit' => 3, + 'usage_count' => 2, + ])->create(); + + $cantBeUsedB = CouponFactory::new([ + 'expires_at' => Carbon::now()->addweek(), + 'usage_limit' => 3, + 'usage_count' => 3, + ])->create(); + + $this->assertTrue($canBeUsed->canBeUsed()); + $this->assertFalse($cantBeUsedA->canBeUsed()); + $this->assertFalse($cantBeUsedB->canBeUsed()); + } } diff --git a/src/Promotion/Tests/Factories/CouponFactory.php b/src/Promotion/Tests/Factories/CouponFactory.php new file mode 100644 index 00000000..572b5a90 --- /dev/null +++ b/src/Promotion/Tests/Factories/CouponFactory.php @@ -0,0 +1,21 @@ +$this->faker->text(15), + 'promotion_id' => PromotionFactory::new()->create()->id, + ]; + } +} diff --git a/src/Promotion/Tests/PromotionTest.php b/src/Promotion/Tests/PromotionTest.php index cd72fa92..e73775e5 100644 --- a/src/Promotion/Tests/PromotionTest.php +++ b/src/Promotion/Tests/PromotionTest.php @@ -6,6 +6,7 @@ use Carbon\Carbon; use Vanilo\Promotion\Models\Promotion; +use Vanilo\Promotion\Tests\Factories\PromotionFactory; class PromotionTest extends TestCase { @@ -104,4 +105,38 @@ public function the_fields_are_of_proper_types() $this->assertInstanceOf(Carbon::class, $promotion->created_at); $this->assertInstanceOf(Carbon::class, $promotion->updated_at); } + + /** @test */ + public function can_determine_if_its_valid() + { + $validPromotionA = PromotionFactory::new([ + 'ends_at' => Carbon::now()->addMonth(), + 'usage_limit' => 100, + 'usage_count' => 35, + ])->create(); + + $validPromotionB = PromotionFactory::new([ + 'usage_limit' => 100, + 'usage_count' => 35, + ])->create(); + + $invalidPromotionA = PromotionFactory::new([ + 'ends_at' => Carbon::now()->addMonth(), + 'usage_limit' => 100, + 'usage_count' => 101, + ])->create(); + + $invalidPromotionB = PromotionFactory::new([ + 'ends_at' => Carbon::now()->subMonths(), + 'usage_limit' => 100, + 'usage_count' => 5, + ])->create(); + + $this->assertTrue($validPromotionA->isValid()); + $this->assertTrue($validPromotionB->isValid()); + $this->assertFalse($invalidPromotionA->isValid()); + $this->assertFalse($invalidPromotionB->isValid()); + $this->assertFalse($validPromotionA->isValid(Carbon::now()->addYear())); + $this->assertTrue($validPromotionA->isValid(Carbon::now()->addWeek())); + } } From 3bb84ecea2c807c2c3d76898de03c4ba56f5aa5e Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 9 Jul 2024 12:04:30 +0000 Subject: [PATCH 14/32] Apply fixes from StyleCI --- src/Promotion/Tests/CouponTest.php | 2 +- src/Promotion/Tests/Factories/CouponFactory.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Promotion/Tests/CouponTest.php b/src/Promotion/Tests/CouponTest.php index c716897a..afe8fee8 100644 --- a/src/Promotion/Tests/CouponTest.php +++ b/src/Promotion/Tests/CouponTest.php @@ -5,8 +5,8 @@ namespace Vanilo\Promotion\Tests; use Carbon\Carbon; -use Vanilo\Promotion\Tests\Factories\CouponFactory; use Vanilo\Promotion\Models\Coupon; +use Vanilo\Promotion\Tests\Factories\CouponFactory; use Vanilo\Promotion\Tests\Factories\PromotionFactory; class CouponTest extends TestCase diff --git a/src/Promotion/Tests/Factories/CouponFactory.php b/src/Promotion/Tests/Factories/CouponFactory.php index 572b5a90..a9e28c1e 100644 --- a/src/Promotion/Tests/Factories/CouponFactory.php +++ b/src/Promotion/Tests/Factories/CouponFactory.php @@ -14,7 +14,7 @@ class CouponFactory extends Factory public function definition(): array { return [ - 'code' =>$this->faker->text(15), + 'code' => $this->faker->text(15), 'promotion_id' => PromotionFactory::new()->create()->id, ]; } From 9f5edd663428f85255cc2f9e57fad99134f93bd1 Mon Sep 17 00:00:00 2001 From: Hunor Kedves Date: Thu, 11 Jul 2024 13:27:27 +0300 Subject: [PATCH 15/32] - Added PromotionRule and PromotionRuleTypes with registry - Added CartQuantity rule - Test --- src/Promotion/Contracts/Promotion.php | 2 + src/Promotion/Contracts/PromotionRule.php | 14 ++++ src/Promotion/Contracts/PromotionRuleType.php | 22 ++++++ .../Exceptions/InexistentRuleException.php | 11 +++ src/Promotion/Models/Coupon.php | 1 + src/Promotion/Models/Promotion.php | 16 ++++ src/Promotion/Models/PromotionRule.php | 49 ++++++++++++ src/Promotion/Models/PromotionRuleProxy.php | 11 +++ src/Promotion/PromotionRuleTypes.php | 72 +++++++++++++++++ .../Providers/ModuleServiceProvider.php | 11 +++ src/Promotion/Rules/CartQuantity.php | 66 ++++++++++++++++ src/Promotion/Tests/CouponTest.php | 2 +- .../Tests/Examples/CartTotalRule.php | 41 ++++++++++ src/Promotion/Tests/Examples/DummyCart.php | 64 +++++++++++++++ src/Promotion/Tests/Examples/NthOrderRule.php | 41 ++++++++++ src/Promotion/Tests/PromotionRuleTest.php | 79 +++++++++++++++++++ .../Tests/PromotionRuleTypesTest.php | 43 ++++++++++ src/Promotion/Tests/PromotionTest.php | 13 +++ .../Tests/Rules/CartQuantityTest.php | 46 +++++++++++ ...09_095853_create_promotion_rules_table.php | 26 ++++++ 20 files changed, 629 insertions(+), 1 deletion(-) create mode 100644 src/Promotion/Contracts/PromotionRule.php create mode 100644 src/Promotion/Contracts/PromotionRuleType.php create mode 100644 src/Promotion/Exceptions/InexistentRuleException.php create mode 100644 src/Promotion/Models/PromotionRule.php create mode 100644 src/Promotion/Models/PromotionRuleProxy.php create mode 100644 src/Promotion/PromotionRuleTypes.php create mode 100644 src/Promotion/Rules/CartQuantity.php create mode 100644 src/Promotion/Tests/Examples/CartTotalRule.php create mode 100644 src/Promotion/Tests/Examples/DummyCart.php create mode 100644 src/Promotion/Tests/Examples/NthOrderRule.php create mode 100644 src/Promotion/Tests/PromotionRuleTest.php create mode 100644 src/Promotion/Tests/PromotionRuleTypesTest.php create mode 100644 src/Promotion/Tests/Rules/CartQuantityTest.php create mode 100644 src/Promotion/resources/database/migrations/2024_06_09_095853_create_promotion_rules_table.php diff --git a/src/Promotion/Contracts/Promotion.php b/src/Promotion/Contracts/Promotion.php index 67fae553..af949681 100644 --- a/src/Promotion/Contracts/Promotion.php +++ b/src/Promotion/Contracts/Promotion.php @@ -17,4 +17,6 @@ interface Promotion { public function isValid(?\DateTimeInterface $at = null): bool; + + public function addRule(PromotionRuleType $ruleType): self; } diff --git a/src/Promotion/Contracts/PromotionRule.php b/src/Promotion/Contracts/PromotionRule.php new file mode 100644 index 00000000..8c0d7419 --- /dev/null +++ b/src/Promotion/Contracts/PromotionRule.php @@ -0,0 +1,14 @@ +hasMany(CouponProxy::modelClass()); } + public function rules(): HasMany + { + return $this->hasMany(PromotionRuleProxy::modelClass()); + } + public function isValid(?\DateTimeInterface $at = null): bool { if ($this->usage_count >= $this->usage_limit) { @@ -63,4 +69,14 @@ public function isValid(?\DateTimeInterface $at = null): bool return $this->ends_at->isFuture(); } + + public function addRule(PromotionRuleType $ruleType): PromotionContract + { + $this->rules()->create([ + 'type' => $ruleType::getID(), + 'configuration' => $ruleType->getConfiguration(), + ]); + + return $this; + } } diff --git a/src/Promotion/Models/PromotionRule.php b/src/Promotion/Models/PromotionRule.php new file mode 100644 index 00000000..b7886ec0 --- /dev/null +++ b/src/Promotion/Models/PromotionRule.php @@ -0,0 +1,49 @@ + 'array', + ]; + + public function promotion(): BelongsTo + { + return $this->belongsTo(PromotionProxy::modelClass()); + } + + public function getRuleType(): PromotionRuleType + { + return PromotionRuleTypes::make($this->type)->setConfiguration($this->configuration); + } + + public function isRuleTypPassing(object $subject): bool + { + return $this->getRuleType()->isPassing($subject); + } +} diff --git a/src/Promotion/Models/PromotionRuleProxy.php b/src/Promotion/Models/PromotionRuleProxy.php new file mode 100644 index 00000000..e4a25c74 --- /dev/null +++ b/src/Promotion/Models/PromotionRuleProxy.php @@ -0,0 +1,11 @@ +make($gwClass); + } + + public static function reset(): void + { + self::$registry = []; + } + + public static function getClass(string $id): ?string + { + return self::$registry[$id] ?? null; + } + + public static function ids(): array + { + return array_keys(self::$registry); + } + + public static function choices(): array + { + $result = []; + + foreach (self::$registry as $type => $class) { + $result[$type] = $class::getName(); + } + + return $result; + } +} diff --git a/src/Promotion/Providers/ModuleServiceProvider.php b/src/Promotion/Providers/ModuleServiceProvider.php index 828c98f5..d842c7dd 100644 --- a/src/Promotion/Providers/ModuleServiceProvider.php +++ b/src/Promotion/Providers/ModuleServiceProvider.php @@ -7,11 +7,22 @@ use Konekt\Concord\BaseModuleServiceProvider; use Vanilo\Promotion\Models\Coupon; use Vanilo\Promotion\Models\Promotion; +use Vanilo\Promotion\Models\PromotionRule; +use Vanilo\Promotion\Rules\CartQuantity; +use Vanilo\Promotion\PromotionRuleTypes; class ModuleServiceProvider extends BaseModuleServiceProvider { protected $models = [ Promotion::class, Coupon::class, + PromotionRule::class, ]; + + public function boot() + { + parent::boot(); + + PromotionRuleTypes::register(CartQuantity::getID(), CartQuantity::class); + } } diff --git a/src/Promotion/Rules/CartQuantity.php b/src/Promotion/Rules/CartQuantity.php new file mode 100644 index 00000000..edfd3d21 --- /dev/null +++ b/src/Promotion/Rules/CartQuantity.php @@ -0,0 +1,66 @@ +getSchema()) { + $configuration = (new Processor())->process($this->getSchema(), $configuration); + } + + $this->configuration = (array)$configuration; + + return $this; + } + + public function getConfiguration(): ?array + { + $configuration = $this->configuration; + + if ($this->getSchema()) { + $configuration = (new Processor())->process($this->getSchema(), $configuration); + } + + return (array)$configuration; + } + + public function getSchema(): ?Schema + { + return Expect::structure(['count' => Expect::int(0)->required()]); + } + + public function isPassing(object $subject): bool + { + if (!$subject instanceof Cart) { + throw new \InvalidArgumentException('Subject must be an instance of Vanilo\Cart\Contracts\Cart'); + } + + if (!$this->getConfiguration()) { + return false; + } + + return $subject->itemCount() <= $this->configuration['count']; + } +} diff --git a/src/Promotion/Tests/CouponTest.php b/src/Promotion/Tests/CouponTest.php index afe8fee8..cbff7da2 100644 --- a/src/Promotion/Tests/CouponTest.php +++ b/src/Promotion/Tests/CouponTest.php @@ -98,7 +98,7 @@ public function can_return_coupon_by_code() } /** @test */ - public function cat_return_promotion() + public function can_return_promotion() { $promotion = PromotionFactory::new(['name' => 'Test promo'])->create(); $coupon = CouponFactory::new(['promotion_id' => $promotion->id])->create(); diff --git a/src/Promotion/Tests/Examples/CartTotalRule.php b/src/Promotion/Tests/Examples/CartTotalRule.php new file mode 100644 index 00000000..b1c6d558 --- /dev/null +++ b/src/Promotion/Tests/Examples/CartTotalRule.php @@ -0,0 +1,41 @@ + 'awesome', 'promotion_id' => PromotionFactory::new()->create()->id] + ); + + $this->assertInstanceOf(PromotionRule::class, $rule); + $this->assertInstanceOf(Promotion::class, $rule->promotion); + $this->assertEquals('awesome', $rule->type); + } + + /** @test */ + public function it_can_store_and_retrieve_configuration() + { + $rule = PromotionRule::create( + [ + 'type' => 'awesome', + 'promotion_id' => PromotionFactory::new()->create()->id, + 'configuration' => ['count' => 10], + ] + ); + + $this->assertEquals(['count' => 10], $rule->configuration()); + } + + /** @test */ + public function it_can_run_the_type_passing() + { + $ruleA = PromotionRule::create( + [ + 'type' => CartQuantity::getID(), + 'promotion_id' => PromotionFactory::new()->create()->id, + 'configuration' => ['count' => 10], + ] + ); + + $ruleB = PromotionRule::create( + [ + 'type' => CartQuantity::getID(), + 'promotion_id' => PromotionFactory::new()->create()->id, + 'configuration' => ['count' => 3], + ] + ); + + $this->assertEquals(['count' => 10], $ruleA->configuration()); + $this->assertTrue($ruleA->isRuleTypPassing(new DummyCart())); + + $this->assertEquals(['count' => 3], $ruleB->configuration()); + $this->assertFalse($ruleB->isRuleTypPassing(new DummyCart())); + } + + /** @test */ + public function throws_exception_if_configuration_needed_but_its_not_there() + { + $this->expectException(ValidationException::class); + + $rule = PromotionRule::create( + ['type' => CartQuantity::getID(), 'promotion_id' => PromotionFactory::new()->create()->id] + ); + + $rule->isRuleTypPassing(new DummyCart()); + } +} diff --git a/src/Promotion/Tests/PromotionRuleTypesTest.php b/src/Promotion/Tests/PromotionRuleTypesTest.php new file mode 100644 index 00000000..7ecb61ef --- /dev/null +++ b/src/Promotion/Tests/PromotionRuleTypesTest.php @@ -0,0 +1,43 @@ +assertCount($originalCount + 2, PromotionRuleTypes::choices()); + } + + /** @test */ + public function registered_gateway_instances_can_be_returned() + { + PromotionRuleTypes::register('nt_order', NthOrderRule::class); + + $this->assertInstanceOf(NthOrderRule::class, PromotionRuleTypes::make('nt_order')); + } + + /** @test */ + public function attempting_to_retrieve_an_unregistered_gateway_returns_null() + { + $this->assertNull(PromotionRuleTypes::getClass('randomness')); + } + + /** @test */ + public function registering_a_gateway_without_implementing_the_interface_is_not_allowed() + { + $this->expectException(\InvalidArgumentException::class); + PromotionRuleTypes::register('whatever', \stdClass::class); + } +} diff --git a/src/Promotion/Tests/PromotionTest.php b/src/Promotion/Tests/PromotionTest.php index e73775e5..20fcc0eb 100644 --- a/src/Promotion/Tests/PromotionTest.php +++ b/src/Promotion/Tests/PromotionTest.php @@ -6,6 +6,8 @@ use Carbon\Carbon; use Vanilo\Promotion\Models\Promotion; +use Vanilo\Promotion\Rules\CartQuantity; +use Vanilo\Promotion\PromotionRuleTypes; use Vanilo\Promotion\Tests\Factories\PromotionFactory; class PromotionTest extends TestCase @@ -139,4 +141,15 @@ public function can_determine_if_its_valid() $this->assertFalse($validPromotionA->isValid(Carbon::now()->addYear())); $this->assertTrue($validPromotionA->isValid(Carbon::now()->addWeek())); } + + /** @test */ + public function it_can_add_rule_and_validate() + { + $promotion = PromotionFactory::new()->create(); + $promotion->addRule(PromotionRuleTypes::make(CartQuantity::getID())->setConfiguration(['count' => 3])); + + $this->assertEquals(1, $promotion->rules()->count()); + $this->assertEquals(['count' => 3], $promotion->rules()->first()->configuration); + $this->assertEquals(CartQuantity::getID(), $promotion->rules()->first()->type); + } } diff --git a/src/Promotion/Tests/Rules/CartQuantityTest.php b/src/Promotion/Tests/Rules/CartQuantityTest.php new file mode 100644 index 00000000..e09c5906 --- /dev/null +++ b/src/Promotion/Tests/Rules/CartQuantityTest.php @@ -0,0 +1,46 @@ +assertInstanceOf(CartQuantity::class, $rule); + } + + /** @test */ + public function throws_exception_if_configuration_is_wrong() + { + $this->expectException(ValidationException::class); + PromotionRuleTypes::register(CartQuantity::getID(), CartQuantity::class); + $rule = PromotionRuleTypes::make(CartQuantity::getID()); + + $this->assertFalse($rule->isPassing(new DummyCart())); + } + + /** @test */ + public function passes_if_rule_is_valid() + { + PromotionRuleTypes::register(CartQuantity::getID(), CartQuantity::class); + $ruleA = PromotionRuleTypes::make(CartQuantity::getID())->setConfiguration(['count' => 3]); + $ruleB = PromotionRuleTypes::make(CartQuantity::getID())->setConfiguration(['count' => 6]); + + $this->assertFalse($ruleA->isPassing(new DummyCart())); + $this->assertTrue($ruleB->isPassing(new DummyCart())); + } +} + diff --git a/src/Promotion/resources/database/migrations/2024_06_09_095853_create_promotion_rules_table.php b/src/Promotion/resources/database/migrations/2024_06_09_095853_create_promotion_rules_table.php new file mode 100644 index 00000000..5191d4dd --- /dev/null +++ b/src/Promotion/resources/database/migrations/2024_06_09_095853_create_promotion_rules_table.php @@ -0,0 +1,26 @@ +id(); + $table->unsignedInteger('promotion_id'); + $table->string('type'); + $table->json('configuration')->nullable(); + $table->timestamps(); + $table->foreign('promotion_id')->references('id')->on('promotions'); + }); + } + + public function down(): void + { + Schema::drop('promotion_rules'); + } +}; From f402f15e937c169b56790602858585bc7a96db4b Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Thu, 11 Jul 2024 10:27:38 +0000 Subject: [PATCH 16/32] Apply fixes from StyleCI --- src/Promotion/PromotionRuleTypes.php | 2 +- src/Promotion/Providers/ModuleServiceProvider.php | 2 +- src/Promotion/Rules/CartQuantity.php | 4 ++-- src/Promotion/Tests/PromotionRuleTypesTest.php | 2 +- src/Promotion/Tests/PromotionTest.php | 2 +- src/Promotion/Tests/Rules/CartQuantityTest.php | 1 - 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Promotion/PromotionRuleTypes.php b/src/Promotion/PromotionRuleTypes.php index f19f29bc..c1c1a9cc 100644 --- a/src/Promotion/PromotionRuleTypes.php +++ b/src/Promotion/PromotionRuleTypes.php @@ -20,7 +20,7 @@ public static function register(string $id, string $class) if (!in_array(PromotionRuleType::class, class_implements($class))) { throw new \InvalidArgumentException( sprintf( - 'The class you are trying to register (%s) as promotion rule, '. + 'The class you are trying to register (%s) as promotion rule, ' . 'must implement the %s interface.', $class, PromotionRuleType::class diff --git a/src/Promotion/Providers/ModuleServiceProvider.php b/src/Promotion/Providers/ModuleServiceProvider.php index d842c7dd..179ef782 100644 --- a/src/Promotion/Providers/ModuleServiceProvider.php +++ b/src/Promotion/Providers/ModuleServiceProvider.php @@ -8,8 +8,8 @@ use Vanilo\Promotion\Models\Coupon; use Vanilo\Promotion\Models\Promotion; use Vanilo\Promotion\Models\PromotionRule; -use Vanilo\Promotion\Rules\CartQuantity; use Vanilo\Promotion\PromotionRuleTypes; +use Vanilo\Promotion\Rules\CartQuantity; class ModuleServiceProvider extends BaseModuleServiceProvider { diff --git a/src/Promotion/Rules/CartQuantity.php b/src/Promotion/Rules/CartQuantity.php index edfd3d21..c21353c5 100644 --- a/src/Promotion/Rules/CartQuantity.php +++ b/src/Promotion/Rules/CartQuantity.php @@ -30,7 +30,7 @@ public function setConfiguration(array $configuration): self $configuration = (new Processor())->process($this->getSchema(), $configuration); } - $this->configuration = (array)$configuration; + $this->configuration = (array) $configuration; return $this; } @@ -43,7 +43,7 @@ public function getConfiguration(): ?array $configuration = (new Processor())->process($this->getSchema(), $configuration); } - return (array)$configuration; + return (array) $configuration; } public function getSchema(): ?Schema diff --git a/src/Promotion/Tests/PromotionRuleTypesTest.php b/src/Promotion/Tests/PromotionRuleTypesTest.php index 7ecb61ef..4c44b11e 100644 --- a/src/Promotion/Tests/PromotionRuleTypesTest.php +++ b/src/Promotion/Tests/PromotionRuleTypesTest.php @@ -4,9 +4,9 @@ namespace Vanilo\Promotion\Tests; +use Vanilo\Promotion\PromotionRuleTypes; use Vanilo\Promotion\Tests\Examples\CartTotalRule; use Vanilo\Promotion\Tests\Examples\NthOrderRule; -use Vanilo\Promotion\PromotionRuleTypes; class PromotionRuleTypesTest extends TestCase { diff --git a/src/Promotion/Tests/PromotionTest.php b/src/Promotion/Tests/PromotionTest.php index 20fcc0eb..33d48d6b 100644 --- a/src/Promotion/Tests/PromotionTest.php +++ b/src/Promotion/Tests/PromotionTest.php @@ -6,8 +6,8 @@ use Carbon\Carbon; use Vanilo\Promotion\Models\Promotion; -use Vanilo\Promotion\Rules\CartQuantity; use Vanilo\Promotion\PromotionRuleTypes; +use Vanilo\Promotion\Rules\CartQuantity; use Vanilo\Promotion\Tests\Factories\PromotionFactory; class PromotionTest extends TestCase diff --git a/src/Promotion/Tests/Rules/CartQuantityTest.php b/src/Promotion/Tests/Rules/CartQuantityTest.php index e09c5906..38da0541 100644 --- a/src/Promotion/Tests/Rules/CartQuantityTest.php +++ b/src/Promotion/Tests/Rules/CartQuantityTest.php @@ -43,4 +43,3 @@ public function passes_if_rule_is_valid() $this->assertTrue($ruleB->isPassing(new DummyCart())); } } - From 99bb8ac517f2565e6bee01562ea8bf1847041571 Mon Sep 17 00:00:00 2001 From: Hunor Kedves Date: Fri, 12 Jul 2024 14:44:51 +0300 Subject: [PATCH 17/32] Added PromotionAction and CartFixedDiscount action type --- src/Promotion/Actions/CartFixedDiscount.php | 64 +++++++++++++++++ src/Promotion/Contracts/PromotionAction.php | 15 ++++ .../Contracts/PromotionActionType.php | 23 ++++++ .../InexistentPromotionActionException.php | 11 +++ ...p => InexistentPromotionRuleException.php} | 2 +- src/Promotion/Models/PromotionAction.php | 51 +++++++++++++ src/Promotion/Models/PromotionActionProxy.php | 11 +++ src/Promotion/PromotionActionTypes.php | 72 +++++++++++++++++++ src/Promotion/PromotionRuleTypes.php | 4 +- .../Providers/ModuleServiceProvider.php | 2 + src/Promotion/composer.json | 3 +- ..._175853_create_promotion_actions_table.php | 26 +++++++ 12 files changed, 280 insertions(+), 4 deletions(-) create mode 100644 src/Promotion/Actions/CartFixedDiscount.php create mode 100644 src/Promotion/Contracts/PromotionAction.php create mode 100644 src/Promotion/Contracts/PromotionActionType.php create mode 100644 src/Promotion/Exceptions/InexistentPromotionActionException.php rename src/Promotion/Exceptions/{InexistentRuleException.php => InexistentPromotionRuleException.php} (60%) create mode 100644 src/Promotion/Models/PromotionAction.php create mode 100644 src/Promotion/Models/PromotionActionProxy.php create mode 100644 src/Promotion/PromotionActionTypes.php create mode 100644 src/Promotion/resources/database/migrations/2024_06_10_175853_create_promotion_actions_table.php diff --git a/src/Promotion/Actions/CartFixedDiscount.php b/src/Promotion/Actions/CartFixedDiscount.php new file mode 100644 index 00000000..3844de27 --- /dev/null +++ b/src/Promotion/Actions/CartFixedDiscount.php @@ -0,0 +1,64 @@ +getConfiguration()['discount_amount'] / 100 * $subject->total()); + } + + public function getSchema(): ?Schema + { + return Expect::structure(['discount_amount' => Expect::int(0)->required()]); + } + + public function setConfiguration(array $configuration): self + { + if ($this->getSchema()) { + $configuration = (new Processor())->process($this->getSchema(), $configuration); + } + + $this->configuration = (array)$configuration; + + return $this; + } + + public function getConfiguration(): ?array + { + $configuration = $this->configuration; + + if ($this->getSchema()) { + $configuration = (new Processor())->process($this->getSchema(), $configuration); + } + + return (array)$configuration; + } +} diff --git a/src/Promotion/Contracts/PromotionAction.php b/src/Promotion/Contracts/PromotionAction.php new file mode 100644 index 00000000..0927f6fd --- /dev/null +++ b/src/Promotion/Contracts/PromotionAction.php @@ -0,0 +1,15 @@ + 'array', + ]; + + public function promotion(): BelongsTo + { + return $this->belongsTo(PromotionProxy::modelClass()); + } + + public function getActionType(): PromotionActionType + { + // TODO: Implement getActionType() method. + } + + public function executeActionType(object $subject): Adjustable + { + // TODO: Implement executeActionType() method. + } +} diff --git a/src/Promotion/Models/PromotionActionProxy.php b/src/Promotion/Models/PromotionActionProxy.php new file mode 100644 index 00000000..63df5afa --- /dev/null +++ b/src/Promotion/Models/PromotionActionProxy.php @@ -0,0 +1,11 @@ +make($gwClass); + } + + public static function reset(): void + { + self::$registry = []; + } + + public static function getClass(string $id): ?string + { + return self::$registry[$id] ?? null; + } + + public static function ids(): array + { + return array_keys(self::$registry); + } + + public static function choices(): array + { + $result = []; + + foreach (self::$registry as $type => $class) { + $result[$type] = $class::getName(); + } + + return $result; + } +} diff --git a/src/Promotion/PromotionRuleTypes.php b/src/Promotion/PromotionRuleTypes.php index c1c1a9cc..93a293be 100644 --- a/src/Promotion/PromotionRuleTypes.php +++ b/src/Promotion/PromotionRuleTypes.php @@ -5,7 +5,7 @@ namespace Vanilo\Promotion; use Vanilo\Promotion\Contracts\PromotionRuleType; -use Vanilo\Promotion\Exceptions\InexistentRuleException; +use Vanilo\Promotion\Exceptions\InexistentPromotionRuleException; final class PromotionRuleTypes { @@ -36,7 +36,7 @@ public static function make(string $id): PromotionRuleType $gwClass = self::getClass($id); if (null === $gwClass) { - throw new InexistentRuleException( + throw new InexistentPromotionRuleException( "No rule is registered with the id `$id`." ); } diff --git a/src/Promotion/Providers/ModuleServiceProvider.php b/src/Promotion/Providers/ModuleServiceProvider.php index 179ef782..c4033066 100644 --- a/src/Promotion/Providers/ModuleServiceProvider.php +++ b/src/Promotion/Providers/ModuleServiceProvider.php @@ -7,6 +7,7 @@ use Konekt\Concord\BaseModuleServiceProvider; use Vanilo\Promotion\Models\Coupon; use Vanilo\Promotion\Models\Promotion; +use Vanilo\Promotion\Models\PromotionAction; use Vanilo\Promotion\Models\PromotionRule; use Vanilo\Promotion\PromotionRuleTypes; use Vanilo\Promotion\Rules\CartQuantity; @@ -17,6 +18,7 @@ class ModuleServiceProvider extends BaseModuleServiceProvider Promotion::class, Coupon::class, PromotionRule::class, + PromotionAction::class, ]; public function boot() diff --git a/src/Promotion/composer.json b/src/Promotion/composer.json index be30e039..076b978d 100644 --- a/src/Promotion/composer.json +++ b/src/Promotion/composer.json @@ -24,7 +24,8 @@ "php": "^8.2", "konekt/concord": "^1.13", "laravel/framework": "^10.43|^11.0", - "vanilo/support": "^4.0" + "vanilo/support": "^4.0", + "vanilo/adjustments": "^4.0" }, "require-dev": { "phpunit/phpunit": "^10.0", diff --git a/src/Promotion/resources/database/migrations/2024_06_10_175853_create_promotion_actions_table.php b/src/Promotion/resources/database/migrations/2024_06_10_175853_create_promotion_actions_table.php new file mode 100644 index 00000000..278af36b --- /dev/null +++ b/src/Promotion/resources/database/migrations/2024_06_10_175853_create_promotion_actions_table.php @@ -0,0 +1,26 @@ +id(); + $table->unsignedInteger('promotion_id'); + $table->string('type'); + $table->json('configuration')->nullable(); + $table->timestamps(); + $table->foreign('promotion_id')->references('id')->on('promotions'); + }); + } + + public function down(): void + { + Schema::drop('promotion_actions'); + } +}; From c5cc1b872b875b059840d39de81726c94ac6e562 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 12 Jul 2024 11:45:01 +0000 Subject: [PATCH 18/32] Apply fixes from StyleCI --- src/Promotion/Actions/CartFixedDiscount.php | 6 +++--- src/Promotion/Models/PromotionAction.php | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Promotion/Actions/CartFixedDiscount.php b/src/Promotion/Actions/CartFixedDiscount.php index 3844de27..9eb049aa 100644 --- a/src/Promotion/Actions/CartFixedDiscount.php +++ b/src/Promotion/Actions/CartFixedDiscount.php @@ -29,7 +29,7 @@ public static function getID(): string public function adjust(object $subject): Adjuster { if (!$subject instanceof Cart) { - throw new \InvalidArgumentException('Subject must be an instance of '.Cart::class); + throw new \InvalidArgumentException('Subject must be an instance of ' . Cart::class); } return new SimpleDiscount($this->getConfiguration()['discount_amount'] / 100 * $subject->total()); @@ -46,7 +46,7 @@ public function setConfiguration(array $configuration): self $configuration = (new Processor())->process($this->getSchema(), $configuration); } - $this->configuration = (array)$configuration; + $this->configuration = (array) $configuration; return $this; } @@ -59,6 +59,6 @@ public function getConfiguration(): ?array $configuration = (new Processor())->process($this->getSchema(), $configuration); } - return (array)$configuration; + return (array) $configuration; } } diff --git a/src/Promotion/Models/PromotionAction.php b/src/Promotion/Models/PromotionAction.php index 70eb7a9c..42e7e6aa 100644 --- a/src/Promotion/Models/PromotionAction.php +++ b/src/Promotion/Models/PromotionAction.php @@ -10,8 +10,6 @@ use Vanilo\Promotion\Contracts\Promotion; use Vanilo\Promotion\Contracts\PromotionAction as PromotionActionContract; use Vanilo\Promotion\Contracts\PromotionActionType; -use Vanilo\Promotion\Contracts\PromotionRuleType; -use Vanilo\Promotion\PromotionRuleTypes; use Vanilo\Support\Traits\ConfigurableModel; use Vanilo\Support\Traits\ConfigurationHasNoSchema; From c73290fc0b4baabb4f28f7d3dfe3473daf5913fc Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:47:57 +0300 Subject: [PATCH 19/32] Using Laravel 11.14 for CI --- src/Promotion/.github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Promotion/.github/workflows/tests.yml b/src/Promotion/.github/workflows/tests.yml index a8810fdf..c4c6fd52 100644 --- a/src/Promotion/.github/workflows/tests.yml +++ b/src/Promotion/.github/workflows/tests.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: php: ['8.2', '8.3'] - laravel: ['10.43', '10.48', '11.0', '11.5'] + laravel: ['10.43', '10.48', '11.0', '11.14'] name: PHP ${{ matrix.php }} Laravel ${{ matrix.laravel }} steps: - name: Checkout From be003514ff6dc7b656da3804a134618a571c81f3 Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:49:58 +0300 Subject: [PATCH 20/32] License name fix --- src/Promotion/LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Promotion/LICENSE.md b/src/Promotion/LICENSE.md index 38695c2a..d270b64d 100644 --- a/src/Promotion/LICENSE.md +++ b/src/Promotion/LICENSE.md @@ -1,6 +1,6 @@ # The MIT License (MIT) -Copyright (c) 2019 Attila Fulop +Copyright (c) 2024 Vanilo UG > Permission is hereby granted, free of charge, to any person obtaining a copy > of this software and associated documentation files (the "Software"), to deal From 87c8fe3119e2fb37f749130afee7bed9eec014d6 Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:07:50 +0300 Subject: [PATCH 21/32] Using Xtend's Registry for promotion action types --- composer.json | 1 + src/Promotion/PromotionActionTypes.php | 57 +++++--------------------- src/Promotion/composer.json | 1 + 3 files changed, 13 insertions(+), 46 deletions(-) diff --git a/composer.json b/composer.json index 84872e00..1040a6a7 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "laravel/framework": "^10.43|^11.0", "konekt/enum": "^4.2", "konekt/concord": "^1.13", + "konekt/xtend": "^1.1", "spatie/laravel-medialibrary": "^11.0", "cviebrock/eloquent-sluggable": "^10.0|^11.0", "konekt/laravel-migration-compatibility": "^1.6", diff --git a/src/Promotion/PromotionActionTypes.php b/src/Promotion/PromotionActionTypes.php index 07ca424e..0d6c8ab8 100644 --- a/src/Promotion/PromotionActionTypes.php +++ b/src/Promotion/PromotionActionTypes.php @@ -4,36 +4,27 @@ namespace Vanilo\Promotion; +use Konekt\Extend\Concerns\HasRegistry; +use Konekt\Extend\Concerns\RequiresClassOrInterface; +use Konekt\Extend\Contracts\Registry; use Vanilo\Promotion\Contracts\PromotionActionType; use Vanilo\Promotion\Exceptions\InexistentPromotionActionException; -final class PromotionActionTypes +final class PromotionActionTypes implements Registry { - private static array $registry = []; + use HasRegistry; + use RequiresClassOrInterface; + + private static string $requiredInterface = PromotionActionType::class; public static function register(string $id, string $class) { - if (array_key_exists($id, self::$registry)) { - return; - } - - if (!in_array(PromotionActionType::class, class_implements($class))) { - throw new \InvalidArgumentException( - sprintf( - 'The class you are trying to register (%s) as promotion action, ' . - 'must implement the %s interface.', - $class, - PromotionActionType::class - ) - ); - } - - self::$registry[$id] = $class; + self::add($id, $class); } - public static function make(string $id): PromotionActionType + public static function make(string $id, array $parameters = []): PromotionActionType { - $gwClass = self::getClass($id); + $gwClass = self::getClassOf($id); if (null === $gwClass) { throw new InexistentPromotionActionException( @@ -43,30 +34,4 @@ public static function make(string $id): PromotionActionType return app()->make($gwClass); } - - public static function reset(): void - { - self::$registry = []; - } - - public static function getClass(string $id): ?string - { - return self::$registry[$id] ?? null; - } - - public static function ids(): array - { - return array_keys(self::$registry); - } - - public static function choices(): array - { - $result = []; - - foreach (self::$registry as $type => $class) { - $result[$type] = $class::getName(); - } - - return $result; - } } diff --git a/src/Promotion/composer.json b/src/Promotion/composer.json index 076b978d..e331b6b6 100644 --- a/src/Promotion/composer.json +++ b/src/Promotion/composer.json @@ -23,6 +23,7 @@ "require": { "php": "^8.2", "konekt/concord": "^1.13", + "konekt/xtend": "^1.1", "laravel/framework": "^10.43|^11.0", "vanilo/support": "^4.0", "vanilo/adjustments": "^4.0" From 9d2f4a617aee58a74dd3deca4e26094e8b82a789 Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:11:24 +0300 Subject: [PATCH 22/32] Using Xtend's Registry for promotion rule types --- src/Promotion/PromotionRuleTypes.php | 59 ++++--------------- .../Tests/PromotionRuleTypesTest.php | 2 +- 2 files changed, 13 insertions(+), 48 deletions(-) diff --git a/src/Promotion/PromotionRuleTypes.php b/src/Promotion/PromotionRuleTypes.php index 93a293be..39ee46a6 100644 --- a/src/Promotion/PromotionRuleTypes.php +++ b/src/Promotion/PromotionRuleTypes.php @@ -4,36 +4,27 @@ namespace Vanilo\Promotion; +use Konekt\Extend\Concerns\HasRegistry; +use Konekt\Extend\Concerns\RequiresClassOrInterface; +use Konekt\Extend\Contracts\Registry; use Vanilo\Promotion\Contracts\PromotionRuleType; use Vanilo\Promotion\Exceptions\InexistentPromotionRuleException; -final class PromotionRuleTypes +final class PromotionRuleTypes implements Registry { - private static array $registry = []; + use HasRegistry; + use RequiresClassOrInterface; + + private static string $requiredInterface = PromotionRuleType::class; public static function register(string $id, string $class) { - if (array_key_exists($id, self::$registry)) { - return; - } - - if (!in_array(PromotionRuleType::class, class_implements($class))) { - throw new \InvalidArgumentException( - sprintf( - 'The class you are trying to register (%s) as promotion rule, ' . - 'must implement the %s interface.', - $class, - PromotionRuleType::class - ) - ); - } - - self::$registry[$id] = $class; + return self::add($id, $class); } - public static function make(string $id): PromotionRuleType + public static function make(string $id, array $parameters = []): PromotionRuleType { - $gwClass = self::getClass($id); + $gwClass = self::getClassOf($id); if (null === $gwClass) { throw new InexistentPromotionRuleException( @@ -41,32 +32,6 @@ public static function make(string $id): PromotionRuleType ); } - return app()->make($gwClass); - } - - public static function reset(): void - { - self::$registry = []; - } - - public static function getClass(string $id): ?string - { - return self::$registry[$id] ?? null; - } - - public static function ids(): array - { - return array_keys(self::$registry); - } - - public static function choices(): array - { - $result = []; - - foreach (self::$registry as $type => $class) { - $result[$type] = $class::getName(); - } - - return $result; + return app()->make($gwClass, $parameters); } } diff --git a/src/Promotion/Tests/PromotionRuleTypesTest.php b/src/Promotion/Tests/PromotionRuleTypesTest.php index 4c44b11e..7b45eba4 100644 --- a/src/Promotion/Tests/PromotionRuleTypesTest.php +++ b/src/Promotion/Tests/PromotionRuleTypesTest.php @@ -31,7 +31,7 @@ public function registered_gateway_instances_can_be_returned() /** @test */ public function attempting_to_retrieve_an_unregistered_gateway_returns_null() { - $this->assertNull(PromotionRuleTypes::getClass('randomness')); + $this->assertNull(PromotionRuleTypes::getClassOf('randomness')); } /** @test */ From 69e668288127fce602f0af0eac01b584e7310b68 Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:40:04 +0300 Subject: [PATCH 23/32] Added `--stop-on-defect` explicitly to CI phpunit command --- .github/workflows/tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b0db2cf0..7cd19be0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,22 +27,22 @@ jobs: - name: Create SQLite Database run: mkdir -p database && touch database/database.sqlite - name: Run Tests [sqlite] - run: php vendor/bin/phpunit --testdox + run: php vendor/bin/phpunit --stop-on-defect --testdox env: TEST_DB_ENGINE: sqlite - name: Run Tests [postgres] - run: php vendor/bin/phpunit --testdox + run: php vendor/bin/phpunit --stop-on-defect --testdox env: TEST_DB_ENGINE: pgsql TEST_DB_PORT: ${{ job.services.postgres.ports[5432] }} TEST_DB_PASSWORD: postgres - name: Run Tests [mysql 5.7] - run: php vendor/bin/phpunit --testdox + run: php vendor/bin/phpunit --stop-on-defect --testdox env: TEST_DB_ENGINE: mysql TEST_DB_PORT: ${{ job.services.mysql.ports[3306] }} - name: Run Tests [mysql 8.0] - run: php vendor/bin/phpunit --testdox + run: php vendor/bin/phpunit --stop-on-defect --testdox env: TEST_DB_ENGINE: mysql TEST_DB_PORT: ${{ job.services.mysql8.ports[3306] }} From aff7240c165a47b7ddad4c5ac8749534ac9e6fe8 Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:41:30 +0300 Subject: [PATCH 24/32] Made `PromotionRuleType` `Schematized` instead of `Configurable` - Upgrade Xtend to v1.2 - code relies on its `getIdOf()` method --- composer.json | 2 +- src/Promotion/Contracts/Promotion.php | 2 +- src/Promotion/Contracts/PromotionRule.php | 2 +- src/Promotion/Contracts/PromotionRuleType.php | 15 ++---- src/Promotion/Models/Promotion.php | 13 +++-- src/Promotion/Models/PromotionRule.php | 14 +++-- src/Promotion/PromotionRuleTypes.php | 6 +-- .../Providers/ModuleServiceProvider.php | 2 +- src/Promotion/Rules/CartQuantity.php | 51 +++++-------------- .../Tests/Examples/CartTotalRule.php | 18 ++----- src/Promotion/Tests/Examples/DummyCart.php | 7 ++- src/Promotion/Tests/Examples/NthOrderRule.php | 18 ++----- src/Promotion/Tests/PromotionRuleTest.php | 12 ++--- src/Promotion/Tests/PromotionTest.php | 4 +- .../Tests/Rules/CartQuantityTest.php | 19 +++---- src/Promotion/composer.json | 2 +- 16 files changed, 75 insertions(+), 112 deletions(-) diff --git a/composer.json b/composer.json index 1040a6a7..bb48f4fd 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "laravel/framework": "^10.43|^11.0", "konekt/enum": "^4.2", "konekt/concord": "^1.13", - "konekt/xtend": "^1.1", + "konekt/xtend": "^1.2", "spatie/laravel-medialibrary": "^11.0", "cviebrock/eloquent-sluggable": "^10.0|^11.0", "konekt/laravel-migration-compatibility": "^1.6", diff --git a/src/Promotion/Contracts/Promotion.php b/src/Promotion/Contracts/Promotion.php index af949681..d4649370 100644 --- a/src/Promotion/Contracts/Promotion.php +++ b/src/Promotion/Contracts/Promotion.php @@ -18,5 +18,5 @@ interface Promotion { public function isValid(?\DateTimeInterface $at = null): bool; - public function addRule(PromotionRuleType $ruleType): self; + public function addRule(PromotionRuleType|string $type, array $configuration): self; } diff --git a/src/Promotion/Contracts/PromotionRule.php b/src/Promotion/Contracts/PromotionRule.php index 8c0d7419..b322a3eb 100644 --- a/src/Promotion/Contracts/PromotionRule.php +++ b/src/Promotion/Contracts/PromotionRule.php @@ -10,5 +10,5 @@ interface PromotionRule extends Configurable { public function getRuleType(): PromotionRuleType; - public function isRuleTypPassing(object $subject): bool; + public function isPassing(object $subject): bool; } diff --git a/src/Promotion/Contracts/PromotionRuleType.php b/src/Promotion/Contracts/PromotionRuleType.php index d4739c17..45445901 100644 --- a/src/Promotion/Contracts/PromotionRuleType.php +++ b/src/Promotion/Contracts/PromotionRuleType.php @@ -4,19 +4,12 @@ namespace Vanilo\Promotion\Contracts; -use Nette\Schema\Schema; +use Konekt\Extend\Contracts\Registerable; +use Vanilo\Contracts\Schematized; -interface PromotionRuleType +interface PromotionRuleType extends Schematized, Registerable { public static function getName(): string; - public static function getID(): string; - - public function isPassing(object $subject): bool; - - public function getSchema(): ?Schema; - - public function setConfiguration(array $configuration): self; - - public function getConfiguration(): ?array; + public function isPassing(object $subject, array $configuration): bool; } diff --git a/src/Promotion/Models/Promotion.php b/src/Promotion/Models/Promotion.php index d4e02f38..8c8314fc 100644 --- a/src/Promotion/Models/Promotion.php +++ b/src/Promotion/Models/Promotion.php @@ -10,6 +10,7 @@ use Illuminate\Support\Collection; use Vanilo\Promotion\Contracts\Promotion as PromotionContract; use Vanilo\Promotion\Contracts\PromotionRuleType; +use Vanilo\Promotion\PromotionRuleTypes; /** * @property int $id @@ -70,11 +71,17 @@ public function isValid(?\DateTimeInterface $at = null): bool return $this->ends_at->isFuture(); } - public function addRule(PromotionRuleType $ruleType): PromotionContract + public function addRule(PromotionRuleType|string $type, array $configuration): self { + $typeId = match (true) { + $type instanceof PromotionRuleType => PromotionRuleTypes::getIdOf($type::class), // $type is an object + null !== PromotionRuleTypes::getClassOf($type) => $type, // $type is the registered type ID + default => PromotionRuleTypes::getIdOf($type), // $type is the class name of the rule type + }; + $this->rules()->create([ - 'type' => $ruleType::getID(), - 'configuration' => $ruleType->getConfiguration(), + 'type' => $typeId, + 'configuration' => $configuration, ]); return $this; diff --git a/src/Promotion/Models/PromotionRule.php b/src/Promotion/Models/PromotionRule.php index b7886ec0..a1cd84b9 100644 --- a/src/Promotion/Models/PromotionRule.php +++ b/src/Promotion/Models/PromotionRule.php @@ -6,10 +6,12 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Vanilo\Contracts\Schematized; use Vanilo\Promotion\Contracts\Promotion; use Vanilo\Promotion\Contracts\PromotionRule as PromotionRuleContract; use Vanilo\Promotion\Contracts\PromotionRuleType; use Vanilo\Promotion\PromotionRuleTypes; +use Vanilo\Support\Dto\SchemaDefinition; use Vanilo\Support\Traits\ConfigurableModel; use Vanilo\Support\Traits\ConfigurationHasNoSchema; @@ -24,7 +26,6 @@ class PromotionRule extends Model implements PromotionRuleContract { use ConfigurableModel; - use ConfigurationHasNoSchema; protected $guarded = ['id', 'created_at', 'updated_at']; @@ -39,11 +40,16 @@ public function promotion(): BelongsTo public function getRuleType(): PromotionRuleType { - return PromotionRuleTypes::make($this->type)->setConfiguration($this->configuration); + return PromotionRuleTypes::make($this->type); } - public function isRuleTypPassing(object $subject): bool + public function isPassing(object $subject): bool { - return $this->getRuleType()->isPassing($subject); + return $this->getRuleType()->isPassing($subject, $this->configuration()); + } + + public function getConfigurationSchema(): ?Schematized + { + return SchemaDefinition::wrap($this->getRuleType()); } } diff --git a/src/Promotion/PromotionRuleTypes.php b/src/Promotion/PromotionRuleTypes.php index 39ee46a6..f3883d99 100644 --- a/src/Promotion/PromotionRuleTypes.php +++ b/src/Promotion/PromotionRuleTypes.php @@ -24,14 +24,14 @@ public static function register(string $id, string $class) public static function make(string $id, array $parameters = []): PromotionRuleType { - $gwClass = self::getClassOf($id); + $class = self::getClassOf($id); - if (null === $gwClass) { + if (null === $class) { throw new InexistentPromotionRuleException( "No rule is registered with the id `$id`." ); } - return app()->make($gwClass, $parameters); + return app()->make($class, $parameters); } } diff --git a/src/Promotion/Providers/ModuleServiceProvider.php b/src/Promotion/Providers/ModuleServiceProvider.php index c4033066..455807ba 100644 --- a/src/Promotion/Providers/ModuleServiceProvider.php +++ b/src/Promotion/Providers/ModuleServiceProvider.php @@ -25,6 +25,6 @@ public function boot() { parent::boot(); - PromotionRuleTypes::register(CartQuantity::getID(), CartQuantity::class); + PromotionRuleTypes::register(CartQuantity::ID, CartQuantity::class); } } diff --git a/src/Promotion/Rules/CartQuantity.php b/src/Promotion/Rules/CartQuantity.php index c21353c5..65ac39ab 100644 --- a/src/Promotion/Rules/CartQuantity.php +++ b/src/Promotion/Rules/CartQuantity.php @@ -7,60 +7,37 @@ use Nette\Schema\Expect; use Nette\Schema\Processor; use Nette\Schema\Schema; -use Vanilo\Cart\Contracts\Cart; use Vanilo\Promotion\Contracts\PromotionRuleType; class CartQuantity implements PromotionRuleType { - private ?array $configuration = null; + public const ID = 'cart_quantity'; public static function getName(): string { - return __('Cart quantity'); + return __('Cart Quantity'); } - public static function getID(): string + public function getSchema(): Schema { - return 'cart_quantity'; + return Expect::structure(['count' => Expect::int(0)->required()])->castTo('array'); } - public function setConfiguration(array $configuration): self + public function getSchemaSample(array $mergeWith = null): array { - if ($this->getSchema()) { - $configuration = (new Processor())->process($this->getSchema(), $configuration); - } - - $this->configuration = (array) $configuration; - - return $this; - } - - public function getConfiguration(): ?array - { - $configuration = $this->configuration; - - if ($this->getSchema()) { - $configuration = (new Processor())->process($this->getSchema(), $configuration); - } - - return (array) $configuration; - } - - public function getSchema(): ?Schema - { - return Expect::structure(['count' => Expect::int(0)->required()]); + return ['count' => 2]; } - public function isPassing(object $subject): bool + public function isPassing(object $subject, array $configuration): bool { - if (!$subject instanceof Cart) { - throw new \InvalidArgumentException('Subject must be an instance of Vanilo\Cart\Contracts\Cart'); - } + $count = match(true) { + method_exists($subject, 'itemCount') => $subject->itemCount(), + method_exists($subject, 'getItems') => count($subject->getItems()), + default => throw new \InvalidArgumentException('The cart quantity promotion rule requires either `itemCount()` or `getItems()` method on its subject'), + }; - if (!$this->getConfiguration()) { - return false; - } + $configuration = (new Processor())->process($this->getSchema(), $configuration); - return $subject->itemCount() <= $this->configuration['count']; + return $count >= $configuration['count']; } } diff --git a/src/Promotion/Tests/Examples/CartTotalRule.php b/src/Promotion/Tests/Examples/CartTotalRule.php index b1c6d558..9eff510b 100644 --- a/src/Promotion/Tests/Examples/CartTotalRule.php +++ b/src/Promotion/Tests/Examples/CartTotalRule.php @@ -14,28 +14,18 @@ public static function getName(): string return 'Cart Total'; } - public static function getID(): string - { - // TODO: Implement getID() method. - } - - public function isPassing(object $subject): bool + public function isPassing(object $subject, array $configuration): bool { // TODO: Implement isPassing() method. } - public function getSchema(): ?Schema + public function getSchema(): Schema { // TODO: Implement getSchema() method. } - public function setConfiguration(array $configuration): PromotionRuleType - { - // TODO: Implement setConfiguration() method. - } - - public function getConfiguration(): ?array + public function getSchemaSample(array $mergeWith = null): array { - // TODO: Implement getConfiguration() method. + // TODO: Implement getSchemaSample() method. } } diff --git a/src/Promotion/Tests/Examples/DummyCart.php b/src/Promotion/Tests/Examples/DummyCart.php index ba846fc4..166d4a83 100644 --- a/src/Promotion/Tests/Examples/DummyCart.php +++ b/src/Promotion/Tests/Examples/DummyCart.php @@ -12,6 +12,11 @@ class DummyCart implements Cart { + public function __construct( + private int $itemCount = 5 + ) { + } + public function addItem(Buyable $product, float|int $qty = 1, array $params = []): CartItem { // TODO: Implement addItem() method. @@ -34,7 +39,7 @@ public function clear(): void public function itemCount(): int { - return 5; + return $this->itemCount; } public function getUser(): ?Authenticatable diff --git a/src/Promotion/Tests/Examples/NthOrderRule.php b/src/Promotion/Tests/Examples/NthOrderRule.php index dd314cba..7cdbb0ef 100644 --- a/src/Promotion/Tests/Examples/NthOrderRule.php +++ b/src/Promotion/Tests/Examples/NthOrderRule.php @@ -14,28 +14,18 @@ public static function getName(): string return 'Nth Order'; } - public static function getID(): string - { - // TODO: Implement getID() method. - } - - public function isPassing(object $subject): bool + public function isPassing(object $subject, array $configuration): bool { // TODO: Implement isPassing() method. } - public function getSchema(): ?Schema + public function getSchema(): Schema { // TODO: Implement getSchema() method. } - public function setConfiguration(array $configuration): PromotionRuleType - { - // TODO: Implement setConfiguration() method. - } - - public function getConfiguration(): ?array + public function getSchemaSample(array $mergeWith = null): array { - // TODO: Implement getConfiguration() method. + // TODO: Implement getSchemaSample() method. } } diff --git a/src/Promotion/Tests/PromotionRuleTest.php b/src/Promotion/Tests/PromotionRuleTest.php index a93dfc2f..09affe33 100644 --- a/src/Promotion/Tests/PromotionRuleTest.php +++ b/src/Promotion/Tests/PromotionRuleTest.php @@ -44,7 +44,7 @@ public function it_can_run_the_type_passing() { $ruleA = PromotionRule::create( [ - 'type' => CartQuantity::getID(), + 'type' => CartQuantity::ID, 'promotion_id' => PromotionFactory::new()->create()->id, 'configuration' => ['count' => 10], ] @@ -52,17 +52,17 @@ public function it_can_run_the_type_passing() $ruleB = PromotionRule::create( [ - 'type' => CartQuantity::getID(), + 'type' => CartQuantity::ID, 'promotion_id' => PromotionFactory::new()->create()->id, 'configuration' => ['count' => 3], ] ); $this->assertEquals(['count' => 10], $ruleA->configuration()); - $this->assertTrue($ruleA->isRuleTypPassing(new DummyCart())); + $this->assertFalse($ruleA->isPassing(new DummyCart())); $this->assertEquals(['count' => 3], $ruleB->configuration()); - $this->assertFalse($ruleB->isRuleTypPassing(new DummyCart())); + $this->assertTrue($ruleB->isPassing(new DummyCart())); } /** @test */ @@ -71,9 +71,9 @@ public function throws_exception_if_configuration_needed_but_its_not_there() $this->expectException(ValidationException::class); $rule = PromotionRule::create( - ['type' => CartQuantity::getID(), 'promotion_id' => PromotionFactory::new()->create()->id] + ['type' => CartQuantity::ID, 'promotion_id' => PromotionFactory::new()->create()->id] ); - $rule->isRuleTypPassing(new DummyCart()); + $rule->isPassing(new DummyCart()); } } diff --git a/src/Promotion/Tests/PromotionTest.php b/src/Promotion/Tests/PromotionTest.php index 33d48d6b..a2c8eaea 100644 --- a/src/Promotion/Tests/PromotionTest.php +++ b/src/Promotion/Tests/PromotionTest.php @@ -146,10 +146,10 @@ public function can_determine_if_its_valid() public function it_can_add_rule_and_validate() { $promotion = PromotionFactory::new()->create(); - $promotion->addRule(PromotionRuleTypes::make(CartQuantity::getID())->setConfiguration(['count' => 3])); + $promotion->addRule(PromotionRuleTypes::make(CartQuantity::ID), ['count' => 3]); $this->assertEquals(1, $promotion->rules()->count()); $this->assertEquals(['count' => 3], $promotion->rules()->first()->configuration); - $this->assertEquals(CartQuantity::getID(), $promotion->rules()->first()->type); + $this->assertEquals(CartQuantity::ID, $promotion->rules()->first()->type); } } diff --git a/src/Promotion/Tests/Rules/CartQuantityTest.php b/src/Promotion/Tests/Rules/CartQuantityTest.php index 38da0541..619e56b5 100644 --- a/src/Promotion/Tests/Rules/CartQuantityTest.php +++ b/src/Promotion/Tests/Rules/CartQuantityTest.php @@ -15,31 +15,26 @@ class CartQuantityTest extends TestCase /** @test */ public function can_be_created() { - PromotionRuleTypes::register(CartQuantity::getID(), CartQuantity::class); + $ruleType = PromotionRuleTypes::make(CartQuantity::ID); - $rule = PromotionRuleTypes::make(CartQuantity::getID()); - - $this->assertInstanceOf(CartQuantity::class, $rule); + $this->assertInstanceOf(CartQuantity::class, $ruleType); } /** @test */ public function throws_exception_if_configuration_is_wrong() { $this->expectException(ValidationException::class); - PromotionRuleTypes::register(CartQuantity::getID(), CartQuantity::class); - $rule = PromotionRuleTypes::make(CartQuantity::getID()); + $cartQuantityRule = PromotionRuleTypes::make(CartQuantity::ID); - $this->assertFalse($rule->isPassing(new DummyCart())); + $this->assertFalse($cartQuantityRule->isPassing(new DummyCart(), ['wrong' => 'config'])); } /** @test */ public function passes_if_rule_is_valid() { - PromotionRuleTypes::register(CartQuantity::getID(), CartQuantity::class); - $ruleA = PromotionRuleTypes::make(CartQuantity::getID())->setConfiguration(['count' => 3]); - $ruleB = PromotionRuleTypes::make(CartQuantity::getID())->setConfiguration(['count' => 6]); + $cartQuantityRuleType = PromotionRuleTypes::make(CartQuantity::ID); - $this->assertFalse($ruleA->isPassing(new DummyCart())); - $this->assertTrue($ruleB->isPassing(new DummyCart())); + $this->assertTrue($cartQuantityRuleType->isPassing(new DummyCart(4), ['count' => 3])); + $this->assertFalse($cartQuantityRuleType->isPassing(new DummyCart(6), ['count' => 7])); } } diff --git a/src/Promotion/composer.json b/src/Promotion/composer.json index e331b6b6..3ae65375 100644 --- a/src/Promotion/composer.json +++ b/src/Promotion/composer.json @@ -23,7 +23,7 @@ "require": { "php": "^8.2", "konekt/concord": "^1.13", - "konekt/xtend": "^1.1", + "konekt/xtend": "^1.2", "laravel/framework": "^10.43|^11.0", "vanilo/support": "^4.0", "vanilo/adjustments": "^4.0" From 90f1d685ebf44ea6d754944c4a168e81092cf898 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 15 Jul 2024 14:41:46 +0000 Subject: [PATCH 25/32] Apply fixes from StyleCI --- src/Promotion/Models/PromotionRule.php | 1 - src/Promotion/Rules/CartQuantity.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Promotion/Models/PromotionRule.php b/src/Promotion/Models/PromotionRule.php index a1cd84b9..2d613d77 100644 --- a/src/Promotion/Models/PromotionRule.php +++ b/src/Promotion/Models/PromotionRule.php @@ -13,7 +13,6 @@ use Vanilo\Promotion\PromotionRuleTypes; use Vanilo\Support\Dto\SchemaDefinition; use Vanilo\Support\Traits\ConfigurableModel; -use Vanilo\Support\Traits\ConfigurationHasNoSchema; /** * @property int $id diff --git a/src/Promotion/Rules/CartQuantity.php b/src/Promotion/Rules/CartQuantity.php index 65ac39ab..748f9c9e 100644 --- a/src/Promotion/Rules/CartQuantity.php +++ b/src/Promotion/Rules/CartQuantity.php @@ -30,7 +30,7 @@ public function getSchemaSample(array $mergeWith = null): array public function isPassing(object $subject, array $configuration): bool { - $count = match(true) { + $count = match (true) { method_exists($subject, 'itemCount') => $subject->itemCount(), method_exists($subject, 'getItems') => count($subject->getItems()), default => throw new \InvalidArgumentException('The cart quantity promotion rule requires either `itemCount()` or `getItems()` method on its subject'), From 17659536c9563c4b7ec337f55e1a84096e9dd750 Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Mon, 15 Jul 2024 18:03:05 +0300 Subject: [PATCH 26/32] Refactoring Promotion Action Types from Configurable -> Schematized - IN PROG --- src/Promotion/Actions/CartFixedDiscount.php | 36 ++++--------------- src/Promotion/Contracts/PromotionAction.php | 2 +- .../Contracts/PromotionActionType.php | 15 +++----- src/Promotion/Models/PromotionAction.php | 3 +- src/Promotion/PromotionActionTypes.php | 6 ++-- 5 files changed, 17 insertions(+), 45 deletions(-) diff --git a/src/Promotion/Actions/CartFixedDiscount.php b/src/Promotion/Actions/CartFixedDiscount.php index 9eb049aa..2046aac7 100644 --- a/src/Promotion/Actions/CartFixedDiscount.php +++ b/src/Promotion/Actions/CartFixedDiscount.php @@ -14,19 +14,14 @@ class CartFixedDiscount implements PromotionActionType { - private ?array $configuration = null; + public const DEFAULT_ID = 'cart_fixed_discount'; public static function getName(): string { - return __('Cart fixed discount'); + return __('Cart Fixed Discount'); } - public static function getID(): string - { - return 'cart_fixed_discount'; - } - - public function adjust(object $subject): Adjuster + public function getAdjuster(array $configuration): Adjuster { if (!$subject instanceof Cart) { throw new \InvalidArgumentException('Subject must be an instance of ' . Cart::class); @@ -35,30 +30,13 @@ public function adjust(object $subject): Adjuster return new SimpleDiscount($this->getConfiguration()['discount_amount'] / 100 * $subject->total()); } - public function getSchema(): ?Schema - { - return Expect::structure(['discount_amount' => Expect::int(0)->required()]); - } - - public function setConfiguration(array $configuration): self + public function getSchema(): Schema { - if ($this->getSchema()) { - $configuration = (new Processor())->process($this->getSchema(), $configuration); - } - - $this->configuration = (array) $configuration; - - return $this; + return Expect::structure(['discount_amount' => Expect::float(0)->required()])->castTo('array'); } - public function getConfiguration(): ?array + public function getSchemaSample(array $mergeWith = null): array { - $configuration = $this->configuration; - - if ($this->getSchema()) { - $configuration = (new Processor())->process($this->getSchema(), $configuration); - } - - return (array) $configuration; + return ['discount_amount' => 19.99]; } } diff --git a/src/Promotion/Contracts/PromotionAction.php b/src/Promotion/Contracts/PromotionAction.php index 0927f6fd..7b6c0c0d 100644 --- a/src/Promotion/Contracts/PromotionAction.php +++ b/src/Promotion/Contracts/PromotionAction.php @@ -11,5 +11,5 @@ interface PromotionAction extends Configurable { public function getActionType(): PromotionActionType; - public function executeActionType(object $subject): Adjustable; + public function execute(object $subject): Adjustable; } diff --git a/src/Promotion/Contracts/PromotionActionType.php b/src/Promotion/Contracts/PromotionActionType.php index ebbdb1a8..8ed6dfb4 100644 --- a/src/Promotion/Contracts/PromotionActionType.php +++ b/src/Promotion/Contracts/PromotionActionType.php @@ -4,20 +4,13 @@ namespace Vanilo\Promotion\Contracts; -use Nette\Schema\Schema; +use Konekt\Extend\Contracts\Registerable; use Vanilo\Adjustments\Contracts\Adjuster; +use Vanilo\Contracts\Schematized; -interface PromotionActionType +interface PromotionActionType extends Schematized, Registerable { public static function getName(): string; - public static function getID(): string; - - public function adjust(object $subject): Adjuster; - - public function getSchema(): ?Schema; - - public function setConfiguration(array $configuration): self; - - public function getConfiguration(): ?array; + public function getAdjuster(array $configuration): Adjuster; } diff --git a/src/Promotion/Models/PromotionAction.php b/src/Promotion/Models/PromotionAction.php index 42e7e6aa..6330275d 100644 --- a/src/Promotion/Models/PromotionAction.php +++ b/src/Promotion/Models/PromotionAction.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Nette\Schema\Schema; use Vanilo\Adjustments\Contracts\Adjustable; use Vanilo\Promotion\Contracts\Promotion; use Vanilo\Promotion\Contracts\PromotionAction as PromotionActionContract; @@ -42,7 +43,7 @@ public function getActionType(): PromotionActionType // TODO: Implement getActionType() method. } - public function executeActionType(object $subject): Adjustable + public function execute(object $subject): Adjustable { // TODO: Implement executeActionType() method. } diff --git a/src/Promotion/PromotionActionTypes.php b/src/Promotion/PromotionActionTypes.php index 0d6c8ab8..583c01f3 100644 --- a/src/Promotion/PromotionActionTypes.php +++ b/src/Promotion/PromotionActionTypes.php @@ -24,14 +24,14 @@ public static function register(string $id, string $class) public static function make(string $id, array $parameters = []): PromotionActionType { - $gwClass = self::getClassOf($id); + $class = self::getClassOf($id); - if (null === $gwClass) { + if (null === $class) { throw new InexistentPromotionActionException( "No action is registered with the id `$id`." ); } - return app()->make($gwClass); + return app()->make($class, $parameters); } } From f50e131b509b85c62fceaeab1efa6e52f36a3b4a Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 15 Jul 2024 15:04:41 +0000 Subject: [PATCH 27/32] Apply fixes from StyleCI --- src/Promotion/Actions/CartFixedDiscount.php | 1 - src/Promotion/Models/PromotionAction.php | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Promotion/Actions/CartFixedDiscount.php b/src/Promotion/Actions/CartFixedDiscount.php index 2046aac7..7d87b60f 100644 --- a/src/Promotion/Actions/CartFixedDiscount.php +++ b/src/Promotion/Actions/CartFixedDiscount.php @@ -5,7 +5,6 @@ namespace Vanilo\Promotion\Actions; use Nette\Schema\Expect; -use Nette\Schema\Processor; use Nette\Schema\Schema; use Vanilo\Adjustments\Adjusters\SimpleDiscount; use Vanilo\Adjustments\Contracts\Adjuster; diff --git a/src/Promotion/Models/PromotionAction.php b/src/Promotion/Models/PromotionAction.php index 6330275d..0a167a65 100644 --- a/src/Promotion/Models/PromotionAction.php +++ b/src/Promotion/Models/PromotionAction.php @@ -6,7 +6,6 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Nette\Schema\Schema; use Vanilo\Adjustments\Contracts\Adjustable; use Vanilo\Promotion\Contracts\Promotion; use Vanilo\Promotion\Contracts\PromotionAction as PromotionActionContract; From 6ae05176f8c23faacc0be17ba1a51a04210c3b76 Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Tue, 16 Jul 2024 09:39:40 +0300 Subject: [PATCH 28/32] Modified the promotion action adjuster logic --- src/Promotion/Actions/CartFixedDiscount.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Promotion/Actions/CartFixedDiscount.php b/src/Promotion/Actions/CartFixedDiscount.php index 2046aac7..b0f90b23 100644 --- a/src/Promotion/Actions/CartFixedDiscount.php +++ b/src/Promotion/Actions/CartFixedDiscount.php @@ -23,20 +23,16 @@ public static function getName(): string public function getAdjuster(array $configuration): Adjuster { - if (!$subject instanceof Cart) { - throw new \InvalidArgumentException('Subject must be an instance of ' . Cart::class); - } - - return new SimpleDiscount($this->getConfiguration()['discount_amount'] / 100 * $subject->total()); + return new SimpleDiscount($configuration['amount']); } public function getSchema(): Schema { - return Expect::structure(['discount_amount' => Expect::float(0)->required()])->castTo('array'); + return Expect::structure(['amount' => Expect::float(0)->required()])->castTo('array'); } public function getSchemaSample(array $mergeWith = null): array { - return ['discount_amount' => 19.99]; + return ['amount' => 19.99]; } } From b417512a8aada369cf4d729fb1d35cab7ddd977b Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 16 Jul 2024 06:40:12 +0000 Subject: [PATCH 29/32] Apply fixes from StyleCI --- src/Promotion/Actions/CartFixedDiscount.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Promotion/Actions/CartFixedDiscount.php b/src/Promotion/Actions/CartFixedDiscount.php index ce2c7104..d01e337f 100644 --- a/src/Promotion/Actions/CartFixedDiscount.php +++ b/src/Promotion/Actions/CartFixedDiscount.php @@ -8,7 +8,6 @@ use Nette\Schema\Schema; use Vanilo\Adjustments\Adjusters\SimpleDiscount; use Vanilo\Adjustments\Contracts\Adjuster; -use Vanilo\Cart\Contracts\Cart; use Vanilo\Promotion\Contracts\PromotionActionType; class CartFixedDiscount implements PromotionActionType From fc3ba3331c503d634f8aba5df10580920bad9a85 Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:38:15 +0300 Subject: [PATCH 30/32] Temporary switch back to Laravel 11.16 in CI --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5d8e2794..f63417a0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,9 +7,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 45 strategy: + fail-fast: false matrix: php: ['8.2', '8.3'] - laravel: ['10.43', '10.48', '11.0', '11.17'] + laravel: ['10.43', '10.48', '11.0', '11.16'] name: PHP ${{ matrix.php }} Laravel ${{ matrix.laravel }} steps: - name: Checkout From 0d205541a21c6381802197492ca5826f78e06e12 Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:41:04 +0300 Subject: [PATCH 31/32] Temporary switch back to Laravel 11.15 in CI --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f63417a0..2f30798f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: fail-fast: false matrix: php: ['8.2', '8.3'] - laravel: ['10.43', '10.48', '11.0', '11.16'] + laravel: ['10.43', '10.48', '11.0', '11.15'] name: PHP ${{ matrix.php }} Laravel ${{ matrix.laravel }} steps: - name: Checkout From f4a5cfb38b73f7b798adda76f271ff7c71314244 Mon Sep 17 00:00:00 2001 From: Attila Fulop <1162360+fulopattila122@users.noreply.github.com> Date: Thu, 25 Jul 2024 17:18:25 +0300 Subject: [PATCH 32/32] Temporary switch back to Laravel 11.14 in CI --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2f30798f..fd0bfb3a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: fail-fast: false matrix: php: ['8.2', '8.3'] - laravel: ['10.43', '10.48', '11.0', '11.15'] + laravel: ['10.43', '10.48', '11.0', '11.14'] name: PHP ${{ matrix.php }} Laravel ${{ matrix.laravel }} steps: - name: Checkout