$this->invoice_number, 'invoice_type' => $this->invoice_type, 'status' => $this->status, 'status_text' => InvoiceStatus::tryFrom((int) $this->status)?->label() ?? '-', 'sub_total' => (float) $this->sub_total, 'discount_amount' => (float) ($this->discount_amount ?? 0), 'coupon_code' => $this->coupon_code, 'tax' => (float) $this->tax, 'lookup_code' => $this->lookup_code, 'vat_number' => $this->vat_number, 'total' => (float) $this->total, // Công nợ: đã thu / còn thiếu. Dịch vụ chỉ kích hoạt khi outstanding = 0. 'amount_paid' => (float) $this->amount_paid, 'outstanding' => (float) \App\Services\InvoicePaymentService::outstanding($this->resource), 'payment_method' => $this->resolvePaymentMethod($this->resource), 'auto_renew' => (bool) $this->auto_renew, 'created_at' => $this->created_at?->toISOString(), 'due_date' => $this->due_date?->toISOString(), 'billing_period_start' => $this->billing_period_start ? Carbon::parse($this->billing_period_start)->toDateString() : null, 'billing_period_end' => $this->billing_period_end ? Carbon::parse($this->billing_period_end)->toDateString() : null, 'next_renewal_date' => $this->whenLoaded('order', fn () => $this->nextRenewalDate()), 'paid_at' => $this->paid_at?->toISOString(), 'customer' => $this->whenLoaded('customer', fn () => [ 'name' => $this->customer->fullname, 'email' => $this->customer->email, 'address' => $this->customer->address, ]), 'order' => $this->whenLoaded('order', fn () => [ 'order_num' => $this->order->order_num, 'status' => (int) $this->order->status, 'status_text' => OrderStatus::tryFrom((int) $this->order->status)?->label() ?? '-', 'total' => (float) $this->order->total, 'created_at' => $this->order->created_at?->toISOString(), ]), 'items' => $this->whenLoaded('order', fn () => $this->isRenewal() ? $this->renewalItems() : $this->order->orderItems->map(fn ($item) => [ 'product_name' => $item->product?->name, 'server_name' => $item->server_name, 'billing_cycle' => $item->billing_cycle, 'price' => (float) $item->price, 'months' => $item->months, 'os_id' => $item->os_id, ])->values()->all()), 'service' => $this->whenLoaded('order', fn () => $this->resolveService()), 'attachments' => $this->whenLoaded('attachments', fn () => $this->attachments ->whereNotIn('type', [InvoiceAttachment::TYPE_DEPLOYMENT, InvoiceAttachment::TYPE_CONTRACT]) ->values() ->map(fn ($a) => [ 'filename' => $a->filename, 'url' => $a->url, 'mime_type' => $a->mime_type, ])->all()), 'vmware' => $this->when( $this->order?->relationLoaded('vmWare') && $this->order->vmWare->isNotEmpty(), fn () => $this->resolveVmware($this->order->vmWare->first()), ), ]; } private function isRenewal(): bool { return (int) $this->invoice_type === 2; } private function resolveOrderItem(): mixed { $items = $this->order->orderItems; $item = $this->service_id ? $items->first(fn ($i) => (int) $i->service?->id === (int) $this->service_id) : null; return $item ?? $items->first(); } private function renewalMonths(): ?int { return $this->invoiceItems->firstWhere('type', 'renewal')?->months; } private function renewalItems(): array { $serverName = $this->resolveOrderItem()?->server_name; return $this->invoiceItems->map(fn ($item) => [ 'product_name' => $item->description, 'server_name' => $serverName, 'billing_cycle' => $item->months ? 'monthly' : null, 'price' => (float) $item->total_price, 'months' => $item->months, 'os_id' => null, ])->values()->all(); } private function nextRenewalDate(): ?string { if ($this->isRenewal() && $this->billing_period_end) { return Carbon::parse($this->billing_period_end)->toDateString(); } return $this->resolveOrderItem()?->service?->expires_at?->toDateString(); } private function resolveService(): ?array { $item = $this->resolveOrderItem(); $renewalMonths = $this->isRenewal() ? $this->renewalMonths() : null; return $item ? [ 'product_id' => $item->product_id, 'name' => $item->product?->name, 'billing_cycle' => $renewalMonths ? 'monthly' : $item->billing_cycle, 'months' => $renewalMonths ?: $item->months, 'server_name' => $item->server_name, 'product_type' => $item->product?->product_type, ] : null; } private function resolveVmware(\App\Models\ModuleVmware $vm): array { return [ 'id' => $vm->id, 'uuid' => $vm->service?->uuid, 'name' => $vm->name, 'hostname' => $vm->hostname, 'ip_address' => $vm->ip, 'os_name' => $vm->os?->name, 'status' => $vm->status, 'username' => $vm->user, 'port' => $vm->port, 'power_state' => match ($vm->virtualMachine?->power_state) { 'POWERED_ON' => 'poweredOn', 'POWERED_OFF' => 'poweredOff', 'SUSPENDED' => 'suspended', default => null, }, 'next_due_date' => $vm->next_due_date ? \Illuminate\Support\Carbon::createFromTimestamp($vm->next_due_date)->toISOString() : null, ]; } }