diff --git a/app/Http/Controllers/Api/Admin/Customer/CustomerController.php b/app/Http/Controllers/Api/Admin/Customer/CustomerController.php index e9c5b62..9e78190 100644 --- a/app/Http/Controllers/Api/Admin/Customer/CustomerController.php +++ b/app/Http/Controllers/Api/Admin/Customer/CustomerController.php @@ -931,7 +931,7 @@ class CustomerController extends Controller { $services = $this->serviceRepository->filter( ['customer_uuid' => $uuid], - ['product', 'orderItem', 'invoice'] + ['product', 'orderItem', 'invoice', 'vmware.os.osGroup'] ); return response()->json([ @@ -992,18 +992,45 @@ class CustomerController extends Controller 'started_at' => 'nullable|date', 'expires_at' => 'nullable|date', 'note' => 'nullable|string', + // module_vmware fields + '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', ]); try { DB::beginTransaction(); - $service->fill($data); + $service->fill(collect($data)->except([ + 'os_id', 'vm_cpu', 'vm_ram_mb', 'vm_disk_gb', 'vm_ip', 'vm_dns', 'vm_user', 'vm_password', 'vm_port', + ])->all()); $service->save(); - // Đồng bộ hostname sang module_vmware nếu có gán VM - if ($service->vm_id && array_key_exists('server_name', $data)) { - \App\Models\ModuleVmware::where('id', $service->vm_id) - ->update(['hostname' => $data['server_name']]); + // Đồng bộ sang module_vmware nếu có gán VM + if ($service->vm_id) { + $vmData = array_filter([ + 'hostname' => $data['server_name'] ?? null, + 'os_id' => $data['os_id'] ?? null, + 'cpu' => $data['vm_cpu'] ?? null, + 'ram_mb' => $data['vm_ram_mb'] ?? null, + 'disk_gb' => $data['vm_disk_gb'] ?? null, + 'ip' => $data['vm_ip'] ?? null, + 'dns' => $data['vm_dns'] ?? null, + 'user' => $data['vm_user'] ?? null, + 'password' => $data['vm_password'] ?? null, + 'plain_password' => $data['vm_password'] ?? null, + 'port' => $data['vm_port'] ?? null, + ], fn ($v) => $v !== null); + + if ($vmData) { + \App\Models\ModuleVmware::where('id', $service->vm_id)->update($vmData); + } } DB::commit(); @@ -1011,7 +1038,7 @@ class CustomerController extends Controller return response()->json([ 'error' => false, 'message' => 'Cập nhật dịch vụ thành công', - 'data' => $service->load(['product', 'orderItem', 'invoice']), + 'data' => $service->load(['product', 'orderItem', 'invoice', 'vmware.os.osGroup']), ]); } catch (\Throwable $e) { DB::rollBack(); @@ -1043,8 +1070,19 @@ class CustomerController extends Controller // New: billing & payment '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', + // module_vmware fields — admin can override the vCenter snapshot / fill in what's not tracked there (disk, OS, credentials, port) + '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', ]); $product = \App\Models\Product::with('pricings')->find($data['product_id']); @@ -1061,7 +1099,10 @@ class CustomerController extends Controller $pricing = $product->pricings ->where('billing_cycle', $billingCycle) ->first(); - $basePrice = (int) ($pricing->price ?? 0); + // Số tiền đã thanh toán: admin nhập tay để khớp với thực tế (có thể lệch giá niêm yết do khuyến mãi/thoả thuận) + $basePrice = isset($data['amount_paid']) + ? (int) $data['amount_paid'] + : (int) ($pricing->price ?? 0); $paymentMethod = $data['payment_method']; @@ -1088,32 +1129,7 @@ class CustomerController extends Controller try { DB::beginTransaction(); - // 1) ModuleVmware: snapshot từ virtual_machines - $vmId = null; - if (!empty($data['virtual_machine_id'])) { - $vm = \App\Models\VirtualMachines::with('datacenter') - ->find($data['virtual_machine_id']); - - $vcenterId = $vm->datacenter->vcenter_id ?? null; - - $moduleVm = \App\Models\ModuleVmware::updateOrCreate( - ['virtual_machine_id' => $vm->id], - [ - 'customer_id' => $customer->id, - 'name' => $vm->name, - 'vmware_id' => $vm->vm_id, - 'vcenter_id' => $vcenterId, - 'hostname' => $data['server_name'] ?? $vm->name, - 'cpu' => $vm->cpu, - 'ram_mb' => $vm->ram_mb, - 'disk_gb' => $vm->disk_gb ?? null, - 'status' => 1, - ] - ); - $vmId = $moduleVm->id; - } - - // 2) Order + OrderItem (mô phỏng flow customer đặt hàng) + // 1) Order + OrderItem (mô phỏng flow customer đặt hàng) $order = Order::create([ 'order_num' => OrderHelper::generateOrderNumber(), 'customer_id' => $customer->id, @@ -1135,6 +1151,41 @@ class CustomerController extends Controller 'price' => $basePrice, ]); + // 2) ModuleVmware: snapshot từ virtual_machines. `order_id` phải trỏ về + // order vừa tạo — client console-ticket/rebuild resolve VM qua + // service->orderItem->order->vmWare, không qua services.vm_id. + $vmId = null; + if (!empty($data['virtual_machine_id'])) { + $vm = \App\Models\VirtualMachines::with('datacenter') + ->find($data['virtual_machine_id']); + + $vcenterId = $vm->datacenter->vcenter_id ?? null; + + $moduleVm = \App\Models\ModuleVmware::updateOrCreate( + ['virtual_machine_id' => $vm->id], + [ + 'customer_id' => $customer->id, + 'order_id' => $order->id, + 'name' => $vm->name, + 'vmware_id' => $vm->vm_id, + 'vcenter_id' => $vcenterId, + 'hostname' => $data['server_name'] ?? $vm->name, + 'cpu' => $data['vm_cpu'] ?? $vm->cpu, + 'ram_mb' => $data['vm_ram_mb'] ?? $vm->ram_mb, + 'disk_gb' => $data['vm_disk_gb'] ?? null, + 'os_id' => $data['os_id'] ?? null, + 'ip' => $data['vm_ip'] ?? $vm->ip, + 'dns' => $data['vm_dns'] ?? null, + 'user' => $data['vm_user'] ?? null, + 'password' => $data['vm_password'] ?? null, + 'plain_password' => $data['vm_password'] ?? null, + 'port' => $data['vm_port'] ?? null, + 'status' => 1, + ] + ); + $vmId = $moduleVm->id; + } + // 3) Invoice — đánh dấu PAID luôn vì admin đứng ra tạo & xác nhận thanh toán $invoice = Invoice::create([ 'invoice_number' => OrderHelper::generateOrderNumber('INV'), @@ -1217,7 +1268,7 @@ class CustomerController extends Controller return response()->json([ 'error' => false, 'message' => 'Service created', - 'data' => $service->load(['product']), + 'data' => $service->load(['product', 'vmware.os.osGroup']), ]); } catch (\Throwable $e) { DB::rollBack(); @@ -1244,16 +1295,24 @@ class CustomerController extends Controller ->get() ->keyBy('virtual_machine_id'); - $vms = DB::table('virtual_machines') - ->select('id', 'vm_id', 'name', 'power_state', 'cpu', 'ram_mb', 'ip') - ->where('is_template', 0) - ->where('is_system', 0) + $vms = DB::table('virtual_machines as vm') + ->leftJoin('module_datacenter as dc', 'dc.id', '=', 'vm.datacenter_id') + ->leftJoin('module_host as h', 'h.id', '=', 'vm.host_id') + ->leftJoin('module_vcenter as vc', 'vc.id', '=', 'dc.vcenter_id') + ->select( + 'vm.id', 'vm.vm_id', 'vm.name', 'vm.power_state', 'vm.cpu', 'vm.ram_mb', 'vm.ip', + 'vc.host as vcenter_name', + 'dc.name as datacenter_name', + 'h.name as host_name', + ) + ->where('vm.is_template', 0) + ->where('vm.is_system', 0) ->when($search, fn ($q) => $q->where(function ($w) use ($search) { - $w->where('name', 'like', "%{$search}%") - ->orWhere('vm_id', 'like', "%{$search}%") - ->orWhere('ip', 'like', "%{$search}%"); + $w->where('vm.name', 'like', "%{$search}%") + ->orWhere('vm.vm_id', 'like', "%{$search}%") + ->orWhere('vm.ip', 'like', "%{$search}%"); })) - ->orderBy('name') + ->orderBy('vm.name') ->limit(200) ->get() ->map(function ($vm) use ($assignments) { diff --git a/database/migrations/2026_07_24_000001_change_services_note_to_longtext.php b/database/migrations/2026_07_24_000001_change_services_note_to_longtext.php new file mode 100644 index 0000000..36f95dd --- /dev/null +++ b/database/migrations/2026_07_24_000001_change_services_note_to_longtext.php @@ -0,0 +1,18 @@ +change() — this repo doesn't have doctrine/dbal installed +return new class extends Migration +{ + public function up(): void + { + DB::statement('ALTER TABLE services MODIFY note LONGTEXT NULL'); + } + + public function down(): void + { + DB::statement('ALTER TABLE services MODIFY note TEXT NULL'); + } +}; diff --git a/storage/app/public/payment-proofs/AHhHaFPvx1Fl5ioaCQ56NJTQdnwFwUq7zKhFiOSY.png b/storage/app/public/payment-proofs/AHhHaFPvx1Fl5ioaCQ56NJTQdnwFwUq7zKhFiOSY.png new file mode 100644 index 0000000..26c16c8 Binary files /dev/null and b/storage/app/public/payment-proofs/AHhHaFPvx1Fl5ioaCQ56NJTQdnwFwUq7zKhFiOSY.png differ diff --git a/storage/app/public/payment-proofs/BlrM304DpAMLeP3BkQo6N8OlUBH12Jiv94fM65tF.png b/storage/app/public/payment-proofs/BlrM304DpAMLeP3BkQo6N8OlUBH12Jiv94fM65tF.png new file mode 100644 index 0000000..8c2dc62 Binary files /dev/null and b/storage/app/public/payment-proofs/BlrM304DpAMLeP3BkQo6N8OlUBH12Jiv94fM65tF.png differ diff --git a/storage/app/public/payment-proofs/ScCRsSFgXtlIgIKMWzaKaCwPTfDlwGVLCTvTrMzY.png b/storage/app/public/payment-proofs/ScCRsSFgXtlIgIKMWzaKaCwPTfDlwGVLCTvTrMzY.png new file mode 100644 index 0000000..26c16c8 Binary files /dev/null and b/storage/app/public/payment-proofs/ScCRsSFgXtlIgIKMWzaKaCwPTfDlwGVLCTvTrMzY.png differ