where('kind', 'invoice')->firstOrFail(); $numbers = []; foreach (range(1, 3) as $ignored) { DB::transaction(function () use ($s, &$numbers) { [$number] = app(InvoiceNumbers::class)->next($s, 2026); $numbers[] = $number; }); } expect($numbers)->toBe(['RE-2026-0001', 'RE-2026-0002', 'RE-2026-0003']); }); it('restarts a yearly series in a new year, and only then', function () { // Checked against the stored year, not against "is it January": an // installation that issued nothing for fourteen months must still restart, // and one issuing at 23:59 on New Year's Eve must not restart the invoice // that follows a minute later under the old year. $s = InvoiceSeries::query()->where('kind', 'invoice')->firstOrFail(); DB::transaction(fn () => app(InvoiceNumbers::class)->next($s, 2026)); DB::transaction(fn () => app(InvoiceNumbers::class)->next($s, 2026)); [$next] = DB::transaction(fn () => app(InvoiceNumbers::class)->next($s->refresh(), 2027)); expect($next)->toBe('RE-2027-0001'); [$again] = DB::transaction(fn () => app(InvoiceNumbers::class)->next($s->refresh(), 2027)); expect($again)->toBe('RE-2027-0002'); }); it('never restarts a series that does not reset yearly', function () { $s = InvoiceSeries::query()->where('kind', 'invoice')->firstOrFail(); $s->update(['yearly_reset' => false, 'next_number' => 41]); [$number] = DB::transaction(fn () => app(InvoiceNumbers::class)->next($s->refresh(), 2026)); expect($number)->toBe('RE-0041'); [$across] = DB::transaction(fn () => app(InvoiceNumbers::class)->next($s->refresh(), 2027)); expect($across)->toBe('RE-0042'); }); it('keeps every Rechnungskreis on its own counter', function () { // A credit note is not an invoice with a minus sign in front of it, and // sharing a counter would put gaps in both. $invoice = InvoiceSeries::query()->where('kind', 'invoice')->firstOrFail(); $credit = InvoiceSeries::query()->where('kind', 'credit_note')->firstOrFail(); [$a] = DB::transaction(fn () => app(InvoiceNumbers::class)->next($invoice, 2026)); [$b] = DB::transaction(fn () => app(InvoiceNumbers::class)->next($credit, 2026)); [$c] = DB::transaction(fn () => app(InvoiceNumbers::class)->next($invoice->refresh(), 2026)); expect($a)->toBe('RE-2026-0001') ->and($b)->toBe('GS-2026-0001') ->and($c)->toBe('RE-2026-0002'); }); /* * There is deliberately no test for the "must be inside a transaction" guard. * * RefreshDatabase wraps every test in one, so DB::transactionLevel() is already * above zero before the first line of any test here runs — the guard cannot * fire, and a test written to prove it does would be green for a reason that * has nothing to do with the guard. A test that passes for the wrong reason is * worse than no test, so this note stands in its place. */ it('starts every seeded Rechnungskreis at one', function () { // An installation with no series cannot issue an invoice at all, and // finding that out at the moment of the first sale is the worst possible // time. // // toEqual, not toBe: pluck returns them in whatever order the database // hands them back, and the order of four seeded rows is not the assertion. expect(InvoiceSeries::query()->pluck('prefix', 'kind')->all())->toEqual([ 'invoice' => 'RE', 'credit_note' => 'GS', 'cancellation' => 'ST', 'deposit' => 'AZ', ]); });