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
@@ -17,6 +17,7 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
use App\Support\BillingCycleCatalog;
use App\Support\RenewalPeriodGuard;
use App\Support\ServiceProvisionLog;
trait ManagesCustomerServices
{
@@ -134,6 +135,19 @@ trait ManagesCustomerServices
return response()->json(['error' => false, 'data' => $renewals]);
}
public function serviceProvisioningLogs(string $uuid, int $serviceId): JsonResponse
{
$service = Service::where('customer_id', $this->resolveCustomer($uuid)->id)->findOrFail($serviceId);
$logs = ActivityLog::where('subject_type', Service::class)
->where('subject_id', $service->id)
->whereIn('event', ServiceProvisionLog::EVENTS)
->orderBy('id')
->get(['id', 'event', 'description', 'causer_type', 'causer_name', 'new_values', 'created_at']);
return response()->json(['error' => false, 'data' => $logs]);
}
public function renewService(string $uuid, int $serviceId, Request $request): JsonResponse
{
$data = $request->validate([
@@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers\Api\Admin\Customer;
use App\Http\Controllers\Controller;
use App\Models\Customer;
use App\Support\CustomerIdentity;
use Illuminate\Http\JsonResponse;
class CustomerIdentityController extends Controller
{
public function show(string $uuid): JsonResponse
{
return response()->json([
'error' => false,
'data' => CustomerIdentity::payload(Customer::where('uuid', $uuid)->firstOrFail()),
]);
}
public function image(Customer $customer, string $side)
{
return CustomerIdentity::response($customer, $side);
}
}