fix : 부서관리 기능 수정
- Route, Controller, Service, Swagger, DB 수정 - 모델 위치 이동
This commit is contained in:
@@ -92,18 +92,18 @@ public function listPermissions($id, Request $request)
|
||||
}
|
||||
|
||||
// POST /v1/departments/{id}/permissions
|
||||
public function upsertPermission($id, Request $request)
|
||||
public function upsertPermissions($id, Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id, $request) {
|
||||
return $this->service->upsertPermission((int)$id, $request->all());
|
||||
return $this->service->upsertPermissions((int)$id, $request->all());
|
||||
}, '부서 권한 부여/차단');
|
||||
}
|
||||
|
||||
// DELETE /v1/departments/{id}/permissions/{permission}
|
||||
public function revokePermission($id, $permission, Request $request)
|
||||
public function revokePermissions($id, $permission, Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id, $permission, $request) {
|
||||
return $this->service->revokePermission((int)$id, (int)$permission, $request->all());
|
||||
return $this->service->revokePermissions((int)$id, (int)$permission, $request->all());
|
||||
}, '부서 권한 제거');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Commons;
|
||||
|
||||
use App\Models\Members\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Traits\BelongsToTenant;
|
||||
use App\Traits\ModelTrait;
|
||||
|
||||
/**
|
||||
* @mixin IdeHelperDepartment
|
||||
*/
|
||||
class Department extends Model
|
||||
{
|
||||
use SoftDeletes, BelongsToTenant, ModelTrait;
|
||||
|
||||
protected $table = 'departments';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id','code','name','description','is_active','sort_order',
|
||||
'created_by','updated_by','deleted_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'tenant_id' => 'integer',
|
||||
'is_active' => 'integer',
|
||||
'sort_order'=> 'integer',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'deleted_by','deleted_at'
|
||||
];
|
||||
|
||||
/** Relations */
|
||||
public function departmentUsers()
|
||||
{
|
||||
return $this->hasMany(DepartmentUser::class, 'department_id');
|
||||
}
|
||||
|
||||
public function users()
|
||||
{
|
||||
// User 네임스페이스가 다르면 여기만 맞춰줘.
|
||||
return $this->belongsToMany(User::class, 'department_user', 'department_id', 'user_id')
|
||||
->withPivot(['tenant_id','is_primary','joined_at','left_at','created_at','updated_at','deleted_at'])
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
public function departmentPermissions()
|
||||
{
|
||||
return $this->hasMany(DepartmentPermission::class, 'department_id');
|
||||
}
|
||||
|
||||
public function permissions()
|
||||
{
|
||||
return $this->belongsToMany(\Spatie\Permission\Models\Permission::class, 'department_permissions', 'department_id', 'permission_id')
|
||||
->withPivot(['tenant_id','menu_id','is_allowed','created_at','updated_at','deleted_at'])
|
||||
->withTimestamps();
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Commons;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Traits\BelongsToTenant;
|
||||
use App\Traits\ModelTrait;
|
||||
|
||||
/**
|
||||
* @mixin IdeHelperDepartmentPermission
|
||||
*/
|
||||
class DepartmentPermission extends Model
|
||||
{
|
||||
use SoftDeletes, BelongsToTenant, ModelTrait;
|
||||
|
||||
protected $table = 'department_permissions';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id','department_id','permission_id','menu_id','is_allowed',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'tenant_id' => 'integer',
|
||||
'department_id' => 'integer',
|
||||
'permission_id' => 'integer',
|
||||
'menu_id' => 'integer',
|
||||
'is_allowed' => 'integer',
|
||||
];
|
||||
|
||||
public function department()
|
||||
{
|
||||
return $this->belongsTo(Department::class, 'department_id');
|
||||
}
|
||||
|
||||
public function permission()
|
||||
{
|
||||
return $this->belongsTo(\Spatie\Permission\Models\Permission::class, 'permission_id');
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,8 @@
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
use Spatie\Permission\Models\Role as SpatieRole;
|
||||
use App\Models\Commons\Role as CommonRole;
|
||||
|
||||
/**
|
||||
* @mixin IdeHelperUser
|
||||
*/
|
||||
@@ -37,7 +33,7 @@ class User extends Authenticatable
|
||||
protected $casts = [
|
||||
'email_verified_at' => 'datetime',
|
||||
'last_login_at' => 'datetime',
|
||||
'options' => 'array',
|
||||
'options' => 'array',
|
||||
'deleted_at' => 'datetime',
|
||||
'password' => 'hashed', // ← 이걸 쓰면 자동 해싱
|
||||
];
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Models\Members;
|
||||
|
||||
use App\Models\Commons\Role;
|
||||
use App\Models\Permissions\Role;
|
||||
use App\Models\Tenants\Tenant;
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
40
app/Models/Permissions/PermissionOverride.php
Normal file
40
app/Models/Permissions/PermissionOverride.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Permissions;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Spatie\Permission\Models\Permission as SpatiePermission;
|
||||
|
||||
class PermissionOverride extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'permission_overrides';
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected $casts = [
|
||||
'tenant_id' => 'int',
|
||||
'model_id' => 'int',
|
||||
'permission_id' => 'int',
|
||||
'effect' => 'int', // 1=ALLOW, -1=DENY
|
||||
'effective_from' => 'datetime',
|
||||
'effective_to' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* 예외가 걸린 주체(User, Department 등)
|
||||
* model_type, model_id를 사용하는 morphTo
|
||||
*/
|
||||
public function model()
|
||||
{
|
||||
// morphTo(relationshipName, type, id)
|
||||
return $this->morphTo('model', 'model_type', 'model_id');
|
||||
}
|
||||
|
||||
/** 연결된 Spatie Permission */
|
||||
public function permission()
|
||||
{
|
||||
return $this->belongsTo(SpatiePermission::class, 'permission_id');
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Commons;
|
||||
namespace App\Models\Permissions;
|
||||
|
||||
use App\Models\Commons\IdeHelperRole;
|
||||
use App\Models\Members\UserRole;
|
||||
use App\Models\Tenants\Tenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -1,7 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Commons;
|
||||
namespace App\Models\Permissions;
|
||||
|
||||
use App\Models\Commons\IdeHelperRoleMenuPermission;
|
||||
use App\Models\Commons\Menu;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
65
app/Models/Tenants/Department.php
Normal file
65
app/Models/Tenants/Department.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Tenants;
|
||||
|
||||
use App\Models\Members\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
use App\Traits\ModelTrait;
|
||||
use App\Models\Tenants\Pivots\DepartmentUser;
|
||||
use App\Models\Permissions\PermissionOverride;
|
||||
|
||||
class Department extends Model
|
||||
{
|
||||
use HasRoles, ModelTrait; // 부서도 권한/역할을 가짐
|
||||
|
||||
protected $table = 'departments';
|
||||
protected $guarded = ['id'];
|
||||
protected $casts = [
|
||||
'tenant_id' => 'int',
|
||||
'parent_id' => 'int',
|
||||
'is_active' => 'bool',
|
||||
'sort_order'=> 'int',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'deleted_by','deleted_at'
|
||||
];
|
||||
|
||||
// 스파티 가드명(프로젝트 설정에 맞게 조정)
|
||||
protected string $guard_name = 'web';
|
||||
|
||||
/** 상위/하위 부서 */
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id');
|
||||
}
|
||||
|
||||
public function children()
|
||||
{
|
||||
return $this->hasMany(self::class, 'parent_id');
|
||||
}
|
||||
|
||||
/** 부서-사용자 N:N (추가 컬럼 포함 Pivot) */
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'department_user')
|
||||
->using(DepartmentUser::class)
|
||||
->withTimestamps()
|
||||
->withPivot(['tenant_id','is_primary','joined_at','left_at']);
|
||||
}
|
||||
|
||||
/** 부서의 권한 오버라이드(DENY/임시허용) */
|
||||
public function permissionOverrides(): MorphMany
|
||||
{
|
||||
return $this->morphMany(PermissionOverride::class, 'model');
|
||||
}
|
||||
|
||||
/** 부서-사용자 매핑 로우들(피벗 테이블의 레코드들) */
|
||||
public function departmentUsers()
|
||||
{
|
||||
return $this->hasMany(DepartmentUser::class, 'department_id');
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Commons;
|
||||
namespace App\Models\Tenants\Pivots;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Models\Commons\IdeHelperDepartmentUser;
|
||||
use App\Models\Members\User;
|
||||
use App\Models\Tenants\Department;
|
||||
use App\Traits\BelongsToTenant;
|
||||
use App\Traits\ModelTrait;
|
||||
use App\Models\Members\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* @mixin IdeHelperDepartmentUser
|
||||
@@ -36,7 +38,6 @@ public function department()
|
||||
|
||||
public function user()
|
||||
{
|
||||
// User 네임스페이스가 다르면 여기만 맞춰줘.
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace App\Models\Tenants;
|
||||
|
||||
use App\Models\Commons\Role;
|
||||
use App\Models\Commons\File;
|
||||
use App\Models\Members\User;
|
||||
use App\Models\Members\UserRole;
|
||||
use App\Models\Members\UserTenant;
|
||||
use App\Models\Permissions\Role;
|
||||
use App\Traits\ModelTrait;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Models\Tenants;
|
||||
|
||||
use App\Models\Members\User;
|
||||
use App\Models\Commons\Department;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Commons\Department;
|
||||
use App\Models\Commons\DepartmentPermission;
|
||||
use App\Models\Commons\DepartmentUser;
|
||||
use App\Models\Tenants\Department;
|
||||
use App\Models\Tenants\Pivots\DepartmentUser;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -299,6 +298,7 @@ public function setPrimary(int $deptId, int $userId, array $params)
|
||||
/** 부서 권한 목록 */
|
||||
public function listPermissions(int $deptId, array $params)
|
||||
{
|
||||
// 1) 파라미터 검증
|
||||
$p = $this->v($params, [
|
||||
'menu_id' => 'nullable|integer|min:1',
|
||||
'is_allowed' => 'nullable|in:0,1',
|
||||
@@ -308,18 +308,78 @@ public function listPermissions(int $deptId, array $params)
|
||||
if ($p instanceof JsonResponse) return $p;
|
||||
if (isset($p['error'])) return $p;
|
||||
|
||||
// 2) 부서 확인
|
||||
$dept = Department::query()->find($deptId);
|
||||
if (!$dept) return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404];
|
||||
|
||||
$q = DepartmentPermission::query()
|
||||
->whereNull('deleted_at')
|
||||
->where('department_id', $deptId);
|
||||
$tenantId = (int)$dept->tenant_id;
|
||||
$modelType = Department::class;
|
||||
|
||||
if (isset($p['menu_id'])) $q->where('menu_id', $p['menu_id']);
|
||||
if (isset($p['is_allowed'])) $q->where('is_allowed', (int)$p['is_allowed']);
|
||||
// 3) ALLOW/DENY 서브쿼리 준비 (컬럼 형태 통일)
|
||||
$allowSub = DB::table('model_has_permissions')
|
||||
->select([
|
||||
'permission_id',
|
||||
DB::raw('1 as is_allowed'),
|
||||
DB::raw('NULL as reason'),
|
||||
DB::raw('NULL as effective_from'),
|
||||
DB::raw('NULL as effective_to'),
|
||||
DB::raw('NULL as override_id'),
|
||||
DB::raw('NULL as override_updated_at'),
|
||||
])
|
||||
->where([
|
||||
'model_type' => $modelType,
|
||||
'model_id' => $deptId,
|
||||
'tenant_id' => $tenantId,
|
||||
]);
|
||||
|
||||
$q->orderByDesc('is_allowed')->orderBy('permission_id');
|
||||
$denySub = DB::table('permission_overrides')
|
||||
->select([
|
||||
'permission_id',
|
||||
DB::raw('0 as is_allowed'),
|
||||
'reason',
|
||||
'effective_from',
|
||||
'effective_to',
|
||||
'id as override_id',
|
||||
'updated_at as override_updated_at',
|
||||
])
|
||||
->where([
|
||||
'tenant_id' => $tenantId,
|
||||
'model_type' => $modelType,
|
||||
'model_id' => $deptId,
|
||||
])
|
||||
->where('effect', -1);
|
||||
|
||||
// 4) 합치고 permissions 조인
|
||||
$q = DB::query()
|
||||
->fromSub(function ($sub) use ($allowSub, $denySub) {
|
||||
$sub->from($allowSub->unionAll($denySub), 'u');
|
||||
}, 'u')
|
||||
->join('permissions', 'permissions.id', '=', 'u.permission_id')
|
||||
->select([
|
||||
'u.permission_id',
|
||||
'u.is_allowed',
|
||||
'permissions.name as permission_code',
|
||||
'permissions.guard_name',
|
||||
'u.reason',
|
||||
'u.effective_from',
|
||||
'u.effective_to',
|
||||
'u.override_id',
|
||||
'u.override_updated_at',
|
||||
]);
|
||||
|
||||
// 5) 필터
|
||||
if (isset($p['is_allowed'])) {
|
||||
$q->where('u.is_allowed', (int)$p['is_allowed']);
|
||||
}
|
||||
if (isset($p['menu_id'])) {
|
||||
// 권한코드가 menu.{id}.xxx 형태라는 전제
|
||||
$q->where('permissions.name', 'like', 'menu.' . (int)$p['menu_id'] . '.%');
|
||||
}
|
||||
|
||||
// 6) 정렬(ALLOW 우선, 그 다음 permission_id 오름차순)
|
||||
$q->orderByDesc('u.is_allowed')->orderBy('u.permission_id');
|
||||
|
||||
// 7) 페이지네이션
|
||||
$perPage = $p['per_page'] ?? 20;
|
||||
$page = $p['page'] ?? null;
|
||||
|
||||
@@ -327,58 +387,170 @@ public function listPermissions(int $deptId, array $params)
|
||||
}
|
||||
|
||||
/** 권한 부여/차단 upsert */
|
||||
public function upsertPermission(int $deptId, array $params)
|
||||
public function upsertPermissions(int $deptId, array $payload): array
|
||||
{
|
||||
$p = $this->v($params, [
|
||||
'permission_id' => 'required|integer|min:1',
|
||||
'menu_id' => 'nullable|integer|min:1',
|
||||
'is_allowed' => 'nullable|in:0,1',
|
||||
// 단일이면 items로 감싸기
|
||||
$items = isset($payload['permission_id'])
|
||||
? [ $payload ]
|
||||
: ($payload['items'] ?? $payload);
|
||||
|
||||
$v = Validator::make(['items'=>$items], [
|
||||
'items' => 'required|array|max:1000',
|
||||
'items.*.permission_id' => 'required|integer|min:1',
|
||||
'items.*.is_allowed' => 'nullable|in:0,1', // 생략 시 1(허용)
|
||||
]);
|
||||
if ($p instanceof JsonResponse) return $p;
|
||||
if (isset($p['error'])) return $p;
|
||||
if ($v->fails()) return ['error'=>$v->errors()->first(),'code'=>422];
|
||||
|
||||
$dept = Department::query()->find($deptId);
|
||||
if (!$dept) return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404];
|
||||
|
||||
$payload = [
|
||||
'department_id' => $deptId,
|
||||
'permission_id' => $p['permission_id'],
|
||||
'menu_id' => $p['menu_id'] ?? null,
|
||||
$tenantId = (int)$dept->tenant_id;
|
||||
$modelType = Department::class;
|
||||
|
||||
$ok = 0; $failed = [];
|
||||
|
||||
DB::transaction(function () use ($items, $tenantId, $deptId, $modelType, &$ok, &$failed) {
|
||||
foreach ($items as $i => $r) {
|
||||
try {
|
||||
$permissionId = (int)$r['permission_id'];
|
||||
$isAllowed = array_key_exists('is_allowed', $r) ? (int)$r['is_allowed'] : 1;
|
||||
|
||||
if ($isAllowed === 1) {
|
||||
// ALLOW: Spatie 표준에 넣고, 동일 권한의 DENY 제거
|
||||
$exists = DB::table('model_has_permissions')->where([
|
||||
'permission_id' => $permissionId,
|
||||
'model_type' => $modelType,
|
||||
'model_id' => $deptId,
|
||||
'tenant_id' => $tenantId,
|
||||
])->exists();
|
||||
|
||||
if (!$exists) {
|
||||
DB::table('model_has_permissions')->insert([
|
||||
'permission_id' => $permissionId,
|
||||
'model_type' => $modelType,
|
||||
'model_id' => $deptId,
|
||||
'tenant_id' => $tenantId,
|
||||
]);
|
||||
}
|
||||
|
||||
DB::table('permission_overrides')->where([
|
||||
'tenant_id' => $tenantId,
|
||||
'model_type' => $modelType,
|
||||
'model_id' => $deptId,
|
||||
'permission_id' => $permissionId,
|
||||
'effect' => -1,
|
||||
])->delete();
|
||||
} else {
|
||||
// DENY: overrides(effect=-1) upsert, 그리고 ALLOW 제거
|
||||
$exists = DB::table('permission_overrides')->where([
|
||||
'tenant_id' => $tenantId,
|
||||
'model_type' => $modelType,
|
||||
'model_id' => $deptId,
|
||||
'permission_id' => $permissionId,
|
||||
])->exists();
|
||||
|
||||
if ($exists) {
|
||||
DB::table('permission_overrides')->where([
|
||||
'tenant_id' => $tenantId,
|
||||
'model_type' => $modelType,
|
||||
'model_id' => $deptId,
|
||||
'permission_id' => $permissionId,
|
||||
])->update(['effect' => -1, 'updated_at' => now()]);
|
||||
} else {
|
||||
DB::table('permission_overrides')->insert([
|
||||
'tenant_id' => $tenantId,
|
||||
'model_type' => $modelType,
|
||||
'model_id' => $deptId,
|
||||
'permission_id' => $permissionId,
|
||||
'effect' => -1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
DB::table('model_has_permissions')->where([
|
||||
'permission_id' => $permissionId,
|
||||
'model_type' => $modelType,
|
||||
'model_id' => $deptId,
|
||||
'tenant_id' => $tenantId,
|
||||
])->delete();
|
||||
}
|
||||
|
||||
$ok++;
|
||||
} catch (\Throwable $e) {
|
||||
$failed[] = [
|
||||
'index' => $i,
|
||||
'permission_id' => $r['permission_id'] ?? null,
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return [
|
||||
'processed' => count($items),
|
||||
'succeeded' => $ok,
|
||||
'failed' => $failed
|
||||
];
|
||||
|
||||
$model = DepartmentPermission::withTrashed()->firstOrNew($payload);
|
||||
$model->is_allowed = isset($p['is_allowed']) ? (int)$p['is_allowed'] : 1;
|
||||
$model->deleted_at = null;
|
||||
$model->save();
|
||||
|
||||
// 변경 후 목록 반환
|
||||
return $this->listPermissions($deptId, []);
|
||||
}
|
||||
|
||||
/** 권한 제거 (menu_id 없으면 전체 제거) */
|
||||
public function revokePermission(int $deptId, int $permissionId, array $params)
|
||||
public function revokePermissions(int $deptId, array $payload): array
|
||||
{
|
||||
$p = $this->v($params, [
|
||||
'menu_id' => 'nullable|integer|min:1',
|
||||
$items = isset($payload['permission_id'])
|
||||
? [ $payload ]
|
||||
: ($payload['items'] ?? $payload);
|
||||
|
||||
$v = Validator::make(['items'=>$items], [
|
||||
'items' => 'required|array|max:1000',
|
||||
'items.*.permission_id' => 'required|integer|min:1',
|
||||
]);
|
||||
if ($p instanceof JsonResponse) return $p;
|
||||
if (isset($p['error'])) return $p;
|
||||
if ($v->fails()) return ['error'=>$v->errors()->first(),'code'=>422];
|
||||
|
||||
$q = DepartmentPermission::whereNull('deleted_at')
|
||||
->where('department_id', $deptId)
|
||||
->where('permission_id', $permissionId);
|
||||
$dept = Department::query()->find($deptId);
|
||||
if (!$dept) return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404];
|
||||
|
||||
if (isset($p['menu_id'])) $q->where('menu_id', $p['menu_id']);
|
||||
$tenantId = (int)$dept->tenant_id;
|
||||
$modelType = Department::class;
|
||||
|
||||
$rows = $q->get();
|
||||
if ($rows->isEmpty()) return ['error' => '대상 권한을 찾을 수 없습니다.', 'code' => 404];
|
||||
$ok = 0; $failed = [];
|
||||
|
||||
foreach ($rows as $row) $row->delete();
|
||||
DB::transaction(function () use ($items, $tenantId, $deptId, $modelType, &$ok, &$failed) {
|
||||
foreach ($items as $i => $r) {
|
||||
try {
|
||||
$permissionId = (int)$r['permission_id'];
|
||||
|
||||
// ALLOW 제거
|
||||
DB::table('model_has_permissions')->where([
|
||||
'permission_id' => $permissionId,
|
||||
'model_type' => $modelType,
|
||||
'model_id' => $deptId,
|
||||
'tenant_id' => $tenantId,
|
||||
])->delete();
|
||||
|
||||
// DENY/임시허용 오버라이드 제거
|
||||
DB::table('permission_overrides')->where([
|
||||
'tenant_id' => $tenantId,
|
||||
'model_type' => $modelType,
|
||||
'model_id' => $deptId,
|
||||
'permission_id' => $permissionId,
|
||||
])->delete();
|
||||
|
||||
$ok++;
|
||||
} catch (\Throwable $e) {
|
||||
$failed[] = [
|
||||
'index' => $i,
|
||||
'permission_id' => $r['permission_id'] ?? null,
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return [
|
||||
'permission_id' => $permissionId,
|
||||
'menu_id' => $p['menu_id'] ?? null,
|
||||
'deleted_count' => $rows->count(),
|
||||
'processed' => count($items),
|
||||
'succeeded' => $ok,
|
||||
'failed' => $failed
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,33 @@
|
||||
|
||||
/**
|
||||
* =========================
|
||||
* Schemas
|
||||
* 공통 응답 스키마
|
||||
* =========================
|
||||
*/
|
||||
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="ApiResponse",
|
||||
* type="object",
|
||||
* required={"success","message"},
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="부서 생성 성공"),
|
||||
* @OA\Property(property="data", nullable=true)
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="ErrorResponse",
|
||||
* type="object",
|
||||
* required={"success","message"},
|
||||
* @OA\Property(property="success", type="boolean", example=false),
|
||||
* @OA\Property(property="message", type="string", example="부서 생성 실패"),
|
||||
* @OA\Property(property="data", type="null", example=null)
|
||||
* )
|
||||
*/
|
||||
|
||||
/**
|
||||
* =========================
|
||||
* Domain 스키마
|
||||
* =========================
|
||||
*/
|
||||
|
||||
@@ -76,16 +102,8 @@
|
||||
* type="object",
|
||||
* required={"user_id"},
|
||||
* @OA\Property(property="user_id", type="integer", example=12),
|
||||
* @OA\Property(property="is_primary", type="integer", enum={0,1}, nullable=true, example=0)
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="DepartmentPermissionUpsertRequest",
|
||||
* type="object",
|
||||
* required={"permission_id"},
|
||||
* @OA\Property(property="permission_id", type="integer", example=25),
|
||||
* @OA\Property(property="menu_id", type="integer", nullable=true, example=101, description="특정 메뉴에 한정하려면 지정"),
|
||||
* @OA\Property(property="is_allowed", type="integer", enum={0,1}, example=1, description="1=ALLOW, 0=DENY(차단)")
|
||||
* @OA\Property(property="is_primary", type="integer", enum={0,1}, nullable=true, example=0),
|
||||
* @OA\Property(property="joined_at", type="string", format="date-time", nullable=true, example="2025-08-21 10:00:00")
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
@@ -98,8 +116,46 @@
|
||||
* @OA\Property(property="email", type="string", format="email", example="hong@example.com"),
|
||||
* @OA\Property(property="phone", type="string", nullable=true, example="010-1234-5678"),
|
||||
* @OA\Property(property="is_active", type="integer", example=1)
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="DepartmentPermissionUpsertSingle",
|
||||
* type="object",
|
||||
* required={"permission_id"},
|
||||
* @OA\Property(property="permission_id", type="integer", example=25),
|
||||
* @OA\Property(property="is_allowed", type="integer", enum={0,1}, example=1, description="1=ALLOW, 0=DENY(차단)")
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="DepartmentPermissionUpsertMany",
|
||||
* type="object",
|
||||
* required={"items"},
|
||||
* @OA\Property(
|
||||
* property="items",
|
||||
* type="array",
|
||||
* @OA\Items(ref="#/components/schemas/DepartmentPermissionUpsertSingle")
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="DepartmentPermissionRevokeSingle",
|
||||
* type="object",
|
||||
* required={"permission_id"},
|
||||
* @OA\Property(property="permission_id", type="integer", example=25)
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="DepartmentPermissionRevokeMany",
|
||||
* type="object",
|
||||
* required={"items"},
|
||||
* @OA\Property(
|
||||
* property="items",
|
||||
* type="array",
|
||||
* @OA\Items(ref="#/components/schemas/DepartmentPermissionRevokeSingle")
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
|
||||
class DepartmentApi
|
||||
{
|
||||
/**
|
||||
@@ -113,22 +169,19 @@ class DepartmentApi
|
||||
* @OA\Parameter(name="per_page", in="query", required=false, @OA\Schema(type="integer", example=10)),
|
||||
* @OA\Parameter(name="q", in="query", required=false, @OA\Schema(type="string", example="운영")),
|
||||
* @OA\Parameter(name="is_active", in="query", required=false, @OA\Schema(type="integer", enum={0,1}, example=1)),
|
||||
* @OA\Response(response=200, description="목록 조회 성공",
|
||||
* @OA\Response(response=200, description="부서 목록 조회 성공",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="current_page", type="integer", example=1),
|
||||
* @OA\Property(property="per_page", type="integer", example=10),
|
||||
* @OA\Property(property="total", type="integer", example=2),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/DepartmentList")
|
||||
* ))
|
||||
* }
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="부서 목록 조회 성공"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="current_page", type="integer", example=1),
|
||||
* @OA\Property(property="per_page", type="integer", example=10),
|
||||
* @OA\Property(property="total", type="integer", example=2),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/DepartmentList")
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function index() {}
|
||||
@@ -137,22 +190,17 @@ public function index() {}
|
||||
* @OA\Get(
|
||||
* path="/api/v1/departments/{id}",
|
||||
* summary="부서 단건 조회",
|
||||
* description="ID로 부서 상세를 조회합니다.",
|
||||
* tags={"Department"},
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer", example=7)),
|
||||
* @OA\Response(response=200, description="단건 조회 성공",
|
||||
* @OA\Response(response=200, description="부서 조회 성공",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/Department"))
|
||||
* }
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="부서 조회 성공"),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/Department")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function show() {}
|
||||
@@ -161,22 +209,17 @@ public function show() {}
|
||||
* @OA\Post(
|
||||
* path="/api/v1/departments",
|
||||
* summary="부서 생성",
|
||||
* description="새 부서를 생성합니다.",
|
||||
* tags={"Department"},
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\RequestBody(required=true, @OA\JsonContent(ref="#/components/schemas/DepartmentCreateRequest")),
|
||||
* @OA\Response(response=200, description="생성 성공",
|
||||
* @OA\Response(response=200, description="부서 생성 성공",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/Department"))
|
||||
* }
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="부서 생성 성공"),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/Department")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function store() {}
|
||||
@@ -185,24 +228,19 @@ public function store() {}
|
||||
* @OA\Patch(
|
||||
* path="/api/v1/departments/{id}",
|
||||
* summary="부서 수정",
|
||||
* description="기존 부서 정보를 부분 수정합니다.",
|
||||
* tags={"Department"},
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer", example=7)),
|
||||
* @OA\RequestBody(required=true, @OA\JsonContent(ref="#/components/schemas/DepartmentUpdateRequest")),
|
||||
* @OA\Response(response=200, description="수정 성공",
|
||||
* @OA\Response(response=200, description="부서 수정 성공",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/Department"))
|
||||
* }
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="부서 수정 성공"),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/Department")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function update() {}
|
||||
@@ -210,18 +248,21 @@ public function update() {}
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/api/v1/departments/{id}",
|
||||
* summary="부서 삭제",
|
||||
* description="지정한 부서를 삭제합니다(소프트 삭제 권장).",
|
||||
* summary="부서 삭제(소프트)",
|
||||
* tags={"Department"},
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer", example=7)),
|
||||
* @OA\Response(response=200, description="삭제 성공",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ApiResponse")
|
||||
* @OA\Response(response=200, description="부서 삭제 성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="부서 삭제 성공"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="id", type="integer", example=7),
|
||||
* @OA\Property(property="deleted_at", type="string", format="date-time", example="2025-08-21 11:00:00")
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function destroy() {}
|
||||
@@ -236,22 +277,19 @@ public function destroy() {}
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer", example=7)),
|
||||
* @OA\Parameter(name="page", in="query", required=false, @OA\Schema(type="integer", example=1)),
|
||||
* @OA\Parameter(name="per_page", in="query", required=false, @OA\Schema(type="integer", example=20)),
|
||||
* @OA\Response(response=200, description="조회 성공",
|
||||
* @OA\Response(response=200, description="부서 사용자 목록 조회 성공",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="current_page", type="integer", example=1),
|
||||
* @OA\Property(property="per_page", type="integer", example=20),
|
||||
* @OA\Property(property="total", type="integer", example=1),
|
||||
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/UserBrief"))
|
||||
* ))
|
||||
* }
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="부서 사용자 목록 조회 성공"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="current_page", type="integer", example=1),
|
||||
* @OA\Property(property="per_page", type="integer", example=20),
|
||||
* @OA\Property(property="total", type="integer", example=1),
|
||||
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/UserBrief"))
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function users() {}
|
||||
@@ -260,16 +298,23 @@ public function users() {}
|
||||
* @OA\Post(
|
||||
* path="/api/v1/departments/{id}/users",
|
||||
* summary="부서 사용자 배정(단건)",
|
||||
* description="특정 사용자 한 명을 해당 부서에 배정합니다.",
|
||||
* tags={"Department"},
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer", example=7)),
|
||||
* @OA\RequestBody(required=true, @OA\JsonContent(ref="#/components/schemas/DepartmentUserAttachRequest")),
|
||||
* @OA\Response(response=200, description="배정 성공", @OA\JsonContent(ref="#/components/schemas/ApiResponse")),
|
||||
* @OA\Response(response=200, description="부서 사용자 배정 성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="부서 사용자 배정 성공"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="department_id", type="integer", example=7),
|
||||
* @OA\Property(property="user_id", type="integer", example=12)
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=409, description="이미 배정됨", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* @OA\Response(response=404, description="부서 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function attachUser() {}
|
||||
@@ -278,15 +323,21 @@ public function attachUser() {}
|
||||
* @OA\Delete(
|
||||
* path="/api/v1/departments/{id}/users/{user}",
|
||||
* summary="부서 사용자 해제(단건)",
|
||||
* description="해당 부서에서 특정 사용자를 제거합니다.",
|
||||
* tags={"Department"},
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer", example=7)),
|
||||
* @OA\Parameter(name="user", in="path", required=true, @OA\Schema(type="integer", example=12)),
|
||||
* @OA\Response(response=200, description="해제 성공", @OA\JsonContent(ref="#/components/schemas/ApiResponse")),
|
||||
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* @OA\Response(response=200, description="부서 사용자 해제 성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="부서 사용자 해제 성공"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="user_id", type="integer", example=12),
|
||||
* @OA\Property(property="deleted_at", type="string", format="date-time", example="2025-08-21 11:00:00")
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function detachUser() {}
|
||||
@@ -295,7 +346,6 @@ public function detachUser() {}
|
||||
* @OA\Patch(
|
||||
* path="/api/v1/departments/{id}/users/{user}/primary",
|
||||
* summary="주부서 설정/해제",
|
||||
* description="부서 사용자 주부서 여부를 설정 또는 해제합니다.",
|
||||
* tags={"Department"},
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer", example=7)),
|
||||
@@ -306,11 +356,19 @@ public function detachUser() {}
|
||||
* @OA\Property(property="is_primary", type="integer", enum={0,1}, example=1)
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=200, description="설정 성공", @OA\JsonContent(ref="#/components/schemas/ApiResponse")),
|
||||
* @OA\Response(response=200, description="주부서 설정 성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="주부서 설정 성공"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="user_id", type="integer", example=12),
|
||||
* @OA\Property(property="department_id", type="integer", example=7),
|
||||
* @OA\Property(property="is_primary", type="integer", example=1)
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function setPrimary() {}
|
||||
@@ -319,29 +377,36 @@ public function setPrimary() {}
|
||||
* @OA\Get(
|
||||
* path="/api/v1/departments/{id}/permissions",
|
||||
* summary="부서 권한 목록",
|
||||
* description="부서에 매핑된 권한(메뉴 한정 포함)을 조회합니다.",
|
||||
* description="부서에 설정된 ALLOW/DENY 목록을 조회합니다. (is_allowed=1|0, menu_id 필터 지원)",
|
||||
* tags={"Department"},
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer", example=7)),
|
||||
* @OA\Response(response=200, description="조회 성공",
|
||||
* @OA\Parameter(name="menu_id", in="query", required=false, @OA\Schema(type="integer", example=101)),
|
||||
* @OA\Parameter(name="is_allowed", in="query", required=false, @OA\Schema(type="integer", enum={0,1}, example=1)),
|
||||
* @OA\Parameter(name="page", in="query", required=false, @OA\Schema(type="integer", example=1)),
|
||||
* @OA\Parameter(name="per_page", in="query", required=false, @OA\Schema(type="integer", example=20)),
|
||||
* @OA\Response(response=200, description="부서 권한 목록 조회 성공",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(
|
||||
* property="data",
|
||||
* type="array",
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="부서 권한 목록 조회 성공"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="current_page", type="integer", example=1),
|
||||
* @OA\Property(property="per_page", type="integer", example=20),
|
||||
* @OA\Property(property="total", type="integer", example=3),
|
||||
* @OA\Property(property="data", type="array",
|
||||
* @OA\Items(type="object",
|
||||
* @OA\Property(property="permission_id", type="integer", example=25),
|
||||
* @OA\Property(property="menu_id", type="integer", nullable=true, example=101),
|
||||
* @OA\Property(property="is_allowed", type="integer", example=1)
|
||||
* @OA\Property(property="permission_code", type="string", example="menu.101.read"),
|
||||
* @OA\Property(property="is_allowed", type="integer", example=1),
|
||||
* @OA\Property(property="reason", type="string", nullable=true, example="보안 이슈"),
|
||||
* @OA\Property(property="effective_from", type="string", format="date-time", nullable=true),
|
||||
* @OA\Property(property="effective_to", type="string", format="date-time", nullable=true)
|
||||
* )
|
||||
* ))
|
||||
* }
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* @OA\Response(response=404, description="부서 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function listPermissions() {}
|
||||
@@ -349,34 +414,63 @@ public function listPermissions() {}
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/departments/{id}/permissions",
|
||||
* summary="부서 권한 부여/차단(단건 Upsert)",
|
||||
* description="permission_id 기준으로 ALLOW(1) 또는 DENY(0) 처리합니다. menu_id 지정 시 해당 메뉴 범위로 제한됩니다.",
|
||||
* summary="부서 권한 부여/차단(Upsert: 단건/배치)",
|
||||
* description="permission_id 기준으로 ALLOW(1) 또는 DENY(0) 처리합니다. 단건 또는 items 배열을 모두 지원합니다.",
|
||||
* tags={"Department"},
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer", example=7)),
|
||||
* @OA\RequestBody(required=true, @OA\JsonContent(ref="#/components/schemas/DepartmentPermissionUpsertRequest")),
|
||||
* @OA\Response(response=200, description="적용 성공", @OA\JsonContent(ref="#/components/schemas/ApiResponse")),
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(oneOf={
|
||||
* @OA\Schema(ref="#/components/schemas/DepartmentPermissionUpsertSingle"),
|
||||
* @OA\Schema(ref="#/components/schemas/DepartmentPermissionUpsertMany")
|
||||
* })
|
||||
* ),
|
||||
* @OA\Response(response=200, description="부서 권한 적용 성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="부서 권한 적용 성공"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="processed", type="integer", example=2),
|
||||
* @OA\Property(property="succeeded", type="integer", example=2),
|
||||
* @OA\Property(property="failed", type="array", @OA\Items(type="object"))
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="부서 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function upsertPermission() {}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/api/v1/departments/{id}/permissions/{permission}",
|
||||
* summary="부서 권한 해제(단건)",
|
||||
* description="지정 권한을 부서 매핑에서 제거합니다. (menu_id 쿼리 파라미터로 특정 메뉴 범위를 지정 가능)",
|
||||
* path="/api/v1/departments/{id}/permissions",
|
||||
* summary="부서 권한 해제(단건/배치)",
|
||||
* description="지정 권한을 부서 매핑에서 제거합니다. 단건 또는 items 배열을 모두 지원합니다.",
|
||||
* tags={"Department"},
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer", example=7)),
|
||||
* @OA\Parameter(name="permission", in="path", required=true, @OA\Schema(type="integer", example=25)),
|
||||
* @OA\Parameter(name="menu_id", in="query", required=false, @OA\Schema(type="integer", example=101)),
|
||||
* @OA\Response(response=200, description="해제 성공", @OA\JsonContent(ref="#/components/schemas/ApiResponse")),
|
||||
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(oneOf={
|
||||
* @OA\Schema(ref="#/components/schemas/DepartmentPermissionRevokeSingle"),
|
||||
* @OA\Schema(ref="#/components/schemas/DepartmentPermissionRevokeMany")
|
||||
* })
|
||||
* ),
|
||||
* @OA\Response(response=200, description="부서 권한 해제 성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="부서 권한 해제 성공"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="processed", type="integer", example=2),
|
||||
* @OA\Property(property="succeeded", type="integer", example=2),
|
||||
* @OA\Property(property="failed", type="array", @OA\Items(type="object"))
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="부서 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function revokePermission() {}
|
||||
|
||||
Reference in New Issue
Block a user