Commit 2ee9537f authored by Mahmoud Aglan's avatar Mahmoud Aglan

Fix unique constraint violations on re-creation after delete

Add ManglesUniqueOnDelete trait that appends a timestamp suffix to unique
fields (code, slug, sku, employee_number) when a record is soft-deleted,
freeing the value for new records. Also make group code generation
collision-resistant by checking withTrashed().
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 497401cb
......@@ -8,6 +8,7 @@
use App\Domain\Shared\Traits\Auditable;
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use App\Domain\Shared\Traits\ManglesUniqueOnDelete;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
......@@ -16,7 +17,9 @@
class Facility extends Model
{
use BelongsToAcademy, HasUuid, SoftDeletes, Auditable;
use BelongsToAcademy, HasUuid, SoftDeletes, Auditable, ManglesUniqueOnDelete;
protected array $uniqueFieldsToMangle = ['code'];
protected $fillable = [
'academy_id',
......
......@@ -7,6 +7,7 @@
use App\Domain\HR\Enums\SalaryFrequency;
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use App\Domain\Shared\Traits\ManglesUniqueOnDelete;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
......@@ -17,7 +18,9 @@
class Employee extends Model
{
use BelongsToAcademy, HasUuid, SoftDeletes;
use BelongsToAcademy, HasUuid, SoftDeletes, ManglesUniqueOnDelete;
protected array $uniqueFieldsToMangle = ['employee_number'];
protected $fillable = [
'academy_id',
......
......@@ -4,6 +4,7 @@
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use App\Domain\Shared\Traits\ManglesUniqueOnDelete;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
......@@ -12,7 +13,9 @@
class Branch extends Model
{
use BelongsToAcademy, HasUuid, SoftDeletes;
use BelongsToAcademy, HasUuid, SoftDeletes, ManglesUniqueOnDelete;
protected array $uniqueFieldsToMangle = ['code'];
protected $fillable = [
'academy_id', 'name', 'name_ar', 'code',
......
......@@ -5,6 +5,7 @@
use App\Domain\Shared\Traits\Auditable;
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use App\Domain\Shared\Traits\ManglesUniqueOnDelete;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
......@@ -12,7 +13,9 @@
class Kit extends Model
{
use BelongsToAcademy, HasUuid, SoftDeletes, Auditable;
use BelongsToAcademy, HasUuid, SoftDeletes, Auditable, ManglesUniqueOnDelete;
protected array $uniqueFieldsToMangle = ['sku'];
protected $fillable = [
'academy_id',
......
......@@ -5,6 +5,7 @@
use App\Domain\Inventory\Enums\WarehouseType;
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use App\Domain\Shared\Traits\ManglesUniqueOnDelete;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
......@@ -14,7 +15,9 @@
class Warehouse extends Model
{
use BelongsToAcademy, HasUuid, SoftDeletes;
use BelongsToAcademy, HasUuid, SoftDeletes, ManglesUniqueOnDelete;
protected array $uniqueFieldsToMangle = ['code'];
protected $fillable = [
'academy_id',
......
<?php
namespace App\Domain\Shared\Traits;
use Illuminate\Support\Str;
trait ManglesUniqueOnDelete
{
public static function bootManglesUniqueOnDelete(): void
{
static::deleting(function ($model) {
$suffix = '_del_' . now()->format('ymdHis');
foreach ($model->getUniqueFieldsToMangle() as $field) {
if ($model->{$field} !== null) {
$model->{$field} = Str::limit($model->{$field}, 200, '') . $suffix;
}
}
$model->saveQuietly();
});
}
public function getUniqueFieldsToMangle(): array
{
return $this->uniqueFieldsToMangle ?? [];
}
}
......@@ -6,6 +6,7 @@
use App\Domain\Shared\Traits\Auditable;
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use App\Domain\Shared\Traits\ManglesUniqueOnDelete;
use App\Domain\Training\Enums\GroupStatus;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
......@@ -15,7 +16,9 @@
class TrainingGroup extends Model
{
use BelongsToAcademy, HasUuid, SoftDeletes, Auditable;
use BelongsToAcademy, HasUuid, SoftDeletes, Auditable, ManglesUniqueOnDelete;
protected array $uniqueFieldsToMangle = ['code'];
protected $fillable = [
'academy_id', 'training_program_id', 'branch_id',
......
......@@ -6,6 +6,7 @@
use App\Domain\Shared\Traits\Auditable;
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use App\Domain\Shared\Traits\ManglesUniqueOnDelete;
use App\Domain\Training\Enums\ProgramStatus;
use App\Domain\Training\Enums\RenewalPolicy;
use App\Models\User;
......@@ -16,7 +17,9 @@
class TrainingProgram extends Model
{
use BelongsToAcademy, HasUuid, SoftDeletes, Auditable;
use BelongsToAcademy, HasUuid, SoftDeletes, Auditable, ManglesUniqueOnDelete;
protected array $uniqueFieldsToMangle = ['slug'];
protected $fillable = [
'academy_id', 'activity_id', 'branch_id',
......
......@@ -15,7 +15,7 @@ public function create(array $data, User $actor): TrainingProgram
{
return DB::transaction(function () use ($data, $actor) {
$slug = Str::limit($data['slug'] ?? Str::slug($data['name']), 90, '');
$exists = TrainingProgram::where('slug', $slug)->exists();
$exists = TrainingProgram::withTrashed()->where('slug', $slug)->exists();
if ($exists) {
$slug = Str::limit($slug, 85, '') . '-' . Str::random(4);
}
......@@ -34,7 +34,14 @@ public function create(array $data, User $actor): TrainingProgram
private function createDefaultGroup(TrainingProgram $program, User $actor): TrainingGroup
{
$programName = $program->name_ar ?: $program->name;
$code = strtoupper(substr(preg_replace('/[^a-zA-Z0-9]/', '', $program->name ?: $program->name_ar), 0, 6)) . '01';
$baseCode = strtoupper(substr(preg_replace('/[^a-zA-Z0-9]/', '', $program->name ?: $program->name_ar), 0, 6)) . '01';
$code = $baseCode;
$attempt = 0;
while (TrainingGroup::withTrashed()->where('academy_id', $program->academy_id)->where('code', $code)->exists()) {
$attempt++;
$code = $baseCode . '-' . $attempt;
}
return TrainingGroup::create([
'academy_id' => $program->academy_id,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment