feat(portal): lead module, WHMCS sync report/contacts/mapping, service lifecycle email templates
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
3c7710f1de
commit
a443ec3bf6
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class DomainServiceSummary
|
||||
{
|
||||
public const DUE_WITHIN_DAYS = 30;
|
||||
public const RECENT_RENEWAL_DAYS = 30;
|
||||
private const YEAR_CYCLES = [12, 24, 36];
|
||||
|
||||
/**
|
||||
* @param Collection<int, \App\Models\DnsDomain> $domains
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
public static function forDomains(Collection $domains): array
|
||||
{
|
||||
$keys = $domains->map(fn ($d) => self::key($d->domain_name))->unique()->values()->all();
|
||||
if (!$keys) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$services = Service::query()
|
||||
->with('orderItem:id,months')
|
||||
->where('type', 'domain')
|
||||
->whereIn(\DB::raw('LOWER(TRIM(server_name))'), $keys)
|
||||
->orderByRaw("status = 'cancelled'")
|
||||
->orderBy('id')
|
||||
->get()
|
||||
->unique(fn (Service $s) => self::key($s->server_name));
|
||||
|
||||
$lastRenewal = ActivityLog::query()
|
||||
->where('subject_type', Service::class)
|
||||
->where('event', 'renewed')
|
||||
->whereIn('subject_id', $services->pluck('id'))
|
||||
->selectRaw('subject_id, MAX(created_at) as last_at')
|
||||
->groupBy('subject_id')
|
||||
->pluck('last_at', 'subject_id');
|
||||
|
||||
return $services->mapWithKeys(fn (Service $s) => [
|
||||
self::key($s->server_name) => self::summarize($s, $lastRenewal[$s->id] ?? null),
|
||||
])->all();
|
||||
}
|
||||
|
||||
public static function key(?string $name): string
|
||||
{
|
||||
return mb_strtolower(trim((string) $name));
|
||||
}
|
||||
|
||||
private static function summarize(Service $service, $lastRenewalAt): array
|
||||
{
|
||||
$months = (int) $service->orderItem?->months;
|
||||
|
||||
return [
|
||||
'service_id' => $service->id,
|
||||
'service_state' => self::state($service, $lastRenewalAt),
|
||||
'registered_at' => ($service->started_at ?? $service->created_at)?->toDateString(),
|
||||
'expires_at' => $service->expires_at?->toDateString(),
|
||||
'cycle_years' => in_array($months, self::YEAR_CYCLES, true) ? intdiv($months, 12) : null,
|
||||
];
|
||||
}
|
||||
|
||||
private static function state(Service $service, $lastRenewalAt): string
|
||||
{
|
||||
if ($service->status === 'cancelled' || $service->cancelled_at) {
|
||||
return 'cancelled';
|
||||
}
|
||||
if ($service->expires_at && $service->expires_at->lte(now()->addDays(self::DUE_WITHIN_DAYS))) {
|
||||
return 'due';
|
||||
}
|
||||
if (!$lastRenewalAt) {
|
||||
return 'new';
|
||||
}
|
||||
|
||||
return now()->parse($lastRenewalAt)->gte(now()->subDays(self::RECENT_RENEWAL_DAYS))
|
||||
? 'recently_renewed'
|
||||
: 'renewed';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user