227 lines
9.0 KiB
PHP
227 lines
9.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Enums\Product\ProductBillingCycle;
|
|
use App\Http\Helpers\Helper;
|
|
use App\Jobs\Hosting\CreateHostingJobs;
|
|
use App\Jobs\VirtualMachine\Create\CreateVirtualMachineJob;
|
|
use App\Models\Customer;
|
|
use App\Models\Order;
|
|
use App\Models\OrderItem;
|
|
use App\Models\Payment;
|
|
use App\Repositories\Invoice\InvoiceRepositoryInterface;
|
|
use App\Repositories\InvoiceItem\InvoiceItemRepositoryInterface;
|
|
use App\Repositories\Module\Hosting\HostingRepositoryInterface as ModuleHostingRepositoryInterface;
|
|
use App\Repositories\Module\Vmware\VmwareRepositoryInterface;
|
|
use App\Repositories\Order\OrderRepositoryInterface;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class OrderService
|
|
{
|
|
public function __construct(
|
|
private readonly OrderRepositoryInterface $orderRepository,
|
|
private readonly InvoiceRepositoryInterface $invoiceRepository,
|
|
private readonly InvoiceItemRepositoryInterface $invoiceItemRepository,
|
|
private readonly ModuleHostingRepositoryInterface $moduleHostingRepository,
|
|
private readonly VmwareRepositoryInterface $moduleVmwareRepository,
|
|
) {}
|
|
|
|
public function createOrder(array $data): Order
|
|
{
|
|
$customer = Customer::find($data['customer_id']);
|
|
if (!$customer || (int) $customer->status !== 1) {
|
|
throw new \InvalidArgumentException('Tài khoản khách hàng không hoạt động. Không thể tạo đơn hàng.');
|
|
}
|
|
|
|
$paymentRecord = Payment::find((int) $data['payment_method']);
|
|
$isBankTransfer = $paymentRecord && (int) $paymentRecord->type === 1;
|
|
|
|
[$productItems, $relIdData, $total] = $this->buildProductItems($data);
|
|
|
|
$invoiceData = [
|
|
'invoice_number' => Helper::generateUniqueCode(),
|
|
'due_date' => Carbon::today()->toDateString(),
|
|
'paid_at' => (int) $data['status'] === 1 ? Carbon::today()->toDateString() : null,
|
|
'sub_total' => $total,
|
|
'tax' => $total * (10 / 100),
|
|
'total' => $total + ($total * (10 / 100)),
|
|
'payment_method' => $paymentRecord?->id,
|
|
'customer_id' => $data['customer_id'],
|
|
'status' => $data['status'],
|
|
];
|
|
|
|
$orderData = [
|
|
'order_num' => Helper::generateUniqueCode(),
|
|
'customer_id' => $data['customer_id'],
|
|
'discount_id' => $data['discount_id'] ?? null,
|
|
'sub_total' => $total,
|
|
'total' => $total + ($total * (10 / 100)),
|
|
'payment_method' => $paymentRecord?->code ?? null,
|
|
'payment_proof' => $data['payment_proof'] ?? null,
|
|
'user_id' => Auth::guard('admin')->id(),
|
|
'status' => $data['status'],
|
|
'client_ip' => request()->getClientIp(),
|
|
];
|
|
|
|
return DB::transaction(function () use ($invoiceData, $orderData, $productItems, $relIdData, $data, $total, $isBankTransfer) {
|
|
$invoice = $this->invoiceRepository->create($invoiceData);
|
|
|
|
$productItems = array_map(fn ($item) => array_merge($item, ['invoice_id' => $invoice->id]), $productItems);
|
|
|
|
if ($data['create_invoice'] ?? false) {
|
|
$orderData['invoice_id'] = $invoice->id;
|
|
}
|
|
|
|
$order = $this->orderRepository->create($orderData);
|
|
|
|
$relIdData = array_map(function ($item) use ($order) {
|
|
$item['data']['order_id'] = $order->id;
|
|
return $item;
|
|
}, $relIdData);
|
|
|
|
$this->createModules($relIdData);
|
|
|
|
$invoiceItemData = array_map(fn ($item) => [
|
|
'invoice_id' => $item['invoice_id'],
|
|
'product_id' => $item['product_id'],
|
|
'type' => 'service',
|
|
'months' => $item['months'],
|
|
'quantity' => $item['quantity'],
|
|
'unit_price' => $item['price'],
|
|
'total_price' => $item['price'] * $item['quantity'],
|
|
], $productItems);
|
|
$this->invoiceItemRepository->insert($invoiceItemData);
|
|
|
|
$now = now();
|
|
OrderItem::insert(array_map(fn ($item) => [
|
|
'order_id' => $order->id,
|
|
'product_id' => $item['product_id'],
|
|
'billing_cycle' => $item['billing_cycle'],
|
|
'months' => $item['months'],
|
|
'os_id' => $item['os_id'],
|
|
'quantity' => $item['quantity'],
|
|
'server_name' => $item['server_name'],
|
|
'price' => $item['price'],
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
], $productItems));
|
|
|
|
$vpsTypes = ['vps', 'cloud'];
|
|
foreach (OrderItem::where('order_id', $order->id)->get() as $orderItem) {
|
|
if (in_array($orderItem->product?->product_type, $vpsTypes)) {
|
|
CreateVirtualMachineJob::dispatch($orderItem)->delay(now()->addSeconds(10));
|
|
}
|
|
}
|
|
|
|
if (!$isBankTransfer) {
|
|
$orderTotal = (int) ($total + ($total * (10 / 100)));
|
|
$customer = Customer::lockForUpdate()->find($data['customer_id']);
|
|
|
|
if ($customer->total < $orderTotal) {
|
|
throw new \Exception('Số dư tài khoản không đủ để thanh toán đơn hàng.');
|
|
}
|
|
|
|
$customer->decrement('total', $orderTotal);
|
|
}
|
|
|
|
return $order;
|
|
});
|
|
}
|
|
|
|
private function buildProductItems(array $data): array
|
|
{
|
|
$total = 0;
|
|
$productItems = [];
|
|
$relIdData = [];
|
|
|
|
foreach ($data['product'] as $key => $item) {
|
|
$product = \App\Models\Product::with('category')->find($item['product_id']);
|
|
|
|
if (!$product) {
|
|
throw new \InvalidArgumentException("Sản phẩm không tồn tại (ID: {$item['product_id']}).");
|
|
}
|
|
|
|
$moduleType = match ($product->product_type) {
|
|
'hosting' => 3,
|
|
'vps' => 4,
|
|
default => 0,
|
|
};
|
|
|
|
$billingDetails = $this->calculateBillingDetails($item['billing']);
|
|
$priceSelect = $item['price_custom'] ?? $product->price_sale ?? $product->price;
|
|
|
|
$billingCycle = ProductBillingCycle::fromLegacyCode((int) $item['billing']);
|
|
$generalCode = Helper::generateUniqueCode();
|
|
|
|
$productItems[$key] = [
|
|
'product_id' => $product->id,
|
|
'product_type' => $product->product_type,
|
|
'months' => $billingCycle->months(),
|
|
'billing_cycle' => $billingCycle->value,
|
|
'os_id' => $item['os_id'] ?? null,
|
|
'quantity' => (int) ($item['quantity'] ?? 1),
|
|
'server_name' => $item['domain'] ?? null,
|
|
'price' => (int) $priceSelect,
|
|
'invoice_type' => 1,
|
|
'code' => $generalCode,
|
|
];
|
|
|
|
$relIdData[$key] = [
|
|
'type' => $moduleType,
|
|
'code' => $generalCode,
|
|
'data' => [
|
|
'customer_id' => $data['customer_id'],
|
|
'status' => 0,
|
|
'name' => $item['domain'] ?? $product->name,
|
|
'first_payment' => $priceSelect * $billingDetails['billing'],
|
|
'next_payment' => ($item['price_custom'] ?? $product->price) * $billingDetails['billing'],
|
|
...$billingDetails,
|
|
],
|
|
];
|
|
|
|
if ($item['quantity'] > 1) {
|
|
for ($i = 1; $i < $item['quantity']; $i++) {
|
|
$newCode = Helper::generateUniqueCode();
|
|
$productItems[] = array_merge($productItems[$key], ['code' => $newCode]);
|
|
$relIdData[] = array_merge($relIdData[$key], ['code' => $newCode]);
|
|
}
|
|
}
|
|
|
|
$total += $item['quantity'] * ($item['price_custom'] ?? ($product->price_sale ?? $product->price)) * $billingDetails['billing'];
|
|
}
|
|
|
|
return [$productItems, $relIdData, $total];
|
|
}
|
|
|
|
private function createModules(array $relIdData): void
|
|
{
|
|
foreach ($relIdData as $item) {
|
|
match ($item['type']) {
|
|
3 => $this->moduleHostingRepository->create($item['data']),
|
|
4 => $this->moduleVmwareRepository->create(array_merge($item['data'], ['status' => 2])),
|
|
default => null,
|
|
};
|
|
}
|
|
}
|
|
|
|
private function calculateBillingDetails(int|string $billing): array
|
|
{
|
|
$months = match ((int) $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,
|
|
];
|
|
}
|
|
}
|