From dbd3cdafcdc38213b249d7ba9ea03d8eea1084a6 Mon Sep 17 00:00:00 2001 From: pbao9 Date: Thu, 12 Mar 2026 16:30:02 +0700 Subject: [PATCH] feat: Implement UUID-based customer routing, enhance order request validation, and refactor VM creation job with updated payment logic and `ModuleVmware` handling. --- .../Api/Admin/Customer/CustomerController.php | 142 ++++++++---------- app/Http/Requests/OrderRequest.php | 10 +- .../Resources/Api/Admin/CustomerResource.php | 1 + app/Http/Services/vCenter.php | 8 - .../VirtualMachine/Create/CreateVMJobs.php | 115 -------------- .../Create/CreateVirtualMachineJob.php | 51 ++----- app/Models/Invoice.php | 6 + app/Models/ModuleVmware.php | 4 +- app/Observers/ModuleVmwareObserver.php | 105 ++++++++++--- app/Services/OrderService.php | 24 +-- routes/api.php | 24 +-- 11 files changed, 207 insertions(+), 283 deletions(-) delete mode 100644 app/Jobs/VirtualMachine/Create/CreateVMJobs.php diff --git a/app/Http/Controllers/Api/Admin/Customer/CustomerController.php b/app/Http/Controllers/Api/Admin/Customer/CustomerController.php index 07226be..773b023 100644 --- a/app/Http/Controllers/Api/Admin/Customer/CustomerController.php +++ b/app/Http/Controllers/Api/Admin/Customer/CustomerController.php @@ -23,7 +23,6 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; -use Yajra\DataTables\Facades\DataTables; class CustomerController extends Controller { @@ -62,23 +61,6 @@ class CustomerController extends Controller ]); } - public function datatable(Request $request): JsonResponse - { - $query = $this->customerRepository->filter( - $request->all(), - [], - ['hosting', 'vps', 'domain'], - ['*'], - true - ); - - return DataTables::eloquent($query) - ->addColumn('status', fn($d) => $d->status) - ->addColumn('action', fn($d) => $d->id) - ->rawColumns(['status', 'action']) - ->make(true); - } - public function store(CreateCustomerRequest $request): JsonResponse { $request->validated(); @@ -114,10 +96,19 @@ class CustomerController extends Controller } } - public function show(int $id): JsonResponse + public function show(string $uuid): JsonResponse { + $customer = $this->customerRepository->findBy('uuid', $uuid); + + if (!$customer) { + return response()->json([ + 'error' => true, + 'message' => "Can't find customer by UUID #" . $uuid + ], 404); + } + $customer = $this->customerRepository->findWithRelationships( - $id, + $customer->id, ['mail', 'file'], [ 'invoice' => [ @@ -140,7 +131,7 @@ class CustomerController extends Controller if (!$customer) { return response()->json([ 'error' => true, - 'message' => "Can't find customer by ID #" . $id + 'message' => "Can't find customer by UUID #" . $uuid ], 404); } @@ -150,14 +141,14 @@ class CustomerController extends Controller ]); } - public function update(int $id, CreateCustomerRequest $request): JsonResponse + public function update(string $uuid, CreateCustomerRequest $request): JsonResponse { - $customer = $this->customerRepository->find($id); + $customer = $this->customerRepository->findBy('uuid', $uuid); if (!$customer) { return response()->json([ 'error' => true, - 'message' => "Can't find customer by ID #" . $id + 'message' => "Can't find customer by UUID #" . $uuid ], 404); } @@ -171,13 +162,13 @@ class CustomerController extends Controller $data['password'] = Hash::make($request->password); } - $this->customerRepository->update($id, $data); + $this->customerRepository->update($customer->id, $data); DB::commit(); return response()->json([ 'error' => false, 'message' => 'Customer updated successfully', - 'data' => new CustomerResource($this->customerRepository->find($id)) + 'data' => new CustomerResource($this->customerRepository->find($customer->id)) ]); } catch (\Exception $e) { DB::rollBack(); @@ -188,22 +179,22 @@ class CustomerController extends Controller } } - public function destroy(int $id): JsonResponse + public function destroy(string $uuid): JsonResponse { - $customer = $this->customerRepository->find($id); + $customer = $this->customerRepository->findBy('uuid', $uuid); if (!$customer) { return response()->json([ 'error' => true, - 'message' => "Can't find customer by ID #" . $id + 'message' => "Can't find customer by UUID #" . $uuid ], 404); } try { DB::beginTransaction(); - Invoice::where('customer_id', $id)->delete(); - Order::where('customer_id', $id)->delete(); - Transaction::where('customer_id', $id)->delete(); + Invoice::where('customer_id', $customer->id)->delete(); + Order::where('customer_id', $customer->id)->delete(); + Transaction::where('customer_id', $customer->id)->delete(); $customer->delete(); DB::commit(); @@ -224,33 +215,32 @@ class CustomerController extends Controller { $query = $this->customerRepository->onlyTrashed([], ['hosting', 'vps', 'domain']); - return DataTables::eloquent($query) - ->addColumn('status', fn($d) => $d->status) - ->addColumn('action', fn($d) => $d->id) - ->rawColumns(['status', 'action']) - ->make(true); + return response()->json([ + 'error' => false, + 'data' => CustomerResource::collection($query->get()) + ]); } - public function restore(int $id): JsonResponse + public function restore(string $uuid): JsonResponse { - $customer = $this->customerRepository->find($id); + $customer = $this->customerRepository->findBy('uuid', $uuid); if (!$customer) { - $customer = $this->customerRepository->onlyTrashed()->find($id); + $customer = $this->customerRepository->onlyTrashed()->where('uuid', $uuid)->first(); } if (!$customer) { return response()->json([ 'error' => true, - 'message' => "Can't find customer by ID #" . $id + 'message' => "Can't find customer by UUID #" . $uuid ], 404); } try { DB::beginTransaction(); $customer->restore(); - Invoice::withTrashed()->where('customer_id', $id)->restore(); - Order::withTrashed()->where('customer_id', $id)->restore(); - Transaction::withTrashed()->where('customer_id', $id)->restore(); + Invoice::withTrashed()->where('customer_id', $customer->id)->restore(); + Order::withTrashed()->where('customer_id', $customer->id)->restore(); + Transaction::withTrashed()->where('customer_id', $customer->id)->restore(); DB::commit(); return response()->json([ @@ -266,22 +256,22 @@ class CustomerController extends Controller } } - public function forceDelete(int $id): JsonResponse + public function forceDelete(string $uuid): JsonResponse { - $customer = $this->customerRepository->onlyTrashed()->find($id); + $customer = $this->customerRepository->onlyTrashed()->where('uuid', $uuid)->first(); if (!$customer) { return response()->json([ 'error' => true, - 'message' => "Can't find customer by ID #" . $id + 'message' => "Can't find customer by UUID #" . $uuid ], 404); } try { DB::beginTransaction(); - Invoice::withTrashed()->where('customer_id', $id)->forceDelete(); - Order::withTrashed()->where('customer_id', $id)->forceDelete(); - Transaction::withTrashed()->where('customer_id', $id)->forceDelete(); + Invoice::withTrashed()->where('customer_id', $customer->id)->forceDelete(); + Order::withTrashed()->where('customer_id', $customer->id)->forceDelete(); + Transaction::withTrashed()->where('customer_id', $customer->id)->forceDelete(); $customer->forceDelete(); DB::commit(); @@ -298,14 +288,14 @@ class CustomerController extends Controller } } - public function resetPassword(Request $request, int $id): JsonResponse + public function resetPassword(Request $request, string $uuid): JsonResponse { - $customer = $this->customerRepository->find($id); + $customer = $this->customerRepository->findBy('uuid', $uuid); if (!$customer) { return response()->json([ 'error' => true, - 'message' => "Can't find customer by ID #" . $id + 'message' => "Can't find customer by UUID #" . $uuid ], 404); } @@ -313,7 +303,7 @@ class CustomerController extends Controller try { DB::beginTransaction(); - $this->customerRepository->update($id, ['password' => Hash::make($password)]); + $this->customerRepository->update($customer->id, ['password' => Hash::make($password)]); DB::commit(); return response()->json([ @@ -330,19 +320,19 @@ class CustomerController extends Controller } } - public function addCredit(Request $request, int $id): JsonResponse + public function addCredit(Request $request, string $uuid): JsonResponse { $request->validate([ 'amount' => 'required|numeric|min:1', 'action' => 'required|in:+,-' ]); - $customer = $this->customerRepository->find($id); + $customer = $this->customerRepository->findBy('uuid', $uuid); if (!$customer) { return response()->json([ 'error' => true, - 'message' => "Can't find customer by ID #" . $id + 'message' => "Can't find customer by UUID #" . $uuid ], 404); } @@ -352,12 +342,12 @@ class CustomerController extends Controller $amount = (int) $request->amount; if ($request->action === "+") { - $this->customerRepository->update($id, [ + $this->customerRepository->update($customer->id, [ 'credit' => (int) $customer->credit + $amount, 'total' => (int) $customer->total + $amount, ]); } else { - $this->customerRepository->update($id, [ + $this->customerRepository->update($customer->id, [ 'credit' => (int) $customer->credit - $amount, 'total' => (int) $customer->total - $amount, ]); @@ -378,20 +368,20 @@ class CustomerController extends Controller } } - public function getProducts(int $id): JsonResponse + public function getProducts(string $uuid): JsonResponse { - $customer = $this->customerRepository->find($id); + $customer = $this->customerRepository->findBy('uuid', $uuid); if (!$customer) { return response()->json([ 'error' => true, - 'message' => "Can't find customer by ID #" . $id + 'message' => "Can't find customer by UUID #" . $uuid ], 404); } - $hostingServices = $this->moduleHostingRepository->filter(['customer_id' => $id], ['server']); + $hostingServices = $this->moduleHostingRepository->filter(['customer_id' => $customer->id], ['server']); $vpsServices = $this->vmwareRepository->filter( - ['customer_id' => $id], + ['customer_id' => $customer->id], ['ipaddress.host.datacenter.vCenter', 'os.osGroup'] ); @@ -421,19 +411,19 @@ class CustomerController extends Controller ]); } - public function getInvoices(int $id, Request $request): JsonResponse + public function getInvoices(string $uuid, Request $request): JsonResponse { - $customer = $this->customerRepository->find($id); + $customer = $this->customerRepository->findBy('uuid', $uuid); if (!$customer) { return response()->json([ 'error' => true, - 'message' => "Can't find customer by ID #" . $id + 'message' => "Can't find customer by UUID #" . $uuid ], 404); } $invoices = $this->invoiceRepository->filter( - ['customer_id' => $id], + ['customer_id' => $customer->id], ['payment'] ); @@ -443,18 +433,18 @@ class CustomerController extends Controller ]); } - public function getTransactions(int $id, Request $request): JsonResponse + public function getTransactions(string $uuid, Request $request): JsonResponse { - $customer = $this->customerRepository->find($id); + $customer = $this->customerRepository->findBy('uuid', $uuid); if (!$customer) { return response()->json([ 'error' => true, - 'message' => "Can't find customer by ID #" . $id + 'message' => "Can't find customer by UUID #" . $uuid ], 404); } - $transactions = $this->transactionRepository->filter(['customer_id' => $id]); + $transactions = $this->transactionRepository->filter(['customer_id' => $customer->id]); return response()->json([ 'error' => false, @@ -462,19 +452,19 @@ class CustomerController extends Controller ]); } - public function getDomains(int $id, Request $request): JsonResponse + public function getDomains(string $uuid, Request $request): JsonResponse { - $customer = $this->customerRepository->find($id); + $customer = $this->customerRepository->findBy('uuid', $uuid); if (!$customer) { return response()->json([ 'error' => true, - 'message' => "Can't find customer by ID #" . $id + 'message' => "Can't find customer by UUID #" . $uuid ], 404); } $domains = $this->moduleDomainRepository->filter( - ['customer_id' => $id], + ['customer_id' => $customer->id], ['customer'] ); diff --git a/app/Http/Requests/OrderRequest.php b/app/Http/Requests/OrderRequest.php index 71744b9..398f3f9 100644 --- a/app/Http/Requests/OrderRequest.php +++ b/app/Http/Requests/OrderRequest.php @@ -25,10 +25,12 @@ class OrderRequest extends FormRequest 'customer_id' => 'required', 'payment_method' => 'required|integer|min:1', 'status' => 'required', - 'product.*.product_id' => 'required|numeric|not__in:0', - 'product.*.os_id' => 'nullable|integer', - 'product.*.billing' => 'required|not__in:0', - 'product.*.quantity' => 'required|min:1', + 'product.*.product_id' => 'required|numeric|not__in:0', + 'product.*.os_id' => 'nullable|integer', + 'product.*.billing' => 'required|not__in:0', + 'product.*.quantity' => 'required|min:1', + 'product.*.server_name' => 'nullable|string|max:255', + 'product.*.price_custom' => 'nullable|integer|min:0', ]; } } diff --git a/app/Http/Resources/Api/Admin/CustomerResource.php b/app/Http/Resources/Api/Admin/CustomerResource.php index c72e850..0cb93b9 100644 --- a/app/Http/Resources/Api/Admin/CustomerResource.php +++ b/app/Http/Resources/Api/Admin/CustomerResource.php @@ -11,6 +11,7 @@ class CustomerResource extends JsonResource { return [ 'id' => $this->id, + 'uuid' => $this->uuid, 'fullname' => $this->fullname, 'email' => $this->email, 'phone' => $this->phone, diff --git a/app/Http/Services/vCenter.php b/app/Http/Services/vCenter.php index 474b003..94571e0 100644 --- a/app/Http/Services/vCenter.php +++ b/app/Http/Services/vCenter.php @@ -647,7 +647,6 @@ PS; public function CreateVirtualMachine($product_data): array|string { $vcenterModel = $product_data['vcenter']; - Log::info('Vcenter Info', ['data' => $vcenterModel]); if (!$this->loginServer($vcenterModel)) { Log::error('Failed to create VM: API check failed, requires login.'); @@ -673,13 +672,6 @@ PS; $base_ram = max(1, (int) $base_ram); $base_disk = max(20, (int) $base_disk); - Log::info("Cấu hình cơ bản", [ - 'base_cpu' => $base_cpu, - 'base_ram' => $base_ram, - 'base_disk' => $base_disk, - 'addon' => $addon - ]); - // Lấy tất cả datastore liên quan host, không lấy first() $datastores = \App\Models\ModuleDatastore::where('module_host_id', $host->id)->get(); diff --git a/app/Jobs/VirtualMachine/Create/CreateVMJobs.php b/app/Jobs/VirtualMachine/Create/CreateVMJobs.php deleted file mode 100644 index 76296a5..0000000 --- a/app/Jobs/VirtualMachine/Create/CreateVMJobs.php +++ /dev/null @@ -1,115 +0,0 @@ -data = $product; - $this->telegram = new Telegram; - } - - - public function handle(ServiceVCenter $serviceVCenter) - { - - $product = ModuleVmware::with([ - 'Order.OrderItem', - 'Customer', - 'IpAddress.Network', - 'IpAddress.Host.OneDatastore', - 'IpAddress.Host.Datacenter.vCenter','Os.OsGroup' - ])->find($this->data); - - $serviceVCenter->token = $product->IpAddress->Host->Datacenter->vCenter->token; - $serviceVCenter->apiUri = $product->IpAddress->Host->Datacenter->vCenter->host; - - $CheckToken = $serviceVCenter->checkApiExits(); - - if (! $CheckToken) - { - $token = $serviceVCenter->loginServer($product->IpAddress->Host->Datacenter->vCenter); - - if ($token) - { - $product->IpAddress->Host->Datacenter->vCenter->update(['token' => $token]); - $serviceVCenter->token = $token; - } - } - - $name = $product->IpAddress->ip . '_' . str_replace('-','_',Str::slug($product->Customer->firstname)); - - $jobs_check = new GetVirtualMachine($name,$product,$serviceVCenter); - - $vmware_id = $jobs_check->handle(); - - if ($vmware_id === false) - { - Log::channel('detail')->info("Bắt đầu tạo: " . time()); - $vmware_id = $serviceVCenter->CreateVirtualMachine($product); - Log::channel('detail')->info("Kết thúc: " . time()); - if (isset($vmware_id['error']) && $vmware_id['error'] === false) { - $vmware_id = $vmware_id['data']; - } - } - - if ($vmware_id && is_string($vmware_id)) { - - $product->update(['vmware_id' => $vmware_id]); - - $jobs_GetStatus = new CheckStatusVirtualMachine($vmware_id,$product,$serviceVCenter); - - dispatch($jobs_GetStatus)->delay(now()->addSeconds(30)); - - } else { - - $this->message = "Notification: " . json_encode($vmware_id); - - throw new \Exception(json_encode($vmware_id)); - } - - } - public function failed(Throwable $exception): void - { - Log::channel('Custom')->info("\r\n"); - Log::channel('Custom')->info("================================="); - Log::channel('Custom')->info("Thông báo lỗi lúc: " .Carbon::now()); - Log::channel('Custom')->info("Notification Create VirtualMachine: "); - Log::channel('Custom')->info("========"); - Log::channel('Custom')->info($exception->getMessage()); - Log::channel('Custom')->info("================================="); - - $this->telegram->SendErrorMessage($exception->getMessage()); - - } - -} diff --git a/app/Jobs/VirtualMachine/Create/CreateVirtualMachineJob.php b/app/Jobs/VirtualMachine/Create/CreateVirtualMachineJob.php index b35a153..5cddf14 100644 --- a/app/Jobs/VirtualMachine/Create/CreateVirtualMachineJob.php +++ b/app/Jobs/VirtualMachine/Create/CreateVirtualMachineJob.php @@ -14,7 +14,6 @@ use App\Models\ModuleVmware; use App\Models\ModuleIpAddress; use App\Models\IpPool; use App\Models\Log as CustomerLog; -use App\Models\Service; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -160,7 +159,7 @@ class CreateVirtualMachineJob implements ShouldQueue 'template' => $template, 'addon' => $addonData, ]); - + Log::info('CreateVirtualMachineJob - Result: ' . json_encode($result)); if (is_array($result) && ($result['error'] ?? false)) { $errorMessage = 'Tạo máy ảo thất bại: ' . json_encode($result); Log::warning($errorMessage); @@ -202,22 +201,14 @@ class CreateVirtualMachineJob implements ShouldQueue if ($vmId !== 'unknown' && $vmId !== 'UNKNOWN') { try { - // Lưu vm_id vào bảng invoice - $invoice = \App\Models\Invoice::where('order_id', $this->item->order->id)->first(); - if ($invoice) { - $invoice->update(['vm_id' => $vmId]); - } - - // Start VM ngay sau khi tạo thành công (sử dụng API trực tiếp) + // vm_id được lưu vào tbl.services qua ModuleVmwareObserver::created() $startResult = $this->startVirtualMachineDirectly($vCenter, $vmId); if (is_array($startResult) && $startResult['error'] === false) { - // Dispatch ConfigCLICommand để cấu hình VM và lấy password $configJob = new ConfigCLICommand($vmId, $this->item, $vCenter); dispatch($configJob)->delay(now()->addMinutes(2)); // Chờ 2 phút để VM boot hoàn toàn } - // Lưu dữ liệu VM vào database try { $customer = $this->item->order->customer; $serverName = $this->item->server_name ?? 'VM-' . $vmId; @@ -236,42 +227,32 @@ class CreateVirtualMachineJob implements ShouldQueue 'status' => 1 // 1 = Assigned ]); + $months = (int) ($this->item->months ?? 0); + $firstPayment = (int) $this->item->price * ($months ?: 1); + $nextDueDate = $months > 0 ? now()->addMonths($months)->timestamp : null; + $moduleVmware = ModuleVmware::create([ 'name' => $serverName, - 'module_ip_address_id' => $moduleIpAddress->id, + 'module_ip_address' => $moduleIpAddress->id, 'port' => 22, 'order_id' => $this->item->order->id, - 'first_payment' => $this->item->order->first_payment ?? 0, - 'next_payment' => $this->item->order->next_payment ?? 0, - 'next_due_date' => $this->item->order->next_due_date ?? null, - 'next_invoice' => $this->item->order->next_invoice ?? 0, - 'user' => 'Administrator', - 'password' => '', + 'first_payment' => $firstPayment, + 'next_payment' => $firstPayment, + 'next_due_date' => $nextDueDate, + 'next_invoice' => $nextDueDate, + 'user' => 'administrator', + 'password' => 'TGS@123', 'hostname' => $serverName, 'vmware_id' => $vmId, + 'vcenter_id' => $vcenter->id, 'os_id' => $this->item->os_id ?? 1, 'customer_id' => $customer->id, - 'status' => 1 + 'status' => 1, ]); AssignIPJob::dispatch($vmId, $moduleVmware->id, $moduleIpAddress->ip) ->delay(now()->addMinutes(5)); - - // Lưu Service record để track dịch vụ của khách hàng - Service::create([ - 'customer_id' => $customer->id, - 'product_id' => $this->item->product_id, - 'order_item_id' => $this->item->id, - 'invoice_id' => $invoice?->id, - 'vm_id' => $vmId, - 'server_name' => $serverName, - 'status' => 'active', - 'auto_renew' => false, - 'started_at' => now(), - 'expires_at' => $this->item->months > 0 - ? now()->addMonths($this->item->months) - : null, - ]); + // Service record sẽ được tạo tự động bởi ModuleVmwareObserver::created() } catch (\Exception $e) { Log::error('❌ Lỗi khi lưu dữ liệu VM và IP address: ' . $e->getMessage(), [ 'vmware_id' => $vmId, diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index eafca2e..874514e 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -50,6 +50,12 @@ class Invoice extends Model return $this->hasMany(Service::class, 'invoice_id'); } + + public function payment() + { + return $this->belongsTo(Payment::class, 'payment_method', 'id'); + } + // ------------------------------------------------------------------------- // Helpers // ------------------------------------------------------------------------- diff --git a/app/Models/ModuleVmware.php b/app/Models/ModuleVmware.php index 57bccb2..7c4e691 100644 --- a/app/Models/ModuleVmware.php +++ b/app/Models/ModuleVmware.php @@ -13,7 +13,7 @@ class ModuleVmware extends Model protected $fillable = [ 'name', - 'module_ip_address_id', + 'module_ip_address', 'port', 'order_id', 'first_payment', @@ -37,7 +37,7 @@ class ModuleVmware extends Model } public function ipAddress() { - return $this->belongsTo(\App\Models\ModuleIpAddress::class, 'module_ip_address_id', 'id'); + return $this->belongsTo(\App\Models\ModuleIpAddress::class, 'module_ip_address', 'id'); } public function vCenter() { diff --git a/app/Observers/ModuleVmwareObserver.php b/app/Observers/ModuleVmwareObserver.php index 5ffe292..0ab943f 100644 --- a/app/Observers/ModuleVmwareObserver.php +++ b/app/Observers/ModuleVmwareObserver.php @@ -4,37 +4,55 @@ declare(strict_types=1); namespace App\Observers; +use App\Models\Invoice; use App\Models\ModuleVmware; -use App\Models\ModuleIpAddress; +use App\Models\OrderItem; +use App\Models\Service; use Illuminate\Support\Facades\Log; class ModuleVmwareObserver { /** * Handle the ModuleVmware "created" event. + * Fired by CreateVirtualMachineJob khi tạo bản ghi mới sau khi VM được provisioned. */ public function created(ModuleVmware $moduleVmware): void { - Log::info('✅ ModuleVmware created', [ - 'vm_id' => $moduleVmware->id, - 'vmware_id' => $moduleVmware->vmware_id, - 'name' => $moduleVmware->name, - 'order_id' => $moduleVmware->order_id, - 'customer_id' => $moduleVmware->customer_id + Log::info('ModuleVmware created', [ + 'id' => $moduleVmware->id, + 'vmware_id' => $moduleVmware->vmware_id, + 'name' => $moduleVmware->name, + 'order_id' => $moduleVmware->order_id, + 'customer_id' => $moduleVmware->customer_id, ]); + + if ($moduleVmware->vmware_id && $moduleVmware->order_id) { + $this->syncServiceRecord($moduleVmware); + } } /** * Handle the ModuleVmware "updated" event. + * Fired by CreateVMJobs khi update vmware_id vào bản ghi đã tồn tại. */ public function updated(ModuleVmware $moduleVmware): void { - Log::info('🔄 ModuleVmware updated', [ - 'vm_id' => $moduleVmware->id, + Log::info('ModuleVmware updated', [ + 'id' => $moduleVmware->id, 'vmware_id' => $moduleVmware->vmware_id, - 'name' => $moduleVmware->name, - 'status' => $moduleVmware->status + 'name' => $moduleVmware->name, + 'status' => $moduleVmware->status, ]); + + // Chỉ tạo Service khi vmware_id vừa được set (chuyển từ null → có giá trị) + if ( + $moduleVmware->wasChanged('vmware_id') + && $moduleVmware->vmware_id + && !$moduleVmware->getOriginal('vmware_id') + && $moduleVmware->order_id + ) { + $this->syncServiceRecord($moduleVmware); + } } /** @@ -42,10 +60,10 @@ class ModuleVmwareObserver */ public function deleted(ModuleVmware $moduleVmware): void { - Log::info('🗑️ ModuleVmware deleted', [ - 'vm_id' => $moduleVmware->id, + Log::info('ModuleVmware deleted', [ + 'id' => $moduleVmware->id, 'vmware_id' => $moduleVmware->vmware_id, - 'name' => $moduleVmware->name + 'name' => $moduleVmware->name, ]); } @@ -54,10 +72,10 @@ class ModuleVmwareObserver */ public function restored(ModuleVmware $moduleVmware): void { - Log::info('🔄 ModuleVmware restored', [ - 'vm_id' => $moduleVmware->id, + Log::info('ModuleVmware restored', [ + 'id' => $moduleVmware->id, 'vmware_id' => $moduleVmware->vmware_id, - 'name' => $moduleVmware->name + 'name' => $moduleVmware->name, ]); } @@ -66,10 +84,55 @@ class ModuleVmwareObserver */ public function forceDeleted(ModuleVmware $moduleVmware): void { - Log::info('🗑️ ModuleVmware force deleted', [ - 'vm_id' => $moduleVmware->id, + Log::info('ModuleVmware force deleted', [ + 'id' => $moduleVmware->id, 'vmware_id' => $moduleVmware->vmware_id, - 'name' => $moduleVmware->name + 'name' => $moduleVmware->name, ]); } -} \ No newline at end of file + + // ------------------------------------------------------------------------- + + /** + * Tạo hoặc cập nhật bản ghi Service tương ứng với VM vừa được provisioned. + * Dùng chung cho cả created và updated event. + */ + private function syncServiceRecord(ModuleVmware $moduleVmware): void + { + try { + $orderItem = OrderItem::where('order_id', $moduleVmware->order_id) + ->where('os_id', $moduleVmware->os_id) + ->first(); + + $invoice = Invoice::where('order_id', $moduleVmware->order_id)->first(); + + Service::firstOrCreate( + ['order_item_id' => $orderItem?->id], + [ + 'customer_id' => $moduleVmware->customer_id, + 'product_id' => $orderItem?->product_id, + 'invoice_id' => $invoice?->id, + 'vm_id' => $moduleVmware->vmware_id, + 'server_name' => $moduleVmware->hostname ?? $moduleVmware->name, + 'status' => 'active', + 'auto_renew' => false, + 'started_at' => now(), + 'expires_at' => ($orderItem && (int) $orderItem->months > 0) + ? now()->addMonths((int) $orderItem->months) + : null, + ] + ); + + Log::info('Service record synced for ModuleVmware', [ + 'module_vmware_id' => $moduleVmware->id, + 'vmware_id' => $moduleVmware->vmware_id, + 'order_item_id' => $orderItem?->id, + ]); + } catch (\Throwable $e) { + Log::error('Lỗi khi tạo Service từ ModuleVmwareObserver: ' . $e->getMessage(), [ + 'module_vmware_id' => $moduleVmware->id, + 'order_id' => $moduleVmware->order_id, + ]); + } + } +} diff --git a/app/Services/OrderService.php b/app/Services/OrderService.php index 7893112..439cba4 100644 --- a/app/Services/OrderService.php +++ b/app/Services/OrderService.php @@ -3,6 +3,7 @@ namespace App\Services; use App\Enums\Product\ProductBillingCycle; +use App\Enums\Product\ProductTypeEnums; use App\Http\Helpers\Helper; use App\Jobs\Hosting\CreateHostingJobs; use App\Jobs\Mail\Customer\OrderCreatedJob; @@ -117,9 +118,10 @@ class OrderService 'updated_at' => $now, ], $productItems)); - $vpsTypes = ['vps', 'cloud']; + $vmTypes = [ProductTypeEnums::VPS, ProductTypeEnums::CLOUD]; foreach (OrderItem::where('order_id', $order->id)->get() as $orderItem) { - if (in_array($orderItem->product?->product_type, $vpsTypes)) { + $type = ProductTypeEnums::tryFrom($orderItem->product?->category?->type ?? ''); + if (in_array($type, $vmTypes)) { CreateVirtualMachineJob::dispatch($orderItem)->delay(now()->addSeconds(10)); } } @@ -163,10 +165,11 @@ class OrderService throw new \InvalidArgumentException("Sản phẩm không tồn tại (ID: {$item['product_id']})."); } - $moduleType = match ($product->product_type) { - 'hosting' => 3, - 'vps' => 4, - default => 0, + $categoryType = ProductTypeEnums::tryFrom($product->category?->type ?? ''); + $moduleType = match ($categoryType) { + ProductTypeEnums::HOSTING => 3, + ProductTypeEnums::VPS, ProductTypeEnums::CLOUD => 4, + default => 0, }; $billingDetails = $this->calculateBillingDetails($item['billing']); @@ -177,12 +180,12 @@ class OrderService $productItems[$key] = [ 'product_id' => $product->id, - 'product_type' => $product->product_type, + 'product_type' => $categoryType?->value, 'months' => $billingCycle->months(), 'billing_cycle' => $billingCycle->value, 'os_id' => $item['os_id'] ?? null, 'quantity' => (int) ($item['quantity'] ?? 1), - 'server_name' => $item['domain'] ?? null, + 'server_name' => $item['server_name'] ?? $item['domain'] ?? null, 'price' => (int) $priceSelect, 'invoice_type' => 1, 'code' => $generalCode, @@ -194,7 +197,7 @@ class OrderService 'data' => [ 'customer_id' => $data['customer_id'], 'status' => 0, - 'name' => $item['domain'] ?? $product->name, + 'name' => $item['server_name'] ?? $item['domain'] ?? $product->name, 'first_payment' => $priceSelect * $billingDetails['billing'], 'next_payment' => ($item['price_custom'] ?? $product->price) * $billingDetails['billing'], ...$billingDetails, @@ -220,7 +223,8 @@ class OrderService foreach ($relIdData as $item) { match ($item['type']) { 3 => $this->moduleHostingRepository->create($item['data']), - 4 => $this->moduleVmwareRepository->create(array_merge($item['data'], ['status' => 2])), + // type=4 (VPS): không pre-create ở đây. + // CreateVirtualMachineJob sẽ tạo module_vmware sau khi VM provisioned xong. default => null, }; } diff --git a/routes/api.php b/routes/api.php index 9f6203d..868f6c8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -74,19 +74,19 @@ Route::prefix('v1') Route::prefix('customers')->group(function () { Route::get('trash', [CustomerController::class, 'trash'])->middleware('perm:customer.view'); Route::get('/', [CustomerController::class, 'index'])->middleware('perm:customer.view'); - Route::get('/datatable', [CustomerController::class, 'datatable'])->middleware('perm:customer.view'); Route::post('/', [CustomerController::class, 'store'])->middleware('perm:customer.create'); - Route::get('{id}', [CustomerController::class, 'show'])->middleware('perm:customer.view'); - Route::put('{id}', [CustomerController::class, 'update'])->middleware('perm:customer.edit'); - Route::delete('{id}', [CustomerController::class, 'destroy'])->middleware('perm:customer.destroy'); - Route::post('{id}/reset-password', [CustomerController::class, 'resetPassword'])->middleware('perm:customer.edit'); - Route::post('{id}/add-credit', [CustomerController::class, 'addCredit'])->middleware('perm:customer.edit'); - Route::get('{id}/products', [CustomerController::class, 'getProducts'])->middleware('perm:customer.view'); - Route::get('{id}/invoices', [CustomerController::class, 'getInvoices'])->middleware('perm:customer.view'); - Route::get('{id}/transactions', [CustomerController::class, 'getTransactions'])->middleware('perm:customer.view'); - Route::get('{id}/domains', [CustomerController::class, 'getDomains'])->middleware('perm:customer.view'); - Route::post('{id}/restore', [CustomerController::class, 'restore'])->middleware('perm:customer.edit'); - Route::delete('{id}/force', [CustomerController::class, 'forceDelete'])->middleware('perm:customer.destroy'); + Route::get('{uuid}', [CustomerController::class, 'show'])->middleware('perm:customer.view'); + Route::put('{uuid}', [CustomerController::class, 'update'])->middleware('perm:customer.edit'); + Route::delete('{uuid}', [CustomerController::class, 'destroy'])->middleware('perm:customer.destroy'); + Route::delete('{uuid}/destroy', [CustomerController::class, 'destroy'])->middleware('perm:customer.destroy'); + Route::post('{uuid}/reset-password', [CustomerController::class, 'resetPassword'])->middleware('perm:customer.edit'); + Route::post('{uuid}/add-credit', [CustomerController::class, 'addCredit'])->middleware('perm:customer.edit'); + Route::get('{uuid}/services', [CustomerController::class, 'getServices'])->middleware('perm:customer.view'); + Route::get('{uuid}/invoices', [CustomerController::class, 'getInvoices'])->middleware('perm:customer.view'); + Route::get('{uuid}/transactions', [CustomerController::class, 'getTransactions'])->middleware('perm:customer.view'); + Route::get('{uuid}/domains', [CustomerController::class, 'getDomains'])->middleware('perm:customer.view'); + Route::post('{uuid}/restore', [CustomerController::class, 'restore'])->middleware('perm:customer.edit'); + Route::delete('{uuid}/force', [CustomerController::class, 'forceDelete'])->middleware('perm:customer.destroy'); }); // Products (module: product-service)