Fix 500 when exiting hosting panel for existing accounts.
Deploy Ladill Hosting / deploy (push) Successful in 20s
Deploy Ladill Hosting / deploy (push) Successful in 20s
The account overview queried a local mailboxes table that the extracted hosting app does not have; guard those lookups and skip email-addon upsells when mailboxes are managed on Ladill Email. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -260,10 +260,11 @@ class HostingProductController extends Controller
|
|||||||
$upsells ??= app(UpsellRecommendationService::class);
|
$upsells ??= app(UpsellRecommendationService::class);
|
||||||
$renewals ??= app(HostingRenewalCheckoutService::class);
|
$renewals ??= app(HostingRenewalCheckoutService::class);
|
||||||
|
|
||||||
$account->load(['product', 'user', 'node', 'sites', 'mailboxes']);
|
$account->load(['product', 'user', 'node', 'sites']);
|
||||||
$maxDomains = $account->resource_limits['max_domains'] ?? $account->product?->max_domains ?? 1;
|
$maxDomains = $account->resource_limits['max_domains'] ?? $account->product?->max_domains ?? 1;
|
||||||
$freeEmailAllowance = $account->freeEmailAllowance();
|
$freeEmailAllowance = $account->freeEmailAllowance();
|
||||||
$mailboxCount = $account->mailboxes->count();
|
$mailboxes = $account->loadedMailboxes();
|
||||||
|
$mailboxCount = $mailboxes->count();
|
||||||
$extraMailboxCount = $freeEmailAllowance === null ? 0 : max($mailboxCount - $freeEmailAllowance, 0);
|
$extraMailboxCount = $freeEmailAllowance === null ? 0 : max($mailboxCount - $freeEmailAllowance, 0);
|
||||||
|
|
||||||
return view('hosting.account', [
|
return view('hosting.account', [
|
||||||
@@ -276,7 +277,7 @@ class HostingProductController extends Controller
|
|||||||
'mailbox_count' => $mailboxCount,
|
'mailbox_count' => $mailboxCount,
|
||||||
'free_allowance' => $freeEmailAllowance,
|
'free_allowance' => $freeEmailAllowance,
|
||||||
'extra_count' => $extraMailboxCount,
|
'extra_count' => $extraMailboxCount,
|
||||||
'paid_count' => $account->mailboxes->where('is_paid', true)->count(),
|
'paid_count' => $mailboxes->where('is_paid', true)->count(),
|
||||||
'extra_total' => round($extraMailboxCount * 5, 2),
|
'extra_total' => round($extraMailboxCount * 5, 2),
|
||||||
],
|
],
|
||||||
'upsellRecommendations' => $upsells->recommendationsForAccount($account),
|
'upsellRecommendations' => $upsells->recommendationsForAccount($account),
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class DeactivateMailboxJob implements ShouldQueue
|
|||||||
$mailbox->delete();
|
$mailbox->delete();
|
||||||
|
|
||||||
if ($hostingAccountId) {
|
if ($hostingAccountId) {
|
||||||
$account = HostingAccount::query()->with(['product', 'mailboxes'])->find($hostingAccountId);
|
$account = HostingAccount::query()->with(['product'])->find($hostingAccountId);
|
||||||
if ($account) {
|
if ($account) {
|
||||||
$hostingMailboxes->syncAddonInvoice($account);
|
$hostingMailboxes->syncAddonInvoice($account);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Carbon\CarbonInterface;
|
use Carbon\CarbonInterface;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
@@ -10,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
class HostingAccount extends Model
|
class HostingAccount extends Model
|
||||||
{
|
{
|
||||||
@@ -159,6 +161,28 @@ class HostingAccount extends Model
|
|||||||
return $this->hasMany(Mailbox::class, 'hosting_account_id');
|
return $this->hasMany(Mailbox::class, 'hosting_account_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The extracted hosting app does not store mailboxes locally — they live on Ladill Email. */
|
||||||
|
public static function tracksLocalMailboxes(): bool
|
||||||
|
{
|
||||||
|
static $hasTable = null;
|
||||||
|
|
||||||
|
return $hasTable ??= Schema::hasTable('mailboxes');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return Collection<int, Mailbox> */
|
||||||
|
public function loadedMailboxes(): Collection
|
||||||
|
{
|
||||||
|
if (! static::tracksLocalMailboxes()) {
|
||||||
|
return new Collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->relationLoaded('mailboxes')) {
|
||||||
|
$this->load('mailboxes');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->mailboxes;
|
||||||
|
}
|
||||||
|
|
||||||
public function billingInvoices(): HasMany
|
public function billingInvoices(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(HostingBillingInvoice::class, 'hosting_account_id');
|
return $this->hasMany(HostingBillingInvoice::class, 'hosting_account_id');
|
||||||
@@ -268,9 +292,11 @@ class HostingAccount extends Model
|
|||||||
|
|
||||||
public function paidMailboxCount(): int
|
public function paidMailboxCount(): int
|
||||||
{
|
{
|
||||||
return $this->relationLoaded('mailboxes')
|
if (! static::tracksLocalMailboxes()) {
|
||||||
? $this->mailboxes->where('is_paid', true)->count()
|
return 0;
|
||||||
: $this->mailboxes()->where('is_paid', true)->count();
|
}
|
||||||
|
|
||||||
|
return $this->loadedMailboxes()->where('is_paid', true)->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function renew(?int $durationMonths = null, array $metadata = []): void
|
public function renew(?int $durationMonths = null, array $metadata = []): void
|
||||||
|
|||||||
@@ -26,11 +26,12 @@ class HostingMailboxService
|
|||||||
*/
|
*/
|
||||||
public function usageSummary(HostingAccount $account): array
|
public function usageSummary(HostingAccount $account): array
|
||||||
{
|
{
|
||||||
$account->loadMissing(['product', 'mailboxes']);
|
$account->loadMissing(['product']);
|
||||||
|
|
||||||
|
$mailboxes = $account->loadedMailboxes();
|
||||||
$freeAllowance = $account->freeEmailAllowance();
|
$freeAllowance = $account->freeEmailAllowance();
|
||||||
$mailboxCount = $account->mailboxes->count();
|
$mailboxCount = $mailboxes->count();
|
||||||
$paidCount = $account->mailboxes->where('is_paid', true)->count();
|
$paidCount = $mailboxes->where('is_paid', true)->count();
|
||||||
$freeUsed = $freeAllowance === null ? $mailboxCount : min($mailboxCount, $freeAllowance);
|
$freeUsed = $freeAllowance === null ? $mailboxCount : min($mailboxCount, $freeAllowance);
|
||||||
$extraCount = max($mailboxCount - ($freeAllowance ?? $mailboxCount), 0);
|
$extraCount = max($mailboxCount - ($freeAllowance ?? $mailboxCount), 0);
|
||||||
|
|
||||||
@@ -53,7 +54,15 @@ class HostingMailboxService
|
|||||||
|
|
||||||
public function createMailbox(HostingAccount $account, array $input): array
|
public function createMailbox(HostingAccount $account, array $input): array
|
||||||
{
|
{
|
||||||
$account->loadMissing(['product', 'mailboxes', 'sites']);
|
$account->loadMissing(['product', 'sites']);
|
||||||
|
|
||||||
|
if (! HostingAccount::tracksLocalMailboxes()) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'local_part' => 'Mailbox creation is managed in Ladill Email.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$account->loadMissing(['mailboxes']);
|
||||||
|
|
||||||
$domain = Domain::query()
|
$domain = Domain::query()
|
||||||
->whereKey((int) ($input['domain_id'] ?? 0))
|
->whereKey((int) ($input['domain_id'] ?? 0))
|
||||||
@@ -113,11 +122,11 @@ class HostingMailboxService
|
|||||||
|
|
||||||
ProvisionMailboxJob::dispatch($mailbox->id, encrypt((string) $input['password']));
|
ProvisionMailboxJob::dispatch($mailbox->id, encrypt((string) $input['password']));
|
||||||
|
|
||||||
$invoice = $this->syncAddonInvoice($account->fresh(['mailboxes']));
|
$invoice = $this->syncAddonInvoice($account->fresh());
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'mailbox' => $mailbox->fresh(['domain']),
|
'mailbox' => $mailbox->fresh(['domain']),
|
||||||
'usage' => $this->usageSummary($account->fresh(['product', 'mailboxes'])),
|
'usage' => $this->usageSummary($account->fresh(['product'])),
|
||||||
'invoice' => $invoice,
|
'invoice' => $invoice,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -133,12 +142,12 @@ class HostingMailboxService
|
|||||||
$mailbox->update(['status' => 'deleting']);
|
$mailbox->update(['status' => 'deleting']);
|
||||||
DeactivateMailboxJob::dispatch($mailbox->id);
|
DeactivateMailboxJob::dispatch($mailbox->id);
|
||||||
|
|
||||||
return $this->syncAddonInvoice($account->fresh(['mailboxes']));
|
return $this->syncAddonInvoice($account->fresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function syncAddonInvoice(HostingAccount $account): ?HostingBillingInvoice
|
public function syncAddonInvoice(HostingAccount $account): ?HostingBillingInvoice
|
||||||
{
|
{
|
||||||
$account->loadMissing(['product', 'mailboxes']);
|
$account->loadMissing(['product']);
|
||||||
$summary = $this->usageSummary($account);
|
$summary = $this->usageSummary($account);
|
||||||
if ($summary['extra_count'] === 0) {
|
if ($summary['extra_count'] === 0) {
|
||||||
HostingBillingInvoice::query()
|
HostingBillingInvoice::query()
|
||||||
|
|||||||
@@ -20,11 +20,16 @@ class UpsellRecommendationService
|
|||||||
*/
|
*/
|
||||||
public function recommendationsForUser(User $user): Collection
|
public function recommendationsForUser(User $user): Collection
|
||||||
{
|
{
|
||||||
$accounts = HostingAccount::query()
|
$accountsQuery = HostingAccount::query()
|
||||||
->where('user_id', $user->id)
|
->where('user_id', $user->id)
|
||||||
->where('status', HostingAccount::STATUS_ACTIVE)
|
->where('status', HostingAccount::STATUS_ACTIVE)
|
||||||
->with(['product', 'mailboxes:id,hosting_account_id,is_paid'])
|
->with(['product']);
|
||||||
->get();
|
|
||||||
|
if (HostingAccount::tracksLocalMailboxes()) {
|
||||||
|
$accountsQuery->with(['mailboxes:id,hosting_account_id,is_paid']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$accounts = $accountsQuery->get();
|
||||||
|
|
||||||
return $accounts
|
return $accounts
|
||||||
->flatMap(fn (HostingAccount $account): array => $this->recommendationsForAccount($account))
|
->flatMap(fn (HostingAccount $account): array => $this->recommendationsForAccount($account))
|
||||||
@@ -37,7 +42,7 @@ class UpsellRecommendationService
|
|||||||
*/
|
*/
|
||||||
public function recommendationsForAccount(HostingAccount $account): array
|
public function recommendationsForAccount(HostingAccount $account): array
|
||||||
{
|
{
|
||||||
$account->loadMissing(['product', 'mailboxes']);
|
$account->loadMissing(['product']);
|
||||||
|
|
||||||
$recommendations = [];
|
$recommendations = [];
|
||||||
$upgradeProduct = $this->nextUpgradeProduct($account);
|
$upgradeProduct = $this->nextUpgradeProduct($account);
|
||||||
@@ -75,8 +80,8 @@ class UpsellRecommendationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
$freeAllowance = $account->freeEmailAllowance();
|
$freeAllowance = $account->freeEmailAllowance();
|
||||||
$mailboxCount = $account->mailboxes->count();
|
$mailboxCount = $account->loadedMailboxes()->count();
|
||||||
if ($freeAllowance !== null && $mailboxCount > $freeAllowance) {
|
if (HostingAccount::tracksLocalMailboxes() && $freeAllowance !== null && $mailboxCount > $freeAllowance) {
|
||||||
$extraMailboxes = $mailboxCount - $freeAllowance;
|
$extraMailboxes = $mailboxCount - $freeAllowance;
|
||||||
$addonAmount = self::EMAIL_ADDON_QUANTITY * self::EMAIL_ADDON_UNIT_PRICE;
|
$addonAmount = self::EMAIL_ADDON_QUANTITY * self::EMAIL_ADDON_UNIT_PRICE;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user