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:
pbao9
2026-03-12 16:30:02 +07:00
parent 508d5186db
commit dbd3cdafcd
11 changed files with 207 additions and 283 deletions
@@ -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']
);
+6 -4
View File
@@ -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,
-8
View File
@@ -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,
+6
View File
@@ -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
// -------------------------------------------------------------------------
+2 -2
View File
@@ -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()
{
+84 -21
View File
@@ -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,
]);
}
}
}
+14 -10
View File
@@ -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
View File
@@ -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)