feat: khởi tạo/huỷ domain Plesk qua job, gửi mail hoá đơn qua queue, đính kèm triển khai

- ProvisionDnsDomainJob/SuspendDnsDomainJob: tạo domain admin lên Plesk, huỷ = suspend (is_created=3)
- Khách xem zone DNS được gán ở /dich-vu/domain, quản lý bản ghi A/AAAA/CNAME/TXT/MX/SRV
- send-mail hoá đơn đẩy SendInvoiceMailJob, preview + sửa nội dung, ghi mail.order_id
- invoice_attachments.type (payment_proof/deployment)
- CacheService xoá theo SCAN pattern, Redis không persistent mặc định
- Backfill transactions.public_code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
baodev0
2026-10-11 09:56:18 +07:00
co-authored by Claude Opus 5.5
parent 30898ea46f
commit 94614d9cb3
55 changed files with 1190 additions and 236 deletions
@@ -3,6 +3,9 @@
namespace App\Http\Controllers\Api\Admin\DnsManager;
use App\Http\Controllers\Controller;
use App\Jobs\DnsManager\ProvisionDnsDomainJob;
use App\Jobs\DnsManager\SuspendDnsDomainJob;
use App\Models\DnsDomain;
use App\Jobs\DnsManager\SyncDnsFromPlesk;
use App\Repositories\DnsManager\Domain\DnsDomainRepositoryInterface;
use App\Repositories\DnsManager\Record\DnsRecordRepositoryInterface;
@@ -97,7 +100,7 @@ class DomainController extends Controller
'dns_manager_server_id' => 'required|integer|exists:dns_manager_servers,id',
'customer_ids' => 'nullable|array',
'customer_ids.*' => 'integer|exists:customers,id',
'is_created' => 'nullable|integer|in:0,1,2',
'is_created' => 'nullable|integer|in:0,1,2,3',
'registrar_id' => 'nullable|integer|exists:registrars,id',
]);
@@ -116,9 +119,14 @@ class DomainController extends Controller
DB::commit();
$queued = $this->queueProvisioning($domain->fresh());
return response()->json([
'error' => false,
'data' => ['id' => $domain->id, 'domain_name' => $domain->domain_name],
'error' => false,
'message' => $queued
? "Đã lưu domain {$domain->domain_name}, đang khởi tạo trên server (queue)."
: "Đã lưu domain {$domain->domain_name}.",
'data' => ['id' => $domain->id, 'domain_name' => $domain->domain_name, 'provisioning_queued' => $queued],
], 201);
} catch (\Exception $e) {
DB::rollBack();
@@ -139,7 +147,7 @@ class DomainController extends Controller
'dns_manager_server_id' => 'nullable|integer|exists:dns_manager_servers,id',
'customer_ids' => 'nullable|array',
'customer_ids.*' => 'integer|exists:customers,id',
'is_created' => 'nullable|integer|in:0,1,2',
'is_created' => 'nullable|integer|in:0,1,2,3',
'registrar_id' => 'nullable|integer|exists:registrars,id',
]);
@@ -159,6 +167,8 @@ class DomainController extends Controller
DB::commit();
$this->queueProvisioning($domain->fresh());
return $this->show($id);
} catch (\Exception $e) {
DB::rollBack();
@@ -166,6 +176,30 @@ class DomainController extends Controller
}
}
public function provision(int $id): JsonResponse
{
$domain = $this->domainRepository->find($id);
if (!$domain) {
return response()->json(['error' => true, 'message' => "Không tìm thấy domain #$id"], 404);
}
if ($domain->plesk_id) {
return response()->json(['error' => true, 'message' => "Domain {$domain->domain_name} đã được khởi tạo trên Plesk."], 422);
}
if ((int) $domain->is_created === DnsDomain::STATUS_CANCELLED) {
return response()->json(['error' => true, 'message' => "Domain {$domain->domain_name} đã huỷ, không thể khởi tạo."], 422);
}
ProvisionDnsDomainJob::dispatch($domain->id);
return response()->json([
'error' => false,
'message' => "Đã đưa {$domain->domain_name} vào queue khởi tạo trên Plesk.",
]);
}
public function destroy(int $id): JsonResponse
{
$domain = $this->domainRepository->find($id);
@@ -174,24 +208,40 @@ class DomainController extends Controller
return response()->json(['error' => true, 'message' => "Không tìm thấy domain #$id"], 404);
}
DB::beginTransaction();
try {
$domain->customer()->detach();
$domain->record()->delete();
$this->domainRepository->delete($id);
DB::commit();
return response()->json(['error' => false, 'message' => 'Đã xoá domain thành công']);
} catch (\Exception $e) {
DB::rollBack();
return response()->json(['error' => true, 'message' => $e->getMessage()], 500);
if ((int) $domain->is_created === DnsDomain::STATUS_CANCELLED) {
return response()->json(['error' => false, 'message' => "Domain {$domain->domain_name} đã được huỷ trước đó."]);
}
$onPlesk = (bool) $domain->plesk_id;
$domain->update(['is_created' => DnsDomain::STATUS_CANCELLED]);
if ($onPlesk) {
SuspendDnsDomainJob::dispatch($domain->id);
}
return response()->json([
'error' => false,
'message' => $onPlesk
? "Đã huỷ domain {$domain->domain_name}, đang suspend trên server (queue)."
: "Đã huỷ domain {$domain->domain_name}.",
]);
}
// ─── DNS Records ─────────────────────────────────────────────────────────
// ─── Helpers ─────────────────────────────────────────────────────────────
private function queueProvisioning($domain): bool
{
if ($domain->plesk_id || in_array((int) $domain->is_created, [DnsDomain::STATUS_INACTIVE, DnsDomain::STATUS_CANCELLED], true)) {
return false;
}
ProvisionDnsDomainJob::dispatch($domain->id);
return true;
}
private function formatDomain($domain): array
{
return [
@@ -206,6 +256,7 @@ class DomainController extends Controller
'email' => $c->email,
])->values()->toArray(),
'is_created' => $domain->is_created,
'plesk_id' => $domain->plesk_id,
'registrar_id' => $domain->registrar_id,
'registrar' => $domain->registrar ? [
'id' => $domain->registrar->id,