feat(phone): implement PhoneNumber and TenantPhoneNumber models with relationships and migration

This commit is contained in:
2026-09-30 11:53:21 -03:00
parent 6588d9f1a8
commit 917f2dff09
10 changed files with 326 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
$driver = DB::connection()->getDriverName();
if (! in_array($driver, ['mysql', 'mariadb', 'sqlite', 'pgsql', 'sqlsrv'], true)) {
throw new RuntimeException("Unsupported phone number database driver: {$driver}");
}
Schema::create('phone_numbers', function (Blueprint $table): void {
$table->id();
$table->string('label');
$table->string('number');
$table->timestamps();
});
Schema::create('tenant_phone_numbers', function (Blueprint $table) use ($driver): void {
$table->id();
$table->string('tenant_codigo');
$table->foreignId('phone_number_id')->constrained('phone_numbers')->cascadeOnDelete();
$table->boolean('is_main')->default(false);
$table->timestamps();
$table->foreign('tenant_codigo')->references('codigo')->on('tenants')->cascadeOnDelete();
$table->unique(['tenant_codigo', 'phone_number_id']);
if (in_array($driver, ['mysql', 'mariadb'], true)) {
$table->string('main_tenant_codigo')->nullable()
->virtualAs('CASE WHEN is_main = 1 THEN tenant_codigo ELSE NULL END');
$table->unique('main_tenant_codigo', 'tenant_phones_one_main');
}
});
if (! in_array($driver, ['mysql', 'mariadb'], true)) {
$condition = $driver === 'pgsql' ? 'is_main = true' : 'is_main = 1';
DB::statement("CREATE UNIQUE INDEX tenant_phones_one_main ON tenant_phone_numbers (tenant_codigo) WHERE {$condition}");
}
}
public function down(): void
{
Schema::dropIfExists('tenant_phone_numbers');
Schema::dropIfExists('phone_numbers');
}
};

View File

@@ -0,0 +1,65 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
DB::transaction(function (): void {
DB::table('tenants')->orderBy('id')->chunkById(100, function ($tenants): void {
foreach ($tenants as $tenant) {
$this->copyPhone($tenant->codigo, $tenant->phone, true);
}
});
DB::table('tenants_menues')->where('menu_code', 'help.contact')
->whereNotNull('static_content')->orderBy('id')->chunkById(100, function ($menus): void {
foreach ($menus as $menu) {
$content = json_decode($menu->static_content, true, flags: JSON_THROW_ON_ERROR);
if ($content === null) {
continue;
}
if (! is_array($content)) {
throw new RuntimeException("Invalid contact content for tenant {$menu->tenant_code}");
}
$this->copyPhone($menu->tenant_code, $content['phone'] ?? null, false);
}
});
});
}
private function copyPhone(string $tenantCode, mixed $number, bool $fromTenant): void
{
if ($number === null || (is_string($number) && trim($number) === '')) {
return;
}
if (! is_string($number) || mb_strlen($number) > 255 || ! preg_match('/[0-9]/', $number)) {
throw new RuntimeException("Invalid phone number for tenant {$tenantCode}");
}
$numbers = DB::table('phone_numbers')
->join('tenant_phone_numbers', 'phone_numbers.id', '=', 'tenant_phone_numbers.phone_number_id')
->where('tenant_codigo', $tenantCode)->get(['phone_numbers.number']);
$dialable = preg_replace('/[^0-9]/', '', $number);
foreach ($numbers as $existing) {
if (preg_replace('/[^0-9]/', '', $existing->number) === $dialable) {
return;
}
}
$isMain = ! DB::table('tenant_phone_numbers')->where('tenant_codigo', $tenantCode)->where('is_main', true)->exists();
$now = now();
$id = DB::table('phone_numbers')->insertGetId([
'label' => $fromTenant ? 'Principal' : 'Contacto',
'number' => trim($number), 'created_at' => $now, 'updated_at' => $now,
]);
DB::table('tenant_phone_numbers')->insert([
'tenant_codigo' => $tenantCode, 'phone_number_id' => $id, 'is_main' => $isMain,
'created_at' => $now, 'updated_at' => $now,
]);
}
public function down(): void
{
// Preserve copies that may have been edited. The legacy sources were not changed.
}
};