Files
portal-api/app/Http/Controllers/Api/Admin/Order/OrderController.php
T

562 lines
20 KiB
PHP

<?php
namespace App\Http\Controllers\Api\Admin\Order;
use App\Http\Controllers\Controller;
use App\Http\Helpers\Helper;
use App\Http\Requests\OrderRequest;
use App\Http\Requests\UpdateOrderRequest;
use App\Http\Resources\Api\Admin\OrderResource;
use App\Jobs\Hosting\CreateHostingJobs;
use App\Jobs\VirtualMachine\Create\CreateVMJobs;
use App\Jobs\VirtualMachine\Upgrade\UpgradeVirtualMachine;
use App\Models\Order;
use App\Repositories\Addon\AddonRepositoryInterface;
use App\Repositories\Hosting\HostingRepositoryInterface;
use App\Repositories\Invoice\InvoiceRepositoryInterface;
use App\Repositories\InvoiceItem\InvoiceItemRepositoryInterface;
use App\Repositories\Module\Hosting\HostingRepositoryInterface as ModuleHostingRepositoryInterface;
use App\Repositories\Module\HostingServer\HostingServerRepositoryInterface;
use App\Repositories\Module\IpAddress\IpAddressRepositoryInterface;
use App\Repositories\Module\Vmware\VmwareRepositoryInterface;
use App\Repositories\Order\OrderRepositoryInterface;
use App\Repositories\Os\OsRepositoryInterface;
use App\Repositories\VPS\VPSRepositoryInterface;
use App\Models\Customer;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Yajra\DataTables\Facades\DataTables;
class OrderController extends Controller
{
public function __construct(
private readonly OrderRepositoryInterface $orderRepository,
private readonly HostingRepositoryInterface $hostingRepository,
private readonly VPSRepositoryInterface $vPSRepository,
private readonly InvoiceRepositoryInterface $invoiceRepository,
private readonly InvoiceItemRepositoryInterface $invoiceItemRepository,
private readonly ModuleHostingRepositoryInterface $moduleHostingRepository,
private readonly VmwareRepositoryInterface $moduleVmwareRepository,
private readonly HostingServerRepositoryInterface $moduleHostingServerRepository,
private readonly OsRepositoryInterface $osRepository,
private readonly IpAddressRepositoryInterface $ipAddressRepository,
private readonly AddonRepositoryInterface $addonRepository,
) {}
public function index(Request $request): JsonResponse
{
$perPage = min((int) $request->get('per_page', 15), 100);
$query = $this->orderRepository->filter(
$request->all(),
['payment', 'customer', 'invoice'],
[],
['*'],
true
);
return response()->json([
'error' => false,
'data' => [
'items' => OrderResource::collection($query->get())->resolve(),
'total' => $query->count(),
'per_page' => $perPage,
],
]);
}
public function datatable(Request $request): JsonResponse
{
$query = $this->orderRepository->filter(
$request->all(),
['payment', 'customer', 'invoice'],
[],
['*'],
true
);
return DataTables::eloquent($query)
->addColumn('order_num', fn($d) => $d->order_num)
->addColumn('date', fn($d) => $d->date)
->addColumn('customer', fn($d) => $d->customer?->fullname)
->addColumn('payment', fn($d) => $d->payment?->name)
->addColumn('amount', fn($d) => $d->amount)
->addColumn('invoice_status', fn($d) => $d->invoice?->status)
->addColumn('status', fn($d) => $d->status)
->addColumn('action', fn($d) => $d->id)
->rawColumns(['order_num', 'date', 'customer', 'payment', 'amount', 'invoice_status', 'status', 'action'])
->make(true);
}
public function store(OrderRequest $request): JsonResponse
{
$request->validated();
$customer = Customer::find($request->customer_id);
if (!$customer || (int) $customer->status !== 1) {
return response()->json([
'error' => true,
'message' => 'Tài khoản khách hàng không hoạt động. Không thể tạo đơn hàng.',
], 422);
}
$total = 0;
$product_item = [];
$relid_data = [];
foreach ($request->product as $key => $item) {
$product = \App\Models\Product::with('category')->find($item['product_id']);
if (!$product) {
return response()->json([
'error' => true,
'message' => "Sản phẩm không tồn tại (ID: {$item['product_id']})."
], 422);
}
// Derive legacy numeric type from product_type for module creation
$moduleType = match ($product->product_type) {
'hosting' => 3,
'vps' => 4,
default => 0,
};
$relid_data[$key]['type'] = $moduleType;
$message = $product->name;
$relid_data[$key]['data'] = [
'customer_id' => $request->customer_id,
'status' => 0,
'name' => $item['domain'] ?? $product->name,
];
$billingDetails = $this->calculateBillingDetails($item['billing']);
$relid_data[$key]['data'] = array_merge($relid_data[$key]['data'], $billingDetails);
$price_select = $item['price_custom'] ?? $product->price_sale ?? $product->price;
$relid_data[$key]['data']['first_payment'] = $price_select * $billingDetails['billing'];
$relid_data[$key]['data']['next_payment'] = ($item['price_custom'] ?? $product->price) * $billingDetails['billing'];
$generalCode = Helper::generateUniqueCode();
$billingCycle = match ((int) $item['billing']) {
2 => 'monthly',
3 => 'quarterly',
4 => 'semi_annually',
5 => 'annually',
default => 'monthly',
};
$product_item[$key] = [
'product_id' => $product->id,
'product_type' => $product->product_type,
'months' => $billingDetails['billing'],
'billing_cycle' => $billingCycle,
'os_id' => $item['os_id'] ?? null,
'quantity' => (int) ($item['quantity'] ?? 1),
'server_name' => $item['domain'] ?? null,
'price' => (int) $price_select,
'invoice_type' => 1,
'code' => $generalCode,
];
$relid_data[$key]['code'] = $generalCode;
if ($item['quantity'] > 1) {
for ($i = 1; $i < $item['quantity']; $i++) {
$newCode = Helper::generateUniqueCode();
$product_item[] = array_merge($product_item[$key], ['code' => $newCode]);
$relid_data[] = array_merge($relid_data[$key], ['code' => $newCode]);
}
}
$total += $item['quantity'] * ($item['price_custom'] ?? ($product->price_sale ?? $product->price)) * $billingDetails['billing'];
}
$paymentRecord = \App\Models\Payment::find((int) $request->payment_method);
$isBankTransfer = $paymentRecord && (int) $paymentRecord->type === 1;
$order_item = [
'order_num' => Helper::generateUniqueCode(),
'customer_id' => $request->customer_id,
'invoice_id' => 0,
'discount_id' => $request->discount_id ?: null,
'amount' => $total + ($total * (10 / 100)),
'payment_id' => $paymentRecord?->id,
'payment_proof' => $request->payment_proof ?? null,
'user_id' => Auth::guard('admin')->id(),
'status' => $request->status,
'client_ip' => $request->getClientIp(),
];
$invoice_data = [
'date' => Carbon::today()->toDateString(),
'due_date' => Carbon::today()->toDateString(),
'date_paid' => (int)$request->status === 1 ? Carbon::today()->toDateString() : null,
'sub_total' => $total,
'tax' => $total * (10 / 100),
'total' => $total + ($total * (10 / 100)),
'payment_id' => $paymentRecord?->id,
'customer_id' => $request->create_invoice ? $request->customer_id : 0,
'status' => $request->status
];
DB::beginTransaction();
try {
$invoice = $this->invoiceRepository->create($invoice_data);
$product_item = array_map(function ($array) use ($invoice) {
$array['invoice_id'] = $invoice->id;
return $array;
}, $product_item);
if ($request->create_invoice) {
$order_item['invoice_id'] = $invoice->id;
}
$order = $this->orderRepository->create($order_item);
$relid_data = array_map(function ($array) use ($order) {
$array['data']['order_id'] = $order->id;
return $array;
}, $relid_data);
$relid = [];
foreach ($relid_data as $item) {
$code = $item['code'];
switch ($item['type']) {
case 3:
$relid_id = $this->moduleHostingRepository->create($item['data']);
$relid[$code] = $relid_id->id;
break;
case 4:
$item['data']['status'] = 2;
$relid_id = $this->moduleVmwareRepository->create($item['data']);
$relid[$code] = $relid_id->id;
break;
}
}
$product_item = array_map(function ($array) {
unset($array['code']);
return $array;
}, $product_item);
$invoiceItemData = array_map(function ($item) {
return \Illuminate\Support\Arr::except($item, ['billing_cycle']);
}, $product_item);
$this->invoiceItemRepository->insert($invoiceItemData);
$now = now();
\App\Models\OrderItem::insert(array_map(function ($item) use ($now) {
$expiryDate = $now->copy()->addMonths($item['months']);
return [
'order_id' => $order->id,
'product_id' => $item['product_id'],
'billing_cycle' => $item['billing_cycle'],
'months' => $item['months'],
'expiry_date' => $expiryDate,
'os_id' => $item['os_id'],
'quantity' => $item['quantity'],
'server_name' => $item['server_name'],
'price' => $item['price'],
'created_at' => $now,
'updated_at' => $now,
];
}, $product_item));
// Dispatch VM creation job for VPS/cloud products
$vpsTypes = ['vps', 'cloud'];
$createdOrderItems = \App\Models\OrderItem::where('order_id', $order->id)->get();
foreach ($createdOrderItems as $orderItem) {
if (in_array($orderItem->product?->product_type, $vpsTypes)) {
\App\Jobs\VirtualMachine\Create\CreateVirtualMachineJob::dispatch($orderItem)
->delay(now()->addSeconds(10));
}
}
// Deduct customer balance for non-bank-transfer payments
if (!$isBankTransfer) {
Customer::where('id', $request->customer_id)
->decrement('total', (int) ($total + ($total * (10 / 100))));
}
DB::commit();
return response()->json([
'error' => false,
'message' => 'Order created successfully',
'data' => new OrderResource($order)
], 201);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'error' => true,
'message' => $e->getMessage()
], 500);
}
}
public function show(int $id): JsonResponse
{
$order = Order::with([
'orderItems.product',
'orderItems.os',
'user',
'customer',
'payment',
'discount',
'invoice'
])->find($id);
if (!$order) {
return response()->json([
'error' => true,
'message' => "Can't find order by ID #" . $id
], 404);
}
return response()->json([
'error' => false,
'data' => new OrderResource($order)
]);
}
public function update(int $id, UpdateOrderRequest $request): JsonResponse
{
$order = $this->orderRepository->findWithRelationships($id, ['invoice', 'orderItem']);
if (!$order) {
return response()->json([
'error' => true,
'message' => "Can't find order by ID #" . $id
], 404);
}
$status = match ($request->action) {
"accept" => 1,
"cancel" => 2,
default => 0
};
try {
DB::beginTransaction();
$this->orderRepository->update($id, ['status' => $status]);
if ($status === 1 && $request->module == 1) {
$jobs = [];
$addon = [];
$product = 0;
foreach ($order->orderItem as $key => $item) {
switch ((int) $item->type_id) {
case 1:
$itemData = $this->addonRepository->find($item->relid);
if ($itemData) {
$price = match ((int) $item->billing) {
2 => $item->amount / 1,
3 => $item->amount / 3,
4 => $item->amount / 6,
5 => $item->amount / 12,
default => 0
};
$addon[strtolower($itemData->name)] = (int) $price / $itemData->price;
$product = $item->parent_id;
}
break;
case 3:
$hostingServer = $this->moduleHostingServerRepository->get_active();
if ($hostingServer) {
$this->moduleHostingRepository->update($item->relid, ['module_server_id' => $hostingServer->first()->id]);
$jobs[] = new CreateHostingJobs($item->relid);
}
break;
case 4:
$product = $this->moduleVmwareRepository->find($item->relid);
if ($product && $product->os_id) {
$os = $this->osRepository->findWithRelationships($product->os_id, ['osGroup']);
if (!$os || !$os->osGroup->isNotEmpty()) break;
$ip_address = $this->ipAddressRepository->filter([
'module_host_id' => $os->osGroup->module_host_id,
'status' => 0
]);
if (count($ip_address) > 0) {
$this->moduleVmwareRepository->update($item->relid, [
'module_ipaddress_id' => $ip_address->first()->id,
]);
$this->ipAddressRepository->update($ip_address->first()->id, [
'status' => 1
]);
$jobs[] = new CreateVMJobs($product->id);
}
}
break;
}
}
if (count($order->orderItem) === count($addon)) {
dispatch(new UpgradeVirtualMachine($product, $addon, $order->id))->delay(now()->addSeconds(10));
}
foreach ($jobs as $item) {
dispatch($item)->delay(now()->addSeconds(10));
}
}
DB::commit();
return response()->json([
'error' => false,
'message' => 'Order updated successfully'
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'error' => true,
'message' => $e->getMessage()
], 500);
}
}
public function destroy(int $id): JsonResponse
{
$order = Order::with('Invoice', 'Invoice.InvoiceItem')->find($id);
if (!$order) {
return response()->json([
'error' => true,
'message' => "Can't find order by ID #" . $id
], 404);
}
try {
DB::beginTransaction();
if ($order->Invoice) {
$order->Invoice->delete();
if ($order->Invoice->InvoiceItem->count()) {
foreach ($order->Invoice->InvoiceItem->all() as $item) {
$item->delete();
}
}
}
$order->delete();
DB::commit();
return response()->json([
'error' => false,
'message' => 'Order deleted successfully'
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'error' => true,
'message' => $e->getMessage()
], 500);
}
}
public function accept(int $id): JsonResponse
{
$order = $this->orderRepository->findWithRelationships($id, ['invoice', 'orderItem']);
if (!$order) {
return response()->json([
'error' => true,
'message' => "Can't find order by ID #" . $id
], 404);
}
try {
DB::beginTransaction();
$this->orderRepository->update($id, ['status' => 1]);
DB::commit();
return response()->json([
'error' => false,
'message' => 'Order accepted successfully'
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'error' => true,
'message' => $e->getMessage()
], 500);
}
}
public function cancel(int $id): JsonResponse
{
$order = $this->orderRepository->find($id);
if (!$order) {
return response()->json([
'error' => true,
'message' => "Can't find order by ID #" . $id
], 404);
}
try {
DB::beginTransaction();
$this->orderRepository->update($id, ['status' => 2]);
DB::commit();
return response()->json([
'error' => false,
'message' => 'Order cancelled successfully'
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'error' => true,
'message' => $e->getMessage()
], 500);
}
}
public function uploadProof(Request $request): JsonResponse
{
$request->validate(['file' => 'required|image|max:5120']);
$path = $request->file('file')->store('payment-proofs', 'public');
return response()->json([
'error' => false,
'url' => Storage::disk('public')->url($path),
'path' => $path,
]);
}
private function calculateBillingDetails($billing): array
{
$months = match ($billing) {
2 => 1,
3 => 3,
4 => 6,
5 => 12,
default => 0
};
return [
'next_due_date' => $months ? Carbon::today()->addMonths($months)->timestamp : null,
'next_invoice' => $months ? Carbon::today()->addMonths($months)->timestamp : null,
'billing' => $months ?: 1
];
}
}