fix : 메뉴 모델 및 일부 서비스파일 response 오류 수정
This commit is contained in:
@@ -4,14 +4,25 @@
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Traits\BelongsToTenant;
|
||||
use App\Traits\ModelTrait;
|
||||
use App\Models\Scopes\TenantScope;
|
||||
|
||||
class Menu extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
use SoftDeletes, BelongsToTenant, ModelTrait;
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id', 'parent_id', 'slug', 'name', 'url', 'is_active', 'sort_order',
|
||||
'hidden', 'is_external', 'external_url', 'icon'
|
||||
'tenant_id', 'parent_id', 'name', 'url', 'is_active', 'sort_order',
|
||||
'hidden', 'is_external', 'external_url', 'icon',
|
||||
'created_by', 'updated_by', 'deleted_by',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'deleted_by',
|
||||
'deleted_at'
|
||||
];
|
||||
|
||||
public function parent()
|
||||
@@ -23,4 +34,23 @@ public function children()
|
||||
{
|
||||
return $this->hasMany(Menu::class, 'parent_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 공유(NULL) + 현재 테넌트 모두 포함해서 조회
|
||||
* (SoftDeletes 글로벌 스코프는 그대로 유지)
|
||||
*/
|
||||
public function scopeWithShared($query, ?int $tenantId = null)
|
||||
{
|
||||
$tenantId = $tenantId ?? app('tenant_id');
|
||||
|
||||
return $query
|
||||
->withoutGlobalScope(TenantScope::class)
|
||||
->where(function ($w) use ($tenantId) {
|
||||
if (is_null($tenantId)) {
|
||||
$w->whereNull('tenant_id');
|
||||
} else {
|
||||
$w->whereNull('tenant_id')->orWhere('tenant_id', $tenantId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
namespace App\Services\Authz;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use App\Helpers\ApiResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
use App\Helpers\ApiResponse;
|
||||
|
||||
class RoleService
|
||||
{
|
||||
@@ -26,13 +26,13 @@ public static function index(array $params = [])
|
||||
->where('guard_name', self::$guard);
|
||||
|
||||
if ($q !== '') {
|
||||
$query->where(function($w) use ($q) {
|
||||
$query->where(function ($w) use ($q) {
|
||||
$w->where('name', 'like', "%{$q}%")
|
||||
->orWhere('description', 'like', "%{$q}%");
|
||||
});
|
||||
}
|
||||
|
||||
$list = $query->orderBy('id','desc')
|
||||
$list = $query->orderByDesc('id')
|
||||
->paginate($size, ['*'], 'page', $page);
|
||||
|
||||
return ApiResponse::response('result', $list);
|
||||
@@ -44,10 +44,10 @@ public static function store(array $params = [])
|
||||
$tenantId = (int) app('tenant_id');
|
||||
|
||||
$v = Validator::make($params, [
|
||||
'name' => [
|
||||
'required','string','max:100',
|
||||
Rule::unique('roles','name')->where(fn($q)=>$q
|
||||
->where('tenant_id',$tenantId)
|
||||
'name' => [
|
||||
'required', 'string', 'max:100',
|
||||
Rule::unique('roles', 'name')->where(fn($q) => $q
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('guard_name', self::$guard)),
|
||||
],
|
||||
'description' => 'nullable|string|max:255',
|
||||
@@ -57,13 +57,14 @@ public static function store(array $params = [])
|
||||
return ApiResponse::error($v->errors()->first(), 422);
|
||||
}
|
||||
|
||||
// Spatie 팀(테넌트) 컨텍스트
|
||||
app(PermissionRegistrar::class)->setPermissionsTeamId($tenantId);
|
||||
|
||||
$role = Role::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'guard_name' => self::$guard,
|
||||
'name' => $v->validated()['name'],
|
||||
'description'=> $params['description'] ?? null,
|
||||
'tenant_id' => $tenantId,
|
||||
'guard_name' => self::$guard,
|
||||
'name' => $v->validated()['name'],
|
||||
'description' => $params['description'] ?? null,
|
||||
]);
|
||||
|
||||
return ApiResponse::response('result', $role);
|
||||
@@ -90,7 +91,7 @@ public static function update(int $id, array $params = [])
|
||||
{
|
||||
$tenantId = (int) app('tenant_id');
|
||||
|
||||
$role = Role::where('tenant_id',$tenantId)
|
||||
$role = Role::where('tenant_id', $tenantId)
|
||||
->where('guard_name', self::$guard)
|
||||
->find($id);
|
||||
|
||||
@@ -112,18 +113,17 @@ public static function update(int $id, array $params = [])
|
||||
return ApiResponse::error($v->errors()->first(), 422);
|
||||
}
|
||||
|
||||
$payload = $v->validated();
|
||||
$role->fill($payload)->save();
|
||||
$role->fill($v->validated())->save();
|
||||
|
||||
return ApiResponse::response('result', $role);
|
||||
return ApiResponse::response('result', $role->fresh());
|
||||
}
|
||||
|
||||
/** 삭제 (현재는 하드삭제) */
|
||||
/** 삭제 (하드삭제) */
|
||||
public static function destroy(int $id)
|
||||
{
|
||||
$tenantId = (int) app('tenant_id');
|
||||
|
||||
$role = Role::where('tenant_id',$tenantId)
|
||||
$role = Role::where('tenant_id', $tenantId)
|
||||
->where('guard_name', self::$guard)
|
||||
->find($id);
|
||||
|
||||
@@ -132,8 +132,7 @@ public static function destroy(int $id)
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($role) {
|
||||
// 연관 피벗은 스파티가 onDelete cascade 하므로 기본 동작으로 OK
|
||||
$role->delete(); // ※ 기본 Spatie Role은 SoftDeletes 미사용 → 하드 삭제
|
||||
$role->delete(); // Spatie Role 기본: soft delete 없음
|
||||
});
|
||||
|
||||
return ApiResponse::response('success');
|
||||
|
||||
@@ -54,18 +54,14 @@ protected static function resolveRoleNames(int $tenantId, array $params): array
|
||||
// 정제
|
||||
$names = array_values(array_unique(array_filter($names)));
|
||||
|
||||
// 존재하지 않는 이름이 섞였는지 확인(실패 시 422로 안내하고 싶다면 여기서 검사)
|
||||
// 존재 확인(필요시 에러 처리 확장 가능)
|
||||
if (!empty($names)) {
|
||||
$count = Role::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('guard_name', self::$guard)
|
||||
->whereIn('name', $names)
|
||||
->count();
|
||||
|
||||
if ($count !== count($names)) {
|
||||
// 존재하지 않는 역할 이름이 포함됨
|
||||
// 필요하면 어떤 이름이 없는지 찾아서 에러 반환하도록 개선 가능
|
||||
}
|
||||
// if ($count !== count($names)) { ... 필요시 상세 에러 반환 }
|
||||
}
|
||||
|
||||
return $names;
|
||||
@@ -84,13 +80,13 @@ public static function list(int $userId)
|
||||
self::setTeam($tenantId);
|
||||
|
||||
// 현재 테넌트의 역할만
|
||||
$roles = $user->roles()
|
||||
$builder = $user->roles()
|
||||
->where('roles.tenant_id', $tenantId)
|
||||
->where('roles.guard_name', self::$guard)
|
||||
->orderBy('roles.id', 'desc')
|
||||
->get(['roles.id','roles.tenant_id','roles.name','roles.description','roles.guard_name','roles.created_at','roles.updated_at']);
|
||||
->select(['roles.id','roles.tenant_id','roles.name','roles.description','roles.guard_name','roles.created_at','roles.updated_at'])
|
||||
->orderBy('roles.id', 'desc');
|
||||
|
||||
return ApiResponse::response('result', $roles);
|
||||
return ApiResponse::response('get', $builder);
|
||||
}
|
||||
|
||||
/** 부여 (중복 무시) */
|
||||
@@ -104,10 +100,10 @@ public static function grant(int $userId, array $params = [])
|
||||
}
|
||||
|
||||
$v = Validator::make($params, [
|
||||
'role_names' => 'sometimes|array',
|
||||
'role_names' => 'sometimes|array',
|
||||
'role_names.*' => 'string|min:1',
|
||||
'role_ids' => 'sometimes|array',
|
||||
'role_ids.*' => 'integer|min:1',
|
||||
'role_ids' => 'sometimes|array',
|
||||
'role_ids.*' => 'integer|min:1',
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return ApiResponse::error($v->errors()->first(), 422);
|
||||
@@ -123,9 +119,7 @@ public static function grant(int $userId, array $params = [])
|
||||
return ApiResponse::error('유효한 역할이 없습니다.', 422);
|
||||
}
|
||||
|
||||
// Spatie: 이름 배열로 부여 (teams 컨텍스트 적용)
|
||||
$user->assignRole($names);
|
||||
|
||||
$user->assignRole($names); // teams 컨텍스트 적용됨
|
||||
return ApiResponse::response('success');
|
||||
}
|
||||
|
||||
@@ -140,10 +134,10 @@ public static function revoke(int $userId, array $params = [])
|
||||
}
|
||||
|
||||
$v = Validator::make($params, [
|
||||
'role_names' => 'sometimes|array',
|
||||
'role_names' => 'sometimes|array',
|
||||
'role_names.*' => 'string|min:1',
|
||||
'role_ids' => 'sometimes|array',
|
||||
'role_ids.*' => 'integer|min:1',
|
||||
'role_ids' => 'sometimes|array',
|
||||
'role_ids.*' => 'integer|min:1',
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return ApiResponse::error($v->errors()->first(), 422);
|
||||
@@ -160,7 +154,6 @@ public static function revoke(int $userId, array $params = [])
|
||||
}
|
||||
|
||||
$user->removeRole($names); // 배열 허용
|
||||
|
||||
return ApiResponse::response('success');
|
||||
}
|
||||
|
||||
@@ -175,10 +168,10 @@ public static function sync(int $userId, array $params = [])
|
||||
}
|
||||
|
||||
$v = Validator::make($params, [
|
||||
'role_names' => 'sometimes|array',
|
||||
'role_names' => 'sometimes|array',
|
||||
'role_names.*' => 'string|min:1',
|
||||
'role_ids' => 'sometimes|array',
|
||||
'role_ids.*' => 'integer|min:1',
|
||||
'role_ids' => 'sometimes|array',
|
||||
'role_ids.*' => 'integer|min:1',
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return ApiResponse::error($v->errors()->first(), 422);
|
||||
@@ -191,13 +184,11 @@ public static function sync(int $userId, array $params = [])
|
||||
|
||||
$names = self::resolveRoleNames($tenantId, $params);
|
||||
if (empty($names)) {
|
||||
// 빈 목록으로 sync = 모두 제거 의도라면 허용할 수도 있음.
|
||||
// 정책에 맞춰 처리: 여기서는 빈 목록이면 실패 처리
|
||||
// 정책상 빈 목록 sync 허용 시: $user->syncRoles([]) 로 전부 제거 가능
|
||||
return ApiResponse::error('유효한 역할이 없습니다.', 422);
|
||||
}
|
||||
|
||||
$user->syncRoles($names); // 교체
|
||||
|
||||
$user->syncRoles($names);
|
||||
return ApiResponse::response('success');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,26 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Models\Commons\Department;
|
||||
use App\Models\Commons\DepartmentUser;
|
||||
use App\Models\Commons\DepartmentPermission;
|
||||
use App\Models\Commons\DepartmentUser;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class DepartmentService
|
||||
{
|
||||
/**
|
||||
* 공통 검증 헬퍼: 실패 시 JsonResponse 반환
|
||||
*/
|
||||
private static function v(array $params, array $rules)
|
||||
{
|
||||
$v = Validator::make($params, $rules);
|
||||
if ($v->fails()) abort(422, $v->errors()->first());
|
||||
if ($v->fails()) {
|
||||
return ApiResponse::error($v->errors()->first(), 422);
|
||||
}
|
||||
return $v->validated();
|
||||
}
|
||||
|
||||
@@ -27,18 +34,27 @@ public static function index(array $params)
|
||||
'page' => 'nullable|integer|min:1',
|
||||
'per_page' => 'nullable|integer|min:1|max:200',
|
||||
]);
|
||||
if ($p instanceof JsonResponse) return $p;
|
||||
|
||||
$q = Department::query();
|
||||
|
||||
if (isset($p['is_active'])) $q->where('is_active', (int)$p['is_active']);
|
||||
if (isset($p['is_active'])) {
|
||||
$q->where('is_active', (int)$p['is_active']);
|
||||
}
|
||||
if (!empty($p['q'])) {
|
||||
$q->where(function($w) use ($p) {
|
||||
$w->where('name','like','%'.$p['q'].'%')
|
||||
->orWhere('code','like','%'.$p['q'].'%');
|
||||
$q->where(function ($w) use ($p) {
|
||||
$w->where('name', 'like', '%' . $p['q'] . '%')
|
||||
->orWhere('code', 'like', '%' . $p['q'] . '%');
|
||||
});
|
||||
}
|
||||
|
||||
return $q->orderBy('sort_order')->orderBy('name')->paginate($p['per_page'] ?? 20);
|
||||
$q->orderBy('sort_order')->orderBy('name');
|
||||
|
||||
$perPage = $p['per_page'] ?? 20;
|
||||
$page = $p['page'] ?? null;
|
||||
|
||||
// 페이징 객체는 'result'로 반환
|
||||
return ApiResponse::response('result', $q->paginate($perPage, ['*'], 'page', $page));
|
||||
}
|
||||
|
||||
/** 생성 */
|
||||
@@ -52,10 +68,11 @@ public static function store(array $params)
|
||||
'sort_order' => 'nullable|integer',
|
||||
'created_by' => 'nullable|integer|min:1',
|
||||
]);
|
||||
if ($p instanceof JsonResponse) return $p;
|
||||
|
||||
if (!empty($p['code'])) {
|
||||
$exists = Department::query()->where('code', $p['code'])->exists();
|
||||
if ($exists) abort(409, '이미 존재하는 부서 코드입니다.');
|
||||
if ($exists) return ApiResponse::error('이미 존재하는 부서 코드입니다.', 409);
|
||||
}
|
||||
|
||||
$dept = Department::create([
|
||||
@@ -68,20 +85,28 @@ public static function store(array $params)
|
||||
'updated_by' => $p['created_by'] ?? null,
|
||||
]);
|
||||
|
||||
return self::show($dept->id, []);
|
||||
return ApiResponse::response('result', $dept->fresh());
|
||||
}
|
||||
|
||||
/** 단건 */
|
||||
public static function show(int $id, array $params)
|
||||
{
|
||||
$dept = Department::query()->find($id);
|
||||
if (!$dept) abort(404, '부서를 찾을 수 없습니다.');
|
||||
return $dept;
|
||||
if (!$id) return ApiResponse::error('id가 필요합니다.', 400);
|
||||
|
||||
$q = Department::query()->where('id', $id);
|
||||
$res = ApiResponse::response('first', $q);
|
||||
|
||||
if (empty($res['data'])) {
|
||||
return ApiResponse::error('부서를 찾을 수 없습니다.', 404);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/** 수정 */
|
||||
public static function update(int $id, array $params)
|
||||
{
|
||||
if (!$id) return ApiResponse::error('id가 필요합니다.', 400);
|
||||
|
||||
$p = self::v($params, [
|
||||
'code' => 'nullable|string|max:50',
|
||||
'name' => 'nullable|string|max:100',
|
||||
@@ -90,16 +115,17 @@ public static function update(int $id, array $params)
|
||||
'sort_order' => 'nullable|integer',
|
||||
'updated_by' => 'nullable|integer|min:1',
|
||||
]);
|
||||
if ($p instanceof JsonResponse) return $p;
|
||||
|
||||
$dept = Department::query()->find($id);
|
||||
if (!$dept) abort(404, '부서를 찾을 수 없습니다.');
|
||||
if (!$dept) return ApiResponse::error('부서를 찾을 수 없습니다.', 404);
|
||||
|
||||
if (array_key_exists('code', $p) && !is_null($p['code'])) {
|
||||
$exists = Department::query()
|
||||
->where('code', $p['code'])
|
||||
->where('id','!=',$id)
|
||||
->where('id', '!=', $id)
|
||||
->exists();
|
||||
if ($exists) abort(409, '이미 존재하는 부서 코드입니다.');
|
||||
if ($exists) return ApiResponse::error('이미 존재하는 부서 코드입니다.', 409);
|
||||
}
|
||||
|
||||
$dept->fill([
|
||||
@@ -111,18 +137,21 @@ public static function update(int $id, array $params)
|
||||
'updated_by' => $p['updated_by'] ?? $dept->updated_by,
|
||||
])->save();
|
||||
|
||||
return $dept->fresh();
|
||||
return ApiResponse::response('result', $dept->fresh());
|
||||
}
|
||||
|
||||
/** 삭제(soft) */
|
||||
public static function destroy(int $id, array $params)
|
||||
{
|
||||
if (!$id) return ApiResponse::error('id가 필요합니다.', 400);
|
||||
|
||||
$p = self::v($params, [
|
||||
'deleted_by' => 'nullable|integer|min:1',
|
||||
]);
|
||||
if ($p instanceof JsonResponse) return $p;
|
||||
|
||||
$dept = Department::query()->find($id);
|
||||
if (!$dept) abort(404, '부서를 찾을 수 없습니다.');
|
||||
if (!$dept) return ApiResponse::error('부서를 찾을 수 없습니다.', 404);
|
||||
|
||||
if (!empty($p['deleted_by'])) {
|
||||
$dept->deleted_by = $p['deleted_by'];
|
||||
@@ -130,7 +159,7 @@ public static function destroy(int $id, array $params)
|
||||
}
|
||||
$dept->delete();
|
||||
|
||||
return ['id'=>$id, 'deleted_at'=>now()->toDateTimeString()];
|
||||
return ApiResponse::response('result', ['id' => $id, 'deleted_at' => now()->toDateTimeString()]);
|
||||
}
|
||||
|
||||
/** 부서 사용자 목록 */
|
||||
@@ -140,15 +169,18 @@ public static function listUsers(int $deptId, array $params)
|
||||
'page' => 'nullable|integer|min:1',
|
||||
'per_page' => 'nullable|integer|min:1|max:200',
|
||||
]);
|
||||
if ($p instanceof JsonResponse) return $p;
|
||||
|
||||
$dept = Department::query()->find($deptId);
|
||||
if (!$dept) abort(404, '부서를 찾을 수 없습니다.');
|
||||
if (!$dept) return ApiResponse::error('부서를 찾을 수 없습니다.', 404);
|
||||
|
||||
return $dept->departmentUsers()
|
||||
->with('user')
|
||||
->orderByDesc('is_primary')
|
||||
->orderBy('id')
|
||||
->paginate($p['per_page'] ?? 20);
|
||||
$builder = $dept->departmentUsers()->with('user')
|
||||
->orderByDesc('is_primary')->orderBy('id');
|
||||
|
||||
$perPage = $p['per_page'] ?? 20;
|
||||
$page = $p['page'] ?? null;
|
||||
|
||||
return ApiResponse::response('result', $builder->paginate($perPage, ['*'], 'page', $page));
|
||||
}
|
||||
|
||||
/** 사용자 배정 (단건) */
|
||||
@@ -159,24 +191,25 @@ public static function attachUser(int $deptId, array $params)
|
||||
'is_primary' => 'nullable|in:0,1',
|
||||
'joined_at' => 'nullable|date',
|
||||
]);
|
||||
if ($p instanceof JsonResponse) return $p;
|
||||
|
||||
$dept = Department::query()->find($deptId);
|
||||
if (!$dept) abort(404, '부서를 찾을 수 없습니다.');
|
||||
if (!$dept) return ApiResponse::error('부서를 찾을 수 없습니다.', 404);
|
||||
|
||||
return DB::transaction(function () use ($dept, $p) {
|
||||
$result = DB::transaction(function () use ($dept, $p) {
|
||||
$du = DepartmentUser::withTrashed()
|
||||
->where('department_id', $dept->id)
|
||||
->where('user_id', $p['user_id'])
|
||||
->first();
|
||||
|
||||
if ($du && is_null($du->deleted_at)) {
|
||||
abort(409, '이미 배정된 사용자입니다.');
|
||||
return ApiResponse::error('이미 배정된 사용자입니다.', 409);
|
||||
}
|
||||
|
||||
if (!empty($p['is_primary']) && (int)$p['is_primary'] === 1) {
|
||||
DepartmentUser::whereNull('deleted_at')
|
||||
->where('user_id', $p['user_id'])
|
||||
->update(['is_primary'=>0]);
|
||||
->update(['is_primary' => 0]);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
@@ -194,8 +227,13 @@ public static function attachUser(int $deptId, array $params)
|
||||
DepartmentUser::create($payload);
|
||||
}
|
||||
|
||||
return ['department_id'=>$dept->id, 'user_id'=>$p['user_id']];
|
||||
return ['department_id' => $dept->id, 'user_id' => $p['user_id']];
|
||||
});
|
||||
|
||||
// 트랜잭션 내부에서 에러 응답이 나올 수 있으므로 분기
|
||||
if ($result instanceof JsonResponse) return $result;
|
||||
|
||||
return ApiResponse::response('result', $result);
|
||||
}
|
||||
|
||||
/** 사용자 제거(soft) */
|
||||
@@ -206,10 +244,14 @@ public static function detachUser(int $deptId, int $userId, array $params)
|
||||
->where('user_id', $userId)
|
||||
->first();
|
||||
|
||||
if (!$du) abort(404, '배정된 사용자를 찾을 수 없습니다.');
|
||||
if (!$du) return ApiResponse::error('배정된 사용자를 찾을 수 없습니다.', 404);
|
||||
|
||||
$du->delete();
|
||||
|
||||
return ['user_id'=>$userId, 'deleted_at'=>now()->toDateTimeString()];
|
||||
return ApiResponse::response('result', [
|
||||
'user_id' => $userId,
|
||||
'deleted_at' => now()->toDateTimeString(),
|
||||
]);
|
||||
}
|
||||
|
||||
/** 주부서 설정/해제 */
|
||||
@@ -218,14 +260,17 @@ public static function setPrimary(int $deptId, int $userId, array $params)
|
||||
$p = self::v($params, [
|
||||
'is_primary' => 'required|in:0,1',
|
||||
]);
|
||||
if ($p instanceof JsonResponse) return $p;
|
||||
|
||||
return DB::transaction(function () use ($deptId, $userId, $p) {
|
||||
$result = DB::transaction(function () use ($deptId, $userId, $p) {
|
||||
$du = DepartmentUser::whereNull('deleted_at')
|
||||
->where('department_id', $deptId)
|
||||
->where('user_id', $userId)
|
||||
->first();
|
||||
|
||||
if (!$du) abort(404, '배정된 사용자를 찾을 수 없습니다.');
|
||||
if (!$du) {
|
||||
return ApiResponse::error('배정된 사용자를 찾을 수 없습니다.', 404);
|
||||
}
|
||||
|
||||
if ((int)$p['is_primary'] === 1) {
|
||||
DepartmentUser::whereNull('deleted_at')
|
||||
@@ -236,8 +281,12 @@ public static function setPrimary(int $deptId, int $userId, array $params)
|
||||
$du->is_primary = (int)$p['is_primary'];
|
||||
$du->save();
|
||||
|
||||
return ['user_id'=>$userId,'department_id'=>$deptId,'is_primary'=>$du->is_primary];
|
||||
return ['user_id' => $userId, 'department_id' => $deptId, 'is_primary' => $du->is_primary];
|
||||
});
|
||||
|
||||
if ($result instanceof JsonResponse) return $result;
|
||||
|
||||
return ApiResponse::response('result', $result);
|
||||
}
|
||||
|
||||
/** 부서 권한 목록 */
|
||||
@@ -249,9 +298,10 @@ public static function listPermissions(int $deptId, array $params)
|
||||
'page' => 'nullable|integer|min:1',
|
||||
'per_page' => 'nullable|integer|min:1|max:200',
|
||||
]);
|
||||
if ($p instanceof JsonResponse) return $p;
|
||||
|
||||
$dept = Department::query()->find($deptId);
|
||||
if (!$dept) abort(404, '부서를 찾을 수 없습니다.');
|
||||
if (!$dept) return ApiResponse::error('부서를 찾을 수 없습니다.', 404);
|
||||
|
||||
$q = DepartmentPermission::query()
|
||||
->whereNull('deleted_at')
|
||||
@@ -260,7 +310,12 @@ public static function listPermissions(int $deptId, array $params)
|
||||
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']);
|
||||
|
||||
return $q->orderByDesc('is_allowed')->orderBy('permission_id')->paginate($p['per_page'] ?? 20);
|
||||
$q->orderByDesc('is_allowed')->orderBy('permission_id');
|
||||
|
||||
$perPage = $p['per_page'] ?? 20;
|
||||
$page = $p['page'] ?? null;
|
||||
|
||||
return ApiResponse::response('result', $q->paginate($perPage, ['*'], 'page', $page));
|
||||
}
|
||||
|
||||
/** 권한 부여/차단 upsert */
|
||||
@@ -271,9 +326,10 @@ public static function upsertPermission(int $deptId, array $params)
|
||||
'menu_id' => 'nullable|integer|min:1',
|
||||
'is_allowed' => 'nullable|in:0,1',
|
||||
]);
|
||||
if ($p instanceof JsonResponse) return $p;
|
||||
|
||||
$dept = Department::query()->find($deptId);
|
||||
if (!$dept) abort(404, '부서를 찾을 수 없습니다.');
|
||||
if (!$dept) return ApiResponse::error('부서를 찾을 수 없습니다.', 404);
|
||||
|
||||
$payload = [
|
||||
'department_id' => $deptId,
|
||||
@@ -286,6 +342,7 @@ public static function upsertPermission(int $deptId, array $params)
|
||||
$model->deleted_at = null;
|
||||
$model->save();
|
||||
|
||||
// 변경 후 목록 반환
|
||||
return self::listPermissions($deptId, []);
|
||||
}
|
||||
|
||||
@@ -295,6 +352,7 @@ public static function revokePermission(int $deptId, int $permissionId, array $p
|
||||
$p = self::v($params, [
|
||||
'menu_id' => 'nullable|integer|min:1',
|
||||
]);
|
||||
if ($p instanceof JsonResponse) return $p;
|
||||
|
||||
$q = DepartmentPermission::whereNull('deleted_at')
|
||||
->where('department_id', $deptId)
|
||||
@@ -303,14 +361,14 @@ public static function revokePermission(int $deptId, int $permissionId, array $p
|
||||
if (isset($p['menu_id'])) $q->where('menu_id', $p['menu_id']);
|
||||
|
||||
$rows = $q->get();
|
||||
if ($rows->isEmpty()) abort(404, '대상 권한을 찾을 수 없습니다.');
|
||||
if ($rows->isEmpty()) return ApiResponse::error('대상 권한을 찾을 수 없습니다.', 404);
|
||||
|
||||
foreach ($rows as $row) $row->delete();
|
||||
|
||||
return [
|
||||
return ApiResponse::response('result', [
|
||||
'permission_id' => $permissionId,
|
||||
'menu_id' => $p['menu_id'] ?? null,
|
||||
'deleted_count' => $rows->count(),
|
||||
];
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,67 +2,77 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Models\Commons\Menu;
|
||||
use App\Helpers\ApiResponse;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class MenuService
|
||||
{
|
||||
protected static function tenantId(array $params): ?int
|
||||
protected static function tenantId(): ?int
|
||||
{
|
||||
return $params['tenant_id'] ?? request()->attributes->get('tenant_id');
|
||||
return app('tenant_id');
|
||||
}
|
||||
|
||||
protected static function actorId(array $params): ?int
|
||||
protected static function actorId(): ?int
|
||||
{
|
||||
return $params['user_id'] ?? (request()->user()->id ?? null);
|
||||
$user = app('api_user'); // 컨테이너에 주입된 인증 사용자(객체 or 배열)
|
||||
return is_object($user) ? ($user->id ?? null) : ($user['id'] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 목록 조회
|
||||
*/
|
||||
public static function index(array $params)
|
||||
{
|
||||
$tenantId = self::tenantId($params);
|
||||
$tenantId = self::tenantId();
|
||||
|
||||
$q = DB::table('menus')->whereNull('deleted_at');
|
||||
if (!is_null($tenantId)) {
|
||||
$q->where(function ($w) use ($tenantId) {
|
||||
$w->whereNull('tenant_id')->orWhere('tenant_id', $tenantId);
|
||||
});
|
||||
}
|
||||
// 옵션: parent_id / is_active / hidden 필터
|
||||
if (isset($params['parent_id'])) $q->where('parent_id', $params['parent_id']);
|
||||
if (isset($params['is_active'])) $q->where('is_active', (int)$params['is_active']);
|
||||
if (isset($params['hidden'])) $q->where('hidden', (int)$params['hidden']);
|
||||
$q = Menu::query()->withShared($tenantId);
|
||||
|
||||
return $q->orderBy('parent_id')->orderBy('sort_order')->get();
|
||||
if (array_key_exists('parent_id', $params)) $q->where('parent_id', $params['parent_id']);
|
||||
if (array_key_exists('is_active', $params)) $q->where('is_active', (int)$params['is_active']);
|
||||
if (array_key_exists('hidden', $params)) $q->where('hidden', (int)$params['hidden']);
|
||||
|
||||
$q->orderBy('parent_id')->orderBy('sort_order');
|
||||
|
||||
// Builder 그대로 전달해야 쿼리로그/표준응답 형식 유지
|
||||
return ApiResponse::response('get', $q);
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 단건 조회
|
||||
*/
|
||||
public static function show(array $params)
|
||||
{
|
||||
$id = (int)($params['id'] ?? 0);
|
||||
$tenantId = self::tenantId($params);
|
||||
$id = (int)($params['id'] ?? 0);
|
||||
$tenantId = self::tenantId();
|
||||
|
||||
$q = DB::table('menus')->where('id', $id)->whereNull('deleted_at');
|
||||
if (!is_null($tenantId)) {
|
||||
$q->where(function ($w) use ($tenantId) {
|
||||
$w->whereNull('tenant_id')->orWhere('tenant_id', $tenantId);
|
||||
});
|
||||
if (!$id) {
|
||||
return ApiResponse::error('id가 필요합니다.', 400);
|
||||
}
|
||||
|
||||
$row = $q->first();
|
||||
throw_if(!$row, ValidationException::withMessages(['id' => 'Menu not found']));
|
||||
return $row;
|
||||
$q = Menu::withShared($tenantId)->where('id', $id);
|
||||
|
||||
// first 쿼리를 ApiResponse에 위임 (존재X면 null 반환)
|
||||
$res = ApiResponse::response('first', $q);
|
||||
if (empty($res['data'])) {
|
||||
return ApiResponse::error('Menu not found', 404);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 생성
|
||||
*/
|
||||
public static function store(array $params)
|
||||
{
|
||||
$tenantId = self::tenantId($params);
|
||||
$userId = self::actorId($params);
|
||||
$tenantId = self::tenantId();
|
||||
$userId = self::actorId();
|
||||
|
||||
$v = Validator::make($params, [
|
||||
'parent_id' => ['nullable','integer'],
|
||||
'name' => ['required','string','max:100'],
|
||||
'slug' => ['nullable','string','max:150'],
|
||||
'url' => ['nullable','string','max:255'],
|
||||
'is_active' => ['nullable','boolean'],
|
||||
'sort_order' => ['nullable','integer'],
|
||||
@@ -71,52 +81,47 @@ public static function store(array $params)
|
||||
'external_url' => ['nullable','string','max:255'],
|
||||
'icon' => ['nullable','string','max:50'],
|
||||
]);
|
||||
|
||||
if ($v->fails()) {
|
||||
return ApiResponse::error($v->errors()->first(), 422);
|
||||
}
|
||||
$data = $v->validated();
|
||||
|
||||
// slug 유니크(테넌트 범위) 체크
|
||||
if (!empty($data['slug'])) {
|
||||
$exists = DB::table('menus')
|
||||
->whereNull('deleted_at')
|
||||
->when(!is_null($tenantId), fn($q)=>$q->where('tenant_id',$tenantId), fn($q)=>$q->whereNull('tenant_id'))
|
||||
->where('slug',$data['slug'])
|
||||
->exists();
|
||||
if ($exists) {
|
||||
throw ValidationException::withMessages(['slug'=>'이미 사용 중인 슬러그입니다.']);
|
||||
}
|
||||
}
|
||||
$menu = new Menu();
|
||||
$menu->tenant_id = $tenantId;
|
||||
$menu->parent_id = $data['parent_id'] ?? null;
|
||||
$menu->name = $data['name'];
|
||||
$menu->url = $data['url'] ?? null;
|
||||
$menu->is_active = (int)($data['is_active'] ?? 1);
|
||||
$menu->sort_order = (int)($data['sort_order'] ?? 0);
|
||||
$menu->hidden = (int)($data['hidden'] ?? 0);
|
||||
$menu->is_external = (int)($data['is_external'] ?? 0);
|
||||
$menu->external_url = $data['external_url'] ?? null;
|
||||
$menu->icon = $data['icon'] ?? null;
|
||||
$menu->created_by = $userId;
|
||||
$menu->updated_by = $userId;
|
||||
$menu->save();
|
||||
|
||||
$now = now();
|
||||
$id = DB::table('menus')->insertGetId([
|
||||
'tenant_id' => $tenantId,
|
||||
'parent_id' => $data['parent_id'] ?? null,
|
||||
'name' => $data['name'],
|
||||
'slug' => $data['slug'] ?? null,
|
||||
'url' => $data['url'] ?? null,
|
||||
'is_active' => (int)($data['is_active'] ?? 1),
|
||||
'sort_order' => (int)($data['sort_order'] ?? 0),
|
||||
'hidden' => (int)($data['hidden'] ?? 0),
|
||||
'is_external' => (int)($data['is_external'] ?? 0),
|
||||
'external_url' => $data['external_url'] ?? null,
|
||||
'icon' => $data['icon'] ?? null,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
'created_by' => $userId,
|
||||
'updated_by' => $userId,
|
||||
]);
|
||||
|
||||
return ['id' => $id];
|
||||
// 생성 결과를 그대로 전달
|
||||
return ApiResponse::response('result', $menu->fresh());
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 수정
|
||||
*/
|
||||
public static function update(array $params)
|
||||
{
|
||||
$id = (int)($params['id'] ?? 0);
|
||||
$tenantId = self::tenantId($params);
|
||||
$userId = self::actorId($params);
|
||||
$tenantId = self::tenantId();
|
||||
$userId = self::actorId();
|
||||
|
||||
if (!$id) {
|
||||
return ApiResponse::error('id가 필요합니다.', 400);
|
||||
}
|
||||
|
||||
$v = Validator::make($params, [
|
||||
'parent_id' => ['nullable','integer'],
|
||||
'name' => ['nullable','string','max:100'],
|
||||
'slug' => ['nullable','string','max:150'],
|
||||
'url' => ['nullable','string','max:255'],
|
||||
'is_active' => ['nullable','boolean'],
|
||||
'sort_order' => ['nullable','integer'],
|
||||
@@ -125,50 +130,55 @@ public static function update(array $params)
|
||||
'external_url' => ['nullable','string','max:255'],
|
||||
'icon' => ['nullable','string','max:50'],
|
||||
]);
|
||||
|
||||
if ($v->fails()) {
|
||||
return ApiResponse::error($v->errors()->first(), 422);
|
||||
}
|
||||
$data = $v->validated();
|
||||
|
||||
// 대상 존재 확인 & 테넌트 범위
|
||||
$exists = DB::table('menus')->where('id',$id)->whereNull('deleted_at')
|
||||
->when(!is_null($tenantId), fn($q)=>$q->where('tenant_id',$tenantId), fn($q)=>$q->whereNull('tenant_id'))
|
||||
->exists();
|
||||
if (!$exists) throw ValidationException::withMessages(['id'=>'Menu not found']);
|
||||
|
||||
// slug 유니크(테넌트 범위) 체크
|
||||
if (!empty($data['slug'])) {
|
||||
$dup = DB::table('menus')->whereNull('deleted_at')
|
||||
->when(!is_null($tenantId), fn($q)=>$q->where('tenant_id',$tenantId), fn($q)=>$q->whereNull('tenant_id'))
|
||||
->where('slug',$data['slug'])->where('id','<>',$id)->exists();
|
||||
if ($dup) throw ValidationException::withMessages(['slug'=>'이미 사용 중인 슬러그입니다.']);
|
||||
$menu = Menu::withShared($tenantId)->where('id', $id)->first();
|
||||
if (!$menu) {
|
||||
return ApiResponse::error('Menu not found', 404);
|
||||
}
|
||||
|
||||
$update = Arr::only($data, ['parent_id','name','slug','url','is_active','sort_order','hidden','is_external','external_url','icon']);
|
||||
$update = array_filter($update, fn($v)=>!is_null($v));
|
||||
$update['updated_at'] = now();
|
||||
$update['updated_by'] = $userId;
|
||||
$update = Arr::only($data, [
|
||||
'parent_id','name','url','is_active','sort_order','hidden','is_external','external_url','icon'
|
||||
]);
|
||||
$update = array_filter($update, fn($v) => !is_null($v));
|
||||
|
||||
DB::table('menus')->where('id',$id)->update($update);
|
||||
return ['id' => $id];
|
||||
if (empty($update)) {
|
||||
return ApiResponse::error('수정할 데이터가 없습니다.', 400);
|
||||
}
|
||||
|
||||
$update['updated_by'] = $userId;
|
||||
$menu->fill($update)->save();
|
||||
|
||||
return ApiResponse::response('result', $menu->fresh());
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 삭제(소프트)
|
||||
*/
|
||||
public static function destroy(array $params)
|
||||
{
|
||||
$id = (int)($params['id'] ?? 0);
|
||||
$tenantId = self::tenantId($params);
|
||||
$userId = self::actorId($params);
|
||||
$tenantId = self::tenantId();
|
||||
$userId = self::actorId();
|
||||
|
||||
$q = DB::table('menus')->where('id',$id)->whereNull('deleted_at');
|
||||
$q = !is_null($tenantId)
|
||||
? $q->where('tenant_id',$tenantId)
|
||||
: $q->whereNull('tenant_id');
|
||||
if (!$id) {
|
||||
return ApiResponse::error('id가 필요합니다.', 400);
|
||||
}
|
||||
|
||||
$row = $q->first();
|
||||
if (!$row) throw ValidationException::withMessages(['id'=>'Menu not found']);
|
||||
$menu = Menu::withShared($tenantId)->where('id', $id)->first();
|
||||
if (!$menu) {
|
||||
return ApiResponse::error('Menu not found', 404);
|
||||
}
|
||||
|
||||
DB::table('menus')->where('id',$id)->update([
|
||||
'deleted_at' => now(),
|
||||
'deleted_by' => $userId,
|
||||
]);
|
||||
return ['id' => $id, 'deleted' => true];
|
||||
$menu->deleted_by = $userId;
|
||||
$menu->save();
|
||||
$menu->delete();
|
||||
|
||||
return ApiResponse::response('success');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,43 +188,56 @@ public static function destroy(array $params)
|
||||
public static function reorder(array $params)
|
||||
{
|
||||
if (!is_array($params) || empty($params)) {
|
||||
throw ValidationException::withMessages(['items'=>'유효한 정렬 목록이 필요합니다.']);
|
||||
return ApiResponse::error('유효한 정렬 목록이 필요합니다.', 422);
|
||||
}
|
||||
DB::transaction(function () use ($params) {
|
||||
$tenantId = self::tenantId();
|
||||
|
||||
DB::transaction(function () use ($params, $tenantId) {
|
||||
foreach ($params as $it) {
|
||||
if (!isset($it['id'], $it['sort_order'])) continue;
|
||||
DB::table('menus')->where('id',(int)$it['id'])->update([
|
||||
'sort_order' => (int)$it['sort_order'],
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$menu = Menu::withShared($tenantId)->find((int)$it['id']);
|
||||
if ($menu) {
|
||||
$menu->sort_order = (int)$it['sort_order'];
|
||||
$menu->save();
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
|
||||
return ApiResponse::response('success');
|
||||
}
|
||||
|
||||
/**
|
||||
* 상태 토글
|
||||
* 허용 필드: is_active / hidden / is_external
|
||||
* 상태 토글: is_active / hidden / is_external
|
||||
*/
|
||||
public static function toggle(array $params)
|
||||
{
|
||||
$id = (int)($params['id'] ?? 0);
|
||||
$userId = self::actorId($params);
|
||||
$id = (int)($params['id'] ?? 0);
|
||||
$tenantId = self::tenantId();
|
||||
$userId = self::actorId();
|
||||
|
||||
$payload = array_filter([
|
||||
'is_active' => isset($params['is_active']) ? (int)$params['is_active'] : null,
|
||||
'hidden' => isset($params['hidden']) ? (int)$params['hidden'] : null,
|
||||
'is_external' => isset($params['is_external']) ? (int)$params['is_external'] : null,
|
||||
], fn($v)=>!is_null($v));
|
||||
|
||||
if (empty($payload)) {
|
||||
throw ValidationException::withMessages(['toggle'=>'변경할 필드가 없습니다.']);
|
||||
if (!$id) {
|
||||
return ApiResponse::error('id가 필요합니다.', 400);
|
||||
}
|
||||
|
||||
$payload['updated_at'] = now();
|
||||
$payload['updated_by'] = $userId;
|
||||
$payload = array_filter([
|
||||
'is_active' => array_key_exists('is_active', $params) ? (int)$params['is_active'] : null,
|
||||
'hidden' => array_key_exists('hidden', $params) ? (int)$params['hidden'] : null,
|
||||
'is_external' => array_key_exists('is_external', $params) ? (int)$params['is_external'] : null,
|
||||
], fn($v) => !is_null($v));
|
||||
|
||||
DB::table('menus')->where('id',$id)->update($payload);
|
||||
return ['id' => $id];
|
||||
if (empty($payload)) {
|
||||
return ApiResponse::error('변경할 필드가 없습니다.', 422);
|
||||
}
|
||||
|
||||
$menu = Menu::withShared($tenantId)->find($id);
|
||||
if (!$menu) {
|
||||
return ApiResponse::error('Menu not found', 404);
|
||||
}
|
||||
|
||||
$payload['updated_by'] = $userId;
|
||||
$menu->fill($payload)->save();
|
||||
|
||||
return ApiResponse::response('result', $menu->fresh());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user