- Customer tab "delete" no longer deletes the service: it detaches the
customer, discards the order/invoices and logs a `detached` activity
with the previous customer and order.
- New POST customers/{uuid}/services/attach assigns an unassigned
service (GET services?unassigned=1) to a customer, creating a pending
order + unpaid invoice when missing, and logs `attached`.
- Detach/attach also sync module_vmware/module_hosting_accounts
customer_id: ResolvesVm authorises by module_vmware.customer_id, so a
detached customer could still control the VM.
- /services detach-customer (single + bulk) go through the same path.
- GET services/{id} returns the service activity logs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
141 lines
6.3 KiB
PHP
141 lines
6.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api\Admin\Customer\Concerns;
|
|
|
|
use App\Models\Service;
|
|
use App\Support\AutoEmailGate;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
|
|
trait ManagesCustomerServices
|
|
{
|
|
public function getServices(string $uuid): JsonResponse
|
|
{
|
|
return response()->json(['error' => false, 'data' => $this->serviceProvisioning->getServices($uuid)]);
|
|
}
|
|
|
|
public function attachService(string $uuid, Request $request): JsonResponse
|
|
{
|
|
$data = $request->validate(['service_id' => 'required|integer|exists:services,id']);
|
|
|
|
$service = $this->serviceProvisioning->attachService($uuid, $data['service_id']);
|
|
|
|
return response()->json(['error' => false, 'message' => 'Đã gán dịch vụ cho khách hàng', 'data' => $service]);
|
|
}
|
|
|
|
public function bulkDeleteServices(string $uuid, Request $request): JsonResponse
|
|
{
|
|
$data = $request->validate(['ids' => 'required|array|min:1', 'ids.*' => 'integer']);
|
|
|
|
$deleted = $this->serviceProvisioning->bulkDeleteServices($uuid, $data['ids']);
|
|
|
|
return response()->json(['error' => false, 'message' => "Đã gỡ {$deleted} dịch vụ khỏi khách hàng", 'data' => ['deleted' => $deleted]]);
|
|
}
|
|
|
|
public function updateService(string $uuid, int $serviceId, Request $request): JsonResponse
|
|
{
|
|
$data = $request->validate([
|
|
'server_name' => 'nullable|string|max:255',
|
|
'status' => 'nullable|in:pending,active,suspended,expired,cancelled,terminated',
|
|
'auto_renew' => 'nullable|boolean',
|
|
'started_at' => 'nullable|date',
|
|
'expires_at' => 'nullable|date',
|
|
'note' => 'nullable|string',
|
|
'requirements' => 'nullable|string',
|
|
'feedback' => 'nullable|string',
|
|
'price' => 'nullable|numeric|min:0',
|
|
'billing_cycle' => 'nullable|in:monthly,quarterly,semi_annually,annually,biennially,triennially',
|
|
'virtual_machine_id' => 'nullable|integer|exists:virtual_machines,id',
|
|
'os_id' => 'nullable|integer|exists:os,id',
|
|
'vm_cpu' => 'nullable|integer|min:1',
|
|
'vm_ram_mb' => 'nullable|integer|min:1',
|
|
'vm_disk_gb' => 'nullable|integer|min:1',
|
|
'vm_ip' => 'nullable|string|max:45',
|
|
'vm_dns' => 'nullable|string|max:100',
|
|
'vm_user' => 'nullable|string|max:255',
|
|
'vm_password' => 'nullable|string|max:255',
|
|
'vm_port' => 'nullable|integer|min:1|max:65535',
|
|
]);
|
|
|
|
$service = $this->serviceProvisioning->updateService($uuid, $serviceId, $data);
|
|
|
|
return response()->json(['error' => false, 'message' => 'Cập nhật dịch vụ thành công', 'data' => $service]);
|
|
}
|
|
|
|
public function changeServiceIp(string $uuid, int $serviceId, Request $request): JsonResponse
|
|
{
|
|
$data = $request->validate(['ip' => 'required|ip']);
|
|
|
|
abort_if(!Service::where('id', $serviceId)->value('vm_id'), 422, 'Dịch vụ chưa gắn VM.');
|
|
|
|
$service = $this->serviceProvisioning->updateService($uuid, $serviceId, ['vm_ip' => $data['ip']]);
|
|
|
|
return response()->json(['error' => false, 'message' => 'Đã đổi IP thành công', 'data' => $service]);
|
|
}
|
|
|
|
public function changeServicePort(string $uuid, int $serviceId, Request $request): JsonResponse
|
|
{
|
|
$data = $request->validate(['port' => 'required|integer|min:1|max:65535']);
|
|
|
|
abort_if(!Service::where('id', $serviceId)->value('vm_id'), 422, 'Dịch vụ chưa gắn VM.');
|
|
|
|
$service = $this->serviceProvisioning->updateService($uuid, $serviceId, ['vm_port' => $data['port']]);
|
|
|
|
return response()->json(['error' => false, 'message' => 'Đã đổi port thành công', 'data' => $service]);
|
|
}
|
|
|
|
public function notifyServiceRenewal(string $uuid, int $serviceId): JsonResponse
|
|
{
|
|
AutoEmailGate::markManual();
|
|
$this->serviceProvisioning->notifyRenewal($uuid, $serviceId);
|
|
|
|
return response()->json(['error' => false, 'message' => 'Đã gửi thông báo gia hạn đến khách hàng.']);
|
|
}
|
|
|
|
public function createService(string $uuid, Request $request): JsonResponse
|
|
{
|
|
$data = $request->validate([
|
|
'product_id' => 'required|integer|exists:products,id',
|
|
'virtual_machine_id' => 'nullable|integer|exists:virtual_machines,id',
|
|
'server_name' => 'nullable|string|max:255',
|
|
'status' => 'nullable|in:pending,active,suspended,expired,cancelled,terminated',
|
|
'auto_renew' => 'nullable|boolean',
|
|
'started_at' => 'nullable|date',
|
|
'expires_at' => 'nullable|date',
|
|
'note' => 'nullable|string',
|
|
'billing_cycle' => 'nullable|in:monthly,quarterly,semi_annually,annually,biennially,triennially',
|
|
'payment_method' => 'required|in:transfer,balance',
|
|
'amount_paid' => 'nullable|numeric|min:0',
|
|
'proof_images' => 'nullable|array',
|
|
'proof_images.*' => 'file|image|max:5120',
|
|
'os_id' => 'nullable|integer|exists:os,id',
|
|
'vm_cpu' => 'nullable|integer|min:1',
|
|
'vm_ram_mb' => 'nullable|integer|min:1',
|
|
'vm_disk_gb' => 'nullable|integer|min:1',
|
|
'vm_ip' => 'nullable|string|max:45',
|
|
'vm_dns' => 'nullable|string|max:100',
|
|
'vm_user' => 'nullable|string|max:255',
|
|
'vm_password' => 'nullable|string|max:255',
|
|
'vm_port' => 'nullable|integer|min:1|max:65535',
|
|
]);
|
|
|
|
// Upload proof images (chuyển khoản) — I/O ở tầng HTTP, service chỉ nhận đường dẫn đã lưu.
|
|
if ($data['payment_method'] === 'transfer' && $request->hasFile('proof_images')) {
|
|
$data['_proof_paths'] = collect($request->file('proof_images'))
|
|
->map(fn($file) => $file->store('payment-proofs', 'public'))
|
|
->all();
|
|
}
|
|
|
|
$service = $this->serviceProvisioning->createService($uuid, $data, $request->ip());
|
|
|
|
return response()->json(['error' => false, 'message' => 'Service created', 'data' => $service], 201);
|
|
}
|
|
|
|
public function listAvailableVms(Request $request): JsonResponse
|
|
{
|
|
$vms = $this->serviceProvisioning->listAvailableVms(trim((string) $request->get('q', '')));
|
|
|
|
return response()->json(['error' => false, 'data' => $vms]);
|
|
}
|
|
}
|