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,61 @@
+orderBy('discount')->get()->each(
+ function ($discount) {
+ $name = $discount->description;
+
+ if ($discount->code) {
+ $name .= " [{$discount->code}]";
+ }
+
+ $this->info(
+ sprintf(
+ "%s %3d%% %s",
+ $discount->id,
+ $discount->discount,
+ $name
+ )
+ );
+ }
+ );
+ }
+}
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,62 @@
+argument('wallet'))->first();
+
+ if (!$wallet) {
+ return 1;
+ }
+
+ // FIXME: Using '0' for delete might be not that 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,59 @@
+ '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/Http/Controllers/API/UsersController.php b/src/app/Http/Controllers/API/UsersController.php
--- a/src/app/Http/Controllers/API/UsersController.php
+++ b/src/app/Http/Controllers/API/UsersController.php
@@ -481,10 +481,22 @@
$response = array_merge($response, self::userStatuses($user));
+ // Add discount info to wallet object output
+ $map_func = function ($wallet) {
+ $result = $wallet->toArray();
+
+ if ($wallet->discount) {
+ $result['discount'] = $wallet->discount->discount;
+ $result['discount_description'] = $wallet->discount->description;
+ }
+
+ return $result;
+ };
+
// Information about wallets and accounts for access checks
- $response['wallets'] = $user->wallets->toArray();
- $response['accounts'] = $user->accounts->toArray();
- $response['wallet'] = $user->wallet()->toArray();
+ $response['wallets'] = $user->wallets->map($map_func)->toArray();
+ $response['accounts'] = $user->accounts->map($map_func)->toArray();
+ $response['wallet'] = $map_func($user->wallet());
return $response;
}
diff --git a/src/app/Observers/DiscountObserver.php b/src/app/Observers/DiscountObserver.php
new file mode 100644
--- /dev/null
+++ b/src/app/Observers/DiscountObserver.php
@@ -0,0 +1,29 @@
+{$discount->getKeyName()} = $allegedly_unique;
+ break;
+ }
+ }
+ }
+}
diff --git a/src/app/Providers/AppServiceProvider.php b/src/app/Providers/AppServiceProvider.php
--- a/src/app/Providers/AppServiceProvider.php
+++ b/src/app/Providers/AppServiceProvider.php
@@ -24,6 +24,7 @@
*/
public function boot()
{
+ \App\Discount::observe(\App\Observers\DiscountObserver::class);
\App\Domain::observe(\App\Observers\DomainObserver::class);
\App\Entitlement::observe(\App\Observers\EntitlementObserver::class);
\App\Package::observe(\App\Observers\PackageObserver::class);
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()->subMonthsWithoutOverflow(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,25 @@
$entitlement->updated_at = $entitlement->updated_at->copy()->addMonthsWithoutOverflow($diff);
$entitlement->save();
- $this->debit($entitlement->cost * $diff);
+ // TODO: This would be better done out of the loop (debit() will call save()),
+ // but then, maybe we should use a db transaction
+ $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,59 @@
+string('id', 36);
+ $table->tinyInteger('discount')->unsigned();
+ $table->json('description');
+ $table->string('code', 32)->nullable();
+ $table->boolean('active')->default(false);
+ $table->timestamps();
+
+ $table->primary('id');
+ }
+ );
+
+ Schema::table(
+ 'wallets',
+ function (Blueprint $table) {
+ $table->string('discount_id', 36)->nullable();
+
+ $table->foreign('discount_id')->references('id')
+ ->on('discounts')->onDelete('set null');
+ }
+ );
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table(
+ 'wallets',
+ function (Blueprint $table) {
+ $table->dropForeign(['discount_id']);
+ $table->dropColumn('discount_id');
+ }
+ );
+
+ Schema::dropIfExists('discounts');
+ }
+}
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',
+ ]
+ );
+ }
+}
diff --git a/src/resources/vue/User/Info.vue b/src/resources/vue/User/Info.vue
--- a/src/resources/vue/User/Info.vue
+++ b/src/resources/vue/User/Info.vue
@@ -69,7 +69,7 @@
{{ pkg.name }}
- {{ $root.price(pkg.cost) + '/month' }}
+ {{ price(pkg.cost) }}
|
|
- {{ $root.price(sku.cost) + '/month' }}
+ {{ price(sku.cost) }}
|
|