toArray(); $products = DB::table('products')->select('id', 'prix_unitaire')->get()->keyBy('id'); $productIds = $products->keys()->toArray(); if (empty($clientIds) || empty($productIds)) { $this->command->warn('InvoiceSeeder: aucun client ou produit trouvé — skip.'); return; } // ── 1. Invoices liées à des devis acceptés ──────────────────────────── $acceptedQuotes = Quote::where('status', 'accepte') ->orderBy('id') ->limit(35) ->get(); foreach ($acceptedQuotes as $quote) { $invoiceDate = $quote->quote_date->copy()->addDays(rand(2, 10))->format('Y-m-d'); $dueDate = date('Y-m-d', strtotime($invoiceDate . ' +30 days')); // Statuses for quote-linked invoices (mostly paid) $s = ['payee', 'payee', 'payee', 'emise', 'envoyee', 'partiellement_payee']; Invoice::create([ 'client_id' => $quote->client_id, 'source_quote_id' => $quote->id, 'status' => $s[array_rand($s)], 'invoice_date' => $invoiceDate, 'due_date' => $dueDate, 'currency' => 'EUR', 'total_ht' => $quote->total_ht, 'total_tva' => $quote->total_tva, 'total_ttc' => $quote->total_ttc, ]); // Lines are not duplicated — source quote already has them } // ── 2. Invoices autonomes ───────────────────────────────────────────── // 2024 historical — statuses varied, many paid $this->createBatch($clientIds, $productIds, $products, [ 'yearRange' => [[2024, 1, 12]], 'countPerMonth' => [3, 5], 'statusPool' => ['payee', 'payee', 'payee', 'emise', 'envoyee', 'partiellement_payee', 'echue'], ]); // 2025 main year — rich dataset $this->createBatch($clientIds, $productIds, $products, [ 'yearRange' => [[2025, 1, 12]], 'countPerMonth' => [4, 7], 'statusPool' => ['payee', 'payee', 'payee', 'emise', 'envoyee', 'partiellement_payee', 'echue', 'echue'], ]); // 2026 Jan-Apr — no echue (due dates not yet past) $this->createBatch($clientIds, $productIds, $products, [ 'yearRange' => [[2026, 1, 4]], 'countPerMonth' => [4, 6], 'statusPool' => ['payee', 'payee', 'emise', 'envoyee', 'partiellement_payee'], ]); // 2026 May 1-8 — recent, mostly emise $this->createBatch($clientIds, $productIds, $products, [ 'yearRange' => [[2026, 5, 5]], 'countPerMonth' => [4, 6], 'maxDay' => 8, 'statusPool' => ['payee', 'emise', 'emise', 'envoyee'], ]); $total = Invoice::count(); $this->command->info("InvoiceSeeder: {$total} factures au total."); } // ───────────────────────────────────────────────────────────────────────── private function createBatch( array $clientIds, array $productIds, \Illuminate\Support\Collection $products, array $opts ): void { foreach ($opts['yearRange'] as [$year, $startM, $endM]) { for ($m = $startM; $m <= $endM; $m++) { $maxDay = $opts['maxDay'] ?? 28; $count = rand(...$opts['countPerMonth']); for ($i = 0; $i < $count; $i++) { $invoiceDate = sprintf('%04d-%02d-%02d', $year, $m, rand(1, $maxDay)); $status = $opts['statusPool'][array_rand($opts['statusPool'])]; // due_date if ($status === 'echue') { $dueDate = date('Y-m-d', strtotime($invoiceDate . ' +10 days')); } else { $dueDate = date('Y-m-d', strtotime($invoiceDate . ' +30 days')); } $clientId = $clientIds[array_rand($clientIds)]; // Build lines $lineData = []; $totalHt = 0.0; for ($l = 0, $nl = rand(1, 3); $l < $nl; $l++) { $pid = $productIds[array_rand($productIds)]; $unitPrice = (float) ($products->get($pid)->prix_unitaire ?? 150.00); $qty = rand(1, 3); $lineHt = round($unitPrice * $qty, 2); $totalHt += $lineHt; $lineData[] = [ 'product_id' => $pid, 'description' => 'Prestation funéraire', 'qty_base' => $qty, 'unit_price' => $unitPrice, 'discount_pct' => 0, 'total_ht' => $lineHt, ]; } $totalTva = round($totalHt * 0.20, 2); $totalTtc = round($totalHt + $totalTva, 2); $invoice = Invoice::create([ 'client_id' => $clientId, 'status' => $status, 'invoice_date' => $invoiceDate, 'due_date' => $dueDate, 'currency' => 'EUR', 'total_ht' => $totalHt, 'total_tva' => $totalTva, 'total_ttc' => $totalTtc, ]); foreach ($lineData as $line) { InvoiceLine::create(array_merge($line, ['invoice_id' => $invoice->id])); } } } } } }