- Design BOM 템플릿 diff/clone 엔드포인트 추가 - 컨트롤러 검증 로직 FormRequest 분리(DiffRequest/CloneRequest/Upsert/ReplaceItems) - BomTemplateService에 diffTemplates/cloneTemplate/replaceItems/쇼우 로직 정리 - ModelVersionController createDraft FormRequest 적용 및 서비스 호출 정리 - 모델버전 release 전 유효성 검사(존재/활성/테넌트 일치, qty>0, 중복 금지) 추가 - DB enum 미사용 방침 준수(status 문자열 유지) - model_versions 인덱스 최적화(tenant_id, model_id, status / 기간 범위) - Swagger 문서(Design BOM) 및 i18n 메시지 키 추가
364 lines
26 KiB
PHP
364 lines
26 KiB
PHP
<?php
|
|
|
|
use App\Http\Controllers\Api\V1\CategoryLogController;
|
|
use App\Http\Controllers\Api\V1\ProductBomItemController;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Route;
|
|
use App\Http\Controllers\Api\V1\CommonController;
|
|
use App\Http\Controllers\Api\V1\ApiController;
|
|
use App\Http\Controllers\Api\V1\FileController;
|
|
use App\Http\Controllers\Api\V1\ProductController;
|
|
use App\Http\Controllers\Api\V1\MaterialController;
|
|
use App\Http\Controllers\Api\V1\ModelController;
|
|
use App\Http\Controllers\Api\V1\BomController;
|
|
use App\Http\Controllers\Api\V1\UserController;
|
|
use App\Http\Controllers\Api\V1\TenantController;
|
|
use App\Http\Controllers\Api\V1\AdminController;
|
|
use App\Http\Controllers\Api\V1\MenuController;
|
|
use App\Http\Controllers\Api\V1\RoleController;
|
|
use App\Http\Controllers\Api\V1\RolePermissionController;
|
|
use App\Http\Controllers\Api\V1\UserRoleController;
|
|
use App\Http\Controllers\Api\V1\DepartmentController;
|
|
use App\Http\Controllers\Api\V1\PermissionController;
|
|
use App\Http\Controllers\Api\V1\TenantFieldSettingController;
|
|
use App\Http\Controllers\Api\V1\TenantOptionGroupController;
|
|
use App\Http\Controllers\Api\V1\TenantOptionValueController;
|
|
use App\Http\Controllers\Api\V1\TenantUserProfileController;
|
|
use App\Http\Controllers\Api\V1\CategoryController;
|
|
use App\Http\Controllers\Api\V1\CategoryFieldController;
|
|
use App\Http\Controllers\Api\V1\CategoryTemplateController;
|
|
use App\Http\Controllers\Api\V1\ClassificationController;
|
|
|
|
|
|
// 설계 전용 (디자인 네임스페이스)
|
|
use App\Http\Controllers\Api\V1\Design\DesignModelController as DesignModelController;
|
|
use App\Http\Controllers\Api\V1\Design\ModelVersionController as DesignModelVersionController;
|
|
use App\Http\Controllers\Api\V1\Design\BomTemplateController as DesignBomTemplateController;
|
|
|
|
// error test
|
|
Route::get('/test-error', function () {
|
|
throw new \Exception('슬랙 전송 테스트 예외');
|
|
});
|
|
|
|
// V1 초기 개발
|
|
Route::prefix('v1')->group(function () {
|
|
|
|
# API KEY 인증
|
|
Route::middleware('auth.apikey')->get('/debug-apikey', [ApiController::class, 'debugApikey']);
|
|
|
|
# SAM API
|
|
Route::middleware('auth.apikey')->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');
|
|
|
|
|
|
// Common API
|
|
Route::prefix('common')->group(function () {
|
|
Route::get('code', [CommonController::class, 'getComeCode'])->name('v1.common.code'); // 공통코드 조회
|
|
});
|
|
|
|
|
|
// Product API
|
|
Route::prefix('product')->group(function () {
|
|
Route::get('category', [ProductController::class, 'getCategory'])->name('v1.product.category'); // 제품 카테고리
|
|
});
|
|
|
|
|
|
// 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'); // 테넌트 복구
|
|
});
|
|
|
|
// File API
|
|
Route::prefix('file')->group(function () {
|
|
Route::post('upload', [FileController::class, 'upload'])->name('v1.file.upload'); // 파일 업로드 (등록/수정)
|
|
Route::get('list', [FileController::class, 'list'])->name('v1.file.list'); // 파일 목록 조회
|
|
Route::delete('delete', [FileController::class, 'delete'])->name('v1.file.delete'); // 파일 삭제
|
|
Route::get('info', [FileController::class, 'findFile'])->name('v1.file.info'); // 파일 정보 조회
|
|
});
|
|
|
|
|
|
// Material, Model, BOM API
|
|
Route::resource('materials', MaterialController::class)->except(['create', 'edit']); // 자재관리
|
|
Route::resource('models', ModelController::class)->except(['v1.create', 'edit']); // 모델관리
|
|
Route::resource('boms', BomController::class)->except(['create', 'edit']); // BOM관리
|
|
|
|
|
|
// 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('departments.index'); // 목록
|
|
Route::post('', [DepartmentController::class, 'store'])->name('departments.store'); // 생성
|
|
Route::get('/{id}', [DepartmentController::class, 'show'])->name('departments.show'); // 단건
|
|
Route::patch('/{id}', [DepartmentController::class, 'update'])->name('departments.update'); // 수정
|
|
Route::delete('/{id}', [DepartmentController::class, 'destroy'])->name('departments.destroy'); // 삭제(soft)
|
|
|
|
// 부서-사용자
|
|
Route::get('/{id}/users', [DepartmentController::class, 'listUsers'])->name('departments.users.index'); // 부서 사용자 목록
|
|
Route::post('/{id}/users', [DepartmentController::class, 'attachUser'])->name('departments.users.attach'); // 사용자 배정(주/부서)
|
|
Route::delete('/{id}/users/{user}', [DepartmentController::class, 'detachUser'])->name('departments.users.detach'); // 사용자 제거
|
|
Route::patch('/{id}/users/{user}/primary', [DepartmentController::class, 'setPrimary'])->name('departments.users.primary'); // 주부서 설정/해제
|
|
|
|
// 부서-권한
|
|
Route::get('/{id}/permissions', [DepartmentController::class, 'listPermissions'])->name('departments.permissions.index'); // 권한 목록
|
|
Route::post('/{id}/permissions', [DepartmentController::class, 'upsertPermissions'])->name('departments.permissions.upsert'); // 권한 부여/차단(메뉴별 가능)
|
|
Route::delete('/{id}/permissions/{permission}', [DepartmentController::class, 'revokePermissions'])->name('departments.permissions.revoke'); // 권한 제거(해당 메뉴 범위까지)
|
|
});
|
|
|
|
|
|
// Permission API
|
|
Route::prefix('permissions')->group(function () {
|
|
Route::get('departments/{dept_id}/menu-matrix', [PermissionController::class, 'deptMenuMatrix'])->name('permissions.deptMenuMatrix'); // 부서별 권한 메트릭스
|
|
Route::get('roles/{role_id}/menu-matrix', [PermissionController::class, 'roleMenuMatrix'])->name('permissions.roleMenuMatrix'); // 부서별 권한 메트릭스
|
|
Route::get('users/{user_id}/menu-matrix', [PermissionController::class, 'userMenuMatrix'])->name('permissions.userMenuMatrix'); // 부서별 권한 메트릭스
|
|
});
|
|
|
|
|
|
// 테넌트 필드 설정
|
|
Route::prefix('fields')->group(function () {
|
|
Route::get('', [TenantFieldSettingController::class, 'index'])->name('v1.fields.index'); // 필드 설정 목록(전역+테넌트 병합 효과값)
|
|
Route::put('/bulk', [TenantFieldSettingController::class, 'bulkUpsert'])->name('v1.fields.bulk'); // 필드 설정 대량 저장(트랜잭션 처리)
|
|
Route::patch('/{key}', [TenantFieldSettingController::class, 'updateOne'])->name('v1.fields.update'); // 필드 설정 단건 수정/업데이트
|
|
});
|
|
|
|
// 옵션 그룹/값
|
|
Route::prefix('opt-groups')->group(function () {
|
|
Route::get('', [TenantOptionGroupController::class, 'index'])->name('v1.opt-groups.index'); // 옵션 그룹 목록
|
|
Route::post('', [TenantOptionGroupController::class, 'store'])->name('v1.opt-groups.store'); // 옵션 그룹 생성
|
|
Route::get('/{id}', [TenantOptionGroupController::class, 'show'])->name('v1.opt-groups.show'); // 옵션 그룹 단건 조회
|
|
Route::patch('/{id}', [TenantOptionGroupController::class, 'update'])->name('v1.opt-groups.update'); // 옵션 그룹 수정
|
|
Route::delete('/{id}', [TenantOptionGroupController::class, 'destroy'])->name('v1.opt-groups.destroy'); // 옵션 그룹 삭제
|
|
Route::get('/{gid}/values', [TenantOptionValueController::class, 'index'])->name('v1.opt-groups.values.index'); // 옵션 값 목록
|
|
Route::post('/{gid}/values', [TenantOptionValueController::class, 'store'])->name('v1.opt-groups.values.store'); // 옵션 값 생성
|
|
Route::get('/{gid}/values/{id}', [TenantOptionValueController::class, 'show'])->name('v1.opt-groups.values.show'); // 옵션 값 단건 조회
|
|
Route::patch('/{gid}/values/{id}', [TenantOptionValueController::class, 'update'])->name('v1.opt-groups.values.update'); // 옵션 값 수정
|
|
Route::delete('/{gid}/values/{id}', [TenantOptionValueController::class, 'destroy'])->name('v1.opt-groups.values.destroy'); // 옵션 값 삭제
|
|
Route::patch('/{gid}/values/reorder', [TenantOptionValueController::class, 'reorder'])->name('v1.opt-groups.values.reorder'); // 옵션 값 정렬순서 재배치
|
|
});
|
|
|
|
// 회원 프로필(테넌트 기준)
|
|
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 () {
|
|
|
|
// 확장 기능
|
|
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'); // 부모/순서 이동
|
|
|
|
// 기본
|
|
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)
|
|
});
|
|
|
|
// Category Field API
|
|
Route::prefix('categories')->group(function () {
|
|
// 목록/생성 (카테고리 기준)
|
|
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 Template API
|
|
Route::prefix('categories')->group(function () {
|
|
// 버전 목록/생성 (카테고리 기준)
|
|
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 Log API
|
|
Route::prefix('categories')->group(function () {
|
|
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'); // 삭제
|
|
});
|
|
|
|
// Products (모델/부품/서브어셈블리)
|
|
Route::prefix('products')->group(function (){
|
|
|
|
// (선택) 드롭다운/모달용 간편 검색 & 활성 토글
|
|
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'); // 해당 제품에서 사용 중
|
|
});
|
|
|
|
// 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');
|
|
});
|
|
|
|
|
|
});
|
|
});
|