feat: email gửi tay/nhắc gia hạn, gói hosting, sửa dữ liệu sản phẩm

- Gửi email theo template cho 1 khách, tab lịch sử email, nhắc gia hạn dịch vụ hàng loạt
- Mail lưu from/bcc/sent_by; mẫu MARKETING và mẫu cũ trong TemplateSeeder
- Gói hosting: giới hạn, trạng thái, lấy plan Plesk qua CLI gateway
- Backfill payment_method và ngày đơn hàng còn thiếu
- Bổ sung field CPU/RAM/Disk/Băng thông cho loại sản phẩm vps và cloud

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
baodev0
2026-10-09 11:32:05 +07:00
co-authored by Claude Opus 5.5
parent 8651dde6b1
commit 30898ea46f
59 changed files with 1771 additions and 108 deletions
+23 -5
View File
@@ -13,6 +13,7 @@ use App\Jobs\Mail\Customer\OrderCreatedJob;
use App\Jobs\VirtualMachine\Create\CreateVirtualMachineJob;
use App\Jobs\VirtualMachine\Upgrade\UpgradeVirtualMachine;
use App\Models\Customer;
use App\Models\DomainPricing;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Payment;
@@ -155,11 +156,15 @@ class OrderService
$orderData = [
'order_num' => Helper::generateUniqueCode(),
'date' => Carbon::today()->toDateString(),
'customer_id' => $data['customer_id'],
'discount_id' => $data['discount_id'] ?? null,
'sub_total' => $total,
'total' => $orderTotal,
'payment_method' => $paymentRecord?->code ?? null,
// `payment` has no `code` column. Store its primary key in the
// legacy string column so the selected method can be resolved
// consistently on both orders and invoices.
'payment_method' => $paymentRecord?->id,
'payment_proof' => $data['payment_proof'] ?? null,
'user_id' => Auth::guard('admin')->id(),
'status' => $effectiveOrderStatus,
@@ -287,7 +292,20 @@ class OrderService
};
$billingDetails = $this->calculateBillingDetails($item['billing']);
$priceSelect = $item['price_custom'] ?? $product->price_sale ?? $product->price;
$isDomain = $categoryType === ProductTypeEnums::DOMAIN;
$billingMultiplier = $isDomain ? 1 : $billingDetails['billing'];
if ($isDomain) {
$domainName = strtolower(trim((string) ($item['server_name'] ?? $item['domain'] ?? '')));
if ($domainName === '') {
throw new \InvalidArgumentException('Vui lòng nhập đầy đủ tên miền.');
}
$domainPrice = DomainPricing::findByDomain($domainName)->orderPrice(1);
$priceSelect = $item['price_custom'] ?? $domainPrice;
} else {
$priceSelect = $item['price_custom'] ?? $product->price_sale ?? $product->price;
}
$billingCycle = ProductBillingCycle::fromLegacyCode((int) $item['billing']);
$generalCode = Helper::generateUniqueCode();
@@ -312,8 +330,8 @@ class OrderService
'customer_id' => $data['customer_id'],
'status' => 0,
'name' => $item['server_name'] ?? $item['domain'] ?? $product->name,
'first_payment' => $priceSelect * $billingDetails['billing'],
'next_payment' => ($item['price_custom'] ?? $product->price) * $billingDetails['billing'],
'first_payment' => $priceSelect * $billingMultiplier,
'next_payment' => $priceSelect * $billingMultiplier,
...$billingDetails,
],
];
@@ -326,7 +344,7 @@ class OrderService
}
}
$total += $item['quantity'] * ($item['price_custom'] ?? ($product->price_sale ?? $product->price)) * $billingDetails['billing'];
$total += $item['quantity'] * $priceSelect * $billingMultiplier;
}
return [$productItems, $relIdData, $total];