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', 'requirements' => 'nullable|string', 'feedback' => '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(), ], ]); } }