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 ]; } }