feat: 회원가입 + 테넌트 생성 통합 API 추가 (/api/v1/register)
- 사용자 등록 + 테넌트 생성 + 시스템 관리자 권한 자동 부여 - 사업자번호 조건부 검증 (active 테넌트만 unique) - 글로벌 메뉴 자동 복제 (parent_id 매핑 알고리즘) - DB 트랜잭션으로 전체 프로세스 원자성 보장 추가: - RegisterRequest: FormRequest 검증 (conditional unique) - RegisterService: 9-step 통합 비즈니스 로직 - RegisterController: ApiResponse::handle() 패턴 - RegisterApi: 완전한 Swagger 문서 수정: - MenusStep: 글로벌 메뉴 복제 로직 구현 - message.php: 'registered' 키 추가 - error.php: 4개 에러 메시지 추가 - routes/api.php: POST /api/v1/register 라우트 SAM API Rules 준수: - Service-First, FormRequest, i18n, Swagger, DB Transaction
This commit is contained in:
273
routes/api.php
273
routes/api.php
@@ -1,62 +1,58 @@
|
||||
<?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\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\ApiController;
|
||||
use App\Http\Controllers\Api\V1\CategoryController;
|
||||
use App\Http\Controllers\Api\V1\CategoryFieldController;
|
||||
use App\Http\Controllers\Api\V1\CategoryLogController;
|
||||
use App\Http\Controllers\Api\V1\CategoryTemplateController;
|
||||
use App\Http\Controllers\Api\V1\ClassificationController;
|
||||
use App\Http\Controllers\Api\V1\ClientController;
|
||||
use App\Http\Controllers\Api\V1\ClientGroupController;
|
||||
use App\Http\Controllers\Api\V1\PricingController;
|
||||
|
||||
// 설계 전용 (디자인 네임스페이스)
|
||||
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;
|
||||
use App\Http\Controllers\Api\V1\CommonController;
|
||||
use App\Http\Controllers\Api\V1\DepartmentController;
|
||||
use App\Http\Controllers\Api\V1\Design\AuditLogController as DesignAuditLogController;
|
||||
use App\Http\Controllers\Api\V1\Design\BomCalculationController;
|
||||
|
||||
// 모델셋 관리 (견적 시스템)
|
||||
use App\Http\Controllers\Api\V1\ModelSetController;
|
||||
use App\Http\Controllers\Api\V1\Design\BomTemplateController as DesignBomTemplateController;
|
||||
use App\Http\Controllers\Api\V1\Design\DesignModelController;
|
||||
use App\Http\Controllers\Api\V1\Design\ModelVersionController as DesignModelVersionController;
|
||||
use App\Http\Controllers\Api\V1\EstimateController;
|
||||
use App\Http\Controllers\Api\V1\FileController;
|
||||
use App\Http\Controllers\Api\V1\MaterialController;
|
||||
use App\Http\Controllers\Api\V1\MenuController;
|
||||
use App\Http\Controllers\Api\V1\ModelSetController;
|
||||
use App\Http\Controllers\Api\V1\PermissionController;
|
||||
use App\Http\Controllers\Api\V1\PricingController;
|
||||
use App\Http\Controllers\Api\V1\ProductBomItemController;
|
||||
use App\Http\Controllers\Api\V1\ProductController;
|
||||
use App\Http\Controllers\Api\V1\RegisterController;
|
||||
use App\Http\Controllers\Api\V1\RoleController;
|
||||
use App\Http\Controllers\Api\V1\RolePermissionController;
|
||||
use App\Http\Controllers\Api\V1\TenantController;
|
||||
// 설계 전용 (디자인 네임스페이스)
|
||||
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\UserController;
|
||||
// 모델셋 관리 (견적 시스템)
|
||||
use App\Http\Controllers\Api\V1\UserRoleController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
// V1 초기 개발
|
||||
Route::prefix('v1')->group(function () {
|
||||
|
||||
# API KEY 인증
|
||||
// API KEY 인증
|
||||
Route::middleware('auth.apikey')->get('/debug-apikey', [ApiController::class, 'debugApikey']);
|
||||
|
||||
# SAM API
|
||||
// SAM API
|
||||
Route::middleware('auth.apikey')->group(function () {
|
||||
|
||||
# Auth API
|
||||
// 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('register', [RegisterController::class, 'register'])->name('v1.register');
|
||||
|
||||
// Tenant Admin API
|
||||
Route::prefix('admin')->group(function () {
|
||||
@@ -83,7 +79,6 @@
|
||||
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'); // 회원 목록 조회
|
||||
@@ -97,7 +92,6 @@
|
||||
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'); // 테넌트 목록 조회
|
||||
@@ -116,7 +110,6 @@
|
||||
Route::get('info', [FileController::class, 'findFile'])->name('v1.file.info'); // 파일 정보 조회
|
||||
});
|
||||
|
||||
|
||||
// Menu API
|
||||
Route::middleware(['perm.map', 'permission'])->prefix('menus')->group(function () {
|
||||
Route::get('/', [MenuController::class, 'index'])->name('v1.menus.index');
|
||||
@@ -128,17 +121,15 @@
|
||||
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
|
||||
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
|
||||
@@ -147,7 +138,6 @@
|
||||
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
|
||||
@@ -156,7 +146,6 @@
|
||||
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'); // 목록
|
||||
@@ -177,7 +166,6 @@
|
||||
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'); // 부서별 권한 메트릭스
|
||||
@@ -185,7 +173,6 @@
|
||||
Route::get('users/{user_id}/menu-matrix', [PermissionController::class, 'userMenuMatrix'])->name('v1.permissions.userMenuMatrix'); // 부서별 권한 메트릭스
|
||||
});
|
||||
|
||||
|
||||
// Settings & Configuration (설정 및 환경설정 통합 관리)
|
||||
Route::prefix('settings')->group(function () {
|
||||
|
||||
@@ -243,97 +230,97 @@
|
||||
|
||||
// === 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('/{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::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,...}]
|
||||
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('/{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::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');// 렌더용 스냅샷
|
||||
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
|
||||
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::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'); // 삭제
|
||||
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'); // 활성/비활성
|
||||
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'); // 활성/비활성
|
||||
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'); // 삭제
|
||||
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 (){
|
||||
Route::prefix('products')->group(function () {
|
||||
|
||||
// 제품 카테고리 (기존 product/category에서 이동)
|
||||
Route::get ('/categories', [ProductController::class, 'getCategory'])->name('v1.products.categories'); // 제품 카테고리
|
||||
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::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('/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)
|
||||
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'); // 전역(테넌트) 추천
|
||||
@@ -342,83 +329,81 @@
|
||||
|
||||
// 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::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('/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('/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::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('/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('/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');
|
||||
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');
|
||||
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::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::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 계산
|
||||
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::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::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'); // 견적 계산 미리보기
|
||||
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'); // 견적 계산 미리보기
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user