Files
baodev0andClaude Sonnet 5.5 55a834a2f8 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>
2026-10-30 18:36:41 +07:00

72 lines
2.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\Admin\MailCampaign\Concerns;
use App\Models\Customer;
use App\Models\MailCampaign;
use App\Models\Service;
use App\Support\RenewalCampaign;
use App\Support\RenewalQuote;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
trait HandlesRenewalCampaigns
{
public function renewalServices(Request $request): JsonResponse
{
$data = $request->validate([
'uuids' => 'required|array|min:1|max:200',
'uuids.*' => 'string|exists:customers,uuid',
'days' => 'nullable|integer|min:1|max:365',
]);
$customerIds = Customer::whereIn('uuid', $data['uuids'])->pluck('id')->all();
$today = now()->startOfDay();
$rows = RenewalQuote::dueServices($customerIds, (int) ($data['days'] ?? 30))
->map(function (Service $service) use ($today) {
$quote = RenewalQuote::forService($service);
return $quote ? [
...$quote,
'customer_uuid' => $service->customer->uuid,
'customer_name' => $service->customer->fullname,
'customer_email' => $service->customer->email,
'days_left' => (int) $today->diffInDays($service->expires_at->copy()->startOfDay(), false),
] : null;
})
->filter()
->values();
return response()->json(['error' => false, 'data' => $rows]);
}
/**
* @param array<int, array{service_id: int, months: int}> $selected
* @return array<int, array{customer_id: int, email: string, service_id: int}>
*/
private function renewalJobs(MailCampaign $campaign, array $selected): array
{
if (! RenewalCampaign::isRenewalTemplate($campaign)) {
throw ValidationException::withMessages(['renewal_services' => 'Template đã chọn không phải mẫu nhắc gia hạn.']);
}
$services = Service::with('customer:id,email')
->whereIn('id', array_column($selected, 'service_id'))
->get();
return $services
->filter(fn (Service $service) => $service->customer && trim((string) $service->customer->email) !== '')
->map(fn (Service $service) => [
'customer_id' => $service->customer->id,
'email' => trim($service->customer->email),
'service_id' => $service->id,
])
->values()
->all();
}
}