id(); $table->string('codigo')->unique(); $table->string('phone')->nullable(); $table->timestamps(); }); Schema::create('tenants_menues', function (Blueprint $table): void { $table->id(); $table->string('tenant_code'); $table->string('menu_code'); $table->json('static_content')->nullable(); }); (require database_path('migrations/2026_09_30_000400_create_phone_numbers_and_tenant_phone_numbers_tables.php'))->up(); } public function test_tenant_phone_wins_and_copy_is_idempotent_and_scoped(): void { DB::table('tenants')->insert([ ['codigo' => 'a', 'phone' => '+54 341 111-1111'], ['codigo' => 'b', 'phone' => '+54 341 111-1111'], ['codigo' => 'c', 'phone' => null], ['codigo' => 'empty', 'phone' => ''], ]); $this->contact('a', '+54 (341) 222-2222'); $this->contact('b', '54 (341) 1111111'); $this->contact('c', '+54 341 3333333'); $copy = require database_path('migrations/2026_09_30_000500_copy_tenant_and_contact_phone_numbers.php'); $copy->up(); $copy->up(); $this->assertDatabaseCount('phone_numbers', 4); $a = Tenant::where('codigo', 'a')->firstOrFail(); $b = Tenant::where('codigo', 'b')->firstOrFail(); $this->assertSame('+54 341 111-1111', $a->mainPhoneNumber->number); $this->assertCount(2, $a->phoneNumbers); $this->assertCount(1, $b->phoneNumbers); $this->assertNotSame($a->mainPhoneNumber->id, $b->mainPhoneNumber->id); $this->assertSame('+54 341 3333333', Tenant::where('codigo', 'c')->firstOrFail()->mainPhoneNumber->number); $this->assertNull(Tenant::where('codigo', 'empty')->firstOrFail()->mainPhoneNumber); $this->assertSame('tel:+543411111111', $a->mainPhoneNumber->telUrl()); $mainResource = PhoneNumberResource::make($a->mainPhoneNumber)->resolve(); $this->assertSame('+54 341 111-1111', $mainResource['number']); $this->assertSame('tel:+543411111111', $mainResource['tel_url']); $resources = PhoneNumberResource::collection($a->phoneNumbers)->resolve(); $this->assertTrue($resources[0]['is_main']); $this->assertFalse($resources[1]['is_main']); $copy->down(); $this->assertDatabaseCount('phone_numbers', 4); $this->assertDatabaseHas('tenants', ['codigo' => 'a', 'phone' => '+54 341 111-1111']); } public function test_database_rejects_two_main_numbers(): void { DB::table('tenants')->insert(['codigo' => 'a', 'phone' => '+54 341 1111111']); $this->contact('a', '+54 341 2222222'); (require database_path('migrations/2026_09_30_000500_copy_tenant_and_contact_phone_numbers.php'))->up(); $this->expectException(QueryException::class); DB::table('tenant_phone_numbers')->where('tenant_codigo', 'a')->update(['is_main' => true]); } public function test_invalid_contact_rolls_back_the_whole_copy(): void { DB::table('tenants')->insert(['codigo' => 'a', 'phone' => '+54 341 1111111']); $this->contact('a', ['invalid']); try { (require database_path('migrations/2026_09_30_000500_copy_tenant_and_contact_phone_numbers.php'))->up(); $this->fail('Invalid legacy numbers must not be silently discarded.'); } catch (\RuntimeException $exception) { $this->assertStringContainsString('Invalid phone number', $exception->getMessage()); $this->assertDatabaseCount('phone_numbers', 0); } } public function test_a_number_cannot_be_linked_to_a_missing_tenant(): void { $id = DB::table('phone_numbers')->insertGetId(['label' => 'Contacto', 'number' => '123456']); $this->expectException(QueryException::class); DB::table('tenant_phone_numbers')->insert([ 'tenant_codigo' => 'missing', 'phone_number_id' => $id, 'is_main' => true, ]); } public function test_it_splits_mobile_and_landline_numbers_and_builds_dialable_urls(): void { DB::table('phone_numbers')->insert([ ['label' => 'Móvil', 'number' => '+54 9 (0341) 6658247'], ['label' => 'Fijo', 'number' => '+54 341 111-1111'], ['label' => 'Local', 'number' => '0341 1234567'], ['label' => 'Móvil sin signo', 'number' => '54 9 (0341) 6658247'], ]); $migration = require database_path('migrations/2026_09_30_000700_split_phone_number_components.php'); $migration->up(); [$mobile, $landline, $local, $mobileWithoutPlus] = PhoneNumber::query()->orderBy('id')->get()->all(); $this->assertSame('+54', $mobile->country_code); $this->assertSame('9', $mobile->mobile_prefix); $this->assertSame('0341', $mobile->area_code); $this->assertSame('6658247', $mobile->local_number); $this->assertSame('+54 9 (0341) 6658247', $mobile->number); $this->assertSame('tel:+5493416658247', $mobile->telUrl()); $this->assertSame('+54 341 1111111', $landline->number); $this->assertSame('tel:+543411111111', $landline->telUrl()); $this->assertSame('0341 1234567', $local->number); $this->assertSame('tel:03411234567', $local->telUrl()); $this->assertSame('+54 9 (0341) 6658247', $mobileWithoutPlus->number); $resource = PhoneNumberResource::make($mobile)->resolve(); $this->assertSame('9', $resource['mobile_prefix']); $this->assertSame('0341', $resource['area_code']); $this->assertSame('tel:+5493416658247', $resource['tel_url']); $created = PhoneNumber::query()->create([ 'label' => 'Nuevo', 'area_code' => '0341', 'local_number' => '7777777', ]); $this->assertSame('+54', $created->fresh()->country_code); $this->assertSame('9', $created->fresh()->mobile_prefix); $this->assertSame('+54 9 (0341) 7777777', $created->number); $this->assertSame('tel:+5493417777777', $created->telUrl()); $migration->down(); $this->assertDatabaseHas('phone_numbers', ['id' => $mobile->id, 'number' => '+54 9 0341 6658247']); } private function contact(string $tenant, mixed $phone): void { DB::table('tenants_menues')->insert([ 'tenant_code' => $tenant, 'menu_code' => 'help.contact', 'static_content' => json_encode(['phone' => $phone], JSON_THROW_ON_ERROR), ]); } }