feat: Reimplement order and invoice management with new service and repository layers, introduce compression middleware, and update database schema.
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
<?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 = [
|
||||
'date' => Carbon::today()->toDateString(),
|
||||
'due_date' => Carbon::today()->toDateString(),
|
||||
'date_paid' => (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['create_invoice'] ?? false) ? $data['customer_id'] : 0,
|
||||
'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) => \Illuminate\Support\Arr::except($item, ['billing_cycle', 'code']),
|
||||
$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,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user