feat(API): Position API 추가 (직급/직책 통합)
- Position 모델 생성 (type: rank | title) - PositionService: CRUD + reorder 구현 - PositionController: REST API 엔드포인트 - Swagger 문서 작성 (PositionApi.php) - 마이그레이션: positions 테이블 + common_codes 등록 - routes/api.php에 라우트 등록 Phase L-3 (직급관리), L-4 (직책관리) 백엔드 완료 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
74
app/Http/Controllers/Api/V1/PositionController.php
Normal file
74
app/Http/Controllers/Api/V1/PositionController.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\PositionReorderRequest;
|
||||
use App\Http\Requests\PositionRequest;
|
||||
use App\Services\PositionService;
|
||||
|
||||
class PositionController extends Controller
|
||||
{
|
||||
public function __construct(private PositionService $service) {}
|
||||
|
||||
/**
|
||||
* GET /v1/positions
|
||||
*/
|
||||
public function index(PositionRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->index($request->validated());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /v1/positions
|
||||
*/
|
||||
public function store(PositionRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->store($request->validated());
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/positions/{id}
|
||||
*/
|
||||
public function show(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->show($id);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /v1/positions/{id}
|
||||
*/
|
||||
public function update(int $id, PositionRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id, $request) {
|
||||
return $this->service->update($id, $request->validated());
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /v1/positions/{id}
|
||||
*/
|
||||
public function destroy(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->destroy($id);
|
||||
}, __('message.deleted'));
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /v1/positions/reorder
|
||||
*/
|
||||
public function reorder(PositionReorderRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->reorder($request->validated()['items']);
|
||||
}, __('message.reordered'));
|
||||
}
|
||||
}
|
||||
32
app/Http/Requests/PositionReorderRequest.php
Normal file
32
app/Http/Requests/PositionReorderRequest.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class PositionReorderRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'items' => 'required|array|min:1',
|
||||
'items.*.id' => 'required|integer|exists:positions,id',
|
||||
'items.*.sort_order' => 'required|integer|min:0',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'items.required' => __('validation.required', ['attribute' => '항목']),
|
||||
'items.*.id.required' => __('validation.required', ['attribute' => 'ID']),
|
||||
'items.*.id.exists' => __('validation.exists', ['attribute' => 'ID']),
|
||||
'items.*.sort_order.required' => __('validation.required', ['attribute' => '정렬순서']),
|
||||
];
|
||||
}
|
||||
}
|
||||
59
app/Http/Requests/PositionRequest.php
Normal file
59
app/Http/Requests/PositionRequest.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\Tenants\Position;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class PositionRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
$method = $this->method();
|
||||
|
||||
if ($method === 'GET') {
|
||||
return [
|
||||
'type' => ['nullable', Rule::in([Position::TYPE_RANK, Position::TYPE_TITLE])],
|
||||
'is_active' => 'nullable|boolean',
|
||||
'q' => 'nullable|string|max:100',
|
||||
'per_page' => 'nullable|integer|min:1|max:100',
|
||||
'page' => 'nullable|integer|min:1',
|
||||
];
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
return [
|
||||
'type' => ['required', Rule::in([Position::TYPE_RANK, Position::TYPE_TITLE])],
|
||||
'name' => 'required|string|max:50',
|
||||
'sort_order' => 'nullable|integer|min:0',
|
||||
'is_active' => 'nullable|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
if (in_array($method, ['PUT', 'PATCH'])) {
|
||||
return [
|
||||
'name' => 'nullable|string|max:50',
|
||||
'sort_order' => 'nullable|integer|min:0',
|
||||
'is_active' => 'nullable|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'type.required' => __('validation.required', ['attribute' => '유형']),
|
||||
'type.in' => __('validation.in', ['attribute' => '유형']),
|
||||
'name.required' => __('validation.required', ['attribute' => '명칭']),
|
||||
'name.max' => __('validation.max.string', ['attribute' => '명칭', 'max' => 50]),
|
||||
];
|
||||
}
|
||||
}
|
||||
75
app/Models/Tenants/Position.php
Normal file
75
app/Models/Tenants/Position.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Tenants;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use App\Traits\ModelTrait;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* 직급/직책 통합 모델
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $tenant_id
|
||||
* @property string $type rank(직급) | title(직책)
|
||||
* @property string $name 명칭
|
||||
* @property int $sort_order 정렬 순서
|
||||
* @property bool $is_active 활성화 여부
|
||||
*/
|
||||
class Position extends Model
|
||||
{
|
||||
use BelongsToTenant, ModelTrait, SoftDeletes;
|
||||
|
||||
protected $table = 'positions';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'type',
|
||||
'name',
|
||||
'sort_order',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'tenant_id' => 'int',
|
||||
'sort_order' => 'int',
|
||||
'is_active' => 'bool',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 상수
|
||||
// =========================================================================
|
||||
|
||||
public const TYPE_RANK = 'rank'; // 직급
|
||||
|
||||
public const TYPE_TITLE = 'title'; // 직책
|
||||
|
||||
// =========================================================================
|
||||
// 스코프
|
||||
// =========================================================================
|
||||
|
||||
public function scopeRanks($query)
|
||||
{
|
||||
return $query->where('type', self::TYPE_RANK);
|
||||
}
|
||||
|
||||
public function scopeTitles($query)
|
||||
{
|
||||
return $query->where('type', self::TYPE_TITLE);
|
||||
}
|
||||
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
public function scopeOrdered($query)
|
||||
{
|
||||
return $query->orderBy('sort_order');
|
||||
}
|
||||
}
|
||||
141
app/Services/PositionService.php
Normal file
141
app/Services/PositionService.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Tenants\Position;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class PositionService extends Service
|
||||
{
|
||||
/**
|
||||
* 목록 조회
|
||||
*/
|
||||
public function index(array $params)
|
||||
{
|
||||
$query = Position::query()
|
||||
->when(isset($params['type']), fn ($q) => $q->where('type', $params['type']))
|
||||
->when(isset($params['is_active']), fn ($q) => $q->where('is_active', (bool) $params['is_active']))
|
||||
->when(! empty($params['q']), fn ($q) => $q->where('name', 'like', '%'.$params['q'].'%'))
|
||||
->ordered();
|
||||
|
||||
if (isset($params['per_page'])) {
|
||||
return $query->paginate((int) $params['per_page']);
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 단건 조회
|
||||
*/
|
||||
public function show(int $id)
|
||||
{
|
||||
$position = Position::find($id);
|
||||
|
||||
if (! $position) {
|
||||
return ['error' => __('error.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
return $position;
|
||||
}
|
||||
|
||||
/**
|
||||
* 생성
|
||||
*/
|
||||
public function store(array $data)
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
// 중복 체크
|
||||
$exists = Position::where('tenant_id', $tenantId)
|
||||
->where('type', $data['type'])
|
||||
->where('name', $data['name'])
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return ['error' => __('error.duplicate'), 'code' => 409];
|
||||
}
|
||||
|
||||
// 정렬 순서 자동 설정
|
||||
if (! isset($data['sort_order'])) {
|
||||
$maxOrder = Position::where('tenant_id', $tenantId)
|
||||
->where('type', $data['type'])
|
||||
->max('sort_order') ?? 0;
|
||||
$data['sort_order'] = $maxOrder + 1;
|
||||
}
|
||||
|
||||
$position = Position::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'type' => $data['type'],
|
||||
'name' => $data['name'],
|
||||
'sort_order' => $data['sort_order'],
|
||||
'is_active' => $data['is_active'] ?? true,
|
||||
]);
|
||||
|
||||
return $position;
|
||||
}
|
||||
|
||||
/**
|
||||
* 수정
|
||||
*/
|
||||
public function update(int $id, array $data)
|
||||
{
|
||||
$position = Position::find($id);
|
||||
|
||||
if (! $position) {
|
||||
return ['error' => __('error.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
// 이름 중복 체크 (자기 자신 제외)
|
||||
if (isset($data['name'])) {
|
||||
$exists = Position::where('tenant_id', $position->tenant_id)
|
||||
->where('type', $position->type)
|
||||
->where('name', $data['name'])
|
||||
->where('id', '!=', $id)
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return ['error' => __('error.duplicate'), 'code' => 409];
|
||||
}
|
||||
}
|
||||
|
||||
$position->update([
|
||||
'name' => $data['name'] ?? $position->name,
|
||||
'sort_order' => $data['sort_order'] ?? $position->sort_order,
|
||||
'is_active' => $data['is_active'] ?? $position->is_active,
|
||||
]);
|
||||
|
||||
return $position->fresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* 삭제
|
||||
*/
|
||||
public function destroy(int $id)
|
||||
{
|
||||
$position = Position::find($id);
|
||||
|
||||
if (! $position) {
|
||||
return ['error' => __('error.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
$position->delete();
|
||||
|
||||
return ['id' => $id, 'deleted_at' => now()->toDateTimeString()];
|
||||
}
|
||||
|
||||
/**
|
||||
* 순서 변경 (벌크)
|
||||
*/
|
||||
public function reorder(array $items)
|
||||
{
|
||||
DB::transaction(function () use ($items) {
|
||||
foreach ($items as $item) {
|
||||
Position::where('id', $item['id'])
|
||||
->update(['sort_order' => $item['sort_order']]);
|
||||
}
|
||||
});
|
||||
|
||||
return ['success' => true, 'updated' => count($items)];
|
||||
}
|
||||
}
|
||||
205
app/Swagger/v1/PositionApi.php
Normal file
205
app/Swagger/v1/PositionApi.php
Normal file
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
namespace App\Swagger\v1;
|
||||
|
||||
/**
|
||||
* @OA\Tag(
|
||||
* name="Position",
|
||||
* description="직급/직책 통합 관리"
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="Position",
|
||||
* type="object",
|
||||
* required={"id", "tenant_id", "type", "name", "sort_order", "is_active"},
|
||||
* @OA\Property(property="id", type="integer", example=1),
|
||||
* @OA\Property(property="tenant_id", type="integer", example=287),
|
||||
* @OA\Property(property="type", type="string", enum={"rank", "title"}, example="rank", description="유형: rank(직급), title(직책)"),
|
||||
* @OA\Property(property="name", type="string", example="대리", description="명칭"),
|
||||
* @OA\Property(property="sort_order", type="integer", example=2, description="정렬 순서"),
|
||||
* @OA\Property(property="is_active", type="boolean", example=true, description="활성화 여부"),
|
||||
* @OA\Property(property="created_at", type="string", format="date-time"),
|
||||
* @OA\Property(property="updated_at", type="string", format="date-time")
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="PositionCreateRequest",
|
||||
* type="object",
|
||||
* required={"type", "name"},
|
||||
* @OA\Property(property="type", type="string", enum={"rank", "title"}, example="rank", description="유형: rank(직급), title(직책)"),
|
||||
* @OA\Property(property="name", type="string", example="과장", description="명칭"),
|
||||
* @OA\Property(property="sort_order", type="integer", example=3, description="정렬 순서 (생략시 자동)"),
|
||||
* @OA\Property(property="is_active", type="boolean", example=true, description="활성화 여부")
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="PositionUpdateRequest",
|
||||
* type="object",
|
||||
* @OA\Property(property="name", type="string", example="과장", description="명칭"),
|
||||
* @OA\Property(property="sort_order", type="integer", example=3, description="정렬 순서"),
|
||||
* @OA\Property(property="is_active", type="boolean", example=true, description="활성화 여부")
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="PositionReorderRequest",
|
||||
* type="object",
|
||||
* required={"items"},
|
||||
* @OA\Property(
|
||||
* property="items",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* type="object",
|
||||
* required={"id", "sort_order"},
|
||||
* @OA\Property(property="id", type="integer", example=1),
|
||||
* @OA\Property(property="sort_order", type="integer", example=1)
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
class PositionApi
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/positions",
|
||||
* tags={"Position"},
|
||||
* summary="직급/직책 목록 조회",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="type", in="query", description="유형 필터 (rank: 직급, title: 직책)", @OA\Schema(type="string", enum={"rank", "title"})),
|
||||
* @OA\Parameter(name="is_active", in="query", description="활성화 필터", @OA\Schema(type="boolean")),
|
||||
* @OA\Parameter(name="q", in="query", description="검색어 (명칭)", @OA\Schema(type="string")),
|
||||
* @OA\Parameter(name="per_page", in="query", description="페이지당 개수 (생략시 전체)", @OA\Schema(type="integer")),
|
||||
* @OA\Parameter(name="page", in="query", description="페이지 번호", @OA\Schema(type="integer")),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/Position"))
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function index() {}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/positions",
|
||||
* tags={"Position"},
|
||||
* summary="직급/직책 생성",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/PositionCreateRequest")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/Position")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=409, description="중복 오류")
|
||||
* )
|
||||
*/
|
||||
public function store() {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/positions/{id}",
|
||||
* tags={"Position"},
|
||||
* summary="직급/직책 단건 조회",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/Position")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="찾을 수 없음")
|
||||
* )
|
||||
*/
|
||||
public function show() {}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/api/v1/positions/{id}",
|
||||
* tags={"Position"},
|
||||
* summary="직급/직책 수정",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/PositionUpdateRequest")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/Position")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="찾을 수 없음"),
|
||||
* @OA\Response(response=409, description="중복 오류")
|
||||
* )
|
||||
*/
|
||||
public function update() {}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/api/v1/positions/{id}",
|
||||
* tags={"Position"},
|
||||
* summary="직급/직책 삭제",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="id", type="integer"),
|
||||
* @OA\Property(property="deleted_at", type="string", format="date-time")
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=404, description="찾을 수 없음")
|
||||
* )
|
||||
*/
|
||||
public function destroy() {}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/api/v1/positions/reorder",
|
||||
* tags={"Position"},
|
||||
* summary="직급/직책 순서 변경 (벌크)",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/PositionReorderRequest")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="success", type="boolean"),
|
||||
* @OA\Property(property="updated", type="integer")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function reorder() {}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('positions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->comment('테넌트 ID');
|
||||
$table->string('type', 20)->comment('유형: rank(직급), title(직책)');
|
||||
$table->string('name', 50)->comment('명칭');
|
||||
$table->integer('sort_order')->default(0)->comment('정렬 순서');
|
||||
$table->boolean('is_active')->default(true)->comment('활성화 여부');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['tenant_id', 'type', 'sort_order']);
|
||||
$table->unique(['tenant_id', 'type', 'name', 'deleted_at'], 'positions_tenant_type_name_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('positions');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$now = now();
|
||||
|
||||
// position_type 코드 그룹 (직급/직책 구분)
|
||||
$positionTypes = [
|
||||
['code' => 'rank', 'name' => '직급', 'sort_order' => 1],
|
||||
['code' => 'title', 'name' => '직책', 'sort_order' => 2],
|
||||
];
|
||||
|
||||
foreach ($positionTypes as $item) {
|
||||
DB::table('common_codes')->updateOrInsert(
|
||||
['code_group' => 'position_type', 'code' => $item['code']],
|
||||
[
|
||||
'code_group' => 'position_type',
|
||||
'code' => $item['code'],
|
||||
'name' => $item['name'],
|
||||
'sort_order' => $item['sort_order'],
|
||||
'is_active' => true,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('common_codes')->where('code_group', 'position_type')->delete();
|
||||
}
|
||||
};
|
||||
@@ -73,6 +73,7 @@
|
||||
use App\Http\Controllers\Api\V1\PermissionController;
|
||||
use App\Http\Controllers\Api\V1\PlanController;
|
||||
use App\Http\Controllers\Api\V1\PopupController;
|
||||
use App\Http\Controllers\Api\V1\PositionController;
|
||||
use App\Http\Controllers\Api\V1\PostController;
|
||||
use App\Http\Controllers\Api\V1\PricingController;
|
||||
use App\Http\Controllers\Api\V1\PurchaseController;
|
||||
@@ -292,6 +293,16 @@
|
||||
Route::delete('/{id}/permissions/{permission}', [DepartmentController::class, 'revokePermissions'])->name('v1.departments.permissions.revoke'); // 권한 제거(해당 메뉴 범위까지)
|
||||
});
|
||||
|
||||
// Position API (직급/직책 통합 관리)
|
||||
Route::prefix('positions')->group(function () {
|
||||
Route::get('', [PositionController::class, 'index'])->name('v1.positions.index');
|
||||
Route::post('', [PositionController::class, 'store'])->name('v1.positions.store');
|
||||
Route::put('/reorder', [PositionController::class, 'reorder'])->name('v1.positions.reorder');
|
||||
Route::get('/{id}', [PositionController::class, 'show'])->name('v1.positions.show');
|
||||
Route::put('/{id}', [PositionController::class, 'update'])->name('v1.positions.update');
|
||||
Route::delete('/{id}', [PositionController::class, 'destroy'])->name('v1.positions.destroy');
|
||||
});
|
||||
|
||||
// Employee API (사원 관리)
|
||||
Route::prefix('employees')->group(function () {
|
||||
Route::get('', [EmployeeController::class, 'index'])->name('v1.employees.index');
|
||||
|
||||
Reference in New Issue
Block a user