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', ]; } public function uniqueIds(): array { return ['uuid']; } public function subscription(): BelongsTo { return $this->belongsTo(Subscription::class); } public function order(): BelongsTo { return $this->belongsTo(Order::class); } 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; } }