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:
baodev0
2026-10-21 16:55:19 +07:00
co-authored by Claude Sonnet 5.5
parent 3c7710f1de
commit a443ec3bf6
66 changed files with 2032 additions and 124 deletions
@@ -10,6 +10,7 @@ use App\Jobs\DnsManager\SyncDnsFromPlesk;
use App\Repositories\DnsManager\Domain\DnsDomainRepositoryInterface;
use App\Repositories\DnsManager\Record\DnsRecordRepositoryInterface;
use App\Services\ServerService;
use App\Support\DomainServiceSummary;
use App\Services\Domain\CustomerDomainServiceSync;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -65,7 +66,10 @@ class DomainController extends Controller
$paginator = $query->orderByDesc('id')->paginate($perPage);
$items = $paginator->getCollection()->map(fn($domain) => $this->formatDomain($domain));
$summaries = DomainServiceSummary::forDomains($paginator->getCollection());
$items = $paginator->getCollection()->map(fn($domain) => $this->formatDomain($domain) + [
'service' => $summaries[DomainServiceSummary::key($domain->domain_name)] ?? null,
]);
return response()->json([
'error' => false,
@@ -238,7 +238,7 @@ final class MailCampaignController extends Controller
}
$subject = $subject !== '' ? $subject : trim((string) $template->subject);
$message = trim((string) $template->message);
$message = $message !== '' ? $message : trim((string) $template->message);
}
if ($subject === '' || $message === '') {
@@ -55,9 +55,10 @@ class ProductResource extends JsonResource
'price_annually' => $this->whenLoaded('pricings', fn () => optional($this->pricings->firstWhere('billing_cycle', 'annually'))->price),
'price_biennially' => $this->whenLoaded('pricings', fn () => optional($this->pricings->firstWhere('billing_cycle', 'biennially'))->price),
'price_triennially' => $this->whenLoaded('pricings', fn () => optional($this->pricings->firstWhere('billing_cycle', 'triennially'))->price),
'custom_prices' => $this->whenLoaded('pricings', fn () => $this->pricings
'custom_prices' => $this->whenLoaded('pricings', fn () => (object) $this->pricings
->filter(fn ($pricing) => str_starts_with((string) $pricing->billing_cycle, BillingCycleCatalog::CODE_PREFIX))
->mapWithKeys(fn ($pricing) => [$pricing->billing_cycle => $pricing->price])),
->mapWithKeys(fn ($pricing) => [$pricing->billing_cycle => $pricing->price])
->all()),
'price_onetime' => $this->whenLoaded('pricings', fn () => optional($this->pricings->first(fn ($pricing) => in_array($pricing->billing_cycle, ['onetime', 'one_time'], true)))->price),
'is_active' => $this->is_active,
'created_at' => $this->created_at,