orderRepository->getOrders($customerId, $filters->toArray()); } public function checkServerName(string $name): array { $exists = OrderItem::where('server_name', $name)->exists() || Service::where('server_name', $name)->exists() || ModuleVmware::where('name', $name)->exists() || ModuleVmware::where('hostname', $name)->exists() || VirtualMachines::where('name', $name)->exists(); return [ 'available' => ! $exists, 'message' => $exists ? 'Tên server này đã được sử dụng, vui lòng chọn tên khác.' : null, ]; } public function findCustomerOrder(int $customerId, int $id): ?Order { return Order::query() ->with(['invoice', 'orderItems.product', 'orderItems.service', 'customer']) ->where('customer_id', $customerId) ->where('id', $id) ->first(); } public function getCustomerInvoices(int $customerId) { return Invoice::query() ->with(['order.orderItems.product']) ->where('customer_id', $customerId) ->orderByDesc('created_at') ->get(); } public function findCustomerInvoice(int $customerId, int $id): ?Invoice { return Invoice::query() ->with([ 'order.orderItems.product', 'order.vmWare.ipAddress', 'order.vmWare.os', 'customer', 'attachments', ]) ->where('customer_id', $customerId) ->where('id', $id) ->first(); } public function findCustomerInvoiceByNumber(int $customerId, string $invoiceNumber): ?Invoice { return Invoice::query() ->with([ 'order.orderItems.product', 'order.vmWare.ipAddress', 'order.vmWare.os', 'customer', 'attachments', 'invoiceItems.product', 'services.product', ]) ->where('customer_id', $customerId) ->where('invoice_number', $invoiceNumber) ->first(); } public function findCustomerInvoiceByOrderNum(int $customerId, string $orderNumber): ?Order { return Order::query() ->with([ 'invoice.invoiceItems', 'invoice.attachments', 'invoice.customer', 'invoice.payment', 'orderItems.product', 'invoices.attachments', 'customer', 'vmWare.ipAddress', 'vmWare.os', ]) ->where('customer_id', $customerId) ->where('order_num', $orderNumber) ->first(); } public function pickOrderInvoice(Order $order, ?string $invoiceNumber): ?Invoice { if (! $invoiceNumber) { return $order->invoice; } return $order->invoices() ->where('invoice_number', $invoiceNumber) ->with(['invoiceItems', 'attachments', 'customer', 'payment']) ->first(); } public function getCustomerServices(int $customerId) { return Service::query() ->with(['product', 'addons', 'invoice', 'orderItem', 'vmware.ipAddress', 'vmware.os']) ->where('customer_id', $customerId) ->orderByDesc('created_at') ->get(); } public function retryProvisioning(Order $order, int $customerId): array { $order = Order::where('id', $order->id) ->with(['invoice', 'orderItems.service']) ->where('customer_id', $customerId) ->first(); if (! $order) { return ['ok' => false, 'message' => 'Đơn hàng không tồn tại.', 'code' => 404]; } if (! $order->invoice || (int) $order->invoice->status !== InvoiceStatus::PAID->value) { return ['ok' => false, 'message' => 'Đơn hàng chưa được thanh toán.', 'code' => 400]; } $paidAt = $order->invoice->paid_at ? Carbon::parse($order->invoice->paid_at) : null; if (! $paidAt || $paidAt->diffInMinutes(now()) < 180) { return ['ok' => false, 'message' => 'Vui lòng chờ khoảng 3 tiếng sau khi thanh toán mới có thể khởi tạo lại dịch vụ.', 'code' => 400]; } $service = $order->orderItems->first()?->service; if (! $service || (int) $service->customer_id !== $customerId) { return ['ok' => false, 'message' => 'Không tìm thấy dịch vụ hợp lệ để khởi tạo lại.', 'code' => 400]; } if ($service->status !== 'pending') { return ['ok' => false, 'message' => 'Dịch vụ đã được khởi tạo hoặc không ở trạng thái chờ.', 'code' => 400]; } if ($service->retry_requested_at) { return ['ok' => false, 'message' => 'Dịch vụ đã được yêu cầu khởi tạo lại một lần. Vui lòng liên hệ kỹ thuật qua mailto:kythuat@tgs.com.vn', 'code' => 400]; } $service->update(['retry_requested_at' => now()]); RetryProvisioningOrchestrator::dispatch($order->id, $customerId); return ['ok' => true, 'message' => 'Đã yêu cầu khởi tạo lại dịch vụ. Hệ thống sẽ kiểm tra và xử lý trong vài phút.']; } public function renewService(int $serviceId, Customer $customer, int $months): array { $issued = $this->issueRenewal($serviceId, $customer, $months); if (! $issued['ok']) { return $issued; } return $this->payRenewalWithBalance($issued['invoice'], $customer, $serviceId); } public function renewServiceByAdmin(int $serviceId, Customer $customer, int $months, string $payment, int $adminId, string $note = ''): array { $issued = $this->issueRenewal($serviceId, $customer, $months); if (! $issued['ok']) { return $issued; } if ($payment === 'balance') { return $this->payRenewalWithBalance($issued['invoice'], $customer, $serviceId); } try { $invoice = $this->invoicePaymentService->markPaidByAdmin($issued['invoice'], $adminId, 'admin', null, $note); } catch (\InvalidArgumentException $e) { return ['ok' => false, 'message' => $e->getMessage(), 'code' => 422]; } return $this->renewalResult($invoice, $serviceId); } private function issueRenewal(int $serviceId, Customer $customer, int $months): array { $service = Service::query() ->with(['orderItem.product', 'customProduct']) ->where('id', $serviceId) ->where('customer_id', $customer->id) ->whereNotIn('status', ['terminated', 'cancelled']) ->first(); if (! $service) { return ['ok' => false, 'message' => 'Không tìm thấy dịch vụ hoặc dịch vụ đã bị hủy.', 'code' => 404]; } $orderItem = $service->orderItem; if (! $orderItem) { return ['ok' => false, 'message' => 'Không thể xác định giá gia hạn dịch vụ.', 'code' => 400]; } $product = $service->customProduct ?? $service->product ?? $orderItem->product; $option = RenewalPricing::option($product, $months); if ($option === null) { $validMonths = array_column(RenewalPricing::options($product), 'months'); return [ 'ok' => false, 'message' => $validMonths === [] ? 'Sản phẩm hiện không còn bán, không thể gia hạn.' : 'Thời hạn gia hạn không hợp lệ với sản phẩm này. Vui lòng chọn: ' . implode(', ', $validMonths) . ' tháng.', 'code' => 422, ]; } $cycles = $months / OrderHelper::cycleToMonths($orderItem->billing_cycle); $renewalPrice = $option['price']; $invoice = DB::transaction(function () use ($service, $orderItem, $renewalPrice, $cycles, $months) { $service = Service::query()->whereKey($service->id)->lockForUpdate()->first(); $pending = $this->pendingRenewalInvoice($service); if ($pending && ((int) $pending->amount_paid > 0 || $this->renewalMonths($pending) === $months)) { return $pending; } return $this->issueRenewalInvoice($service, $orderItem, $renewalPrice, $cycles, $months, $pending); }); return ['ok' => true, 'invoice' => $invoice]; } private function payRenewalWithBalance(Invoice $invoice, Customer $customer, int $serviceId): array { try { $invoice = $this->invoicePaymentService->payWithBalance($invoice, $customer); } catch (\InvalidArgumentException $e) { return [ 'ok' => false, 'message' => $e->getMessage() . ' Hóa đơn gia hạn #' . $invoice->invoice_number . ' đang chờ thanh toán.', 'code' => 422, 'data' => $this->renewalPayload($invoice), ]; } return $this->renewalResult($invoice, $serviceId); } private function renewalResult(Invoice $invoice, int $serviceId): array { return [ 'ok' => true, 'message' => 'Gia hạn dịch vụ thành công.', 'data' => $this->renewalPayload($invoice) + [ 'expires_at' => Service::find($serviceId)?->expires_at?->toISOString(), ], ]; } private function pendingRenewalInvoice(Service $service): ?Invoice { if (! $service->invoice_id) { return null; } return Invoice::query() ->where('id', $service->invoice_id) ->where('invoice_type', 2) ->whereIn('status', [InvoiceStatus::UNPAID->value, InvoiceStatus::OVERDUE->value]) ->first(); } private function renewalMonths(Invoice $invoice): int { return (int) $invoice->invoiceItems()->where('type', 'renewal')->value('months'); } private function issueRenewalInvoice(Service $service, OrderItem $orderItem, int $renewalPrice, float $cycles, int $months, ?Invoice $draft): Invoice { $price = $renewalPrice + RenewalPricing::addonTotal($service, $cycles); $periodStart = $service->expires_at && $service->expires_at->isFuture() ? $service->expires_at->copy() : now(); $invoice = $draft ?? new Invoice([ 'invoice_number' => OrderHelper::generateOrderNumber('INV'), 'status' => InvoiceStatus::UNPAID->value, ]); $invoice->invoiceItems()->delete(); $invoice->fill([ 'customer_id' => $service->customer_id, 'order_id' => $orderItem->order_id, 'invoice_type' => 2, 'sub_total' => $price, 'discount_amount' => 0, 'tax' => 0, 'total' => $price, 'due_date' => now()->addDays(7)->toDateString(), 'billing_period_start' => $periodStart->toDateString(), 'billing_period_end' => $periodStart->copy()->addMonths($months)->toDateString(), ])->save(); InvoiceItem::create([ 'invoice_id' => $invoice->id, 'product_id' => $service->product_id ?: $orderItem->product_id, 'type' => 'renewal', 'description' => 'Gia hạn dịch vụ: ' . ($service->customProduct?->name ?? $orderItem->product?->name ?? $service->server_name ?? ''), 'months' => $months, 'quantity' => 1, 'unit_price' => $renewalPrice, 'total_price' => $renewalPrice, ]); foreach (RenewalPricing::addonInvoiceItems($service, $cycles, $months) as $addonItem) { InvoiceItem::create($addonItem + ['invoice_id' => $invoice->id]); } VatNumbering::settle($invoice); $service->update(['invoice_id' => $invoice->id]); return $invoice; } private function renewalPayload(Invoice $invoice): array { return [ 'order_id' => $invoice->order_id, 'invoice_id' => $invoice->id, 'invoice_number' => $invoice->invoice_number, 'total' => (float) $invoice->total, 'status' => $invoice->status, 'payment_url' => $invoice->clientPath(), ]; } }