subscription ?? app(CustomDomainAccess::class)->contractOf($instance->customer); return self::forPlan((int) ($instance->quota_gb ?? $contract?->quota_gb ?? 0), $contract); } /** * The same question about a package the customer is not on yet. * * The downgrade check needs it: whether a smaller package is big enough * depends on the packs the customer has ALREADY paid for, which move with * them. */ public static function forPlan(int $planGb, ?Subscription $subscription): self { $booked = self::bookedPacks($subscription); return new self( max(0, $planGb), $booked['packs'], $booked['gb'], $booked['disk_gb'], app(AddonCatalogue::class)->packGb(), ); } /** The figure Nextcloud is told, the disk is sized for, and the portal shows. */ public function totalGb(): int { return $this->planGb + $this->packGb(); } public function hasPacks(): bool { return $this->packs > 0; } /** * How many MORE packs it would take to cover this many bytes. * * Zero when the allowance already covers it. Rounded up, because half a pack * is not for sale and an answer that leaves the customer one gigabyte short * is an answer that sends them back a second time. */ public function packsToCover(int $bytes): int { $short = $bytes - $this->totalGb() * 1024 ** 3; if ($short <= 0 || $this->packSizeGb <= 0) { return 0; } return (int) ceil($short / ($this->packSizeGb * 1024 ** 3)); } /** * The packs still running on this contract, with their own sizes. * * Queried rather than read off a loaded relation, for the same reason as * before: this is asked immediately after a pack is booked or cancelled, * and a relation loaded before that would answer with the state before * the change. * * @return array{packs: int, gb: int, disk_gb: int} */ private static function bookedPacks(?Subscription $subscription): array { if ($subscription === null) { return ['packs' => 0, 'gb' => 0, 'disk_gb' => 0]; } $rows = $subscription->addons()->active() ->where('addon_key', AddonCatalogue::STORAGE) ->get(['quantity', 'pack_gb', 'pack_disk_gb']); return [ 'packs' => (int) $rows->sum('quantity'), 'gb' => (int) $rows->sum(fn ($row) => (int) $row->quantity * (int) $row->pack_gb), 'disk_gb' => (int) $rows->sum(fn ($row) => (int) $row->quantity * (int) $row->pack_disk_gb), ]; } /** Everything the booked packs give the customer. */ public function packGb(): int { return $this->bookedGb; } /** Everything the booked packs take up on the host. */ public function packDiskGb(): int { return $this->bookedDiskGb; } }