'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, ]); } }