Files
portal-api/app/Http/Controllers/Api/Admin/Customer/CustomerController.php
T
2026-09-09 16:56:22 +07:00

584 lines
23 KiB
PHP

<?php
namespace App\Http\Controllers\Api\Admin\Customer;
use App\Http\Controllers\Controller;
use App\Http\Requests\CreateCustomerRequest;
use App\Http\Resources\Api\Admin\CustomerResource;
use App\Models\Customer;
use App\Models\DnsDomain;
use App\Models\DnsRecord;
use App\Models\Service;
use App\Repositories\Customer\CustomerRepositoryInterface;
use App\Repositories\Module\Domain\DomainRepositoryInterface;
use App\Services\Customer\Concerns\ResolvesCustomer;
use App\Services\Customer\CustomerAccountService;
use App\Services\Customer\CustomerBillingService;
use App\Services\Customer\CustomerImpersonationService;
use App\Services\Customer\CustomerMailService;
use App\Services\Customer\CustomerNoteService;
use App\Services\Customer\CustomerServiceProvisioningService;
use App\Services\Customer\CustomerWalletService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class CustomerController extends Controller
{
use ResolvesCustomer;
public function __construct(
private readonly CustomerRepositoryInterface $customerRepository,
private readonly DomainRepositoryInterface $moduleDomainRepository,
private readonly CustomerAccountService $accountService,
private readonly CustomerWalletService $walletService,
private readonly CustomerMailService $mailService,
private readonly CustomerImpersonationService $impersonationService,
private readonly CustomerBillingService $billingService,
private readonly CustomerServiceProvisioningService $serviceProvisioning,
private readonly CustomerNoteService $noteService,
) {}
public function index(Request $request): JsonResponse
{
$perPage = min((int) $request->get('per_page', 15), 500);
$paginator = $this->customerRepository
->filter($request->all(), [], ['hosting', 'vps', 'domain'], ['*'], true)
->paginate($perPage);
return $this->paginatedResponse($paginator);
}
public function store(CreateCustomerRequest $request): JsonResponse
{
$customer = $this->accountService->create($request->validated());
return response()->json([
'error' => false,
'message' => 'Customer created successfully',
'data' => new CustomerResource($customer),
], 201);
}
public function show(string $uuid): JsonResponse
{
$customer = $this->resolveCustomer($uuid);
$customer = $this->customerRepository->findWithRelationships(
$customer->id,
['mail', 'file'],
[
'invoice' => [
'invoices_status_count_1' => ['key' => 'status', 'condition' => '=', 'value' => 1],
'invoices_status_count_0' => ['key' => 'status', 'condition' => '=', 'value' => 0],
],
'hosting' => 'hosting_count',
'vps' => 'vps_count',
'domain' => 'domain_count',
'ticket' => 'ticket_count',
],
[
'invoice' => [
'invoices_status_1' => ['key' => 'status', 'condition' => '=', 'value' => 1, 'sum' => 'total'],
'invoices_status_0' => ['key' => 'status', 'condition' => '=', 'value' => 0, 'sum' => 'total'],
],
],
);
abort_if(!$customer, 404, "Can't find customer by UUID #{$uuid}");
$customer->refresh();
return response()->json(['error' => false, 'data' => new CustomerResource($customer)]);
}
public function update(string $uuid, CreateCustomerRequest $request): JsonResponse
{
$data = $request->validated();
if (!empty($request->password)) {
$data['password_raw'] = $request->password;
}
$customer = $this->accountService->update($uuid, $data);
return response()->json([
'error' => false,
'message' => 'Customer updated successfully',
'data' => new CustomerResource($customer),
]);
}
public function destroy(string $uuid): JsonResponse
{
$this->accountService->destroy($uuid);
return response()->json(['error' => false, 'message' => 'Customer deleted successfully']);
}
public function trash(Request $request): JsonResponse
{
$perPage = min((int) $request->get('per_page', 15), 500);
$query = Customer::onlyTrashed()->with(['hosting', 'vps', 'domain']);
if ($filter = $request->get('filter')) {
$filter = trim($filter);
$query->where(function ($q) use ($filter) {
$q->where('fullname', 'LIKE', "%{$filter}%")
->orWhere('email', 'LIKE', "%{$filter}%")
->orWhere('phone', 'LIKE', "%{$filter}%");
});
}
return $this->paginatedResponse($query->paginate($perPage));
}
public function restore(string $uuid): JsonResponse
{
$this->accountService->restore($uuid);
return response()->json(['error' => false, 'message' => 'Customer restored successfully']);
}
public function forceDelete(string $uuid): JsonResponse
{
$this->accountService->forceDelete($uuid);
return response()->json(['error' => false, 'message' => 'Customer permanently deleted']);
}
public function loginAs(Request $request, string $uuid): JsonResponse
{
$code = $this->impersonationService->createLoginCode($uuid, Auth::guard('admin')->user(), $request);
return response()->json(['error' => false, 'data' => ['code' => $code]]);
}
public function resetPassword(string $uuid): JsonResponse
{
$this->mailService->resetPassword($uuid);
return response()->json([
'error' => false,
'message' => 'Password reset successfully, new password has been sent to customer email',
]);
}
public function sendPasswordEmail(Request $request, string $uuid): JsonResponse
{
$data = $request->validate(['password' => 'required|string|min:8|max:64']);
$this->mailService->sendPasswordEmail($uuid, $data['password'], $request->ip());
return response()->json(['error' => false, 'message' => 'Đã gửi mật khẩu mới cho khách hàng.']);
}
public function sendEmail(Request $request, string $uuid): JsonResponse
{
$data = $request->validate([
'template_id' => 'nullable|integer|exists:template,id',
'subject' => 'required_without:template_id|nullable|string|max:255',
'message' => 'required_without:template_id|nullable|string',
'to' => 'nullable|string|max:1000',
'cc' => 'nullable|string|max:1000',
'bcc' => 'nullable|string|max:1000',
]);
$this->mailService->sendCustomEmail($uuid, $data, Auth::guard('admin')->user(), $request->ip());
return response()->json(['error' => false, 'message' => 'Đã gửi email cho khách hàng.']);
}
public function addCredit(Request $request, string $uuid): JsonResponse
{
$request->validate(['amount' => 'required|numeric|min:1', 'action' => 'required|in:+,-']);
$this->walletService->adjustCredit(
$uuid,
Auth::guard('admin')->user(),
(int) $request->amount,
$request->action === '+',
);
return response()->json(['error' => false, 'message' => 'Credit updated successfully']);
}
public function addTotal(Request $request, string $uuid): JsonResponse
{
$data = $request->validate(['amount' => 'required|numeric|min:1', 'note' => 'required|string|max:1000']);
$admin = Auth::guard('admin')->user();
abort_if(!$admin, 401, 'Unauthorized');
$transaction = $this->walletService->addTotal($uuid, $admin, (int) $data['amount'], trim($data['note']), $request->ip());
return response()->json([
'error' => false,
'message' => 'Total updated successfully',
'data' => [
'transaction' => [
'uuid' => $transaction->uuid,
'amount' => $transaction->amount,
'note' => $transaction->note,
'admin' => $admin->username ?? $admin->email,
],
],
]);
}
public function getProducts(string $uuid): JsonResponse
{
return response()->json(['error' => false, 'data' => $this->serviceProvisioning->getProducts($uuid)]);
}
public function getInvoices(string $uuid): JsonResponse
{
return response()->json(['error' => false, 'data' => $this->billingService->getInvoices($uuid)]);
}
public function updateInvoice(string $uuid, int $invoiceId, Request $request): JsonResponse
{
$data = $request->validate([
'invoice_number' => 'nullable|string|max:50',
'status' => 'nullable|integer|in:1,2,3,4,5',
'payment_method' => 'nullable|in:transfer,balance',
'date' => 'nullable|date',
'due_date' => 'nullable|date',
'date_paid' => 'nullable|date',
'note' => 'nullable|string',
'sub_total' => 'nullable|integer|min:0',
'tax' => 'nullable|integer|min:0',
'total' => 'nullable|integer|min:0',
]);
$invoice = $this->billingService->updateInvoice($uuid, $invoiceId, $data);
return response()->json(['error' => false, 'message' => 'Đã cập nhật hóa đơn', 'data' => $invoice]);
}
public function getTransactions(string $uuid): JsonResponse
{
return response()->json(['error' => false, 'data' => $this->billingService->getTransactions($uuid)]);
}
public function updateTransaction(string $uuid, int $transactionId, Request $request): JsonResponse
{
$data = $request->validate([
'trans' => 'nullable|string|max:255',
'note' => 'nullable|string',
'type' => 'nullable|string|max:50',
'amount' => 'nullable|integer',
]);
$transaction = $this->billingService->updateTransaction($uuid, $transactionId, $data);
return response()->json(['error' => false, 'message' => 'Đã cập nhật giao dịch', 'data' => $transaction]);
}
public function getDomains(string $uuid): JsonResponse
{
$customer = $this->resolveCustomer($uuid);
$domains = $this->moduleDomainRepository->filter(['customer_id' => $customer->id], ['customer']);
return response()->json(['error' => false, 'data' => $domains]);
}
public function getDnsDomains(string $uuid): JsonResponse
{
$customer = $this->resolveCustomer($uuid);
$domains = DnsDomain::query()
->with(['server:id,name,ip_address', 'registrar:id,name,short_name,website'])
->withCount(['ns', 'record'])
->whereHas('customer', fn($q) => $q->where('customers.id', $customer->id))
->orderByDesc('id')
->get()
->map(fn($d) => [
'id' => $d->id,
'domain_name' => $d->domain_name,
'is_created' => (int) $d->is_created,
'plesk_id' => $d->plesk_id,
'ns_count' => (int) ($d->ns_count ?? 0),
'record_count' => (int) ($d->record_count ?? 0),
'server' => $d->server ? [
'id' => $d->server->id,
'name' => $d->server->name,
'ip_address' => $d->server->ip_address,
] : null,
'registrar' => $d->registrar ? [
'id' => $d->registrar->id,
'name' => $d->registrar->name,
'short_name' => $d->registrar->short_name,
'website' => $d->registrar->website,
] : null,
'created_at' => $d->created_at,
'updated_at' => $d->updated_at,
]);
return response()->json(['error' => false, 'data' => $domains]);
}
public function getDnsDomainRecords(string $uuid, int $domainId): JsonResponse
{
$customer = $this->resolveCustomer($uuid);
$owns = DB::table('dns_domain_user')
->where('domain_id', $domainId)
->where('user_id', $customer->id)
->exists();
abort_if(!$owns, 403, 'Domain không thuộc về khách hàng này');
$records = DnsRecord::query()
->where('dns_manager_domain_id', $domainId)
->orderBy('type')
->orderBy('name')
->get();
return response()->json(['error' => false, 'data' => $records]);
}
public function getServices(string $uuid): JsonResponse
{
return response()->json(['error' => false, 'data' => $this->serviceProvisioning->getServices($uuid)]);
}
public function bulkDeleteServices(string $uuid, Request $request): JsonResponse
{
$data = $request->validate(['ids' => 'required|array|min:1', 'ids.*' => 'integer']);
$deleted = $this->serviceProvisioning->bulkDeleteServices($uuid, $data['ids']);
return response()->json(['error' => false, 'message' => "Đã xoá {$deleted} dịch vụ", 'data' => ['deleted' => $deleted]]);
}
public function updateService(string $uuid, int $serviceId, Request $request): JsonResponse
{
$data = $request->validate([
'server_name' => 'nullable|string|max:255',
'status' => 'nullable|in:pending,active,suspended,expired,cancelled,terminated',
'auto_renew' => 'nullable|boolean',
'started_at' => 'nullable|date',
'expires_at' => 'nullable|date',
'note' => 'nullable|string',
'price' => 'nullable|numeric|min:0',
'os_id' => 'nullable|integer|exists:os,id',
'vm_cpu' => 'nullable|integer|min:1',
'vm_ram_mb' => 'nullable|integer|min:1',
'vm_disk_gb' => 'nullable|integer|min:1',
'vm_ip' => 'nullable|string|max:45',
'vm_dns' => 'nullable|string|max:100',
'vm_user' => 'nullable|string|max:255',
'vm_password' => 'nullable|string|max:255',
'vm_port' => 'nullable|integer|min:1|max:65535',
]);
$service = $this->serviceProvisioning->updateService($uuid, $serviceId, $data);
return response()->json(['error' => false, 'message' => 'Cập nhật dịch vụ thành công', 'data' => $service]);
}
public function changeServiceIp(string $uuid, int $serviceId, Request $request): JsonResponse
{
$data = $request->validate(['ip' => 'required|ip']);
abort_if(!Service::where('id', $serviceId)->value('vm_id'), 422, 'Dịch vụ chưa gắn VM.');
$service = $this->serviceProvisioning->updateService($uuid, $serviceId, ['vm_ip' => $data['ip']]);
return response()->json(['error' => false, 'message' => 'Đã đổi IP thành công', 'data' => $service]);
}
public function changeServicePort(string $uuid, int $serviceId, Request $request): JsonResponse
{
$data = $request->validate(['port' => 'required|integer|min:1|max:65535']);
abort_if(!Service::where('id', $serviceId)->value('vm_id'), 422, 'Dịch vụ chưa gắn VM.');
$service = $this->serviceProvisioning->updateService($uuid, $serviceId, ['vm_port' => $data['port']]);
return response()->json(['error' => false, 'message' => 'Đã đổi port thành công', 'data' => $service]);
}
public function notifyServiceRenewal(string $uuid, int $serviceId): JsonResponse
{
$this->serviceProvisioning->notifyRenewal($uuid, $serviceId);
return response()->json(['error' => false, 'message' => 'Đã gửi thông báo gia hạn đến khách hàng.']);
}
public function createService(string $uuid, Request $request): JsonResponse
{
$data = $request->validate([
'product_id' => 'required|integer|exists:products,id',
'virtual_machine_id' => 'nullable|integer|exists:virtual_machines,id',
'server_name' => 'nullable|string|max:255',
'status' => 'nullable|in:pending,active,suspended,expired,cancelled,terminated',
'auto_renew' => 'nullable|boolean',
'started_at' => 'nullable|date',
'expires_at' => 'nullable|date',
'note' => 'nullable|string',
'billing_cycle' => 'nullable|in:monthly,quarterly,semi_annually,annually,biennially,triennially',
'payment_method' => 'required|in:transfer,balance',
'amount_paid' => 'nullable|numeric|min:0',
'proof_images' => 'nullable|array',
'proof_images.*' => 'file|image|max:5120',
'os_id' => 'nullable|integer|exists:os,id',
'vm_cpu' => 'nullable|integer|min:1',
'vm_ram_mb' => 'nullable|integer|min:1',
'vm_disk_gb' => 'nullable|integer|min:1',
'vm_ip' => 'nullable|string|max:45',
'vm_dns' => 'nullable|string|max:100',
'vm_user' => 'nullable|string|max:255',
'vm_password' => 'nullable|string|max:255',
'vm_port' => 'nullable|integer|min:1|max:65535',
]);
// Upload proof images (chuyển khoản) — I/O ở tầng HTTP, service chỉ nhận đường dẫn đã lưu.
if ($data['payment_method'] === 'transfer' && $request->hasFile('proof_images')) {
$data['_proof_paths'] = collect($request->file('proof_images'))
->map(fn($file) => $file->store('payment-proofs', 'public'))
->all();
}
$service = $this->serviceProvisioning->createService($uuid, $data, $request->ip());
return response()->json(['error' => false, 'message' => 'Service created', 'data' => $service], 201);
}
public function listAvailableVms(Request $request): JsonResponse
{
$vms = $this->serviceProvisioning->listAvailableVms(trim((string) $request->get('q', '')));
return response()->json(['error' => false, 'data' => $vms]);
}
public function getOrders(string $uuid): JsonResponse
{
return response()->json(['error' => false, 'data' => $this->billingService->getOrders($uuid)]);
}
public function updateOrder(string $uuid, int $orderId, Request $request): JsonResponse
{
$data = $request->validate([
'order_num' => 'nullable|string|max:255',
'status' => 'nullable|integer|in:0,1,2,3',
'payment_method' => 'nullable|in:transfer,balance',
'date' => 'nullable|date',
'sub_total' => 'nullable|integer|min:0',
'discount_amount' => 'nullable|integer|min:0',
'total' => 'nullable|integer|min:0',
]);
$order = $this->billingService->updateOrder($uuid, $orderId, $data);
return response()->json(['error' => false, 'message' => 'Đã cập nhật đơn hàng', 'data' => $order]);
}
public function updateStatus(string $uuid, Request $request): JsonResponse
{
$request->validate(['status' => ['required', 'integer', 'in:0,1,2,3']]);
$this->accountService->updateStatus($uuid, (int) $request->input('status'));
return response()->json(['error' => false, 'message' => 'Cập nhật trạng thái thành công']);
}
public function bulkDelete(Request $request): JsonResponse
{
$request->validate(['uuids' => ['required', 'array', 'min:1'], 'uuids.*' => ['string']]);
$count = $this->accountService->bulkDelete($request->input('uuids'));
return response()->json(['error' => false, 'message' => "Đã xoá {$count} khách hàng"]);
}
public function bulkRestore(Request $request): JsonResponse
{
$request->validate(['uuids' => ['required', 'array', 'min:1'], 'uuids.*' => ['string']]);
$count = $this->accountService->bulkRestore($request->input('uuids'));
return response()->json(['error' => false, 'message' => "Đã khôi phục {$count} khách hàng"]);
}
public function bulkForceDelete(Request $request): JsonResponse
{
$request->validate(['uuids' => ['required', 'array', 'min:1'], 'uuids.*' => ['string']]);
$count = $this->accountService->bulkForceDelete($request->input('uuids'));
return response()->json(['error' => false, 'message' => "Đã xoá vĩnh viễn {$count} khách hàng"]);
}
public function bulkStatus(Request $request): JsonResponse
{
$data = $request->validate([
'uuids' => ['required', 'array', 'min:1'],
'uuids.*' => ['string'],
'status' => ['required', 'integer', 'in:0,1,2,3'],
]);
$count = $this->accountService->bulkStatus($data['uuids'], $data['status']);
return response()->json(['error' => false, 'message' => "Đã cập nhật trạng thái {$count} khách hàng"]);
}
public function getCustomerNotes(string $uuid): JsonResponse
{
return response()->json(['error' => false, 'data' => $this->noteService->getCustomerNotes($uuid)]);
}
public function storeCustomerNote(string $uuid, Request $request): JsonResponse
{
$data = $request->validate(['note' => 'required|string']);
$note = $this->noteService->storeCustomerNote($uuid, $data['note'], Auth::id());
return response()->json(['error' => false, 'message' => 'Đã thêm ghi chú', 'data' => $note]);
}
public function destroyCustomerNote(string $uuid, int $noteId): JsonResponse
{
$this->noteService->destroyNote($uuid, $noteId);
return response()->json(['error' => false, 'message' => 'Đã xoá ghi chú']);
}
public function getServiceNotes(string $uuid, int $serviceId): JsonResponse
{
return response()->json(['error' => false, 'data' => $this->noteService->getServiceNotes($uuid, $serviceId)]);
}
public function storeServiceNote(string $uuid, int $serviceId, Request $request): JsonResponse
{
$data = $request->validate(['note' => 'required|string']);
$note = $this->noteService->storeServiceNote($uuid, $serviceId, $data['note'], Auth::id());
return response()->json(['error' => false, 'message' => 'Đã thêm ghi chú', 'data' => $note]);
}
public function destroyServiceNote(string $uuid, int $serviceId, int $noteId): JsonResponse
{
$this->noteService->destroyNote($uuid, $noteId);
return response()->json(['error' => false, 'message' => 'Đã xoá ghi chú']);
}
private function paginatedResponse(\Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator): JsonResponse
{
return response()->json([
'error' => false,
'data' => [
'items' => CustomerResource::collection($paginator),
'total' => $paginator->total(),
'per_page' => $paginator->perPage(),
'current_page' => $paginator->currentPage(),
'last_page' => $paginator->lastPage(),
],
]);
}
}