group(function () { // API KEY 인증 (글로벌 미들웨어로 이미 적용됨) Route::get('/debug-apikey', [ApiController::class, 'debugApikey']); // SAM API (글로벌 미들웨어로 이미 적용됨) Route::group([], function () { // Auth API Route::post('login', [ApiController::class, 'login'])->name('v1.users.login'); Route::middleware('auth:sanctum')->post('logout', [ApiController::class, 'logout'])->name('v1.users.logout'); Route::post('signup', [ApiController::class, 'signup'])->name('v1.users.signup'); Route::post('refresh', [RefreshController::class, 'refresh'])->name('v1.token.refresh'); Route::post('register', [RegisterController::class, 'register'])->name('v1.register'); // Tenant Admin API Route::prefix('admin')->group(function () { // 목록/생성 Route::get('users', [AdminController::class, 'index'])->name('v1.admin.users.index'); // 테넌트 사용자 목록 조회 Route::post('users', [AdminController::class, 'store'])->name('v1.admin.users.store'); // 테넌트 사용자 생성 // 단건 Route::get('users/{id}', [AdminController::class, 'show'])->name('v1.admin.users.show'); // 테넌트 사용자 단건 조회 Route::put('users/{id}', [AdminController::class, 'update'])->name('v1.admin.users.update'); // 테넌트 사용자 수정 // 소프트 삭제 복구 Route::delete('users/{id}', [AdminController::class, 'destroy'])->name('v1.admin.users.destroy'); // 테넌트 사용자 삭제(연결 삭제) Route::post('users/{id}/restore', [AdminController::class, 'restore'])->name('v1.admin.users.restore'); // 테넌트 사용자 삭제 복구 // 상태 토글 Route::patch('users/{id}/status', [AdminController::class, 'toggle'])->name('v1.admin.users.status.toggle'); // 테넌트 사용자 활성/비활성 // 역할 부여/해제 Route::post('users/{id}/roles', [AdminController::class, 'attach'])->name('v1.admin.users.roles.attach'); // 테넌트 사용자 역할 부여 Route::delete('users/{id}/roles/{role}', [AdminController::class, 'detach'])->name('v1.admin.users.roles.detach'); // 테넌트 사용자 역할 해제 // 비밀번호 초기화 Route::post('users/{id}/reset-password', [AdminController::class, 'reset'])->name('v1.admin.users.password.reset'); // 테넌트 사용자 비밀번호 초기화 }); // Member API Route::prefix('users')->group(function () { Route::get('index', [UserController::class, 'index'])->name('v1.users.index'); // 회원 목록 조회 Route::get('show/{user_no}', [UserController::class, 'show'])->name('v1.users.show'); // 회원 상세 조회 Route::get('me', [UserController::class, 'me'])->name('v1.users.users.me'); // 내 정보 조회 Route::put('me', [UserController::class, 'meUpdate'])->name('v1.users.me.update'); // 내 정보 수정 Route::put('me/password', [UserController::class, 'changePassword'])->name('v1.users.me.password'); // 비밀번호 변겅 Route::get('me/tenants', [UserController::class, 'tenants'])->name('v1.users.me.tenants.index'); // 내 테넌트 목록 Route::patch('me/tenants/switch', [UserController::class, 'switchTenant'])->name('v1.users.me.tenants.switch'); // 활성 테넌트 전환 }); // Tenant API Route::prefix('tenants')->group(function () { Route::get('list', [TenantController::class, 'index'])->name('v1.tenant.index'); // 테넌트 목록 조회 Route::get('/', [TenantController::class, 'show'])->name('v1.tenant.show'); // 테넌트 정보 조회 Route::put('/', [TenantController::class, 'update'])->name('v1.tenant.update'); // 테넌트 정보 수정 Route::post('/', [TenantController::class, 'store'])->name('v1.tenant.store'); // 테넌트 등록 Route::delete('/', [TenantController::class, 'destroy'])->name('v1.tenant.destroy'); // 테넌트 삭제(탈퇴) Route::put('/restore/{tenant_id}', [TenantController::class, 'restore'])->name('v1.tenant.restore'); // 테넌트 복구 }); // Tenant Statistics Field API Route::prefix('tenant-stat-fields')->group(function () { Route::get('/', [TenantStatFieldController::class, 'index'])->name('v1.tenant-stat-fields.index'); // 목록 조회 Route::post('/', [TenantStatFieldController::class, 'store'])->name('v1.tenant-stat-fields.store'); // 생성 Route::get('/{id}', [TenantStatFieldController::class, 'show'])->name('v1.tenant-stat-fields.show'); // 단건 조회 Route::patch('/{id}', [TenantStatFieldController::class, 'update'])->name('v1.tenant-stat-fields.update'); // 수정 Route::delete('/{id}', [TenantStatFieldController::class, 'destroy'])->name('v1.tenant-stat-fields.destroy'); // 삭제 Route::post('/reorder', [TenantStatFieldController::class, 'reorder'])->name('v1.tenant-stat-fields.reorder'); // 정렬 변경 Route::put('/bulk-upsert', [TenantStatFieldController::class, 'bulkUpsert'])->name('v1.tenant-stat-fields.bulk-upsert'); // 일괄 저장 }); // Menu API Route::middleware(['perm.map', 'permission'])->prefix('menus')->group(function () { Route::get('/', [MenuController::class, 'index'])->name('v1.menus.index'); Route::get('/{id}', [MenuController::class, 'show'])->name('v1.menus.show'); Route::post('/', [MenuController::class, 'store'])->name('v1.menus.store'); Route::patch('/{id}', [MenuController::class, 'update'])->name('v1.menus.update'); Route::delete('/{id}', [MenuController::class, 'destroy'])->name('v1.menus.destroy'); Route::post('/reorder', [MenuController::class, 'reorder'])->name('v1.menus.reorder'); Route::post('/{id}/toggle', [MenuController::class, 'toggle'])->name('v1.menus.toggle'); }); // Role API Route::prefix('roles')->group(function () { Route::get('/', [RoleController::class, 'index'])->name('v1.roles.index'); // view Route::post('/', [RoleController::class, 'store'])->name('v1.roles.store'); // create Route::get('/{id}', [RoleController::class, 'show'])->name('v1.roles.show'); // view Route::patch('/{id}', [RoleController::class, 'update'])->name('v1.roles.update'); // update Route::delete('/{id}', [RoleController::class, 'destroy'])->name('v1.roles.destroy'); // delete }); // Role Permission API Route::prefix('roles/{id}/permissions')->group(function () { Route::get('/', [RolePermissionController::class, 'index'])->name('v1.roles.perms.index'); // list Route::post('/', [RolePermissionController::class, 'grant'])->name('v1.roles.perms.grant'); // grant Route::delete('/', [RolePermissionController::class, 'revoke'])->name('v1.roles.perms.revoke'); // revoke Route::put('/sync', [RolePermissionController::class, 'sync'])->name('v1.roles.perms.sync'); // sync }); // User Role API Route::prefix('users/{id}/roles')->group(function () { Route::get('/', [UserRoleController::class, 'index'])->name('v1.users.roles.index'); // list Route::post('/', [UserRoleController::class, 'grant'])->name('v1.users.roles.grant'); // grant Route::delete('/', [UserRoleController::class, 'revoke'])->name('v1.users.roles.revoke'); // revoke Route::put('/sync', [UserRoleController::class, 'sync'])->name('v1.users.roles.sync'); // sync }); // 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('/{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'); // 권한 제거(해당 메뉴 범위까지) }); // Permission API Route::prefix('permissions')->group(function () { Route::get('departments/{dept_id}/menu-matrix', [PermissionController::class, 'deptMenuMatrix'])->name('v1.permissions.deptMenuMatrix'); // 부서별 권한 메트릭스 Route::get('roles/{role_id}/menu-matrix', [PermissionController::class, 'roleMenuMatrix'])->name('v1.permissions.roleMenuMatrix'); // 부서별 권한 메트릭스 Route::get('users/{user_id}/menu-matrix', [PermissionController::class, 'userMenuMatrix'])->name('v1.permissions.userMenuMatrix'); // 부서별 권한 메트릭스 }); // Settings & Configuration (설정 및 환경설정 통합 관리) Route::prefix('settings')->group(function () { // 테넌트 필드 설정 (기존 fields에서 이동) Route::get('/fields', [TenantFieldSettingController::class, 'index'])->name('v1.settings.fields.index'); // 필드 설정 목록(전역+테넌트 병합 효과값) Route::put('/fields/bulk', [TenantFieldSettingController::class, 'bulkUpsert'])->name('v1.settings.fields.bulk'); // 필드 설정 대량 저장(트랜잭션 처리) Route::patch('/fields/{key}', [TenantFieldSettingController::class, 'updateOne'])->name('v1.settings.fields.update'); // 필드 설정 단건 수정/업데이트 // 옵션 그룹/값 (기존 opt-groups에서 이동) Route::get('/options', [TenantOptionGroupController::class, 'index'])->name('v1.settings.options.index'); // 옵션 그룹 목록 Route::post('/options', [TenantOptionGroupController::class, 'store'])->name('v1.settings.options.store'); // 옵션 그룹 생성 Route::get('/options/{id}', [TenantOptionGroupController::class, 'show'])->name('v1.settings.options.show'); // 옵션 그룹 단건 조회 Route::patch('/options/{id}', [TenantOptionGroupController::class, 'update'])->name('v1.settings.options.update'); // 옵션 그룹 수정 Route::delete('/options/{id}', [TenantOptionGroupController::class, 'destroy'])->name('v1.settings.options.destroy'); // 옵션 그룹 삭제 Route::get('/options/{gid}/values', [TenantOptionValueController::class, 'index'])->name('v1.settings.options.values.index'); // 옵션 값 목록 Route::post('/options/{gid}/values', [TenantOptionValueController::class, 'store'])->name('v1.settings.options.values.store'); // 옵션 값 생성 Route::get('/options/{gid}/values/{id}', [TenantOptionValueController::class, 'show'])->name('v1.settings.options.values.show'); // 옵션 값 단건 조회 Route::patch('/options/{gid}/values/{id}', [TenantOptionValueController::class, 'update'])->name('v1.settings.options.values.update'); // 옵션 값 수정 Route::delete('/options/{gid}/values/{id}', [TenantOptionValueController::class, 'destroy'])->name('v1.settings.options.values.destroy'); // 옵션 값 삭제 Route::patch('/options/{gid}/values/reorder', [TenantOptionValueController::class, 'reorder'])->name('v1.settings.options.values.reorder'); // 옵션 값 정렬순서 재배치 // 공통 코드 관리 (기존 common에서 이동) Route::get('/common/code', [CommonController::class, 'getComeCode'])->name('v1.settings.common.code'); // 공통코드 조회 (기존 v1.common.code에서 이동) Route::get('/common', [CommonController::class, 'list'])->name('v1.settings.common.list'); // 공통 코드 목록 Route::get('/common/{group}', [CommonController::class, 'index'])->name('v1.settings.common.index'); // 특정 그룹 코드 목록 Route::post('/common', [CommonController::class, 'store'])->name('v1.settings.common.store'); // 공통 코드 생성 Route::patch('/common/{id}', [CommonController::class, 'update'])->name('v1.settings.common.update'); // 공통 코드 수정 Route::delete('/common/{id}', [CommonController::class, 'destroy'])->name('v1.settings.common.destroy'); // 공통 코드 삭제 }); // 회원 프로필(테넌트 기준) Route::prefix('profiles')->group(function () { Route::get('', [TenantUserProfileController::class, 'index'])->name('v1.profiles.index'); // 프로필 목록(테넌트 기준) Route::get('/{userId}', [TenantUserProfileController::class, 'show'])->name('v1.profiles.show'); // 특정 사용자 프로필 조회 Route::patch('/{userId}', [TenantUserProfileController::class, 'update'])->name('v1.profiles.update'); // 특정 사용자 프로필 수정(관리자) Route::get('/me', [TenantUserProfileController::class, 'me'])->name('v1.profiles.me'); // 내 프로필 조회 Route::patch('/me', [TenantUserProfileController::class, 'updateMe'])->name('v1.profiles.me.update'); // 내 프로필 수정 }); // Category API (통합) Route::prefix('categories')->group(function () { // === 기본 Category CRUD === Route::get('', [CategoryController::class, 'index'])->name('v1.categories.index'); // 목록(페이징) Route::post('', [CategoryController::class, 'store'])->name('v1.categories.store'); // 생성 Route::get('/{id}', [CategoryController::class, 'show'])->name('v1.categories.show'); // 단건 Route::patch('/{id}', [CategoryController::class, 'update'])->name('v1.categories.update'); // 수정 Route::delete('/{id}', [CategoryController::class, 'destroy'])->name('v1.categories.destroy'); // 삭제(soft) // === 확장 기능 === Route::get('/tree', [CategoryController::class, 'tree'])->name('v1.categories.tree'); // 트리 Route::post('/reorder', [CategoryController::class, 'reorder'])->name('v1.categories.reorder'); // 정렬 일괄 Route::post('/{id}/toggle', [CategoryController::class, 'toggle'])->name('v1.categories.toggle'); // 활성 토글 Route::patch('/{id}/move', [CategoryController::class, 'move'])->name('v1.categories.move'); // 부모/순서 이동 // === Category Fields === // 목록/생성 (카테고리 기준) Route::get('/{id}/fields', [CategoryFieldController::class, 'index'])->name('v1.categories.fields.index'); // ?page&size&sort&order Route::post('/{id}/fields', [CategoryFieldController::class, 'store'])->name('v1.categories.fields.store'); // 단건 Route::get('/fields/{field}', [CategoryFieldController::class, 'show'])->name('v1.categories.fields.show'); Route::patch('/fields/{field}', [CategoryFieldController::class, 'update'])->name('v1.categories.fields.update'); Route::delete('/fields/{field}', [CategoryFieldController::class, 'destroy'])->name('v1.categories.fields.destroy'); // 일괄 정렬/업서트 Route::post('/{id}/fields/reorder', [CategoryFieldController::class, 'reorder'])->name('v1.categories.fields.reorder'); // [{id,sort_order}] Route::put('/{id}/fields/bulk-upsert', [CategoryFieldController::class, 'bulkUpsert'])->name('v1.categories.fields.bulk'); // [{id?,field_key,...}] // === Category Templates === // 버전 목록/생성 (카테고리 기준) Route::get('/{id}/templates', [CategoryTemplateController::class, 'index'])->name('v1.categories.templates.index'); // ?page&size Route::post('/{id}/templates', [CategoryTemplateController::class, 'store'])->name('v1.categories.templates.store'); // 새 버전 등록 // 단건 Route::get('/templates/{tpl}', [CategoryTemplateController::class, 'show'])->name('v1.categories.templates.show'); Route::patch('/templates/{tpl}', [CategoryTemplateController::class, 'update'])->name('v1.categories.templates.update'); // remarks 등 메타 수정 Route::delete('/templates/{tpl}', [CategoryTemplateController::class, 'destroy'])->name('v1.categories.templates.destroy'); // 운영 편의 Route::post('/{id}/templates/{tpl}/apply', [CategoryTemplateController::class, 'apply'])->name('v1.categories.templates.apply'); // 해당 버전 활성화 Route::get('/{id}/templates/{tpl}/preview', [CategoryTemplateController::class, 'preview'])->name('v1.categories.templates.preview'); // 렌더용 스냅샷 // (선택) 버전 간 diff Route::get('/{id}/templates/diff', [CategoryTemplateController::class, 'diff'])->name('v1.categories.templates.diff'); // ?a=ver&b=ver // === Category Logs === Route::get('/{id}/logs', [CategoryLogController::class, 'index'])->name('v1.categories.logs.index'); // ?action=&from=&to=&page&size Route::get('/logs/{log}', [CategoryLogController::class, 'show'])->name('v1.categories.logs.show'); // (선택) 특정 변경 시점으로 카테고리 복구(템플릿/필드와 별개) // Route::post('{id}/logs/{log}/restore', [CategoryLogController::class, 'restore'])->name('v1.categories.logs.restore'); }); // Classifications API Route::prefix('classifications')->group(function () { Route::get('', [ClassificationController::class, 'index'])->name('v1.classifications.index'); // 목록 Route::post('', [ClassificationController::class, 'store'])->name('v1.classifications.store'); // 생성 Route::get('/{id}', [ClassificationController::class, 'show'])->whereNumber('id')->name('v1.classifications.show'); // 단건 Route::patch('/{id}', [ClassificationController::class, 'update'])->whereNumber('id')->name('v1.classifications.update'); // 수정 Route::delete('/{id}', [ClassificationController::class, 'destroy'])->whereNumber('id')->name('v1.classifications.destroy'); // 삭제 }); // Clients (거래처 관리) Route::prefix('clients')->group(function () { Route::get('', [ClientController::class, 'index'])->name('v1.clients.index'); // 목록 Route::post('', [ClientController::class, 'store'])->name('v1.clients.store'); // 생성 Route::get('/{id}', [ClientController::class, 'show'])->whereNumber('id')->name('v1.clients.show'); // 단건 Route::put('/{id}', [ClientController::class, 'update'])->whereNumber('id')->name('v1.clients.update'); // 수정 Route::delete('/{id}', [ClientController::class, 'destroy'])->whereNumber('id')->name('v1.clients.destroy'); // 삭제 Route::patch('/{id}/toggle', [ClientController::class, 'toggle'])->whereNumber('id')->name('v1.clients.toggle'); // 활성/비활성 }); // Client Groups (고객 그룹 관리) Route::prefix('client-groups')->group(function () { Route::get('', [ClientGroupController::class, 'index'])->name('v1.client-groups.index'); // 목록 Route::post('', [ClientGroupController::class, 'store'])->name('v1.client-groups.store'); // 생성 Route::get('/{id}', [ClientGroupController::class, 'show'])->whereNumber('id')->name('v1.client-groups.show'); // 단건 Route::put('/{id}', [ClientGroupController::class, 'update'])->whereNumber('id')->name('v1.client-groups.update'); // 수정 Route::delete('/{id}', [ClientGroupController::class, 'destroy'])->whereNumber('id')->name('v1.client-groups.destroy'); // 삭제 Route::patch('/{id}/toggle', [ClientGroupController::class, 'toggle'])->whereNumber('id')->name('v1.client-groups.toggle'); // 활성/비활성 }); // Pricing (가격 이력 관리) Route::prefix('pricing')->group(function () { Route::get('', [PricingController::class, 'index'])->name('v1.pricing.index'); // 목록 Route::get('/show', [PricingController::class, 'show'])->name('v1.pricing.show'); // 단일 항목 가격 조회 Route::post('/bulk', [PricingController::class, 'bulk'])->name('v1.pricing.bulk'); // 여러 항목 일괄 조회 Route::post('/upsert', [PricingController::class, 'upsert'])->name('v1.pricing.upsert'); // 가격 등록/수정 Route::delete('/{id}', [PricingController::class, 'destroy'])->whereNumber('id')->name('v1.pricing.destroy'); // 삭제 }); // Products & Materials (제품/자재 통합 관리) Route::prefix('products')->group(function () { // 제품 카테고리 (기존 product/category에서 이동) Route::get('/categories', [ProductController::class, 'getCategory'])->name('v1.products.categories'); // 제품 카테고리 // 자재 관리 (기존 독립 materials에서 이동) - ProductController 기본 라우팅보다 앞에 위치 Route::get('/materials', [MaterialController::class, 'index'])->name('v1.products.materials.index'); // 자재 목록 Route::post('/materials', [MaterialController::class, 'store'])->name('v1.products.materials.store'); // 자재 생성 Route::get('/materials/{id}', [MaterialController::class, 'show'])->name('v1.products.materials.show'); // 자재 단건 Route::patch('/materials/{id}', [MaterialController::class, 'update'])->name('v1.products.materials.update'); // 자재 수정 Route::delete('/materials/{id}', [MaterialController::class, 'destroy'])->name('v1.products.materials.destroy'); // 자재 삭제 // (선택) 드롭다운/모달용 간편 검색 & 활성 토글 Route::get('/search', [ProductController::class, 'search'])->name('v1.products.search'); Route::post('/{id}/toggle', [ProductController::class, 'toggle'])->name('v1.products.toggle'); Route::get('', [ProductController::class, 'index'])->name('v1.products.index'); // 목록/검색(q, category_id, product_type, active, page/size) Route::post('', [ProductController::class, 'store'])->name('v1.products.store'); // 생성 Route::get('/{id}', [ProductController::class, 'show'])->name('v1.products.show'); // 단건 Route::patch('/{id}', [ProductController::class, 'update'])->name('v1.products.update'); // 수정 Route::delete('/{id}', [ProductController::class, 'destroy'])->name('v1.products.destroy'); // 삭제(soft) // BOM 카테고리 Route::get('bom/categories', [ProductBomItemController::class, 'suggestCategories'])->name('v1.products.bom.categories.suggest'); // 전역(테넌트) 추천 Route::get('{id}/bom/categories', [ProductBomItemController::class, 'listCategories'])->name('v1.products.bom.categories'); // 해당 제품에서 사용 중 }); // Items (통합 품목 조회 - materials + products UNION) Route::prefix('items')->group(function () { Route::get('', [\App\Http\Controllers\Api\V1\ItemsController::class, 'index'])->name('v1.items.index'); // 통합 목록 Route::post('', [\App\Http\Controllers\Api\V1\ItemsController::class, 'store'])->name('v1.items.store'); // 품목 생성 Route::get('/code/{code}', [\App\Http\Controllers\Api\V1\ItemsController::class, 'showByCode'])->name('v1.items.show_by_code'); // code 기반 조회 Route::get('/{id}', [\App\Http\Controllers\Api\V1\ItemsController::class, 'show'])->name('v1.items.show'); // 단건 (item_type 파라미터 필수) Route::put('/{code}', [\App\Http\Controllers\Api\V1\ItemsController::class, 'update'])->name('v1.items.update'); // 품목 수정 Route::delete('/{code}', [\App\Http\Controllers\Api\V1\ItemsController::class, 'destroy'])->name('v1.items.destroy'); // 품목 삭제 }); // Items BOM (Code-based BOM API - adapter for frontend) Route::prefix('items/{code}/bom')->group(function () { Route::get('', [\App\Http\Controllers\Api\V1\ItemsBomController::class, 'index'])->name('v1.items.bom.index'); // BOM 목록 (flat) Route::get('/tree', [\App\Http\Controllers\Api\V1\ItemsBomController::class, 'tree'])->name('v1.items.bom.tree'); // BOM 트리 (계층) Route::post('', [\App\Http\Controllers\Api\V1\ItemsBomController::class, 'store'])->name('v1.items.bom.store'); // BOM 추가 (bulk) Route::put('/{lineId}', [\App\Http\Controllers\Api\V1\ItemsBomController::class, 'update'])->name('v1.items.bom.update'); // BOM 수정 Route::delete('/{lineId}', [\App\Http\Controllers\Api\V1\ItemsBomController::class, 'destroy'])->name('v1.items.bom.destroy'); // BOM 삭제 Route::get('/summary', [\App\Http\Controllers\Api\V1\ItemsBomController::class, 'summary'])->name('v1.items.bom.summary'); // BOM 요약 Route::get('/validate', [\App\Http\Controllers\Api\V1\ItemsBomController::class, 'validate'])->name('v1.items.bom.validate'); // BOM 검증 Route::post('/replace', [\App\Http\Controllers\Api\V1\ItemsBomController::class, 'replace'])->name('v1.items.bom.replace'); // BOM 전체 교체 Route::post('/reorder', [\App\Http\Controllers\Api\V1\ItemsBomController::class, 'reorder'])->name('v1.items.bom.reorder'); // BOM 정렬 Route::get('/categories', [\App\Http\Controllers\Api\V1\ItemsBomController::class, 'listCategories'])->name('v1.items.bom.categories'); // 카테고리 목록 }); // Items Files (Code-based File Upload API) Route::prefix('items/{code}/files')->group(function () { Route::post('', [\App\Http\Controllers\Api\V1\ItemsFileController::class, 'upload'])->name('v1.items.files.upload'); // 파일 업로드 Route::delete('/{type}', [\App\Http\Controllers\Api\V1\ItemsFileController::class, 'delete'])->name('v1.items.files.delete'); // 파일 삭제 (type: bending_diagram|specification|certification) }); // BOM (product_components: ref_type=PRODUCT|MATERIAL) Route::prefix('products/{id}/bom')->group(function () { Route::post('/', [ProductBomItemController::class, 'replace'])->name('v1.products.bom.replace'); Route::get('/items', [ProductBomItemController::class, 'index'])->name('v1.products.bom.items.index'); // 조회(제품+자재 병합) Route::post('/items/bulk', [ProductBomItemController::class, 'bulkUpsert'])->name('v1.products.bom.items.bulk'); // 대량 업서트 Route::patch('/items/{item}', [ProductBomItemController::class, 'update'])->name('v1.products.bom.items.update'); // 단건 수정 Route::delete('/items/{item}', [ProductBomItemController::class, 'destroy'])->name('v1.products.bom.items.destroy'); // 단건 삭제 Route::post('/items/reorder', [ProductBomItemController::class, 'reorder'])->name('v1.products.bom.items.reorder'); // 정렬 변경 // (선택) 합계/검증 Route::get('/summary', [ProductBomItemController::class, 'summary'])->name('v1.products.bom.summary'); Route::get('/validate', [ProductBomItemController::class, 'validateBom'])->name('v1.products.bom.validate'); Route::get('/tree', [ProductBomItemController::class, 'tree'])->name('v1.products.bom.tree'); }); // 설계 전용 (Design) - 운영과 분리된 네임스페이스/경로 Route::prefix('design')->group(function () { Route::get('/models', [DesignModelController::class, 'index'])->name('v1.design.models.index'); Route::post('/models', [DesignModelController::class, 'store'])->name('v1.design.models.store'); Route::get('/models/{id}', [DesignModelController::class, 'show'])->name('v1.design.models.show'); Route::put('/models/{id}', [DesignModelController::class, 'update'])->name('v1.design.models.update'); Route::delete('/models/{id}', [DesignModelController::class, 'destroy'])->name('v1.design.models.destroy'); Route::get('/models/{modelId}/versions', [DesignModelVersionController::class, 'index'])->name('v1.design.models.versions.index'); Route::post('/models/{modelId}/versions', [DesignModelVersionController::class, 'createDraft'])->name('v1.design.models.versions.store'); Route::post('/versions/{versionId}/release', [DesignModelVersionController::class, 'release'])->name('v1.design.versions.release'); Route::get('/versions/{versionId}/bom-templates', [DesignBomTemplateController::class, 'listByVersion'])->name('v1.design.bom.templates.index'); Route::post('/versions/{versionId}/bom-templates', [DesignBomTemplateController::class, 'upsertTemplate'])->name('v1.design.bom.templates.store'); Route::get('/bom-templates/{templateId}', [DesignBomTemplateController::class, 'show'])->name('v1.design.bom.templates.show'); Route::put('/bom-templates/{templateId}/items', [DesignBomTemplateController::class, 'replaceItems'])->name('v1.design.bom.templates.items.replace'); Route::get('/bom-templates/{templateId}/diff', [DesignBomTemplateController::class, 'diff'])->name('v1.design.bom.templates.diff'); Route::post('/bom-templates/{templateId}/clone', [DesignBomTemplateController::class, 'cloneTemplate'])->name('v1.design.bom.templates.clone'); // 감사 로그 조회 Route::get('/audit-logs', [DesignAuditLogController::class, 'index'])->name('v1.design.audit-logs.index'); // BOM 계산 시스템 Route::get('/models/{modelId}/estimate-parameters', [BomCalculationController::class, 'getEstimateParameters'])->name('v1.design.models.estimate-parameters'); Route::post('/bom-templates/{bomTemplateId}/calculate-bom', [BomCalculationController::class, 'calculateBom'])->name('v1.design.bom-templates.calculate-bom'); Route::get('/companies/{companyName}/formulas', [BomCalculationController::class, 'getCompanyFormulas'])->name('v1.design.companies.formulas'); Route::post('/companies/{companyName}/formulas/{formulaType}', [BomCalculationController::class, 'saveCompanyFormula'])->name('v1.design.companies.formulas.save'); Route::post('/formulas/test', [BomCalculationController::class, 'testFormula'])->name('v1.design.formulas.test'); }); // 모델셋 관리 API (견적 시스템) Route::prefix('model-sets')->group(function () { Route::get('/', [ModelSetController::class, 'index'])->name('v1.model-sets.index'); // 모델셋 목록 Route::post('/', [ModelSetController::class, 'store'])->name('v1.model-sets.store'); // 모델셋 생성 Route::get('/{id}', [ModelSetController::class, 'show'])->name('v1.model-sets.show'); // 모델셋 상세 Route::put('/{id}', [ModelSetController::class, 'update'])->name('v1.model-sets.update'); // 모델셋 수정 Route::delete('/{id}', [ModelSetController::class, 'destroy'])->name('v1.model-sets.destroy'); // 모델셋 삭제 Route::post('/{id}/clone', [ModelSetController::class, 'clone'])->name('v1.model-sets.clone'); // 모델셋 복제 // 모델셋 세부 기능 Route::get('/{id}/fields', [ModelSetController::class, 'getCategoryFields'])->name('v1.model-sets.fields'); // 카테고리 필드 조회 Route::get('/{id}/bom-templates', [ModelSetController::class, 'getBomTemplates'])->name('v1.model-sets.bom-templates'); // BOM 템플릿 조회 Route::get('/{id}/estimate-parameters', [ModelSetController::class, 'getEstimateParameters'])->name('v1.model-sets.estimate-parameters'); // 견적 파라미터 Route::post('/{id}/calculate-bom', [ModelSetController::class, 'calculateBom'])->name('v1.model-sets.calculate-bom'); // BOM 계산 }); // 견적 관리 API Route::prefix('estimates')->group(function () { Route::get('/', [EstimateController::class, 'index'])->name('v1.estimates.index'); // 견적 목록 Route::post('/', [EstimateController::class, 'store'])->name('v1.estimates.store'); // 견적 생성 Route::get('/{id}', [EstimateController::class, 'show'])->name('v1.estimates.show'); // 견적 상세 Route::put('/{id}', [EstimateController::class, 'update'])->name('v1.estimates.update'); // 견적 수정 Route::delete('/{id}', [EstimateController::class, 'destroy'])->name('v1.estimates.destroy'); // 견적 삭제 Route::post('/{id}/clone', [EstimateController::class, 'clone'])->name('v1.estimates.clone'); // 견적 복제 Route::put('/{id}/status', [EstimateController::class, 'changeStatus'])->name('v1.estimates.status'); // 견적 상태 변경 // 견적 폼 및 계산 기능 Route::get('/form-schema/{model_set_id}', [EstimateController::class, 'getFormSchema'])->name('v1.estimates.form-schema'); // 견적 폼 스키마 Route::post('/preview/{model_set_id}', [EstimateController::class, 'previewCalculation'])->name('v1.estimates.preview'); // 견적 계산 미리보기 }); // 파일 저장소 API Route::prefix('files')->group(function () { Route::post('/upload', [FileStorageController::class, 'upload'])->name('v1.files.upload'); // 파일 업로드 (임시) Route::post('/move', [FileStorageController::class, 'move'])->name('v1.files.move'); // 파일 이동 (temp → folder) Route::get('/', [FileStorageController::class, 'index'])->name('v1.files.index'); // 파일 목록 Route::get('/trash', [FileStorageController::class, 'trash'])->name('v1.files.trash'); // 휴지통 목록 Route::get('/{id}', [FileStorageController::class, 'show'])->name('v1.files.show'); // 파일 상세 Route::get('/{id}/download', [FileStorageController::class, 'download'])->name('v1.files.download'); // 파일 다운로드 Route::delete('/{id}', [FileStorageController::class, 'destroy'])->name('v1.files.destroy'); // 파일 삭제 (soft) Route::post('/{id}/restore', [FileStorageController::class, 'restore'])->name('v1.files.restore'); // 파일 복구 Route::delete('/{id}/permanent', [FileStorageController::class, 'permanentDelete'])->name('v1.files.permanent'); // 파일 영구 삭제 Route::post('/{id}/share', [FileStorageController::class, 'createShareLink'])->name('v1.files.share'); // 공유 링크 생성 }); // 저장소 사용량 Route::get('/storage/usage', [FileStorageController::class, 'storageUsage'])->name('v1.storage.usage'); // 폴더 관리 API Route::prefix('folders')->group(function () { Route::get('/', [FolderController::class, 'index'])->name('v1.folders.index'); // 폴더 목록 Route::post('/', [FolderController::class, 'store'])->name('v1.folders.store'); // 폴더 생성 Route::get('/{id}', [FolderController::class, 'show'])->name('v1.folders.show'); // 폴더 상세 Route::put('/{id}', [FolderController::class, 'update'])->name('v1.folders.update'); // 폴더 수정 Route::delete('/{id}', [FolderController::class, 'destroy'])->name('v1.folders.destroy'); // 폴더 삭제/비활성화 Route::post('/reorder', [FolderController::class, 'reorder'])->name('v1.folders.reorder'); // 폴더 순서 변경 }); // 품목기준관리 (ItemMaster) API Route::prefix('item-master')->group(function () { // 초기화 Route::get('/init', [ItemMasterController::class, 'init'])->name('v1.item-master.init'); // 페이지 관리 Route::get('/pages', [ItemPageController::class, 'index'])->name('v1.item-master.pages.index'); Route::post('/pages', [ItemPageController::class, 'store'])->name('v1.item-master.pages.store'); Route::put('/pages/{id}', [ItemPageController::class, 'update'])->name('v1.item-master.pages.update'); Route::delete('/pages/{id}', [ItemPageController::class, 'destroy'])->name('v1.item-master.pages.destroy'); // 섹션 관리 Route::post('/pages/{pageId}/sections', [ItemSectionController::class, 'store'])->name('v1.item-master.sections.store'); Route::put('/sections/{id}', [ItemSectionController::class, 'update'])->name('v1.item-master.sections.update'); Route::delete('/sections/{id}', [ItemSectionController::class, 'destroy'])->name('v1.item-master.sections.destroy'); Route::put('/pages/{pageId}/sections/reorder', [ItemSectionController::class, 'reorder'])->name('v1.item-master.sections.reorder'); // 필드 관리 Route::post('/sections/{sectionId}/fields', [ItemFieldController::class, 'store'])->name('v1.item-master.fields.store'); Route::put('/fields/{id}', [ItemFieldController::class, 'update'])->name('v1.item-master.fields.update'); Route::delete('/fields/{id}', [ItemFieldController::class, 'destroy'])->name('v1.item-master.fields.destroy'); Route::put('/sections/{sectionId}/fields/reorder', [ItemFieldController::class, 'reorder'])->name('v1.item-master.fields.reorder'); // BOM 항목 관리 Route::post('/sections/{sectionId}/bom-items', [ItemBomItemController::class, 'store'])->name('v1.item-master.bom-items.store'); Route::put('/bom-items/{id}', [ItemBomItemController::class, 'update'])->name('v1.item-master.bom-items.update'); Route::delete('/bom-items/{id}', [ItemBomItemController::class, 'destroy'])->name('v1.item-master.bom-items.destroy'); // 섹션 템플릿 Route::get('/section-templates', [SectionTemplateController::class, 'index'])->name('v1.item-master.section-templates.index'); Route::post('/section-templates', [SectionTemplateController::class, 'store'])->name('v1.item-master.section-templates.store'); Route::put('/section-templates/{id}', [SectionTemplateController::class, 'update'])->name('v1.item-master.section-templates.update'); Route::delete('/section-templates/{id}', [SectionTemplateController::class, 'destroy'])->name('v1.item-master.section-templates.destroy'); // 마스터 필드 Route::get('/master-fields', [ItemMasterFieldController::class, 'index'])->name('v1.item-master.master-fields.index'); Route::post('/master-fields', [ItemMasterFieldController::class, 'store'])->name('v1.item-master.master-fields.store'); Route::put('/master-fields/{id}', [ItemMasterFieldController::class, 'update'])->name('v1.item-master.master-fields.update'); Route::delete('/master-fields/{id}', [ItemMasterFieldController::class, 'destroy'])->name('v1.item-master.master-fields.destroy'); // 커스텀 탭 Route::get('/custom-tabs', [CustomTabController::class, 'index'])->name('v1.item-master.custom-tabs.index'); Route::post('/custom-tabs', [CustomTabController::class, 'store'])->name('v1.item-master.custom-tabs.store'); Route::put('/custom-tabs/reorder', [CustomTabController::class, 'reorder'])->name('v1.item-master.custom-tabs.reorder'); Route::put('/custom-tabs/{id}', [CustomTabController::class, 'update'])->name('v1.item-master.custom-tabs.update'); Route::delete('/custom-tabs/{id}', [CustomTabController::class, 'destroy'])->name('v1.item-master.custom-tabs.destroy'); // 단위 옵션 Route::get('/unit-options', [UnitOptionController::class, 'index'])->name('v1.item-master.unit-options.index'); Route::post('/unit-options', [UnitOptionController::class, 'store'])->name('v1.item-master.unit-options.store'); Route::delete('/unit-options/{id}', [UnitOptionController::class, 'destroy'])->name('v1.item-master.unit-options.destroy'); }); }); // 공유 링크 다운로드 (인증 불필요 - auth.apikey 그룹 밖) Route::get('/files/share/{token}', [FileStorageController::class, 'downloadShared'])->name('v1.files.share.download'); });