Files
sam-api/routes/api/v1/hr.php
권혁성 a96499a66d feat: API 라우터 분리 및 버전 폴백 시스템 구현
- api.php를 13개 도메인별 파일로 분리 (1,479줄 → 61줄)
- ApiVersionMiddleware 생성 (헤더/쿼리 기반 버전 선택)
- v2 요청 시 v2 없으면 v1으로 자동 폴백
- 지원 헤더: Accept-Version, X-API-Version, api_version 쿼리

분리된 도메인:
auth, admin, users, tenants, hr, finance, sales,
inventory, production, design, files, boards, common

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 17:30:19 +09:00

216 lines
15 KiB
PHP

<?php
/**
* 인사 관리 API 라우트 (v1)
*
* - 부서/직급 관리
* - 직원 관리
* - 근태 관리
* - 휴가 관리
* - 전자결재
* - 현장 관리
* - 시공 관리
*/
use App\Http\Controllers\Api\V1\ApprovalController;
use App\Http\Controllers\Api\V1\ApprovalFormController;
use App\Http\Controllers\Api\V1\ApprovalLineController;
use App\Http\Controllers\Api\V1\AttendanceController;
use App\Http\Controllers\Api\V1\Construction\ContractController;
use App\Http\Controllers\Api\V1\Construction\HandoverReportController;
use App\Http\Controllers\Api\V1\Construction\StructureReviewController;
use App\Http\Controllers\Api\V1\DepartmentController;
use App\Http\Controllers\Api\V1\EmployeeController;
use App\Http\Controllers\Api\V1\LeaveController;
use App\Http\Controllers\Api\V1\LeavePolicyController;
use App\Http\Controllers\Api\V1\PositionController;
use App\Http\Controllers\Api\V1\SiteBriefingController;
use App\Http\Controllers\Api\V1\SiteController;
use Illuminate\Support\Facades\Route;
// Department API
Route::prefix('departments')->group(function () {
Route::get('', [DepartmentController::class, 'index'])->name('v1.departments.index'); // 목록
Route::post('', [DepartmentController::class, 'store'])->name('v1.departments.store'); // 생성
Route::get('/tree', [DepartmentController::class, 'tree'])->name('v1.departments.tree'); // 트리
Route::get('/{id}', [DepartmentController::class, 'show'])->name('v1.departments.show'); // 단건
Route::patch('/{id}', [DepartmentController::class, 'update'])->name('v1.departments.update'); // 수정
Route::delete('/{id}', [DepartmentController::class, 'destroy'])->name('v1.departments.destroy'); // 삭제(soft)
// 부서-사용자
Route::get('/{id}/users', [DepartmentController::class, 'listUsers'])->name('v1.departments.users.index'); // 부서 사용자 목록
Route::post('/{id}/users', [DepartmentController::class, 'attachUser'])->name('v1.departments.users.attach'); // 사용자 배정(주/부서)
Route::delete('/{id}/users/{user}', [DepartmentController::class, 'detachUser'])->name('v1.departments.users.detach'); // 사용자 제거
Route::patch('/{id}/users/{user}/primary', [DepartmentController::class, 'setPrimary'])->name('v1.departments.users.primary'); // 주부서 설정/해제
// 부서-권한
Route::get('/{id}/permissions', [DepartmentController::class, 'listPermissions'])->name('v1.departments.permissions.index'); // 권한 목록
Route::post('/{id}/permissions', [DepartmentController::class, 'upsertPermissions'])->name('v1.departments.permissions.upsert'); // 권한 부여/차단(메뉴별 가능)
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');
Route::post('', [EmployeeController::class, 'store'])->name('v1.employees.store');
Route::get('/stats', [EmployeeController::class, 'stats'])->name('v1.employees.stats');
Route::get('/{id}', [EmployeeController::class, 'show'])->name('v1.employees.show');
Route::patch('/{id}', [EmployeeController::class, 'update'])->name('v1.employees.update');
Route::delete('/{id}', [EmployeeController::class, 'destroy'])->name('v1.employees.destroy');
Route::post('/bulk-delete', [EmployeeController::class, 'bulkDelete'])->name('v1.employees.bulkDelete');
Route::post('/{id}/create-account', [EmployeeController::class, 'createAccount'])->name('v1.employees.createAccount');
Route::post('/{id}/revoke-account', [EmployeeController::class, 'revokeAccount'])->name('v1.employees.revokeAccount');
});
// Attendance API (근태 관리)
Route::prefix('attendances')->group(function () {
Route::get('', [AttendanceController::class, 'index'])->name('v1.attendances.index');
Route::post('', [AttendanceController::class, 'store'])->name('v1.attendances.store');
Route::get('/monthly-stats', [AttendanceController::class, 'monthlyStats'])->name('v1.attendances.monthlyStats');
Route::get('/export', [AttendanceController::class, 'export'])->name('v1.attendances.export');
Route::post('/check-in', [AttendanceController::class, 'checkIn'])->name('v1.attendances.checkIn');
Route::post('/check-out', [AttendanceController::class, 'checkOut'])->name('v1.attendances.checkOut');
Route::get('/{id}', [AttendanceController::class, 'show'])->name('v1.attendances.show');
Route::patch('/{id}', [AttendanceController::class, 'update'])->name('v1.attendances.update');
Route::delete('/{id}', [AttendanceController::class, 'destroy'])->name('v1.attendances.destroy');
Route::post('/bulk-delete', [AttendanceController::class, 'bulkDelete'])->name('v1.attendances.bulkDelete');
});
// Leave API (휴가 관리)
Route::prefix('leaves')->group(function () {
Route::get('', [LeaveController::class, 'index'])->name('v1.leaves.index');
Route::post('', [LeaveController::class, 'store'])->name('v1.leaves.store');
Route::get('/balances', [LeaveController::class, 'balances'])->name('v1.leaves.balances');
Route::get('/balance', [LeaveController::class, 'balance'])->name('v1.leaves.balance');
Route::get('/balance/{userId}', [LeaveController::class, 'userBalance'])->name('v1.leaves.userBalance');
Route::put('/balance', [LeaveController::class, 'setBalance'])->name('v1.leaves.setBalance');
Route::get('/grants', [LeaveController::class, 'grants'])->name('v1.leaves.grants');
Route::post('/grants', [LeaveController::class, 'storeGrant'])->name('v1.leaves.grants.store');
Route::delete('/grants/{id}', [LeaveController::class, 'destroyGrant'])->name('v1.leaves.grants.destroy');
Route::get('/{id}', [LeaveController::class, 'show'])->name('v1.leaves.show');
Route::patch('/{id}', [LeaveController::class, 'update'])->name('v1.leaves.update');
Route::delete('/{id}', [LeaveController::class, 'destroy'])->name('v1.leaves.destroy');
Route::post('/{id}/approve', [LeaveController::class, 'approve'])->name('v1.leaves.approve');
Route::post('/{id}/reject', [LeaveController::class, 'reject'])->name('v1.leaves.reject');
Route::post('/{id}/cancel', [LeaveController::class, 'cancel'])->name('v1.leaves.cancel');
});
// Leave Policy API (휴가 정책)
Route::get('/leave-policy', [LeavePolicyController::class, 'show'])->name('v1.leave-policy.show');
Route::put('/leave-policy', [LeavePolicyController::class, 'update'])->name('v1.leave-policy.update');
// Approval Form API (결재 양식)
Route::prefix('approval-forms')->group(function () {
Route::get('', [ApprovalFormController::class, 'index'])->name('v1.approval-forms.index');
Route::post('', [ApprovalFormController::class, 'store'])->name('v1.approval-forms.store');
Route::get('/active', [ApprovalFormController::class, 'active'])->name('v1.approval-forms.active');
Route::get('/{id}', [ApprovalFormController::class, 'show'])->whereNumber('id')->name('v1.approval-forms.show');
Route::patch('/{id}', [ApprovalFormController::class, 'update'])->whereNumber('id')->name('v1.approval-forms.update');
Route::delete('/{id}', [ApprovalFormController::class, 'destroy'])->whereNumber('id')->name('v1.approval-forms.destroy');
});
// Approval Line API (결재선)
Route::prefix('approval-lines')->group(function () {
Route::get('', [ApprovalLineController::class, 'index'])->name('v1.approval-lines.index');
Route::post('', [ApprovalLineController::class, 'store'])->name('v1.approval-lines.store');
Route::get('/{id}', [ApprovalLineController::class, 'show'])->whereNumber('id')->name('v1.approval-lines.show');
Route::patch('/{id}', [ApprovalLineController::class, 'update'])->whereNumber('id')->name('v1.approval-lines.update');
Route::delete('/{id}', [ApprovalLineController::class, 'destroy'])->whereNumber('id')->name('v1.approval-lines.destroy');
});
// Approval API (전자결재)
Route::prefix('approvals')->group(function () {
// 기안함
Route::get('/drafts', [ApprovalController::class, 'drafts'])->name('v1.approvals.drafts');
Route::get('/drafts/summary', [ApprovalController::class, 'draftsSummary'])->name('v1.approvals.drafts.summary');
// 결재함
Route::get('/inbox', [ApprovalController::class, 'inbox'])->name('v1.approvals.inbox');
Route::get('/inbox/summary', [ApprovalController::class, 'inboxSummary'])->name('v1.approvals.inbox.summary');
// 참조함
Route::get('/reference', [ApprovalController::class, 'reference'])->name('v1.approvals.reference');
// CRUD
Route::post('', [ApprovalController::class, 'store'])->name('v1.approvals.store');
Route::get('/{id}', [ApprovalController::class, 'show'])->whereNumber('id')->name('v1.approvals.show');
Route::patch('/{id}', [ApprovalController::class, 'update'])->whereNumber('id')->name('v1.approvals.update');
Route::delete('/{id}', [ApprovalController::class, 'destroy'])->whereNumber('id')->name('v1.approvals.destroy');
// 액션
Route::post('/{id}/submit', [ApprovalController::class, 'submit'])->whereNumber('id')->name('v1.approvals.submit');
Route::post('/{id}/approve', [ApprovalController::class, 'approve'])->whereNumber('id')->name('v1.approvals.approve');
Route::post('/{id}/reject', [ApprovalController::class, 'reject'])->whereNumber('id')->name('v1.approvals.reject');
Route::post('/{id}/cancel', [ApprovalController::class, 'cancel'])->whereNumber('id')->name('v1.approvals.cancel');
// 참조 열람
Route::post('/{id}/read', [ApprovalController::class, 'markRead'])->whereNumber('id')->name('v1.approvals.read');
Route::post('/{id}/unread', [ApprovalController::class, 'markUnread'])->whereNumber('id')->name('v1.approvals.unread');
});
// Site API (현장 관리)
Route::prefix('sites')->group(function () {
Route::get('', [SiteController::class, 'index'])->name('v1.sites.index');
Route::post('', [SiteController::class, 'store'])->name('v1.sites.store');
Route::get('/stats', [SiteController::class, 'stats'])->name('v1.sites.stats');
Route::get('/active', [SiteController::class, 'active'])->name('v1.sites.active');
Route::delete('/bulk', [SiteController::class, 'bulkDestroy'])->name('v1.sites.bulk-destroy');
Route::get('/{id}', [SiteController::class, 'show'])->whereNumber('id')->name('v1.sites.show');
Route::put('/{id}', [SiteController::class, 'update'])->whereNumber('id')->name('v1.sites.update');
Route::delete('/{id}', [SiteController::class, 'destroy'])->whereNumber('id')->name('v1.sites.destroy');
});
// Site Briefing API (현장설명회 관리)
Route::prefix('site-briefings')->group(function () {
Route::get('', [SiteBriefingController::class, 'index'])->name('v1.site-briefings.index');
Route::post('', [SiteBriefingController::class, 'store'])->name('v1.site-briefings.store');
Route::get('/stats', [SiteBriefingController::class, 'stats'])->name('v1.site-briefings.stats');
Route::delete('/bulk', [SiteBriefingController::class, 'bulkDestroy'])->name('v1.site-briefings.bulk-destroy');
Route::get('/{id}', [SiteBriefingController::class, 'show'])->whereNumber('id')->name('v1.site-briefings.show');
Route::put('/{id}', [SiteBriefingController::class, 'update'])->whereNumber('id')->name('v1.site-briefings.update');
Route::delete('/{id}', [SiteBriefingController::class, 'destroy'])->whereNumber('id')->name('v1.site-briefings.destroy');
});
// Construction API (시공관리)
Route::prefix('construction')->group(function () {
// Contract API (계약관리)
Route::prefix('contracts')->group(function () {
Route::get('', [ContractController::class, 'index'])->name('v1.construction.contracts.index');
Route::post('', [ContractController::class, 'store'])->name('v1.construction.contracts.store');
Route::get('/stats', [ContractController::class, 'stats'])->name('v1.construction.contracts.stats');
Route::get('/stage-counts', [ContractController::class, 'stageCounts'])->name('v1.construction.contracts.stage-counts');
Route::delete('/bulk', [ContractController::class, 'bulkDestroy'])->name('v1.construction.contracts.bulk-destroy');
Route::post('/from-bidding/{biddingId}', [ContractController::class, 'storeFromBidding'])->whereNumber('biddingId')->name('v1.construction.contracts.store-from-bidding');
Route::get('/{id}', [ContractController::class, 'show'])->whereNumber('id')->name('v1.construction.contracts.show');
Route::put('/{id}', [ContractController::class, 'update'])->whereNumber('id')->name('v1.construction.contracts.update');
Route::delete('/{id}', [ContractController::class, 'destroy'])->whereNumber('id')->name('v1.construction.contracts.destroy');
});
// HandoverReport API (인수인계보고서관리)
Route::prefix('handover-reports')->group(function () {
Route::get('', [HandoverReportController::class, 'index'])->name('v1.construction.handover-reports.index');
Route::post('', [HandoverReportController::class, 'store'])->name('v1.construction.handover-reports.store');
Route::get('/stats', [HandoverReportController::class, 'stats'])->name('v1.construction.handover-reports.stats');
Route::delete('/bulk', [HandoverReportController::class, 'bulkDestroy'])->name('v1.construction.handover-reports.bulk-destroy');
Route::get('/{id}', [HandoverReportController::class, 'show'])->whereNumber('id')->name('v1.construction.handover-reports.show');
Route::put('/{id}', [HandoverReportController::class, 'update'])->whereNumber('id')->name('v1.construction.handover-reports.update');
Route::delete('/{id}', [HandoverReportController::class, 'destroy'])->whereNumber('id')->name('v1.construction.handover-reports.destroy');
});
// StructureReview API (구조검토관리)
Route::prefix('structure-reviews')->group(function () {
Route::get('', [StructureReviewController::class, 'index'])->name('v1.construction.structure-reviews.index');
Route::post('', [StructureReviewController::class, 'store'])->name('v1.construction.structure-reviews.store');
Route::get('/stats', [StructureReviewController::class, 'stats'])->name('v1.construction.structure-reviews.stats');
Route::delete('/bulk', [StructureReviewController::class, 'bulkDestroy'])->name('v1.construction.structure-reviews.bulk-destroy');
Route::get('/{id}', [StructureReviewController::class, 'show'])->whereNumber('id')->name('v1.construction.structure-reviews.show');
Route::put('/{id}', [StructureReviewController::class, 'update'])->whereNumber('id')->name('v1.construction.structure-reviews.update');
Route::delete('/{id}', [StructureReviewController::class, 'destroy'])->whereNumber('id')->name('v1.construction.structure-reviews.destroy');
});
});