CluPilotCloud/app/Models/SubscriptionAddon.php

110 lines
3.2 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use RuntimeException;
/**
* One booked module, at the price it was booked at.
*
* Frozen for the same reason the subscription is: the customer agreed to a
* figure, and raising it afterwards would rewrite that agreement. Cancelling
* sets `cancelled_at` rather than deleting the row — what someone was paying,
* and until when, is evidence.
*/
class SubscriptionAddon extends Model
{
use HasFactory;
use HasUuids;
/** What the customer agreed to. */
public const FROZEN = ['subscription_id', 'addon_key', 'price_cents', 'currency', 'quantity', 'booked_at'];
protected $guarded = [];
protected static function booted(): void
{
static::updating(function (self $addon) {
$frozen = array_intersect(array_keys($addon->getDirty()), self::FROZEN);
if ($frozen !== []) {
throw new RuntimeException(
'A booked module is frozen at its booked price; tried to change: '.implode(', ', $frozen).
'. Cancel it and book it again at today\'s price instead.'
);
}
});
}
protected function casts(): array
{
return [
'price_cents' => 'integer',
'quantity' => 'integer',
'booked_at' => 'datetime',
'cancelled_at' => 'datetime',
// The end this booking has an appointment with. A cancellation is
// monthly and lands at the end of the term already paid for, so the
// module keeps running until this date passes — see
// App\Actions\BookAddon::cancelAtPeriodEnd().
'cancels_at' => 'datetime',
'granted_at' => 'datetime',
'granted_until' => 'datetime',
'catalogue_price_cents' => 'integer',
];
}
public function uniqueIds(): array
{
return ['uuid'];
}
public function subscription(): BelongsTo
{
return $this->belongsTo(Subscription::class);
}
public function order(): BelongsTo
{
return $this->belongsTo(Order::class);
}
/** The operator who gave this module away, when it was a grant. */
public function grantedBy(): BelongsTo
{
return $this->belongsTo(Operator::class, 'granted_by');
}
public function isGranted(): bool
{
return $this->granted_at !== null;
}
public function scopeActive(Builder $query): Builder
{
return $query->whereNull('cancelled_at');
}
/** Net per month for this booking, packs included. */
public function monthlyCents(): int
{
return $this->price_cents * $this->quantity;
}
public function isActive(): bool
{
return $this->cancelled_at === null;
}
/** Cancelled, still running, and billed for no further term. */
public function endsAtPeriodEnd(): bool
{
return $this->cancelled_at === null && $this->cancels_at !== null;
}
}