fix: phone uniqueness validation and DB constraint for users
- Replaced broken ->rules(fn (\$record)) with proper Filament ->unique() using ignoreRecord:true + modifyRuleUsing scoped to company_id, applied to both phone and email in MasterResource and UserResource - Added migration 2026_09_26_000001 to add unique(company_id, phone) constraint on users table (nulls out duplicate phones before adding, MySQL allows multiple NULLs in unique index so blank phones are fine) - Exception catch in Create/EditMaster and Create/EditUser now also handles UniqueConstraintViolationException on phone Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ use Filament\Tables\Table;
|
|||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Validation\Rules\Unique;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tehnicieni — vedere filtrată peste users (role=mechanic).
|
* Tehnicieni — vedere filtrată peste users (role=mechanic).
|
||||||
@@ -66,18 +67,22 @@ class MasterResource extends Resource
|
|||||||
->label(__('Telefon'))
|
->label(__('Telefon'))
|
||||||
->tel()
|
->tel()
|
||||||
->maxLength(40)
|
->maxLength(40)
|
||||||
->rules(fn ($record) => $record
|
->unique(
|
||||||
? [Rule::unique('users', 'phone')->where('company_id', auth()->user()?->company_id)->ignore($record->id)]
|
table: 'users',
|
||||||
: [Rule::unique('users', 'phone')->where('company_id', auth()->user()?->company_id)]
|
column: 'phone',
|
||||||
|
ignoreRecord: true,
|
||||||
|
modifyRuleUsing: fn (Unique $rule) => $rule->where('company_id', auth()->user()?->company_id),
|
||||||
)
|
)
|
||||||
->validationMessages(['unique' => __('Acest număr de telefon este deja folosit de un alt utilizator.')]),
|
->validationMessages(['unique' => __('Acest număr de telefon este deja folosit de un alt utilizator.')]),
|
||||||
Forms\Components\TextInput::make('email')
|
Forms\Components\TextInput::make('email')
|
||||||
->label(__('Email'))
|
->label(__('Email'))
|
||||||
->email()
|
->email()
|
||||||
->maxLength(120)
|
->maxLength(120)
|
||||||
->rules(fn ($record) => $record
|
->unique(
|
||||||
? [Rule::unique('users', 'email')->where('company_id', auth()->user()?->company_id)->ignore($record->id)]
|
table: 'users',
|
||||||
: [Rule::unique('users', 'email')->where('company_id', auth()->user()?->company_id)]
|
column: 'email',
|
||||||
|
ignoreRecord: true,
|
||||||
|
modifyRuleUsing: fn (Unique $rule) => $rule->where('company_id', auth()->user()?->company_id),
|
||||||
)
|
)
|
||||||
->validationMessages(['unique' => __('Acest email este deja folosit de un alt utilizator.')]),
|
->validationMessages(['unique' => __('Acest email este deja folosit de un alt utilizator.')]),
|
||||||
Forms\Components\Select::make('status')
|
Forms\Components\Select::make('status')
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use Filament\Tables;
|
|||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Validation\Rules\Unique;
|
||||||
|
|
||||||
class UserResource extends Resource
|
class UserResource extends Resource
|
||||||
{
|
{
|
||||||
@@ -73,17 +74,21 @@ class UserResource extends Resource
|
|||||||
->email()
|
->email()
|
||||||
->required()
|
->required()
|
||||||
->maxLength(120)
|
->maxLength(120)
|
||||||
->rules(fn ($record) => $record
|
->unique(
|
||||||
? [Rule::unique('users', 'email')->where('company_id', auth()->user()?->company_id)->ignore($record->id)]
|
table: 'users',
|
||||||
: [Rule::unique('users', 'email')->where('company_id', auth()->user()?->company_id)]
|
column: 'email',
|
||||||
|
ignoreRecord: true,
|
||||||
|
modifyRuleUsing: fn (Unique $rule) => $rule->where('company_id', auth()->user()?->company_id),
|
||||||
)
|
)
|
||||||
->validationMessages(['unique' => __('Acest email este deja folosit de un alt utilizator.')]),
|
->validationMessages(['unique' => __('Acest email este deja folosit de un alt utilizator.')]),
|
||||||
Forms\Components\TextInput::make('phone')
|
Forms\Components\TextInput::make('phone')
|
||||||
->tel()
|
->tel()
|
||||||
->maxLength(40)
|
->maxLength(40)
|
||||||
->rules(fn ($record) => $record
|
->unique(
|
||||||
? [Rule::unique('users', 'phone')->where('company_id', auth()->user()?->company_id)->ignore($record->id)]
|
table: 'users',
|
||||||
: [Rule::unique('users', 'phone')->where('company_id', auth()->user()?->company_id)]
|
column: 'phone',
|
||||||
|
ignoreRecord: true,
|
||||||
|
modifyRuleUsing: fn (Unique $rule) => $rule->where('company_id', auth()->user()?->company_id),
|
||||||
)
|
)
|
||||||
->validationMessages(['unique' => __('Acest număr de telefon este deja folosit de un alt utilizator.')]),
|
->validationMessages(['unique' => __('Acest număr de telefon este deja folosit de un alt utilizator.')]),
|
||||||
Forms\Components\Select::make('locale')
|
Forms\Components\Select::make('locale')
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?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
|
||||||
|
{
|
||||||
|
// Remove duplicate phones within the same company before adding constraint.
|
||||||
|
// Keep the oldest record (smallest id), null out the phone on duplicates.
|
||||||
|
DB::statement('
|
||||||
|
UPDATE users u1
|
||||||
|
JOIN users u2 ON u2.company_id = u1.company_id
|
||||||
|
AND u2.phone = u1.phone
|
||||||
|
AND u2.id < u1.id
|
||||||
|
SET u1.phone = NULL
|
||||||
|
WHERE u1.phone IS NOT NULL
|
||||||
|
');
|
||||||
|
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
// MySQL/MariaDB allows multiple NULLs in a UNIQUE index,
|
||||||
|
// so users without a phone are unaffected.
|
||||||
|
$table->unique(['company_id', 'phone'], 'users_company_id_phone_unique');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropUnique('users_company_id_phone_unique');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user