$tenantId, 'legal_entity_id' => $leId, 'invoice_number' => $number, 'payer_type' => 'legal', 'amount_net' => '100.00', 'amount_total' => '100.00', 'status' => $status, 'issued_at' => now()->subDays(10), 'expires_at' => $expiresAt, ]); } it('помечает overdue только просроченные неоплаченные счета', function () { $t = Tenant::factory()->create(); $le = LegalEntity::create(['code' => 'exp_'.uniqid(), 'name' => 'ИП', 'legal_form' => 'IP', 'inn' => '770000000020']); $stale = expSeed($t->id, $le->id, 'issued', 'СЧ-2026-02001', now()->subDay()); $fresh = expSeed($t->id, $le->id, 'issued', 'СЧ-2026-02002', now()->addDay()); $paid = expSeed($t->id, $le->id, 'paid', 'СЧ-2026-02003', now()->subDay()); $this->artisan('invoices:expire')->assertExitCode(0); expect(SaasInvoice::find($stale->id)->status)->toBe('overdue') ->and(SaasInvoice::find($fresh->id)->status)->toBe('issued') ->and(SaasInvoice::find($paid->id)->status)->toBe('paid'); }); it('просрочивает счёт даже без контекста фирмы (планировщик бежит под системным tenant 0)', function () { // Регрессия на «тихий no-op»: saas_invoices под RLS, планировщик — без // app.current_tenant_id. Команда обязана просрочить счёт через BYPASSRLS, // а не остаться пустышкой. Раньше overdue не выставлялся никогда. $t = Tenant::factory()->create(); $le = LegalEntity::create(['code' => 'exp_'.uniqid(), 'name' => 'ИП', 'legal_form' => 'IP', 'inn' => '770000000021']); $stale = expSeed($t->id, $le->id, 'issued', 'СЧ-2026-02010', now()->subDay()); // Планировщик реально запускает команду без контекста своей фирмы. DB::statement("SELECT set_config('app.current_tenant_id', '0', true)"); $this->artisan('invoices:expire')->assertExitCode(0); expect(SaasInvoice::on('pgsql_supplier')->find($stale->id)->status)->toBe('overdue'); });