feat: Enhance service management with VMware integration and update note field to LONGTEXT

This commit is contained in:
baodev0
2026-07-24 17:01:25 +07:00
parent 9a3a42061d
commit 5d2eeac6c4
5 changed files with 120 additions and 43 deletions
@@ -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) {
@@ -0,0 +1,18 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
// ponytail: raw SQL instead of Schema::table()->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');
}
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB