206 lines
9.1 KiB
PHP
206 lines
9.1 KiB
PHP
<?php
|
|
|
|
namespace Database\Seeders;
|
|
|
|
use App\Enums\Invoice\InvoiceStatus;
|
|
use App\Enums\Order\OrderStatus;
|
|
use App\Enums\TransactionStatus;
|
|
use App\Helpers\OrderHelper;
|
|
use App\Models\Customer;
|
|
use App\Models\Invoice;
|
|
use App\Models\InvoiceItem;
|
|
use App\Models\Order;
|
|
use App\Models\OrderItem;
|
|
use App\Models\Product;
|
|
use App\Models\Service;
|
|
use App\Models\Transaction;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Database\Seeder;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
|
|
/**
|
|
* Chạy: php artisan db:seed --class=CustomerBillingDemoSeeder
|
|
*/
|
|
class CustomerBillingDemoSeeder extends Seeder
|
|
{
|
|
private const MARKER = 'seed:customer-billing-demo';
|
|
|
|
private const SCENARIOS = [
|
|
['product' => 'email-doanh-nghiep-goi-10-tai-khoan', 'cycle' => 'monthly', 'status' => InvoiceStatus::PAID, 'paid' => 1.0, 'created' => -25, 'due' => -18],
|
|
['product' => 'switch-cisco-catalyst-24-port', 'cycle' => 'onetime', 'status' => InvoiceStatus::PAID, 'paid' => 1.0, 'created' => -60, 'due' => -53],
|
|
['product' => 'thue-may-chu-vat-ly-dell-r740', 'cycle' => 'monthly', 'status' => InvoiceStatus::UNPAID, 'paid' => 0.0, 'created' => -1, 'due' => 6],
|
|
['product' => 'it-support-goi-doanh-nghiep', 'cycle' => 'monthly', 'status' => InvoiceStatus::UNPAID, 'paid' => 0.5, 'created' => -3, 'due' => 4],
|
|
['product' => 'website-landing-page-co-ban', 'cycle' => 'onetime', 'status' => InvoiceStatus::OVERDUE, 'paid' => 0.0, 'created' => -17, 'due' => -10],
|
|
['product' => 'email-doanh-nghiep-goi-10-tai-khoan', 'cycle' => 'monthly', 'status' => InvoiceStatus::OVERDUE, 'paid' => 0.3, 'created' => -10, 'due' => -3],
|
|
['product' => 'it-support-goi-doanh-nghiep', 'cycle' => 'monthly', 'status' => InvoiceStatus::CANCELLED, 'paid' => 0.0, 'created' => -40, 'due' => -33],
|
|
['product' => 'thue-may-chu-vat-ly-dell-r740', 'cycle' => 'monthly', 'status' => InvoiceStatus::REFUNDED, 'paid' => 1.0, 'created' => -45, 'due' => -38],
|
|
];
|
|
|
|
public function run(): void
|
|
{
|
|
$this->call([TestAccountSeeder::class, ProductCategorySeeder::class, ProductTypeFieldSeeder::class, ProductMockupSeeder::class]);
|
|
|
|
$customer = Customer::where('email', 'pbao.dev@gmail.com')->firstOrFail();
|
|
|
|
DB::transaction(function () use ($customer) {
|
|
$this->purgePreviousRun($customer);
|
|
|
|
foreach (self::SCENARIOS as $scenario) {
|
|
$this->seedScenario($customer, $scenario);
|
|
}
|
|
});
|
|
|
|
$this->command?->info('Đã tạo ' . count(self::SCENARIOS) . ' đơn hàng/hoá đơn mẫu cho ' . $customer->email . '.');
|
|
}
|
|
|
|
private function purgePreviousRun(Customer $customer): void
|
|
{
|
|
$orderIds = Order::withTrashed()->where('customer_id', $customer->id)->where('note', self::MARKER)->pluck('id');
|
|
$invoices = Invoice::withTrashed()->whereIn('order_id', $orderIds)->get(['id', 'invoice_number']);
|
|
|
|
Transaction::withTrashed()
|
|
->where('customer_id', $customer->id)
|
|
->where(function ($query) use ($invoices) {
|
|
foreach ($invoices->pluck('invoice_number') as $number) {
|
|
$query->orWhere('trans', 'like', $number . '%');
|
|
}
|
|
})
|
|
->when($invoices->isNotEmpty(), fn ($query) => $query->forceDelete());
|
|
|
|
$orderItemIds = OrderItem::whereIn('order_id', $orderIds)->pluck('id');
|
|
Service::withTrashed()->whereIn('order_item_id', $orderItemIds)->forceDelete();
|
|
InvoiceItem::whereIn('invoice_id', $invoices->pluck('id'))->delete();
|
|
Invoice::withTrashed()->whereIn('id', $invoices->pluck('id'))->forceDelete();
|
|
OrderItem::whereIn('id', $orderItemIds)->delete();
|
|
Order::withTrashed()->whereIn('id', $orderIds)->forceDelete();
|
|
}
|
|
|
|
/**
|
|
* @param array{product: string, cycle: string, status: InvoiceStatus, paid: float, created: int, due: int} $scenario
|
|
*/
|
|
private function seedScenario(Customer $customer, array $scenario): void
|
|
{
|
|
$product = Product::where('slug', $scenario['product'])->firstOrFail();
|
|
$price = (int) $product->pricings()->where('billing_cycle', $scenario['cycle'])->value('price');
|
|
$months = OrderHelper::cycleToMonths($scenario['cycle']);
|
|
$status = $scenario['status'];
|
|
$createdAt = Carbon::now()->addDays($scenario['created']);
|
|
$amountPaid = (int) round($price * $scenario['paid'], -3);
|
|
$isSettled = in_array($status, [InvoiceStatus::PAID, InvoiceStatus::REFUNDED], true);
|
|
$paidAt = $isSettled ? $createdAt->copy()->addHour() : null;
|
|
|
|
$order = Order::query()->forceCreate([
|
|
'order_num' => OrderHelper::generateOrderNumber(),
|
|
'date' => $createdAt->toDateString(),
|
|
'customer_id' => $customer->id,
|
|
'sub_total' => $price,
|
|
'discount_amount' => 0,
|
|
'total' => $price,
|
|
'status' => match ($status) {
|
|
InvoiceStatus::PAID => OrderStatus::ACTIVE->value,
|
|
InvoiceStatus::CANCELLED, InvoiceStatus::REFUNDED => OrderStatus::CANCELLED->value,
|
|
default => OrderStatus::PENDING->value,
|
|
},
|
|
'payment_method' => $amountPaid > 0 ? 'balance' : null,
|
|
'paid_at' => $paidAt,
|
|
'note' => self::MARKER,
|
|
'created_at' => $createdAt,
|
|
'updated_at' => $createdAt,
|
|
]);
|
|
|
|
$orderItem = OrderItem::query()->forceCreate([
|
|
'order_id' => $order->id,
|
|
'product_id' => $product->id,
|
|
'product_pricing_id' => $product->pricings()->where('billing_cycle', $scenario['cycle'])->value('id'),
|
|
'billing_cycle' => $scenario['cycle'],
|
|
'months' => $months,
|
|
'quantity' => 1,
|
|
'price' => $price,
|
|
'created_at' => $createdAt,
|
|
'updated_at' => $createdAt,
|
|
]);
|
|
|
|
$invoice = Invoice::query()->forceCreate([
|
|
'invoice_number' => OrderHelper::generateOrderNumber('INV'),
|
|
'date' => $createdAt->toDateString(),
|
|
'customer_id' => $customer->id,
|
|
'order_id' => $order->id,
|
|
'invoice_type' => 1,
|
|
'sub_total' => $price,
|
|
'discount_amount' => 0,
|
|
'tax' => 0,
|
|
'total' => $price,
|
|
'amount_paid' => $amountPaid,
|
|
'status' => $status->value,
|
|
'payment_method' => $amountPaid > 0 ? 'balance' : null,
|
|
'due_date' => Carbon::now()->addDays($scenario['due'])->toDateString(),
|
|
'billing_period_start' => $createdAt->toDateString(),
|
|
'billing_period_end' => $createdAt->copy()->addMonths($months)->toDateString(),
|
|
'paid_at' => $paidAt,
|
|
'created_at' => $createdAt,
|
|
'updated_at' => $createdAt,
|
|
]);
|
|
|
|
InvoiceItem::query()->forceCreate([
|
|
'invoice_id' => $invoice->id,
|
|
'product_id' => $product->id,
|
|
'type' => 'service',
|
|
'description' => $product->name,
|
|
'months' => $months,
|
|
'quantity' => 1,
|
|
'unit_price' => $price,
|
|
'total_price' => $price,
|
|
'created_at' => $createdAt,
|
|
'updated_at' => $createdAt,
|
|
]);
|
|
|
|
if ($amountPaid > 0) {
|
|
$this->recordPayment($invoice, $amountPaid, $isSettled, $paidAt ?? $createdAt->copy()->addHour());
|
|
}
|
|
|
|
if ($isSettled) {
|
|
$this->seedService($order, $orderItem, $invoice, $status, $paidAt);
|
|
}
|
|
}
|
|
|
|
private function recordPayment(Invoice $invoice, int $amount, bool $isSettled, Carbon $paidAt): void
|
|
{
|
|
Transaction::query()->forceCreate([
|
|
'uuid' => (string) Str::uuid(),
|
|
'trans' => $invoice->invoice_number . ($isSettled ? '' : '-P' . $amount),
|
|
'public_code' => 'INV-PAY-' . $paidAt->format('Ymd') . '-' . strtoupper(Str::random(6)),
|
|
'amount' => $amount,
|
|
'received_amount' => $amount,
|
|
'customer_id' => $invoice->customer_id,
|
|
'status' => TransactionStatus::COMPLETED->value,
|
|
'type' => 'invoice',
|
|
'note' => 'Khách hàng thanh toán hóa đơn #' . $invoice->invoice_number . ' (balance)',
|
|
'created_at' => $paidAt,
|
|
'updated_at' => $paidAt,
|
|
]);
|
|
}
|
|
|
|
private function seedService(Order $order, OrderItem $orderItem, Invoice $invoice, InvoiceStatus $status, Carbon $startedAt): void
|
|
{
|
|
$isRefunded = $status === InvoiceStatus::REFUNDED;
|
|
$refundedAt = $startedAt->copy()->addDays(2);
|
|
|
|
Service::query()->forceCreate([
|
|
'uuid' => (string) Str::uuid(),
|
|
'customer_id' => $order->customer_id,
|
|
'product_id' => $orderItem->product_id,
|
|
'order_item_id' => $orderItem->id,
|
|
'invoice_id' => $invoice->id,
|
|
'status' => $isRefunded ? 'cancelled' : 'active',
|
|
'auto_renew' => false,
|
|
'started_at' => $startedAt,
|
|
'expires_at' => $startedAt->copy()->addMonths($orderItem->months),
|
|
'cancelled_at' => $isRefunded ? $refundedAt : null,
|
|
'refunded_at' => $isRefunded ? $refundedAt : null,
|
|
'created_at' => $startedAt,
|
|
'updated_at' => $isRefunded ? $refundedAt : $startedAt,
|
|
]);
|
|
}
|
|
}
|