delete(); } /** * Test invoice number retrieval * * @test */ public function it_gets_current_invoice_number() { Setting::create([ 'slug' => 'invoice-number', 'type' => 'int', 'int' => 12345, ]); $number = Invoice::getInvoiceNumber(); $this->assertEquals(12345, $number); } /** * Test invoice number increment * * @test */ public function it_increments_invoice_number() { Setting::create([ 'slug' => 'invoice-number', 'type' => 'int', 'int' => 100, ]); $newNumber = Invoice::makeNextInvoiceNumber(); $this->assertEquals(101, $newNumber); // Verify it was persisted $storedNumber = Invoice::getInvoiceNumber(); $this->assertEquals(101, $storedNumber); } /** * Test multiple sequential increments * * @test */ public function it_increments_sequentially() { Setting::create([ 'slug' => 'invoice-number', 'type' => 'int', 'int' => 1, ]); $numbers = []; for ($i = 0; $i < 5; $i++) { $numbers[] = Invoice::makeNextInvoiceNumber(); } $this->assertEquals([2, 3, 4, 5, 6], $numbers); } /** * Test invoice number format with year prefix * * @test */ public function it_formats_invoice_number_with_year_prefix() { $formatted = Invoice::createInvoiceNumber(123, '15.06.2024'); $this->assertEquals('202400123', $formatted); $formatted = Invoice::createInvoiceNumber(1, '01.01.2025'); $this->assertEquals('202500001', $formatted); $formatted = Invoice::createInvoiceNumber(99999, '31.12.2026'); $this->assertEquals('202699999', $formatted); } /** * Test invoice storage directory path * * @test */ public function it_generates_correct_storage_paths() { $path = Invoice::getInvoiceStorageDir('15.06.2024'); $this->assertEquals('invoice/2024/06/', $path); $path = Invoice::getDeliveryStorageDir('01.01.2025'); $this->assertEquals('delivery/2025/01/', $path); } /** * Test invoice filename generation * * @test */ public function it_generates_correct_filenames() { $filename = Invoice::makeInvoiceFilename('202400123'); $this->assertEquals('202400123-MIVITA-Rechnung.pdf', $filename); $filename = Invoice::makeDeliveryFilename('202400123'); $this->assertEquals('202400123-MIVITA-Lieferschein.pdf', $filename); } /** * Test invoice number initialization when not exists * * @test */ public function it_initializes_invoice_number_when_not_exists() { // Make sure it doesn't exist Setting::where('slug', 'invoice-number')->delete(); $number = Invoice::makeNextInvoiceNumber(); $this->assertEquals(1, $number); // Verify setting was created $setting = Setting::where('slug', 'invoice-number')->first(); $this->assertNotNull($setting); $this->assertEquals('int', $setting->type); $this->assertEquals(1, $setting->int); } /** * Test invoice number atomicity with explicit transaction * * @test */ public function it_uses_transaction_for_invoice_number_increment() { Setting::create([ 'slug' => 'invoice-number', 'type' => 'int', 'int' => 500, ]); // makeNextInvoiceNumber includes its own transaction $number = Invoice::makeNextInvoiceNumber(); $this->assertEquals(501, $number); // Can be called within another transaction DB::transaction(function () { $number = Invoice::makeNextInvoiceNumber(); $this->assertEquals(502, $number); }); $finalNumber = Invoice::getInvoiceNumber(); $this->assertEquals(502, $finalNumber); } /** * Test invoice number with database lock * * @test */ public function it_locks_setting_during_increment() { Setting::create([ 'slug' => 'invoice-number', 'type' => 'int', 'int' => 1000, ]); // Verify the lock mechanism works DB::transaction(function () { $setting = Setting::where('slug', 'invoice-number') ->lockForUpdate() ->first(); $this->assertNotNull($setting); $this->assertEquals(1000, $setting->int); }); } /** * Test invoice number padding * * @test */ public function it_pads_invoice_numbers_correctly() { $tests = [ [1, '202400001'], [12, '202400012'], [123, '202400123'], [1234, '202401234'], [12345, '202412345'], ]; foreach ($tests as [$number, $expected]) { $formatted = Invoice::createInvoiceNumber($number, '01.01.2024'); $this->assertEquals($expected, $formatted); } } /** * Test getInvoiceNumber returns 0 when setting doesn't exist * * @test */ public function it_returns_zero_when_invoice_number_not_set() { Setting::where('slug', 'invoice-number')->delete(); $number = Invoice::getInvoiceNumber(); $this->assertEquals(0, $number); } /** * Test concurrent increment simulation * * @test */ public function it_handles_rapid_increments_without_gaps() { Setting::create([ 'slug' => 'invoice-number', 'type' => 'int', 'int' => 7000, ]); $numbers = []; $iterations = 15; for ($i = 0; $i < $iterations; $i++) { $numbers[] = Invoice::makeNextInvoiceNumber(); } // Check for uniqueness $unique = array_unique($numbers); $this->assertCount($iterations, $unique, 'All numbers must be unique'); // Check for no gaps sort($numbers); for ($i = 0; $i < $iterations; $i++) { $expected = 7001 + $i; $this->assertEquals($expected, $numbers[$i], 'No gaps allowed in sequence'); } } /** * Test invoice number type casting * * @test */ public function it_returns_integer_invoice_number() { Setting::create([ 'slug' => 'invoice-number', 'type' => 'int', 'int' => 999, ]); $number = Invoice::getInvoiceNumber(); $this->assertIsInt($number); $this->assertEquals(999, $number); } }