diff --git a/src/app/Console/Commands/DiscountList.php b/src/app/Console/Commands/DiscountList.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/DiscountList.php @@ -0,0 +1,52 @@ +orderBy('discount')->get()->each(function ($discount) { + $name = $discount->description; + + if ($discount->code) { + $name .= " [{$discount->code}]"; + } + + $this->info("{$discount->discount}%: {$name} ({$discount->id})"); + }); + } +} diff --git a/src/app/Console/Commands/UserDiscount.php b/src/app/Console/Commands/UserDiscount.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/UserDiscount.php @@ -0,0 +1,68 @@ +argument('user'))->first(); + + if (!$user) { + return 1; + } + + $this->info("Found user {$user->id}"); + + if ($this->argument('discount') === '0') { + $discount = null; + } else { + $discount = \App\Discount::find($this->argument('discount')); + + if (!$discount) { + return 1; + } + } + + foreach ($user->wallets as $wallet) { + if (!$discount) { + $wallet->discount()->dissociate(); + } else { + $wallet->discount()->associate($discount); + } + + $wallet->save(); + } + } +} diff --git a/src/app/Console/Commands/UserWallets.php b/src/app/Console/Commands/UserWallets.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/UserWallets.php @@ -0,0 +1,50 @@ +argument('user'))->first(); + + if (!$user) { + return 1; + } + + foreach ($user->wallets as $wallet) { + $this->info("{$wallet->id} {$wallet->description}"); + } + } +} diff --git a/src/app/Console/Commands/WalletDiscount.php b/src/app/Console/Commands/WalletDiscount.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/WalletDiscount.php @@ -0,0 +1,64 @@ +argument('wallet'))->first(); + + if (!$wallet) { + return 1; + } + + // FIXME: I see two possible issues with handling the discount argument + // 1. We can't make sure user didn't meant ID=10, but 10% + // 2. Using '0' for delete might also be not obvious + + if ($this->argument('discount') === '0') { + $wallet->discount()->dissociate(); + } else { + $discount = \App\Discount::find($this->argument('discount')); + + if (!$discount) { + return 1; + } + + $wallet->discount()->associate($discount); + } + + $wallet->save(); + } +} diff --git a/src/app/Discount.php b/src/app/Discount.php new file mode 100644 --- /dev/null +++ b/src/app/Discount.php @@ -0,0 +1,56 @@ + 'integer', + ]; + + protected $fillable = [ + 'active', + 'code', + 'description', + 'discount', + ]; + + /** @var array Translatable properties */ + public $translatable = [ + 'description', + ]; + + /** + * Discount value mutator + * + * @throws \Exception + */ + public function setDiscountAttribute($discount) + { + $discount = (int) $discount; + + if ($discount < 0 || $discount > 100) { + throw new \Exception("Invalid discount value, expected integer in range of 0-100"); + } + + $this->attributes['discount'] = $discount; + } + + /** + * List of wallets with this discount assigned. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function wallets() + { + return $this->hasMany('App\Wallet'); + } +} diff --git a/src/app/Wallet.php b/src/app/Wallet.php --- a/src/app/Wallet.php +++ b/src/app/Wallet.php @@ -33,7 +33,7 @@ ]; protected $nullable = [ - 'description' + 'description', ]; protected $casts = [ @@ -59,6 +59,8 @@ public function chargeEntitlements($apply = true) { $charges = 0; + $discount = $this->discount ? $this->discount->discount : 0; + $discount = (100 - $discount) / 100; foreach ($this->entitlements()->get()->fresh() as $entitlement) { // This entitlement has been created less than or equal to 14 days ago (this is at @@ -76,7 +78,9 @@ if ($entitlement->updated_at <= Carbon::now()->subMonths(1)) { $diff = $entitlement->updated_at->diffInMonths(Carbon::now()); - $charges += $entitlement->cost * $diff; + $cost = (int) ($entitlement->cost * $discount * $diff); + + $charges += $cost; // if we're in dry-run, you know... if (!$apply) { @@ -86,13 +90,23 @@ $entitlement->updated_at = $entitlement->updated_at->copy()->addMonths($diff); $entitlement->save(); - $this->debit($entitlement->cost * $diff); + $this->debit($cost); } } return $charges; } + /** + * The discount assigned to the wallet. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function discount() + { + return $this->belongsTo('App\Discount', 'discount_id', 'id'); + } + /** * Calculate the expected charges to this wallet. * diff --git a/src/database/migrations/2020_03_30_100000_create_discounts.php b/src/database/migrations/2020_03_30_100000_create_discounts.php new file mode 100644 --- /dev/null +++ b/src/database/migrations/2020_03_30_100000_create_discounts.php @@ -0,0 +1,56 @@ +increments('id'); + $table->tinyInteger('discount')->unsigned(); + $table->text('description'); + $table->string('code', 32)->nullable(); + $table->boolean('active')->default(false); + $table->timestamps(); + } + ); + + Schema::table( + 'wallets', + function (Blueprint $table) { + $table->integer('discount_id')->nullable(); + + $table->foreign('discount_id')->references('id') + ->on('discounts')->onDelete('set null'); + } + ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('discounts'); + + Schema::table( + 'wallets', + function (Blueprint $table) { + $table->dropColumn('discount_id'); + } + ); + } +} diff --git a/src/database/seeds/DatabaseSeeder.php b/src/database/seeds/DatabaseSeeder.php --- a/src/database/seeds/DatabaseSeeder.php +++ b/src/database/seeds/DatabaseSeeder.php @@ -13,6 +13,7 @@ { $this->call( [ + DiscountSeeder::class, DomainSeeder::class, SkuSeeder::class, PackageSeeder::class, diff --git a/src/database/seeds/DiscountSeeder.php b/src/database/seeds/DiscountSeeder.php new file mode 100644 --- /dev/null +++ b/src/database/seeds/DiscountSeeder.php @@ -0,0 +1,40 @@ + 'Free Account', + 'discount' => 100, + 'active' => true, + ] + ); + + Discount::create( + [ + 'description' => 'Student or Educational Institution', + 'discount' => 30, + 'active' => true, + ] + ); + + Discount::create( + [ + 'description' => 'Test voucher', + 'discount' => 10, + 'active' => true, + 'code' => 'TEST', + ] + ); + } +}