feat: Implement UUID-based customer routing, enhance order request validation, and refactor VM creation job with updated payment logic and ModuleVmware handling.
This commit is contained in:
@@ -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']
|
||||
);
|
||||
|
||||
|
||||
@@ -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',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ class CustomerResource extends JsonResource
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'uuid' => $this->uuid,
|
||||
'fullname' => $this->fullname,
|
||||
'email' => $this->email,
|
||||
'phone' => $this->phone,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\VirtualMachine\Create;
|
||||
|
||||
|
||||
use App\Http\Services\Telegram;
|
||||
use App\Http\Services\vCenter as ServiceVCenter;
|
||||
use App\Models\ModuleVmware;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
class CreateVMJobs implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $data = null;
|
||||
protected $push = null;
|
||||
protected $telegram = null;
|
||||
public int $tries = 1;
|
||||
public $message = "";
|
||||
public $timeout = 0;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($product = 0)
|
||||
{
|
||||
$this->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());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
+12
-12
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user