fix(categories): allow recreate after delete (#444)
## Summary - allow users to recreate category names after soft delete - update category unique validation to ignore trashed rows - add active-row unique index for category names ## Tests - php artisan test --compact tests/Feature/Settings/CategoryTest.php
This commit is contained in:
parent
4f46ae3e2d
commit
2fa822e6d9
|
|
@ -76,7 +76,8 @@ class CategoryController extends Controller
|
|||
|
||||
private function throwDuplicateCategoryNameValidationException(UniqueConstraintViolationException $exception): never
|
||||
{
|
||||
if (! str_contains($exception->getMessage(), 'categories_user_id_name_unique')) {
|
||||
if (! str_contains($exception->getMessage(), 'categories_user_id_name_unique')
|
||||
&& ! str_contains($exception->getMessage(), 'categories_user_id_name_active_unique')) {
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@ class StoreCategoryRequest extends FormRequest
|
|||
'string',
|
||||
'max:255',
|
||||
Rule::unique('categories', 'name')
|
||||
->where('user_id', auth()->id()),
|
||||
->where('user_id', auth()->id())
|
||||
->withoutTrashed(),
|
||||
],
|
||||
'icon' => ['required', 'string'],
|
||||
'color' => [
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ class UpdateCategoryRequest extends FormRequest
|
|||
'max:255',
|
||||
Rule::unique('categories', 'name')
|
||||
->where('user_id', auth()->id())
|
||||
->withoutTrashed()
|
||||
->ignore($this->route('category')),
|
||||
],
|
||||
'icon' => ['required', 'string'],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('categories', function (Blueprint $table) {
|
||||
$table->dropForeign(['user_id']);
|
||||
$table->dropUnique(['user_id', 'name']);
|
||||
$table->boolean('active_unique_marker')
|
||||
->nullable()
|
||||
->virtualAs('if(`deleted_at` is null, 1, null)');
|
||||
$table->unique(['user_id', 'name', 'active_unique_marker'], 'categories_user_id_name_active_unique');
|
||||
$table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('categories', function (Blueprint $table) {
|
||||
$table->dropForeign(['user_id']);
|
||||
$table->dropUnique('categories_user_id_name_active_unique');
|
||||
$table->dropColumn('active_unique_marker');
|
||||
$table->unique(['user_id', 'name']);
|
||||
$table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -129,24 +129,27 @@ test('different users can create categories with the same name', function () {
|
|||
]);
|
||||
});
|
||||
|
||||
test('category names from deleted categories remain reserved', function () {
|
||||
test('users can recreate a category with the same name after deleting it', function () {
|
||||
$user = User::factory()->create();
|
||||
$category = Category::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'name' => 'Healthcare',
|
||||
'name' => 'Supermercados',
|
||||
]);
|
||||
|
||||
$category->delete();
|
||||
|
||||
$response = $this->actingAs($user)->post(route('categories.store'), [
|
||||
'name' => 'Healthcare',
|
||||
'icon' => 'Heart',
|
||||
'color' => 'pink',
|
||||
'name' => 'Supermercados',
|
||||
'icon' => 'ShoppingCart',
|
||||
'color' => 'green',
|
||||
'type' => 'expense',
|
||||
'cashflow_direction' => 'hidden',
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrors(['name']);
|
||||
$response->assertRedirect(route('categories.index'));
|
||||
|
||||
expect($user->categories()->where('name', 'Supermercados')->count())->toBe(1)
|
||||
->and($user->categories()->withTrashed()->where('name', 'Supermercados')->count())->toBe(2);
|
||||
});
|
||||
|
||||
test('category icon is required', function () {
|
||||
|
|
|
|||
Loading…
Reference in New Issue