2025-07-17 10:05:47 +09:00
< ? php
2025-11-06 17:24:42 +09:00
use App\Http\Controllers\Api\V1\AdminController ;
use App\Http\Controllers\Api\V1\ApiController ;
use App\Http\Controllers\Api\V1\CategoryController ;
use App\Http\Controllers\Api\V1\CategoryFieldController ;
2025-08-25 17:46:34 +09:00
use App\Http\Controllers\Api\V1\CategoryLogController ;
2025-11-06 17:24:42 +09:00
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 ;
2025-07-18 11:37:07 +09:00
use App\Http\Controllers\Api\V1\CommonController ;
2025-11-06 17:24:42 +09:00
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\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 ;
feat: 파일 저장 시스템 DB 마이그레이션
- enhance_files_table: 이중 파일명 시스템 (display_name/stored_name), 폴더 관리, 문서 연결 지원
- create_folders_table: 동적 폴더 관리 시스템 (tenant별 커스터마이징 가능)
- 5개 stub 마이그레이션 생성 (file_share_links, file_deletion_logs, storage_usage_history, add_storage_columns_to_tenants)
- FolderSeeder stub 생성
- CURRENT_WORKS.md에 Phase 1 진행상황 문서화
fix: 파일 공유 및 삭제 기능 버그 수정
- ShareLinkRequest: PATH 파라미터 {id}를 file_id로 자동 병합
- routes/api.php: 공유 링크 다운로드를 auth.apikey 그룹 밖으로 이동 (인증 불필요)
- FileShareLink: File, Tenant 클래스 import 추가
- File 모델: softDeleteFile()에서 SoftDeletes의 delete() 메서드 사용
- FileStorageService: getTrash(), restoreFile(), permanentDelete()에서 onlyTrashed() 사용
- File 모델: Tenant 네임스페이스 수정 (App\Models\Tenants\Tenant)
refactor: Swagger 문서 정리 - File 태그를 Files로 통합
- FileApi.php의 모든 태그를 Files로 변경
- 구 파일 시스템 라우트 삭제 (prefix 'file')
- 구 FileController.php 삭제
- 신규 파일 저장소 시스템으로 완전 통합
fix: 모든 legacy 파일 컬럼 nullable 일괄 처리
- 5개 legacy 컬럼을 한 번에 nullable로 변경
* original_name, file_name, file_name_old (string)
* fileable_id, fileable_type (polymorphic)
- foreach 루프로 반복 작업 자동화
- 신규/기존 시스템 간 완전한 하위 호환성 확보
fix: legacy 파일 컬럼 nullable 처리 완료
- file_name, file_name_old 컬럼도 nullable로 변경
- 기존 시스템과 신규 시스템 간 완전한 하위 호환성 확보
- Legacy: original_name, file_name, file_name_old (nullable)
- New: display_name, stored_name (required)
fix: original_name 컬럼 nullable 처리
- original_name을 nullable로 변경하여 하위 호환성 유지
- 새 시스템에서는 display_name 사용, 기존 시스템은 original_name 사용 가능
fix: 파일 업로드 DB 컬럼 누락 및 메시지 구조 개선
- files 테이블에 감사 컬럼 추가 (created_by, updated_by, uploaded_by)
- ApiResponse::handle() 메시지 로직 개선 (접미사 제거)
- 다국어 지원을 위한 완성된 문장 구조 유지
- FileUploadRequest 파일 검증 규칙 수정
fix: 파일 저장소 버그 수정 및 신규 테넌트 폴더 자동 생성
- FolderSeeder 네임스페이스 수정 (App\Models\Tenant → App\Models\Tenants\Tenant)
- FileStorageController use 문 구문 오류 수정 (/ → \)
- TenantObserver에 신규 테넌트 기본 폴더 자동 생성 로직 추가
- 5개 기본 폴더 (생산관리, 품질관리, 회계, 인사, 일반)
- 에러 처리 및 로깅
- 회원가입 시 자동 실행
2025-11-10 19:08:56 +09:00
use App\Http\Controllers\Api\V1\FileStorageController ;
use App\Http\Controllers\Api\V1\FolderController ;
2025-11-20 17:16:03 +09:00
use App\Http\Controllers\Api\V1\ItemMaster\CustomTabController ;
2025-11-20 17:07:40 +09:00
use App\Http\Controllers\Api\V1\ItemMaster\ItemBomItemController ;
2025-11-20 16:55:57 +09:00
use App\Http\Controllers\Api\V1\ItemMaster\ItemFieldController ;
use App\Http\Controllers\Api\V1\ItemMaster\ItemMasterController ;
2025-11-20 17:07:40 +09:00
use App\Http\Controllers\Api\V1\ItemMaster\ItemMasterFieldController ;
2025-11-20 16:55:57 +09:00
use App\Http\Controllers\Api\V1\ItemMaster\ItemPageController ;
use App\Http\Controllers\Api\V1\ItemMaster\ItemSectionController ;
2025-11-20 17:07:40 +09:00
use App\Http\Controllers\Api\V1\ItemMaster\SectionTemplateController ;
2025-11-20 17:16:03 +09:00
use App\Http\Controllers\Api\V1\ItemMaster\UnitOptionController ;
2025-08-01 17:25:31 +09:00
use App\Http\Controllers\Api\V1\MaterialController ;
2025-08-16 03:25:06 +09:00
use App\Http\Controllers\Api\V1\MenuController ;
2025-11-06 17:24:42 +09:00
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 ;
feat: 파일 저장 시스템 DB 마이그레이션
- enhance_files_table: 이중 파일명 시스템 (display_name/stored_name), 폴더 관리, 문서 연결 지원
- create_folders_table: 동적 폴더 관리 시스템 (tenant별 커스터마이징 가능)
- 5개 stub 마이그레이션 생성 (file_share_links, file_deletion_logs, storage_usage_history, add_storage_columns_to_tenants)
- FolderSeeder stub 생성
- CURRENT_WORKS.md에 Phase 1 진행상황 문서화
fix: 파일 공유 및 삭제 기능 버그 수정
- ShareLinkRequest: PATH 파라미터 {id}를 file_id로 자동 병합
- routes/api.php: 공유 링크 다운로드를 auth.apikey 그룹 밖으로 이동 (인증 불필요)
- FileShareLink: File, Tenant 클래스 import 추가
- File 모델: softDeleteFile()에서 SoftDeletes의 delete() 메서드 사용
- FileStorageService: getTrash(), restoreFile(), permanentDelete()에서 onlyTrashed() 사용
- File 모델: Tenant 네임스페이스 수정 (App\Models\Tenants\Tenant)
refactor: Swagger 문서 정리 - File 태그를 Files로 통합
- FileApi.php의 모든 태그를 Files로 변경
- 구 파일 시스템 라우트 삭제 (prefix 'file')
- 구 FileController.php 삭제
- 신규 파일 저장소 시스템으로 완전 통합
fix: 모든 legacy 파일 컬럼 nullable 일괄 처리
- 5개 legacy 컬럼을 한 번에 nullable로 변경
* original_name, file_name, file_name_old (string)
* fileable_id, fileable_type (polymorphic)
- foreach 루프로 반복 작업 자동화
- 신규/기존 시스템 간 완전한 하위 호환성 확보
fix: legacy 파일 컬럼 nullable 처리 완료
- file_name, file_name_old 컬럼도 nullable로 변경
- 기존 시스템과 신규 시스템 간 완전한 하위 호환성 확보
- Legacy: original_name, file_name, file_name_old (nullable)
- New: display_name, stored_name (required)
fix: original_name 컬럼 nullable 처리
- original_name을 nullable로 변경하여 하위 호환성 유지
- 새 시스템에서는 display_name 사용, 기존 시스템은 original_name 사용 가능
fix: 파일 업로드 DB 컬럼 누락 및 메시지 구조 개선
- files 테이블에 감사 컬럼 추가 (created_by, updated_by, uploaded_by)
- ApiResponse::handle() 메시지 로직 개선 (접미사 제거)
- 다국어 지원을 위한 완성된 문장 구조 유지
- FileUploadRequest 파일 검증 규칙 수정
fix: 파일 저장소 버그 수정 및 신규 테넌트 폴더 자동 생성
- FolderSeeder 네임스페이스 수정 (App\Models\Tenant → App\Models\Tenants\Tenant)
- FileStorageController use 문 구문 오류 수정 (/ → \)
- TenantObserver에 신규 테넌트 기본 폴더 자동 생성 로직 추가
- 5개 기본 폴더 (생산관리, 품질관리, 회계, 인사, 일반)
- 에러 처리 및 로깅
- 회원가입 시 자동 실행
2025-11-10 19:08:56 +09:00
use App\Http\Controllers\Api\V1\RefreshController ;
2025-11-06 17:24:42 +09:00
use App\Http\Controllers\Api\V1\RegisterController ;
2025-08-16 03:25:06 +09:00
use App\Http\Controllers\Api\V1\RoleController ;
use App\Http\Controllers\Api\V1\RolePermissionController ;
2025-11-06 17:24:42 +09:00
use App\Http\Controllers\Api\V1\TenantController ;
2025-08-18 19:03:46 +09:00
use App\Http\Controllers\Api\V1\TenantFieldSettingController ;
2025-11-14 14:24:35 +09:00
// 설계 전용 (디자인 네임스페이스)
2025-08-18 19:03:46 +09:00
use App\Http\Controllers\Api\V1\TenantOptionGroupController ;
use App\Http\Controllers\Api\V1\TenantOptionValueController ;
2025-11-14 14:24:35 +09:00
use App\Http\Controllers\Api\V1\TenantStatFieldController ;
2025-08-18 19:03:46 +09:00
use App\Http\Controllers\Api\V1\TenantUserProfileController ;
2025-11-06 17:24:42 +09:00
use App\Http\Controllers\Api\V1\UserController ;
2025-09-24 17:41:26 +09:00
// 모델셋 관리 (견적 시스템)
2025-11-06 17:24:42 +09:00
use App\Http\Controllers\Api\V1\UserRoleController ;
use Illuminate\Support\Facades\Route ;
2025-09-24 17:41:26 +09:00
2025-07-18 11:37:07 +09:00
// V1 초기 개발
Route :: prefix ( 'v1' ) -> group ( function () {
2025-07-17 10:05:47 +09:00
2025-11-06 17:24:42 +09:00
// API KEY 인증
2025-08-04 08:38:47 +09:00
Route :: middleware ( 'auth.apikey' ) -> get ( '/debug-apikey' , [ ApiController :: class , 'debugApikey' ]);
2025-11-06 17:24:42 +09:00
// SAM API
2025-07-18 11:37:07 +09:00
Route :: middleware ( 'auth.apikey' ) -> group ( function () {
2025-07-17 10:05:47 +09:00
2025-11-06 17:24:42 +09:00
// Auth API
2025-08-18 16:37:02 +09:00
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' );
2025-11-10 11:17:32 +09:00
Route :: post ( 'refresh' , [ RefreshController :: class , 'refresh' ]) -> name ( 'v1.token.refresh' );
2025-11-06 17:24:42 +09:00
Route :: post ( 'register' , [ RegisterController :: class , 'register' ]) -> name ( 'v1.register' );
2025-07-23 18:06:33 +09:00
2025-08-15 16:32:11 +09:00
// 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' ); // 테넌트 사용자 비밀번호 초기화
});
2025-07-18 11:37:07 +09:00
// Member API
2025-08-14 00:55:08 +09:00
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' ); // 내 정보 조회
2025-08-18 16:37:02 +09:00
Route :: put ( 'me' , [ UserController :: class , 'meUpdate' ]) -> name ( 'v1.users.me.update' ); // 내 정보 수정
2025-08-14 00:55:08 +09:00
Route :: put ( 'me/password' , [ UserController :: class , 'changePassword' ]) -> name ( 'v1.users.me.password' ); // 비밀번호 변겅
2025-08-18 16:37:02 +09:00
Route :: get ( 'me/tenants' , [ UserController :: class , 'tenants' ]) -> name ( 'v1.users.me.tenants.index' ); // 내 테넌트 목록
2025-08-13 18:34:28 +09:00
Route :: patch ( 'me/tenants/switch' , [ UserController :: class , 'switchTenant' ]) -> name ( 'v1.users.me.tenants.switch' ); // 활성 테넌트 전환
2025-08-14 00:55:08 +09:00
});
2025-07-17 10:05:47 +09:00
2025-08-14 17:20:28 +09:00
// 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' ); // 테넌트 복구
});
2025-11-14 14:09:53 +09:00
// 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' ); // 일괄 저장
});
2025-08-16 03:25:06 +09:00
// Menu API
Route :: middleware ([ 'perm.map' , 'permission' ]) -> prefix ( 'menus' ) -> group ( function () {
2025-08-22 18:08:57 +09:00
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' );
2025-08-16 03:25:06 +09:00
});
2025-07-17 10:05:47 +09:00
2025-08-16 03:25:06 +09:00
// Role API
Route :: prefix ( 'roles' ) -> group ( function () {
2025-08-22 18:08:57 +09:00
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
2025-11-06 17:24:42 +09:00
Route :: delete ( '/{id}' , [ RoleController :: class , 'destroy' ]) -> name ( 'v1.roles.destroy' ); // delete
2025-08-16 03:25:06 +09:00
});
2025-08-14 00:55:08 +09:00
2025-08-16 03:25:06 +09:00
// Role Permission API
Route :: prefix ( 'roles/{id}/permissions' ) -> group ( function () {
2025-08-22 18:08:57 +09:00
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
2025-08-16 03:25:06 +09:00
});
// User Role API
Route :: prefix ( 'users/{id}/roles' ) -> group ( function () {
2025-08-22 18:08:57 +09:00
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
2025-08-16 03:25:06 +09:00
});
// Department API
Route :: prefix ( 'departments' ) -> group ( function () {
2025-09-24 20:35:17 +09:00
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)
2025-08-16 03:25:06 +09:00
// 부서-사용자
2025-09-24 20:35:17 +09:00
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' ); // 주부서 설정/해제
2025-08-16 03:25:06 +09:00
// 부서-권한
2025-09-24 20:35:17 +09:00
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' ); // 권한 제거(해당 메뉴 범위까지)
2025-08-16 03:25:06 +09:00
});
2025-08-14 00:55:08 +09:00
2025-08-21 20:48:39 +09:00
// Permission API
Route :: prefix ( 'permissions' ) -> group ( function () {
2025-09-24 20:35:17 +09:00
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' ); // 부서별 권한 메트릭스
2025-08-21 20:48:39 +09:00
});
2025-09-24 20:35:17 +09:00
// 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' ); // 공통 코드 삭제
2025-08-18 19:03:46 +09:00
});
// 회원 프로필(테넌트 기준)
Route :: prefix ( 'profiles' ) -> group ( function () {
2025-08-22 18:08:57 +09:00
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' ); // 내 프로필 수정
});
2025-09-24 20:35:17 +09:00
// Category API (통합)
2025-08-22 18:08:57 +09:00
Route :: prefix ( 'categories' ) -> group ( function () {
2025-09-24 20:35:17 +09:00
// === 기본 Category CRUD ===
2025-08-22 18:08:57 +09:00
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)
2025-08-18 19:03:46 +09:00
2025-09-24 20:35:17 +09:00
// === 확장 기능 ===
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 ===
2025-08-25 17:46:34 +09:00
// 목록/생성 (카테고리 기준)
2025-11-06 17:24:42 +09:00
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' );
2025-08-25 17:46:34 +09:00
// 단건
2025-11-06 17:24:42 +09:00
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' );
2025-08-25 17:46:34 +09:00
// 일괄 정렬/업서트
2025-11-06 17:24:42 +09:00
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,...}]
2025-08-25 17:46:34 +09:00
2025-09-24 20:35:17 +09:00
// === Category Templates ===
2025-08-25 17:46:34 +09:00
// 버전 목록/생성 (카테고리 기준)
2025-11-06 17:24:42 +09:00
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' ); // 새 버전 등록
2025-08-25 17:46:34 +09:00
// 단건
2025-11-06 17:24:42 +09:00
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' );
2025-08-25 17:46:34 +09:00
// 운영 편의
2025-11-06 17:24:42 +09:00
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' ); // 렌더용 스냅샷
2025-08-25 17:46:34 +09:00
// (선택) 버전 간 diff
2025-11-06 17:24:42 +09:00
Route :: get ( '/{id}/templates/diff' , [ CategoryTemplateController :: class , 'diff' ]) -> name ( 'v1.categories.templates.diff' ); // ?a=ver&b=ver
2025-08-25 17:46:34 +09:00
2025-09-24 20:35:17 +09:00
// === Category Logs ===
2025-11-06 17:24:42 +09:00
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' );
2025-08-25 17:46:34 +09:00
// (선택) 특정 변경 시점으로 카테고리 복구(템플릿/필드와 별개)
// Route::post('{id}/logs/{log}/restore', [CategoryLogController::class, 'restore'])->name('v1.categories.logs.restore');
});
// Classifications API
2025-08-22 19:30:05 +09:00
Route :: prefix ( 'classifications' ) -> group ( function () {
2025-11-06 17:24:42 +09:00
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' ); // 삭제
2025-08-22 19:30:05 +09:00
});
2025-10-13 21:52:34 +09:00
// Clients (거래처 관리)
Route :: prefix ( 'clients' ) -> group ( function () {
2025-11-06 17:24:42 +09:00
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' ); // 활성/비활성
2025-10-13 21:52:34 +09:00
});
2025-10-13 22:06:42 +09:00
// Client Groups (고객 그룹 관리)
Route :: prefix ( 'client-groups' ) -> group ( function () {
2025-11-06 17:24:42 +09:00
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' ); // 활성/비활성
2025-10-13 22:06:42 +09:00
});
// Pricing (가격 이력 관리)
Route :: prefix ( 'pricing' ) -> group ( function () {
2025-11-06 17:24:42 +09:00
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' ); // 삭제
2025-10-13 22:06:42 +09:00
});
2025-09-24 20:35:17 +09:00
// Products & Materials (제품/자재 통합 관리)
2025-11-06 17:24:42 +09:00
Route :: prefix ( 'products' ) -> group ( function () {
2025-09-05 17:59:34 +09:00
2025-09-24 20:35:17 +09:00
// 제품 카테고리 (기존 product/category에서 이동)
2025-11-06 17:24:42 +09:00
Route :: get ( '/categories' , [ ProductController :: class , 'getCategory' ]) -> name ( 'v1.products.categories' ); // 제품 카테고리
2025-09-24 20:35:17 +09:00
2025-10-13 21:52:34 +09:00
// 자재 관리 (기존 독립 materials에서 이동) - ProductController 기본 라우팅보다 앞에 위치
2025-11-06 17:24:42 +09:00
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' ); // 자재 수정
2025-10-13 21:52:34 +09:00
Route :: delete ( '/materials/{id}' , [ MaterialController :: class , 'destroy' ]) -> name ( 'v1.products.materials.destroy' ); // 자재 삭제
2025-09-05 17:59:34 +09:00
// (선택) 드롭다운/모달용 간편 검색 & 활성 토글
2025-11-06 17:24:42 +09:00
Route :: get ( '/search' , [ ProductController :: class , 'search' ]) -> name ( 'v1.products.search' );
Route :: post ( '/{id}/toggle' , [ ProductController :: class , 'toggle' ]) -> name ( 'v1.products.toggle' );
2025-09-05 17:59:34 +09:00
2025-11-06 17:24:42 +09:00
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)
2025-08-25 17:46:34 +09:00
2025-08-29 16:22:05 +09:00
// 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' ); // 해당 제품에서 사용 중
2025-08-25 17:46:34 +09:00
});
2025-11-11 11:30:17 +09:00
// Items (통합 품목 조회 - materials + products UNION)
Route :: prefix ( 'items' ) -> group ( function () {
Route :: get ( '' , [ \App\Http\Controllers\Api\V1\ItemsController :: class , 'index' ]) -> name ( 'v1.items.index' ); // 통합 목록
2025-11-17 11:22:49 +09:00
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 기반 조회
2025-11-11 11:30:17 +09:00
Route :: get ( '/{id}' , [ \App\Http\Controllers\Api\V1\ItemsController :: class , 'show' ]) -> name ( 'v1.items.show' ); // 단건 (item_type 파라미터 필수)
2025-11-17 11:22:49 +09:00
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' ); // 품목 삭제
2025-11-11 11:30:17 +09:00
});
2025-11-17 11:45:16 +09:00
// 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' ); // 카테고리 목록
});
feat: 품목 파일 업로드 API 구현 (절곡도, 시방서, 인정서)
- Products 테이블에 9개 파일 관련 필드 추가
- bending_diagram, bending_details (JSON)
- specification_file, specification_file_name
- certification_file, certification_file_name
- certification_number, certification_start_date, certification_end_date
- ItemsFileController 구현 (Code-based API)
- POST /items/{code}/files - 파일 업로드
- DELETE /items/{code}/files/{type} - 파일 삭제
- 파일 타입: bending_diagram, specification, certification
- ItemsFileUploadRequest 검증
- 파일 타입별 MIME 검증 (이미지/문서)
- 파일 크기 제한 (10MB/20MB)
- 인증 정보 및 절곡 상세 정보 검증
- Swagger 문서 작성 (ItemsFileApi.php)
- 업로드/삭제 API 스펙
- 스키마: ItemFileUploadResponse, ItemFileDeleteResponse
2025-11-17 13:40:07 +09:00
// 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)
});
2025-08-25 17:46:34 +09:00
// BOM (product_components: ref_type=PRODUCT|MATERIAL)
Route :: prefix ( 'products/{id}/bom' ) -> group ( function () {
2025-11-06 17:24:42 +09:00
Route :: post ( '/' , [ ProductBomItemController :: class , 'replace' ]) -> name ( 'v1.products.bom.replace' );
2025-08-29 16:22:05 +09:00
2025-11-06 17:24:42 +09:00
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' ); // 정렬 변경
2025-08-25 17:46:34 +09:00
// (선택) 합계/검증
2025-11-06 17:24:42 +09:00
Route :: get ( '/summary' , [ ProductBomItemController :: class , 'summary' ]) -> name ( 'v1.products.bom.summary' );
Route :: get ( '/validate' , [ ProductBomItemController :: class , 'validateBom' ]) -> name ( 'v1.products.bom.validate' );
2025-08-29 16:22:05 +09:00
2025-09-11 13:34:20 +09:00
Route :: get ( '/tree' , [ ProductBomItemController :: class , 'tree' ]) -> name ( 'v1.products.bom.tree' );
2025-08-25 17:46:34 +09:00
});
2025-09-05 17:59:34 +09:00
// 설계 전용 (Design) - 운영과 분리된 네임스페이스/경로
Route :: prefix ( 'design' ) -> group ( function () {
2025-11-06 17:24:42 +09:00
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' );
2025-09-05 17:59:34 +09:00
Route :: delete ( '/models/{id}' , [ DesignModelController :: class , 'destroy' ]) -> name ( 'v1.design.models.destroy' );
2025-11-06 17:24:42 +09:00
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' );
2025-09-05 17:59:34 +09:00
2025-11-06 17:24:42 +09:00
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' );
2025-09-11 14:39:55 +09:00
// 감사 로그 조회
2025-11-06 17:24:42 +09:00
Route :: get ( '/audit-logs' , [ DesignAuditLogController :: class , 'index' ]) -> name ( 'v1.design.audit-logs.index' );
2025-09-22 22:09:42 +09:00
// BOM 계산 시스템
2025-11-06 17:24:42 +09:00
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' );
2025-09-05 17:59:34 +09:00
});
2025-09-24 17:41:26 +09:00
// 모델셋 관리 API (견적 시스템)
Route :: prefix ( 'model-sets' ) -> group ( function () {
2025-11-06 17:24:42 +09:00
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' ); // 모델셋 수정
2025-09-24 17:41:26 +09:00
Route :: delete ( '/{id}' , [ ModelSetController :: class , 'destroy' ]) -> name ( 'v1.model-sets.destroy' ); // 모델셋 삭제
2025-11-06 17:24:42 +09:00
Route :: post ( '/{id}/clone' , [ ModelSetController :: class , 'clone' ]) -> name ( 'v1.model-sets.clone' ); // 모델셋 복제
2025-09-24 17:41:26 +09:00
// 모델셋 세부 기능
2025-11-06 17:24:42 +09:00
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 계산
2025-09-24 17:41:26 +09:00
});
// 견적 관리 API
Route :: prefix ( 'estimates' ) -> group ( function () {
2025-11-06 17:24:42 +09:00
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' ); // 견적 수정
2025-09-24 17:41:26 +09:00
Route :: delete ( '/{id}' , [ EstimateController :: class , 'destroy' ]) -> name ( 'v1.estimates.destroy' ); // 견적 삭제
2025-11-06 17:24:42 +09:00
Route :: post ( '/{id}/clone' , [ EstimateController :: class , 'clone' ]) -> name ( 'v1.estimates.clone' ); // 견적 복제
Route :: put ( '/{id}/status' , [ EstimateController :: class , 'changeStatus' ]) -> name ( 'v1.estimates.status' ); // 견적 상태 변경
2025-09-24 17:41:26 +09:00
// 견적 폼 및 계산 기능
2025-11-06 17:24:42 +09:00
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' ); // 견적 계산 미리보기
2025-09-24 17:41:26 +09:00
});
feat: 파일 저장 시스템 DB 마이그레이션
- enhance_files_table: 이중 파일명 시스템 (display_name/stored_name), 폴더 관리, 문서 연결 지원
- create_folders_table: 동적 폴더 관리 시스템 (tenant별 커스터마이징 가능)
- 5개 stub 마이그레이션 생성 (file_share_links, file_deletion_logs, storage_usage_history, add_storage_columns_to_tenants)
- FolderSeeder stub 생성
- CURRENT_WORKS.md에 Phase 1 진행상황 문서화
fix: 파일 공유 및 삭제 기능 버그 수정
- ShareLinkRequest: PATH 파라미터 {id}를 file_id로 자동 병합
- routes/api.php: 공유 링크 다운로드를 auth.apikey 그룹 밖으로 이동 (인증 불필요)
- FileShareLink: File, Tenant 클래스 import 추가
- File 모델: softDeleteFile()에서 SoftDeletes의 delete() 메서드 사용
- FileStorageService: getTrash(), restoreFile(), permanentDelete()에서 onlyTrashed() 사용
- File 모델: Tenant 네임스페이스 수정 (App\Models\Tenants\Tenant)
refactor: Swagger 문서 정리 - File 태그를 Files로 통합
- FileApi.php의 모든 태그를 Files로 변경
- 구 파일 시스템 라우트 삭제 (prefix 'file')
- 구 FileController.php 삭제
- 신규 파일 저장소 시스템으로 완전 통합
fix: 모든 legacy 파일 컬럼 nullable 일괄 처리
- 5개 legacy 컬럼을 한 번에 nullable로 변경
* original_name, file_name, file_name_old (string)
* fileable_id, fileable_type (polymorphic)
- foreach 루프로 반복 작업 자동화
- 신규/기존 시스템 간 완전한 하위 호환성 확보
fix: legacy 파일 컬럼 nullable 처리 완료
- file_name, file_name_old 컬럼도 nullable로 변경
- 기존 시스템과 신규 시스템 간 완전한 하위 호환성 확보
- Legacy: original_name, file_name, file_name_old (nullable)
- New: display_name, stored_name (required)
fix: original_name 컬럼 nullable 처리
- original_name을 nullable로 변경하여 하위 호환성 유지
- 새 시스템에서는 display_name 사용, 기존 시스템은 original_name 사용 가능
fix: 파일 업로드 DB 컬럼 누락 및 메시지 구조 개선
- files 테이블에 감사 컬럼 추가 (created_by, updated_by, uploaded_by)
- ApiResponse::handle() 메시지 로직 개선 (접미사 제거)
- 다국어 지원을 위한 완성된 문장 구조 유지
- FileUploadRequest 파일 검증 규칙 수정
fix: 파일 저장소 버그 수정 및 신규 테넌트 폴더 자동 생성
- FolderSeeder 네임스페이스 수정 (App\Models\Tenant → App\Models\Tenants\Tenant)
- FileStorageController use 문 구문 오류 수정 (/ → \)
- TenantObserver에 신규 테넌트 기본 폴더 자동 생성 로직 추가
- 5개 기본 폴더 (생산관리, 품질관리, 회계, 인사, 일반)
- 에러 처리 및 로깅
- 회원가입 시 자동 실행
2025-11-10 19:08:56 +09:00
// 파일 저장소 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' ); // 폴더 순서 변경
});
2025-11-20 16:55:57 +09:00
// 품목기준관리 (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' );
2025-11-20 17:07:40 +09:00
// 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' );
2025-11-20 17:16:03 +09:00
// 커스텀 탭
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' );
2025-11-20 20:28:33 +09:00
Route :: put ( '/custom-tabs/reorder' , [ CustomTabController :: class , 'reorder' ]) -> name ( 'v1.item-master.custom-tabs.reorder' );
2025-11-20 17:16:03 +09:00
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' );
2025-11-20 16:55:57 +09:00
});
2025-08-14 00:55:08 +09:00
});
feat: 파일 저장 시스템 DB 마이그레이션
- enhance_files_table: 이중 파일명 시스템 (display_name/stored_name), 폴더 관리, 문서 연결 지원
- create_folders_table: 동적 폴더 관리 시스템 (tenant별 커스터마이징 가능)
- 5개 stub 마이그레이션 생성 (file_share_links, file_deletion_logs, storage_usage_history, add_storage_columns_to_tenants)
- FolderSeeder stub 생성
- CURRENT_WORKS.md에 Phase 1 진행상황 문서화
fix: 파일 공유 및 삭제 기능 버그 수정
- ShareLinkRequest: PATH 파라미터 {id}를 file_id로 자동 병합
- routes/api.php: 공유 링크 다운로드를 auth.apikey 그룹 밖으로 이동 (인증 불필요)
- FileShareLink: File, Tenant 클래스 import 추가
- File 모델: softDeleteFile()에서 SoftDeletes의 delete() 메서드 사용
- FileStorageService: getTrash(), restoreFile(), permanentDelete()에서 onlyTrashed() 사용
- File 모델: Tenant 네임스페이스 수정 (App\Models\Tenants\Tenant)
refactor: Swagger 문서 정리 - File 태그를 Files로 통합
- FileApi.php의 모든 태그를 Files로 변경
- 구 파일 시스템 라우트 삭제 (prefix 'file')
- 구 FileController.php 삭제
- 신규 파일 저장소 시스템으로 완전 통합
fix: 모든 legacy 파일 컬럼 nullable 일괄 처리
- 5개 legacy 컬럼을 한 번에 nullable로 변경
* original_name, file_name, file_name_old (string)
* fileable_id, fileable_type (polymorphic)
- foreach 루프로 반복 작업 자동화
- 신규/기존 시스템 간 완전한 하위 호환성 확보
fix: legacy 파일 컬럼 nullable 처리 완료
- file_name, file_name_old 컬럼도 nullable로 변경
- 기존 시스템과 신규 시스템 간 완전한 하위 호환성 확보
- Legacy: original_name, file_name, file_name_old (nullable)
- New: display_name, stored_name (required)
fix: original_name 컬럼 nullable 처리
- original_name을 nullable로 변경하여 하위 호환성 유지
- 새 시스템에서는 display_name 사용, 기존 시스템은 original_name 사용 가능
fix: 파일 업로드 DB 컬럼 누락 및 메시지 구조 개선
- files 테이블에 감사 컬럼 추가 (created_by, updated_by, uploaded_by)
- ApiResponse::handle() 메시지 로직 개선 (접미사 제거)
- 다국어 지원을 위한 완성된 문장 구조 유지
- FileUploadRequest 파일 검증 규칙 수정
fix: 파일 저장소 버그 수정 및 신규 테넌트 폴더 자동 생성
- FolderSeeder 네임스페이스 수정 (App\Models\Tenant → App\Models\Tenants\Tenant)
- FileStorageController use 문 구문 오류 수정 (/ → \)
- TenantObserver에 신규 테넌트 기본 폴더 자동 생성 로직 추가
- 5개 기본 폴더 (생산관리, 품질관리, 회계, 인사, 일반)
- 에러 처리 및 로깅
- 회원가입 시 자동 실행
2025-11-10 19:08:56 +09:00
// 공유 링크 다운로드 (인증 불필요 - auth.apikey 그룹 밖)
Route :: get ( '/files/share/{token}' , [ FileStorageController :: class , 'downloadShared' ]) -> name ( 'v1.files.share.download' );
2025-08-16 03:25:06 +09:00
});