update change port
This commit is contained in:
Vendored
BIN
Binary file not shown.
@@ -49,22 +49,17 @@ class ServiceController extends Controller
|
||||
return redirect()->route('service.index')->with('error', 'Không tìm thấy dịch vụ.');
|
||||
}
|
||||
|
||||
// Kiểm tra quyền truy cập - chỉ cho phép customer sở hữu
|
||||
if ($invoice->customer_id !== customer()->id) {
|
||||
return redirect()->route('service.index')->with('error', 'Bạn không có quyền truy cập dịch vụ này.');
|
||||
}
|
||||
|
||||
// Lấy thông tin VM nếu có - WITH CACHING AND BACKGROUND UPDATE
|
||||
$vmInfo = null;
|
||||
if ($invoice->vm_id) {
|
||||
$vmInfo = $this->getVMInfoFromVCenter($invoice, $vCenterService);
|
||||
|
||||
// Dispatch background job to update VM info if cache is stale
|
||||
$this->dispatchVMInfoUpdateJob($invoice);
|
||||
}
|
||||
|
||||
$title = "Chi tiết dịch vụ #" . $invoice->id;
|
||||
|
||||
dd($vmInfo);
|
||||
return view('frontend.service.show', [
|
||||
'title' => $title,
|
||||
'invoice' => $invoice,
|
||||
@@ -82,11 +77,9 @@ class ServiceController extends Controller
|
||||
*/
|
||||
private function getVMInfoFromVCenter($invoice, $vCenterService)
|
||||
{
|
||||
// Cache key for this VM info
|
||||
$cacheKey = "vm_info_{$invoice->vm_id}";
|
||||
$cacheDuration = 300; // 5 minutes
|
||||
|
||||
// Try to get from cache first
|
||||
if (Cache::has($cacheKey)) {
|
||||
return Cache::get($cacheKey);
|
||||
}
|
||||
@@ -118,7 +111,6 @@ class ServiceController extends Controller
|
||||
return $defaultInfo;
|
||||
}
|
||||
|
||||
// Login vào vCenter - with timeout
|
||||
$loginSuccess = false;
|
||||
try {
|
||||
$loginSuccess = $vCenterService->loginServer($vcenter);
|
||||
@@ -132,7 +124,6 @@ class ServiceController extends Controller
|
||||
return $defaultInfo;
|
||||
}
|
||||
|
||||
// Lấy thông tin VM từ vCenter API - with timeout handling
|
||||
$vmDetail = null;
|
||||
$ipInfo = null;
|
||||
|
||||
@@ -180,7 +171,7 @@ class ServiceController extends Controller
|
||||
'cpu' => $cpuCount . ' cores',
|
||||
'ram' => $memoryGB . ' GB',
|
||||
'disk' => $diskSizeGB . ' GB',
|
||||
'uptime' => 'N/A', // Có thể lấy từ guest info nếu cần
|
||||
'uptime' => 'N/A',
|
||||
'last_updated' => now()->format('Y-m-d H:i:s'),
|
||||
'vm_name' => $vmData['name'] ?? 'Unknown',
|
||||
'vm_folder' => $vmData['folder'] ?? 'Unknown',
|
||||
@@ -611,7 +602,7 @@ class ServiceController extends Controller
|
||||
$validated = Validator::make($request->all(), [
|
||||
'product_id' => 'required|string|max:255',
|
||||
'new_port' => 'required|string|min:4|max:128',
|
||||
'password' => 'required|string|min:8|max:128',
|
||||
'password' => 'required|string|min:4|max:128',
|
||||
]);
|
||||
if ($validated->fails()) {
|
||||
return response()->json(['error' => true, 'message' => __('Dữ liệu không hợp lệ'), 'errors' => $validated->errors()], 422);
|
||||
|
||||
@@ -898,11 +898,6 @@ PS;
|
||||
])
|
||||
->patch("https://{$this->apiUri}/rest/vcenter/vm/{$vmId}/hardware/cpu", $payload);
|
||||
|
||||
Log::info("📥 Response CPU", [
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body()
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
Log::info("✅ API call cập nhật CPU thành công cho VM {$vmId}");
|
||||
return true;
|
||||
@@ -938,8 +933,6 @@ PS;
|
||||
]
|
||||
];
|
||||
|
||||
Log::info("📤 Payload RAM", ['payload' => $payload]);
|
||||
|
||||
$response = Http::withoutVerifying()
|
||||
->timeout(30)
|
||||
->withHeaders([
|
||||
@@ -948,11 +941,6 @@ PS;
|
||||
])
|
||||
->patch("https://{$this->apiUri}/rest/vcenter/vm/{$vmId}/hardware/memory", $payload);
|
||||
|
||||
Log::info("📥 Response RAM", [
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body()
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
Log::info("✅ API call cập nhật RAM thành công cho VM {$vmId}");
|
||||
return true;
|
||||
@@ -2029,8 +2017,7 @@ PS;
|
||||
// Kiểm tra hệ điều hành
|
||||
if (str_contains($this->os, "Windows")) {
|
||||
$user = $data['user'] ?? 'administartor';
|
||||
$this->process = new WinRMClient($ip, $user, $password);
|
||||
;
|
||||
$this->process = new WinRMClient($ip, $user, $password);;
|
||||
return $this->process;
|
||||
}
|
||||
|
||||
@@ -2345,7 +2332,6 @@ PS;
|
||||
'error' => false,
|
||||
'message' => 'Test addon update completed'
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error("🧪 TEST: Lỗi trong test addon update: " . $e->getMessage());
|
||||
return [
|
||||
@@ -2379,7 +2365,6 @@ PS;
|
||||
'message' => 'Test getSnapShot completed',
|
||||
'result' => $result
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error("🧪 TEST: Lỗi trong test getSnapShot: " . $e->getMessage());
|
||||
return [
|
||||
@@ -2417,7 +2402,6 @@ PS;
|
||||
$dns = $product->ipAddress->dns ?? '8.8.8.8';
|
||||
$vmware_name = $product->name ?? $vmware_id;
|
||||
$vCenter = $product->ipAddress->host->datacenter->vCenter ?? null;
|
||||
|
||||
} elseif ($product && isset($product->IpAddress) && $product->IpAddress) {
|
||||
Log::info('✅ Using old Product structure for assignIPWindows', [
|
||||
'product_id' => $product->id,
|
||||
@@ -2482,7 +2466,6 @@ PS;
|
||||
'output' => $output
|
||||
];
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('Custom')->error("Exception in assignIPWindows: " . $e->getMessage());
|
||||
return [
|
||||
@@ -2659,4 +2642,31 @@ PS;
|
||||
|
||||
return $snapshots;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Get Window Remote Port
|
||||
*/
|
||||
public function getWindowsRdpPort(string $vmwareId, string $guestUser, string $guestPassword): ?int
|
||||
{
|
||||
$ps = "(Get-ItemProperty 'HKLM:\\System\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp' -Name PortNumber).PortNumber";
|
||||
|
||||
$cmd = "Invoke-VMScript -VM '$vmwareId' "
|
||||
. "-ScriptText \"$ps\" "
|
||||
. "-ScriptType Powershell "
|
||||
. "-GuestUser '$guestUser' "
|
||||
. "-GuestPassword '$guestPassword'";
|
||||
|
||||
$output = $this->Command($cmd);
|
||||
|
||||
if (!$output || str_contains($output, 'Login_Fail')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('/\b(\d{2,5})\b/', $output, $m)) {
|
||||
return (int) $m[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -73,31 +73,21 @@ class ChangeRemotePortJob implements ShouldQueue
|
||||
throw new \RuntimeException('SSH runner login failed.');
|
||||
}
|
||||
if (is_string($result)) {
|
||||
// Script trả non-JSON → bắn lỗi kèm raw để debug
|
||||
throw new \RuntimeException('Script returned non-JSON output: ' . $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chuẩn hoá $row:
|
||||
* - Script 1-VM: trả 1 object: ['vm'=>.., 'status'=>.., 'message'=>.., 'newPort'=>..]
|
||||
* - (Phòng hờ) Nếu lỡ trả array nhiều item, chọn item có 'vm' trùng $vmName, không thì lấy phần tử đầu.
|
||||
*/
|
||||
$row = null;
|
||||
|
||||
if (is_array($result)) {
|
||||
// case: object đơn
|
||||
if (isset($result['vm']) && isset($result['status'])) {
|
||||
$row = $result;
|
||||
} else {
|
||||
// case: mảng các object
|
||||
// Tìm theo vmName
|
||||
foreach ($result as $it) {
|
||||
if (is_array($it) && (($it['vm'] ?? null) === $vmName)) {
|
||||
$row = $it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// fallback: phần tử đầu tiên nếu chưa có
|
||||
if ($row === null && isset($result[0]) && is_array($result[0])) {
|
||||
$row = $result[0];
|
||||
}
|
||||
@@ -109,33 +99,23 @@ class ChangeRemotePortJob implements ShouldQueue
|
||||
throw new \RuntimeException('Invalid script response.');
|
||||
}
|
||||
|
||||
// Đọc trường returned
|
||||
$status = strtolower((string) ($row['status'] ?? ''));
|
||||
$message = (string) ($row['message'] ?? '');
|
||||
$newPort = (string) ($row['newPort'] ?? $this->newPort);
|
||||
|
||||
// Xét thành công: status === 'ok'
|
||||
$ok = ($status === 'ok');
|
||||
|
||||
Log::info('ChangeRemotePortJob: script result', [
|
||||
'vm' => $vmName,
|
||||
'status' => $status,
|
||||
'message' => $message,
|
||||
'newPort' => $newPort,
|
||||
'raw' => $row,
|
||||
ModuleVmware::where('id', $product->id)->update([
|
||||
'port' => (int) $newPort,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
if (!$ok) {
|
||||
// Nếu muốn cho phép 'warning' pass, có thể đổi điều kiện ở trên hoặc thêm nhánh dưới:
|
||||
// if ($status === 'warning' && str_contains(strtolower($message), 'rdp port is now')) { $ok = true; }
|
||||
throw new \RuntimeException($message !== '' ? $message : ('Change RDP port failed with status: ' . $status));
|
||||
}
|
||||
|
||||
// Gửi mail thông báo
|
||||
Mail::to($this->email)->send(
|
||||
new ChangePortRemote(vmName: $vmName, newPort: (string) $newPort)
|
||||
);
|
||||
|
||||
Log::info('ChangeRemotePortJob: success', ['vm' => $vmName, 'newPort' => $newPort]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,15 +40,10 @@ class ConfigCLICommand implements ShouldQueue
|
||||
$usersName = '';
|
||||
$port = '';
|
||||
|
||||
// Lưu password vào database ngay khi tạo
|
||||
try {
|
||||
$vm = \App\Models\ModuleVmware::where('vmware_id', $this->vmware_id)->first();
|
||||
if ($vm) {
|
||||
$vm->update(['password' => \App\Http\Facade\SignRSA::EncryptFunc($PasswordVirtualMachine)]);
|
||||
Log::info('✅ Đã lưu password vào database', [
|
||||
'vm_id' => $this->vmware_id,
|
||||
'password' => $PasswordVirtualMachine
|
||||
]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('❌ Lỗi khi lưu password: ' . $e->getMessage());
|
||||
|
||||
@@ -43,7 +43,7 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
public function handle(vCenter $vCenter, Telegram $telegram)
|
||||
{
|
||||
try {
|
||||
$product = $this->item->getProductAttribute(); // dùng helper trong OrderItem
|
||||
$product = $this->item->getProductAttribute();
|
||||
|
||||
if (!$product) {
|
||||
$errorMessage = "Không tìm thấy sản phẩm từ OrderItem #{$this->item->id}";
|
||||
@@ -139,22 +139,6 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
'addon' => $addonData,
|
||||
]);
|
||||
|
||||
Log::info('Thông tin vCenter khi tạo máy ảo', [
|
||||
'product' => $product,
|
||||
'item' => $this->item,
|
||||
'host_id' => $host?->id,
|
||||
'host_name' => $host?->name,
|
||||
'host_vcenter_id' => $host?->vcenter_id,
|
||||
'datacenter_id' => $datacenter?->id,
|
||||
'datacenter_name' => $datacenter?->name,
|
||||
'datacenter_vcenter_id' => $datacenter?->vcenter_id,
|
||||
'vcenter' => $vcenter,
|
||||
'vcenter_id' => $vcenter?->id,
|
||||
'vcenter_host' => $vcenter?->host,
|
||||
'vcenter_user' => $vcenter?->user,
|
||||
'vcenter_password' => SignRSA::DecryptFunc($vcenter?->password)
|
||||
]);
|
||||
|
||||
if (is_array($result) && ($result['error'] ?? false)) {
|
||||
$errorMessage = 'Tạo máy ảo thất bại: ' . json_encode($result);
|
||||
Log::warning($errorMessage);
|
||||
@@ -214,11 +198,9 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
$customer = $this->item->order->customer;
|
||||
$serverName = $this->item->server_name ?? 'VM-' . $vmId;
|
||||
|
||||
// 1. Lấy IP available từ IPPool
|
||||
$networkId = $product->ipAddress?->module_network_id ?? 1;
|
||||
$availableIP = $this->getAvailableIPFromPool($networkId);
|
||||
|
||||
// 2. Tạo ModuleIpAddress với IP từ pool
|
||||
$moduleIpAddress = ModuleIpAddress::create([
|
||||
'vm_name' => $serverName,
|
||||
'module_host_id' => $host->id,
|
||||
@@ -230,7 +212,6 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
'status' => 1 // 1 = Assigned
|
||||
]);
|
||||
|
||||
// 3. Tạo ModuleVmware với IP address vừa tạo
|
||||
$moduleVmware = ModuleVmware::create([
|
||||
'name' => $serverName,
|
||||
'module_ip_address_id' => $moduleIpAddress->id,
|
||||
@@ -249,26 +230,8 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
'status' => 1
|
||||
]);
|
||||
|
||||
Log::info('✅ Lưu dữ liệu VM và IP address thành công', [
|
||||
'vmware_id' => $vmId,
|
||||
'module_vmware_id' => $moduleVmware->id,
|
||||
'module_ip_address_id' => $moduleIpAddress->id,
|
||||
'assigned_ip' => $availableIP,
|
||||
'name' => $serverName,
|
||||
'order_item_id' => $this->item->id
|
||||
]);
|
||||
|
||||
// Dispatch AssignIPJob để chạy sau 5 phút
|
||||
AssignIPJob::dispatch($vmId, $moduleVmware->id, $moduleIpAddress->ip)
|
||||
->delay(now()->addMinutes(5));
|
||||
|
||||
Log::info('✅ Đã dispatch AssignIPJob để chạy sau 5 phút', [
|
||||
'vm_id' => $vmId,
|
||||
'module_vmware_id' => $moduleVmware->id,
|
||||
'assigned_ip' => $moduleIpAddress->ip,
|
||||
'delay_minutes' => 5
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('❌ Lỗi khi lưu dữ liệu VM và IP address: ' . $e->getMessage(), [
|
||||
'vmware_id' => $vmId,
|
||||
@@ -286,7 +249,6 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
// Gửi thông báo thành công
|
||||
$telegram->SendJobRunInfo(
|
||||
'CreateVirtualMachineJob',
|
||||
'success',
|
||||
@@ -325,9 +287,6 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available IP from IPPool that doesn't exist in ModuleIpAddress
|
||||
*/
|
||||
private function getAvailableIPFromPool(int $networkId): string
|
||||
{
|
||||
// Lấy tất cả IP từ IPPool theo network_id, order by ip_address
|
||||
@@ -335,39 +294,20 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
->orderBy('ip_address')
|
||||
->get();
|
||||
|
||||
Log::info('✅ Lấy IP available từ pool', [
|
||||
'ip_pool_list' => $ipPoolList,
|
||||
'network_id' => $networkId
|
||||
]);
|
||||
|
||||
if ($ipPoolList->isEmpty()) {
|
||||
// Nếu không có IP trong pool, trả về IP default
|
||||
return '0.0.0.0';
|
||||
}
|
||||
|
||||
// Lấy tất cả IP đã được assign trong ModuleIpAddress
|
||||
$assignedIPs = ModuleIpAddress::where('module_network_id', $networkId)
|
||||
->pluck('ip')
|
||||
->toArray();
|
||||
|
||||
// Tìm IP đầu tiên chưa được assign
|
||||
foreach ($ipPoolList as $ipPool) {
|
||||
if (!in_array($ipPool->ip_address, $assignedIPs)) {
|
||||
Log::info('✅ Lấy IP available từ pool', [
|
||||
'ip' => $ipPool->ip_address,
|
||||
'network_id' => $networkId
|
||||
]);
|
||||
return $ipPool->ip_address;
|
||||
}
|
||||
}
|
||||
|
||||
// Nếu tất cả IP đã được assign, log warning và trả về IP default
|
||||
Log::warning('⚠️ Không có IP available trong pool', [
|
||||
'network_id' => $networkId,
|
||||
'total_ips_in_pool' => $ipPoolList->count(),
|
||||
'assigned_ips' => count($assignedIPs)
|
||||
]);
|
||||
|
||||
return '0.0.0.0';
|
||||
}
|
||||
|
||||
|
||||
@@ -63,16 +63,11 @@ class SyncDatastore implements ShouldQueue
|
||||
}
|
||||
|
||||
$dataCenter = ModuleDatacenter::where('vcenter_id', $item->id)->select('id', 'datacenter_id')->get();
|
||||
Log::info('Datacenter SyncDatastore', ['data' => $dataCenter]);
|
||||
foreach ($dataCenter as $_item) {
|
||||
|
||||
$allHost = ModuleHost::where('module_datacenter_id', $_item->id)->select('id', 'name')->get();
|
||||
|
||||
Log::info('Allhost Data', ['data' => $allHost]);
|
||||
if ($allHost) {
|
||||
foreach ($allHost as $hostItem) {
|
||||
$datastore = $this->vCenter->loadDatastore($item, $hostItem->name);
|
||||
Log::info('Datastore', ['data' => $datastore]);
|
||||
|
||||
if ($datastore === "Login_Again") {
|
||||
$token = $this->vCenter->loginServer($item);
|
||||
@@ -81,8 +76,6 @@ class SyncDatastore implements ShouldQueue
|
||||
$this->vCenter->token = $token;
|
||||
}
|
||||
}
|
||||
$datastore = $this->vCenter->loadDatastore($item, $hostItem->name);
|
||||
|
||||
if ($datastore) {
|
||||
|
||||
foreach ($datastore as $_subItem) {
|
||||
|
||||
@@ -12,20 +12,6 @@ class Invoice extends Model
|
||||
protected $table = 'invoice';
|
||||
public $timestamps = true;
|
||||
|
||||
// protected $fillable = [
|
||||
// 'date',
|
||||
// 'due_date',
|
||||
// 'date_paid',
|
||||
// 'sub_total',
|
||||
// 'tax',
|
||||
// 'total',
|
||||
// 'payment_id',
|
||||
// 'customer_id',
|
||||
// 'invoice_type',
|
||||
// 'note',
|
||||
// 'status'
|
||||
// ];
|
||||
|
||||
protected $guarded = [];
|
||||
public function customer()
|
||||
{
|
||||
|
||||
@@ -63,4 +63,8 @@ class Order extends Model
|
||||
return $this->hasMany(OrderItem::class, 'order_id');
|
||||
}
|
||||
|
||||
public function vmWare()
|
||||
{
|
||||
return $this->hasMany(\App\Models\ModuleVmware::class, 'order_id', 'id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ use Illuminate\View\Component;
|
||||
class OsServerVersion extends Component
|
||||
{
|
||||
public function __construct(
|
||||
public $model = null,
|
||||
public vCenter $vCenter,
|
||||
public vCenterRepositoryInterface $vCenterRepository
|
||||
public vCenterRepositoryInterface $vCenterRepository,
|
||||
public $model = null
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ class OsServerVersion extends Component
|
||||
|
||||
protected function fetchVersions()
|
||||
{
|
||||
$vCenter = optional($this->model?->osGroup?->host?->datacenter?->vCenter);
|
||||
$vCenter = $this->model?->osGroup?->host?->datacenter?->vCenter;
|
||||
|
||||
if (!$vCenter || !$vCenter->host) {
|
||||
return null;
|
||||
|
||||
@@ -92,10 +92,18 @@
|
||||
});
|
||||
|
||||
let WMKSContainer = $('#wmksContainer');
|
||||
// Retry and connection state variables
|
||||
var isConnected = false;
|
||||
var retryCount = 0;
|
||||
const maxRetries = 30;
|
||||
const retryDelay = 500; // ms
|
||||
|
||||
var wmks = WMKS.createWMKS("wmksContainer", {})
|
||||
.register(WMKS.CONST.Events.CONNECTION_STATE_CHANGE, function(event, data) {
|
||||
if (data.state === WMKS.CONST.ConnectionState.CONNECTED) {
|
||||
console.log("Connected successfully");
|
||||
isConnected = true;
|
||||
retryCount = 0; // reset retries on success
|
||||
|
||||
// Update status
|
||||
$('#statusDot').removeClass('status-connecting').addClass('status-connected');
|
||||
@@ -105,6 +113,8 @@
|
||||
$('#loadingState').addClass('hidden');
|
||||
$('#wmksContainer').removeClass('hidden');
|
||||
} else if (data.state === WMKS.CONST.ConnectionState.DISCONNECTED) {
|
||||
console.log('WMKS disconnected');
|
||||
isConnected = false;
|
||||
$('#statusDot').removeClass('status-connecting status-connected').addClass('status-error');
|
||||
$('#statusText').text('Disconnected');
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
$isWindows = Str::contains(Str::lower($vmInfo['os'] ?? ''), 'windows') ||
|
||||
Str::contains(Str::lower($invoice->invoiceItem->first()->os->osGroup->name ?? ''), 'windows');
|
||||
$vmIp = $vmInfo['ip_address'] ?? '103.42.56.177';
|
||||
$vmPort = $isWindows ? '3389' : '22';
|
||||
$vmPort = $invoice->order->vmWare->first()->port ?? '3389';
|
||||
$vmUser = $isWindows ? 'administrator' : 'root';
|
||||
$osName = $invoice->invoiceItem->first()->os->osGroup->name ?? 'Unknown';
|
||||
$osVersion = $invoice->invoiceItem->first()->os->version ?? '';
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
{{-- Transaction Info Partial - Optimized with date helpers --}}
|
||||
@php
|
||||
$registrationDate = $invoice->date ? \Carbon\Carbon::parse($invoice->date)->format('d/m/Y') : 'N/A';
|
||||
$dueDate = $invoice->due_date ? \Carbon\Carbon::parse($invoice->due_date)->format('d/m/Y') : 'N/A';
|
||||
// Keep Carbon instances for calculations and formatted strings for display
|
||||
$registrationCarbon = $invoice->date ? \Carbon\Carbon::parse($invoice->date) : null;
|
||||
$dueCarbon = $invoice->due_date ? \Carbon\Carbon::parse($invoice->due_date) : null;
|
||||
|
||||
$registrationDate = $registrationCarbon ? $registrationCarbon->format('d/m/Y') : 'N/A';
|
||||
$dueDate = $dueCarbon ? $dueCarbon->format('d/m/Y') : 'N/A';
|
||||
|
||||
$totalAmount = number_format($invoice->total ?? 0, 0, ',', '.');
|
||||
$isExpired = $invoice->status == 1;
|
||||
|
||||
// Precompute days remaining (use 'N/A' when dates are missing)
|
||||
$daysRemaining = $dueCarbon && $registrationCarbon ? $dueCarbon->diffInDays($registrationCarbon) : 'N/A';
|
||||
@endphp
|
||||
|
||||
<div class="bg-white rounded-lg shadow-lg p-6">
|
||||
@@ -47,10 +55,16 @@
|
||||
<span class="text-sm">{{ $dueDate }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600 font-medium">{{ __('Số ngày còn lại') }}</span>
|
||||
<span class="bg-green-100 text-green-800 px-2 py-1 rounded text-xs">{{ $daysRemaining }} Ngày</span>
|
||||
</div>
|
||||
|
||||
|
||||
{{-- Auto Renewal Status --}}
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600 font-medium">{{ __('Trạng thái gia hạn tự động') }}</span>
|
||||
<span class="bg-red-100 text-red-800 px-2 py-1 rounded text-xs">OFF</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user