diff --git a/database/seeders/RealisticDemoSeeder.php b/database/seeders/RealisticDemoSeeder.php index 7b5faab9..e51837d7 100644 --- a/database/seeders/RealisticDemoSeeder.php +++ b/database/seeders/RealisticDemoSeeder.php @@ -2,6 +2,7 @@ namespace Database\Seeders; +use App\Facades\Hashids; use App\Models\Address; use App\Models\AiConversation; use App\Models\CompanySetting; @@ -452,6 +453,10 @@ class RealisticDemoSeeder extends Seeder // Touch timestamps to match the invoice_date so tool queries like // `latest('invoice_date')` match `latest('created_at')` plausibly. + // The PDF routes bind on unique_hash. Creating through the model rather + // than InvoiceService skips the one place that normally assigns it, so + // set it here or every seeded document 404s on preview and download. + $invoice->unique_hash = Hashids::connection(Invoice::class)->encode($invoice->id); $invoice->created_at = $invoiceDate; $invoice->updated_at = $invoiceDate; $invoice->save(); @@ -515,6 +520,8 @@ class RealisticDemoSeeder extends Seeder 'notes' => null, ]); + // See seedInvoice(): the PDF routes bind on unique_hash. + $payment->unique_hash = Hashids::connection(Payment::class)->encode($payment->id); $payment->created_at = $paymentDate; $payment->updated_at = $paymentDate; $payment->save(); @@ -585,6 +592,8 @@ class RealisticDemoSeeder extends Seeder 'notes' => null, ]); + // See seedInvoice(): the PDF routes bind on unique_hash. + $estimate->unique_hash = Hashids::connection(Estimate::class)->encode($estimate->id); $estimate->created_at = $estimateDate; $estimate->updated_at = $estimateDate; $estimate->save(); diff --git a/tests/Feature/Admin/RealisticDemoSeederTest.php b/tests/Feature/Admin/RealisticDemoSeederTest.php new file mode 100644 index 00000000..583145ad --- /dev/null +++ b/tests/Feature/Admin/RealisticDemoSeederTest.php @@ -0,0 +1,31 @@ + 'DatabaseSeeder', '--force' => true]); + Artisan::call('db:seed', ['--class' => 'RealisticDemoSeeder', '--force' => true]); +}); + +/** + * The PDF routes bind on unique_hash (`/invoices/pdf/{invoice:unique_hash}`), and + * the seeder builds its documents with Model::create() rather than through the + * service layer that normally assigns it. When that was missed, every seeded + * document produced a `/invoices/pdf/` URL with an empty segment — a 404, surfaced + * in the UI only as "Unable to load document preview", with nothing in the log. + * + * Anything the seeder creates that the app then serves by hash has to carry one. + */ +test('every seeded document has the unique hash its pdf route binds on', function (string $model) { + expect($model::count())->toBeGreaterThan(0); + + expect($model::whereNull('unique_hash')->orWhere('unique_hash', '')->count()) + ->toBe(0); +})->with([ + Invoice::class, + Estimate::class, + Payment::class, +]);