fix : 부서관리 기능 수정

- Route, Controller, Service, Swagger, DB 수정
- 모델 위치 이동
This commit is contained in:
2025-08-21 18:38:27 +09:00
parent 52352f7896
commit 3ee7719cbc
16 changed files with 727 additions and 285 deletions

View File

@@ -0,0 +1,172 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* 목적
* - 권한 저장을 Spatie 표준(model_has_permissions 등)으로 단일화
* - 개인/부서 단위의 DENY(및 선택적 임시 ALLOW)를 위해 permission_overrides 신설
* - 혼재됐던 매트릭스/전용 테이블 정리(요청에 따라 데이터 무시하고 드롭)
*/
public function up(): void
{
// 1) permission_overrides (DENY 최우선, 선택적 ALLOW 지원)
if (!Schema::hasTable('permission_overrides')) {
Schema::create('permission_overrides', function (Blueprint $table) {
$table->bigIncrements('id')->comment('PK');
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
$table->string('model_type', 255)->comment('App\\Models\\User | App\\Models\\Department 등');
$table->unsignedBigInteger('model_id')->comment('모델 PK');
$table->unsignedBigInteger('permission_id')->comment('permissions.id');
$table->tinyInteger('effect')->comment('1=ALLOW, -1=DENY');
$table->string('reason', 255)->nullable()->comment('사유/메모');
$table->timestamp('effective_from')->nullable()->comment('효력 시작');
$table->timestamp('effective_to')->nullable()->comment('효력 종료');
$table->timestamps();
$table->softDeletes();
$table->unique(['tenant_id', 'model_type', 'model_id', 'permission_id'], 'uq_perm_override');
$table->index(['tenant_id', 'permission_id'], 'idx_po_perm');
$table->foreign('permission_id')
->references('id')->on('permissions')
->onDelete('cascade');
});
}
// 2) 혼재 테이블 드롭(존재하면)
foreach ([
'user_menu_permissions',
'role_menu_permissions',
'department_permissions',
'user_permission_overrides',
] as $legacyTable) {
if (Schema::hasTable($legacyTable)) {
Schema::drop($legacyTable);
}
}
// 3) 보조 인덱스(선택) : model_has_permissions (tenant_id, permission_id)
if (Schema::hasTable('model_has_permissions')) {
if (!$this->mysqlIndexExists('model_has_permissions', 'idx_mhp_tenant_perm')) {
Schema::table('model_has_permissions', function (Blueprint $table) {
$table->index(['tenant_id', 'permission_id'], 'idx_mhp_tenant_perm');
});
}
}
}
public function down(): void
{
// 1) permission_overrides 제거
if (Schema::hasTable('permission_overrides')) {
Schema::drop('permission_overrides');
}
// 2) 드롭했던 테이블 복구(원본 스키마로)
if (!Schema::hasTable('user_menu_permissions')) {
Schema::create('user_menu_permissions', function (Blueprint $table) {
$table->bigIncrements('id')->comment('PK: 사용자-메뉴 권한 ID');
$table->unsignedBigInteger('user_id')->comment('FK: 사용자 ID');
$table->unsignedBigInteger('menu_id')->comment('FK: 메뉴 ID');
$table->tinyInteger('access')->default(0)->comment('메뉴 접근 권한');
$table->tinyInteger('read')->default(0)->comment('조회 권한');
$table->tinyInteger('write')->default(0)->comment('등록/수정/삭제 권한');
$table->tinyInteger('export')->default(0)->comment('다운로드 권한');
$table->tinyInteger('approve')->default(0)->comment('승인/반려 권한');
$table->timestamps();
$table->unique(['user_id', 'menu_id'], 'user_menu_permissions_user_id_menu_id_unique');
$table->foreign('menu_id')->references('id')->on('menus')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
}
if (!Schema::hasTable('role_menu_permissions')) {
Schema::create('role_menu_permissions', function (Blueprint $table) {
$table->bigIncrements('id')->comment('PK: 역할-메뉴 권한 ID');
$table->unsignedBigInteger('role_id')->comment('FK: 역할 ID');
$table->unsignedBigInteger('menu_id')->comment('FK: 메뉴 ID');
$table->tinyInteger('access')->default(0)->comment('메뉴 접근 권한');
$table->tinyInteger('read')->default(0)->comment('조회 권한');
$table->tinyInteger('write')->default(0)->comment('등록/수정/삭제 권한');
$table->tinyInteger('export')->default(0)->comment('다운로드 권한');
$table->tinyInteger('approve')->default(0)->comment('승인/반려 권한');
$table->timestamps();
$table->unique(['role_id', 'menu_id'], 'role_menu_permissions_role_id_menu_id_unique');
$table->foreign('menu_id')->references('id')->on('menus')->onDelete('cascade');
$table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
});
}
if (!Schema::hasTable('department_permissions')) {
Schema::create('department_permissions', function (Blueprint $table) {
$table->bigIncrements('id')->comment('PK');
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
$table->unsignedBigInteger('department_id')->comment('부서 ID');
$table->unsignedBigInteger('permission_id')->comment('Spatie permissions.id');
$table->unsignedBigInteger('menu_id')->nullable()->comment('메뉴 ID (선택), 특정 메뉴 범위 권한');
$table->tinyInteger('is_allowed')->default(1)->comment('허용여부(1=ALLOW,0=DENY)');
$table->timestamps();
$table->softDeletes();
$table->unique(['tenant_id', 'department_id', 'permission_id', 'menu_id'], 'dept_perm_unique');
$table->index(['tenant_id', 'department_id'], 'dept_perm_dept_idx');
$table->index(['tenant_id', 'menu_id'], 'dept_perm_menu_idx');
$table->index(['tenant_id', 'permission_id'], 'dept_perm_perm_idx');
});
}
if (!Schema::hasTable('user_permission_overrides')) {
Schema::create('user_permission_overrides', function (Blueprint $table) {
$table->bigIncrements('id')->comment('PK');
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
$table->unsignedBigInteger('user_id')->comment('사용자 ID');
$table->unsignedBigInteger('permission_id')->comment('Spatie permissions.id');
$table->unsignedBigInteger('menu_id')->nullable()->comment('메뉴 ID (선택)');
$table->tinyInteger('is_allowed')->default(1)->comment('허용여부(1=ALLOW,0=DENY)');
$table->string('reason', 255)->nullable()->comment('사유/메모');
$table->timestamp('effective_from')->nullable()->comment('효력 시작');
$table->timestamp('effective_to')->nullable()->comment('효력 종료');
$table->timestamps();
$table->softDeletes();
$table->unique(['tenant_id', 'user_id', 'permission_id', 'menu_id'], 'user_perm_override_unique');
$table->index(['tenant_id', 'permission_id'], 'user_perm_perm_idx');
$table->index(['tenant_id', 'user_id'], 'user_perm_user_idx');
});
}
// 3) 보조 인덱스 롤백
if (Schema::hasTable('model_has_permissions')) {
if ($this->mysqlIndexExists('model_has_permissions', 'idx_mhp_tenant_perm')) {
Schema::table('model_has_permissions', function (Blueprint $table) {
$table->dropIndex('idx_mhp_tenant_perm');
});
}
}
}
/**
* MySQL: 특정 테이블에 인덱스 존재 여부 확인
*/
private function mysqlIndexExists(string $table, string $indexName): bool
{
// 스키마(데이터베이스)명
$schema = DB::getDatabaseName();
$exists = DB::table('information_schema.STATISTICS')
->where('TABLE_SCHEMA', $schema)
->where('TABLE_NAME', $table)
->where('INDEX_NAME', $indexName)
->exists();
return (bool) $exists;
}
};