feat: nhắc gia hạn QR theo dịch vụ, failover server hosting, CCCD admin, log cấp phát dịch vụ

- Mail campaign: mẫu nhắc gia hạn thanh toán QR, chọn dịch vụ + chu kỳ từng dòng (renewal_services)
- Hosting: nhiều server Plesk cho một gói (service_plan_servers), chuyển server khi server chết
- Admin xem/sửa giấy tờ định danh khách, tuỳ chọn gia hạn cho khách, nhật ký cấp phát dịch vụ
- Đánh dấu mẫu email dịch vụ là non-marketing

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
baodev0
2026-10-30 18:36:41 +07:00
co-authored by Claude Sonnet 5.5
parent 047c23beff
commit 55a834a2f8
53 changed files with 1463 additions and 119 deletions
@@ -128,6 +128,7 @@ class EmailTemplateController extends Controller
'message' => 'nullable|string',
'builder_data' => 'nullable|array',
'status' => 'boolean',
'marketing' => 'nullable|boolean',
'mail_config' => 'nullable|array',
'mail_config.from_email' => 'nullable|email',
'mail_config.from_name' => 'nullable|string|max:255',
@@ -151,7 +152,10 @@ class EmailTemplateController extends Controller
'message' => $data['message'] ?? null,
'builder_data' => $data['builder_data'] ?? null,
'status' => $data['status'] ?? 1,
'metadata' => isset($data['mail_config']) ? ['mail' => $data['mail_config']] : null,
'metadata' => array_filter([
'mail' => $data['mail_config'] ?? null,
'marketing' => isset($data['marketing']) ? (bool) $data['marketing'] : null,
], fn ($value) => $value !== null) ?: null,
]);
$this->syncVariables($template, $data['variables'] ?? []);
$guard->assertCanSave($template);
@@ -177,6 +181,7 @@ class EmailTemplateController extends Controller
'message' => 'sometimes|nullable|string',
'builder_data' => 'sometimes|nullable|array',
'status' => 'sometimes|boolean',
'marketing' => 'sometimes|boolean',
'mail_config' => 'sometimes|nullable|array',
'mail_config.from_email' => 'nullable|email',
'mail_config.from_name' => 'nullable|string|max:255',
@@ -194,8 +199,11 @@ class EmailTemplateController extends Controller
$metadata['mail'] = $data['mail_config'] ?? [];
$data['metadata'] = $metadata;
}
if (array_key_exists('marketing', $data)) {
$data['metadata'] = [...($data['metadata'] ?? $template->metadata ?? []), 'marketing' => (bool) $data['marketing']];
}
$variables = $data['variables'] ?? [];
unset($data['mail_config'], $data['variables']);
unset($data['mail_config'], $data['variables'], $data['marketing']);
$template->fill($data);
$this->syncVariables($template, $variables);
@@ -401,7 +409,8 @@ class EmailTemplateController extends Controller
'variables' => array_values($t->variables()),
'mail_config' => $t->mailConfig(),
'placeholders' => $this->placeholders($t),
...collect(EmailScenarioRegistry::describe($t->key, $t->scenario))->only(['registered', 'category', 'group', 'permissions', 'required_variables'])->all(),
...collect(EmailScenarioRegistry::describe($t->key, $t->scenario))->only(['registered', 'group', 'permissions', 'required_variables'])->all(),
'category' => $t->category(),
'global_variables' => collect(MailGlobals::LABELS)->map(fn ($label, $key) => ['key' => $key, 'label' => $label])->values(),
'created_at' => $t->created_at,
'updated_at' => $t->updated_at,