# 품목기준관리 API 설계 문서 **작성일**: 2025-11-18 **목적**: 품목기준관리 페이지의 설정 데이터를 서버와 동기화하기 위한 API 구조 설계 --- ## 📋 목차 1. [개요](#개요) 2. [데이터 구조 분석](#데이터-구조-분석) 3. [API 엔드포인트 설계](#api-엔드포인트-설계) 4. [데이터 모델](#데이터-모델) 5. [저장/불러오기 시나리오](#저장불러오기-시나리오) 6. [버전 관리 전략](#버전-관리-전략) 7. [에러 처리](#에러-처리) --- ## 개요 ### 테넌트 정보 구조 본 시스템은 로그인 시 받는 실제 테넌트 정보 구조를 기반으로 설계되었습니다. ```typescript // 로그인 성공 시 받는 실제 사용자 정보 { userId: "TestUser3", name: "드미트리", tenant: { id: 282, // ✅ 테넌트 고유 ID (number 타입) company_name: "(주)테크컴퍼니", // 테넌트 회사명 business_num: "123-45-67890", // 사업자 번호 tenant_st_code: "trial" // 테넌트 상태 코드 } } ``` **중요**: API 엔드포인트의 `{tenantId}`는 위 구조의 `tenant.id` 값(number 타입, 예: 282)을 의미합니다. ### 시스템 흐름 ``` ┌──────────────────────────────┐ │ 로그인 (Login) │ │ tenant.id: 282 (number) │ └────────┬─────────────────────┘ │ ▼ ┌──────────────────────────────┐ │ 테넌트 (Tenant) │ │ 고유 필드 구성 │ │ tenant.id 기반 격리 │ └────────┬─────────────────────┘ │ ▼ ┌────────────────────────────────┐ │ 품목기준관리 페이지 │ │ (Item Master Config Page) │ │ │ │ - 페이지 구조 설정 │ │ - 섹션 구성 │ │ - 필드 정의 │ │ - 마스터 데이터 관리 │ │ - 버전 관리 │ └────────┬───────────────────────┘ │ Save (with tenant.id) ▼ ┌────────────────────────────────┐ │ API Server │ │ (Backend) │ │ │ │ - 테넌트별 데이터 저장 │ │ - tenant.id 검증 │ │ - 버전 관리 │ │ - 유효성 검증 │ └────────┬───────────────────────┘ │ Load (filtered by tenant.id) ▼ ┌────────────────────────────────┐ │ 품목관리 페이지 │ │ (Item Management Page) │ │ │ │ - 설정 기반 동적 폼 생성 │ │ - 실제 품목 데이터 입력 │ └────────────────────────────────┘ ``` ### 핵심 요구사항 1. **테넌트 격리**: 각 테넌트별로 독립적인 설정 (`tenant.id` 기반 완전 격리) 2. **계층 구조**: Page → Section → Field 3단계 계층 3. **버전 관리**: 설정 변경 이력 추적 4. **재사용성**: 템플릿 기반 섹션/필드 재사용 5. **동적 생성**: 설정 기반 품목관리 페이지 동적 렌더링 6. **서버 검증**: JWT의 tenant.id와 API 요청의 tenantId 일치 검증 --- ## 데이터 구조 분석 ### 1. 계층 구조 (Hierarchical Structure) ``` ItemMasterConfig (전체 설정) │ ├─ ItemPage[] (페이지 배열) │ ├─ id │ ├─ pageName │ ├─ itemType (FG/PT/SM/RM/CS) │ └─ sections[] │ │ │ ├─ ItemSection (섹션) │ │ ├─ id │ │ ├─ title │ │ ├─ type ('fields' | 'bom') │ │ ├─ order │ │ └─ fields[] │ │ │ │ │ └─ ItemField (필드) │ │ ├─ id │ │ ├─ name │ │ ├─ fieldKey │ │ ├─ property (ItemFieldProperty) │ │ └─ displayCondition │ ├─ SectionTemplate[] (재사용 섹션 템플릿) │ ├─ ItemMasterField[] (재사용 필드 템플릿) │ └─ MasterData (마스터 데이터들) ├─ SpecificationMaster[] ├─ MaterialItemName[] ├─ ItemCategory[] ├─ ItemUnit[] ├─ ItemMaterial[] ├─ SurfaceTreatment[] ├─ PartTypeOption[] ├─ PartUsageOption[] └─ GuideRailOption[] ``` ### 2. 저장해야 할 데이터 범위 #### ✅ 저장 필수 데이터 1. **페이지 구조** (`itemPages`) 2. **섹션 템플릿** (`sectionTemplates`) 3. **항목 마스터** (`itemMasterFields`) 4. **마스터 데이터** (9가지): - 규격 마스터 (`specificationMasters`) - 품목명 마스터 (`materialItemNames`) - 품목 분류 (`itemCategories`) - 단위 (`itemUnits`) - 재질 (`itemMaterials`) - 표면처리 (`surfaceTreatments`) - 부품 유형 옵션 (`partTypeOptions`) - 부품 용도 옵션 (`partUsageOptions`) - 가이드레일 옵션 (`guideRailOptions`) #### ❌ 저장 불필요 데이터 - **실제 품목 데이터** (`itemMasters`) - 별도 API로 관리 --- ## API 엔드포인트 설계 ### Base URL ``` /api/tenants/{tenantId}/item-master-config ``` **참고**: `{tenantId}`는 로그인 응답의 `tenant.id` 값(number 타입)입니다. 예: `/api/tenants/282/item-master-config` ### 서버 검증 (Server-side Validation) 모든 API 요청에서 다음 검증을 수행해야 합니다: ```typescript // Middleware 예시 async function validateTenantAccess(req, res, next) { // 1. JWT에서 사용자의 tenant.id 추출 const userTenantId = req.user.tenant.id; // number (예: 282) // 2. URL 파라미터의 tenantId 추출 및 타입 변환 const requestedTenantId = parseInt(req.params.tenantId, 10); // 3. 일치 검증 if (userTenantId !== requestedTenantId) { return res.status(403).json({ success: false, error: { code: "FORBIDDEN", message: "접근 권한이 없습니다.", details: { userTenantId, requestedTenantId, reason: "테넌트 ID 불일치" } } }); } next(); } ``` ### 1. 전체 설정 조회 (GET) #### 엔드포인트 ``` GET /api/tenants/{tenantId}/item-master-config ``` **예시**: `GET /api/tenants/282/item-master-config` #### Query Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | version | string | No | 버전 (기본값: latest) | | includeInactive | boolean | No | 비활성 항목 포함 여부 (기본값: false) | #### Response ```typescript { "success": true, "data": { "tenantId": 282, // ✅ number 타입 "version": "1.0", "lastUpdated": "2025-11-18T10:30:00Z", "updatedBy": "TestUser3", "config": { // 페이지 구조 "pages": ItemPage[], // 재사용 템플릿 "sectionTemplates": SectionTemplate[], "itemMasterFields": ItemMasterField[], // 마스터 데이터 "masters": { "specifications": SpecificationMaster[], "materialNames": MaterialItemName[], "categories": ItemCategory[], "units": ItemUnit[], "materials": ItemMaterial[], "surfaceTreatments": SurfaceTreatment[], "partTypes": PartTypeOption[], "partUsages": PartUsageOption[], "guideRailOptions": GuideRailOption[] } } } } ``` --- ### 2. 전체 설정 저장 (POST/PUT) #### 엔드포인트 ``` POST /api/tenants/{tenantId}/item-master-config PUT /api/tenants/{tenantId}/item-master-config/{version} ``` #### Request Body ```typescript { "version": "1.0", // 버전 명시 (PUT의 경우 URL의 version과 일치해야 함) "comment": "초기 설정 저장", // 변경 사유 (선택) "config": { "pages": ItemPage[], "sectionTemplates": SectionTemplate[], "itemMasterFields": ItemMasterField[], "masters": { "specifications": SpecificationMaster[], "materialNames": MaterialItemName[], "categories": ItemCategory[], "units": ItemUnit[], "materials": ItemMaterial[], "surfaceTreatments": SurfaceTreatment[], "partTypes": PartTypeOption[], "partUsages": PartUsageOption[], "guideRailOptions": GuideRailOption[] } } } ``` #### Response ```typescript { "success": true, "data": { "tenantId": 282, // ✅ number 타입 "version": "1.0", "savedAt": "2025-11-18T10:30:00Z", "savedBy": "TestUser3" }, "message": "설정이 성공적으로 저장되었습니다." } ``` --- ### 3. 특정 페이지 조회 (GET) #### 엔드포인트 ``` GET /api/tenants/{tenantId}/item-master-config/pages/{pageId} ``` **예시**: `GET /api/tenants/282/item-master-config/pages/PAGE-001` #### Response ```typescript { "success": true, "data": { "page": ItemPage, "metadata": { "tenantId": 282, // ✅ number 타입 "version": "1.0", "lastUpdated": "2025-11-18T10:30:00Z" } } } ``` --- ### 4. 특정 페이지 업데이트 (PUT) #### 엔드포인트 ``` PUT /api/tenants/{tenantId}/item-master-config/pages/{pageId} ``` #### Request Body ```typescript { "page": ItemPage, "comment": "페이지 구조 변경" } ``` --- ### 5. 페이지 추가 (POST) #### 엔드포인트 ``` POST /api/tenants/{tenantId}/item-master-config/pages ``` #### Request Body ```typescript { "page": { "id": "PAGE-001", "pageName": "제품 등록", "itemType": "FG", "sections": [], "isActive": true, "createdAt": "2025-11-18T10:30:00Z" } } ``` --- ### 6. 섹션 템플릿 관리 #### 엔드포인트 ``` GET /api/tenants/{tenantId}/item-master-config/section-templates POST /api/tenants/{tenantId}/item-master-config/section-templates PUT /api/tenants/{tenantId}/item-master-config/section-templates/{templateId} DELETE /api/tenants/{tenantId}/item-master-config/section-templates/{templateId} ``` --- ### 7. 항목 마스터 관리 #### 엔드포인트 ``` GET /api/tenants/{tenantId}/item-master-config/item-master-fields POST /api/tenants/{tenantId}/item-master-config/item-master-fields PUT /api/tenants/{tenantId}/item-master-config/item-master-fields/{fieldId} DELETE /api/tenants/{tenantId}/item-master-config/item-master-fields/{fieldId} ``` --- ### 8. 마스터 데이터 관리 각 마스터 데이터별 CRUD API ``` # 규격 마스터 GET /api/tenants/{tenantId}/item-master-config/masters/specifications POST /api/tenants/{tenantId}/item-master-config/masters/specifications PUT /api/tenants/{tenantId}/item-master-config/masters/specifications/{id} DELETE /api/tenants/{tenantId}/item-master-config/masters/specifications/{id} # 품목명 마스터 GET /api/tenants/{tenantId}/item-master-config/masters/material-names POST /api/tenants/{tenantId}/item-master-config/masters/material-names PUT /api/tenants/{tenantId}/item-master-config/masters/material-names/{id} DELETE /api/tenants/{tenantId}/item-master-config/masters/material-names/{id} # ... (나머지 마스터 데이터도 동일 패턴) ``` --- ## 데이터 모델 ### 1. ItemMasterConfig (전체 설정) ```typescript interface ItemMasterConfig { tenantId: number; // ✅ 테넌트 ID (number 타입, 예: 282) version: string; // 버전 (1.0, 1.1, 2.0...) lastUpdated: string; // 마지막 업데이트 시간 (ISO 8601) updatedBy: string; // 업데이트한 사용자 ID comment?: string; // 변경 사유 config: { pages: ItemPage[]; sectionTemplates: SectionTemplate[]; itemMasterFields: ItemMasterField[]; masters: { specifications: SpecificationMaster[]; materialNames: MaterialItemName[]; categories: ItemCategory[]; units: ItemUnit[]; materials: ItemMaterial[]; surfaceTreatments: SurfaceTreatment[]; partTypes: PartTypeOption[]; partUsages: PartUsageOption[]; guideRailOptions: GuideRailOption[]; }; }; } ``` ### 2. API Response 공통 형식 #### 성공 응답 ```typescript interface ApiSuccessResponse { success: true; data: T; message?: string; metadata?: { total?: number; page?: number; pageSize?: number; }; } ``` #### 에러 응답 ```typescript interface ApiErrorResponse { success: false; error: { code: string; // 에러 코드 (VALIDATION_ERROR, NOT_FOUND 등) message: string; // 사용자용 에러 메시지 details?: any; // 상세 에러 정보 timestamp: string; // 에러 발생 시간 }; } ``` --- ## 저장/불러오기 시나리오 ### 시나리오 1: 초기 설정 저장 **상황**: 품목기준관리 페이지에서 처음으로 설정을 저장 ```typescript // 1. 사용자가 Save 버튼 클릭 // 2. Frontend에서 전체 설정 데이터 준비 const configData = { version: "1.0", comment: "초기 설정", config: { pages: itemPages, sectionTemplates: sectionTemplates, itemMasterFields: itemMasterFields, masters: { specifications: specificationMasters, materialNames: materialItemNames, // ... 나머지 마스터 데이터 } } }; // 3. API 호출 const response = await fetch(`/api/tenants/${tenantId}/item-master-config`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(configData) }); // 4. 성공 시 localStorage 업데이트 if (response.ok) { localStorage.setItem('mes-itemMasterConfig-version', '1.0'); localStorage.setItem('mes-itemMasterConfig-lastSync', new Date().toISOString()); } ``` --- ### 시나리오 2: 설정 불러오기 (페이지 로드) **상황**: 품목기준관리 페이지 접속 시 ```typescript // 1. 컴포넌트 마운트 시 useEffect useEffect(() => { const loadConfig = async () => { try { // 2. 서버에서 최신 설정 조회 const response = await fetch( `/api/tenants/${tenantId}/item-master-config?version=latest` ); const { data } = await response.json(); // 3. Context 상태 업데이트 setItemPages(data.config.pages); setSectionTemplates(data.config.sectionTemplates); setItemMasterFields(data.config.itemMasterFields); setSpecificationMasters(data.config.masters.specifications); // ... 나머지 데이터 설정 // 4. localStorage에 캐시 localStorage.setItem('mes-itemMasterConfig', JSON.stringify(data)); localStorage.setItem('mes-itemMasterConfig-version', data.version); localStorage.setItem('mes-itemMasterConfig-lastSync', new Date().toISOString()); } catch (error) { // 5. 에러 시 localStorage 폴백 const cachedConfig = localStorage.getItem('mes-itemMasterConfig'); if (cachedConfig) { const data = JSON.parse(cachedConfig); // ... 캐시된 데이터로 설정 } } }; loadConfig(); }, [tenantId]); ``` --- ### 시나리오 3: 특정 항목만 업데이트 **상황**: 규격 마스터 1개만 추가 ```typescript // 1. 새 규격 마스터 추가 const newSpec = { id: "SPEC-NEW-001", specificationCode: "2.0T x 1219 x 2438", itemType: "RM", // ... 나머지 필드 }; // 2. 부분 업데이트 API 호출 const response = await fetch( `/api/tenants/${tenantId}/item-master-config/masters/specifications`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newSpec) } ); // 3. Context 상태 업데이트 if (response.ok) { addSpecificationMaster(newSpec); } ``` --- ### 시나리오 4: 버전 업그레이드 **상황**: 기존 설정을 기반으로 새 버전 생성 ```typescript // 1. 현재 버전 조회 const currentConfig = await fetch( `/api/tenants/${tenantId}/item-master-config?version=1.0` ).then(res => res.json()); // 2. 수정사항 반영 const updatedConfig = { ...currentConfig.data.config, pages: [...currentConfig.data.config.pages, newPage] }; // 3. 새 버전으로 저장 const response = await fetch( `/api/tenants/${tenantId}/item-master-config`, { method: 'POST', body: JSON.stringify({ version: "1.1", comment: "신규 페이지 추가", config: updatedConfig }) } ); ``` --- ## 버전 관리 전략 ### 1. 버전 네이밍 규칙 ``` {MAJOR}.{MINOR} MAJOR: 구조적 변경 (페이지 추가/삭제, 필드 타입 변경) MINOR: 데이터 추가 (마스터 데이터 추가, 섹션 추가) 예시: 1.0 - 초기 버전 1.1 - 마스터 데이터 추가 1.2 - 섹션 추가 2.0 - 페이지 구조 변경 ``` ### 2. 버전 관리 테이블 구조 ```sql CREATE TABLE item_master_config_versions ( id VARCHAR(50) PRIMARY KEY, tenant_id BIGINT NOT NULL, -- ✅ number 타입 (tenant.id와 일치) version VARCHAR(10) NOT NULL, config JSON NOT NULL, comment TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_by VARCHAR(50), is_active BOOLEAN DEFAULT TRUE, UNIQUE KEY unique_tenant_version (tenant_id, version), INDEX idx_tenant_active (tenant_id, is_active), FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE ); ``` **참고**: `tenant_id`는 BIGINT 타입으로 정의하여 로그인 응답의 `tenant.id`(number) 값과 정확히 일치하도록 합니다. ### 3. 버전 조회 전략 ```typescript // Latest 버전 조회 GET /api/tenants/{tenantId}/item-master-config?version=latest // 특정 버전 조회 GET /api/tenants/{tenantId}/item-master-config?version=1.0 // 버전 목록 조회 GET /api/tenants/{tenantId}/item-master-config/versions // Response: { "versions": [ { "version": "1.0", "createdAt": "2025-11-01", "comment": "초기 버전" }, { "version": "1.1", "createdAt": "2025-11-10", "comment": "마스터 데이터 추가" }, { "version": "2.0", "createdAt": "2025-11-18", "comment": "페이지 구조 변경" } ], "current": "2.0" } ``` --- ## 에러 처리 ### 1. 에러 코드 정의 | Code | HTTP Status | Description | |------|-------------|-------------| | `VALIDATION_ERROR` | 400 | 데이터 유효성 검증 실패 | | `UNAUTHORIZED` | 401 | 인증 실패 | | `FORBIDDEN` | 403 | 권한 없음 (테넌트 접근 권한 없음) | | `NOT_FOUND` | 404 | 설정 또는 버전을 찾을 수 없음 | | `CONFLICT` | 409 | 버전 충돌 (이미 존재하는 버전) | | `VERSION_MISMATCH` | 409 | 버전 불일치 (동시 수정 충돌) | | `SERVER_ERROR` | 500 | 서버 내부 오류 | ### 2. 에러 응답 예시 #### Validation Error ```typescript { "success": false, "error": { "code": "VALIDATION_ERROR", "message": "입력 데이터가 올바르지 않습니다.", "details": { "field": "config.pages[0].sections[0].fields[0].property.inputType", "message": "inputType은 필수입니다.", "value": null }, "timestamp": "2025-11-18T10:30:00Z" } } ``` #### Version Conflict ```typescript { "success": false, "error": { "code": "CONFLICT", "message": "버전 1.0이 이미 존재합니다.", "details": { "existingVersion": "1.0", "suggestedVersion": "1.1" }, "timestamp": "2025-11-18T10:30:00Z" } } ``` --- ## 프론트엔드 구현 가이드 ### 1. API 클라이언트 생성 ```typescript // src/lib/api/itemMasterConfigApi.ts export const ItemMasterConfigAPI = { // 전체 설정 조회 async getConfig(tenantId: number, version = 'latest') { // ✅ number 타입 const response = await fetch( `/api/tenants/${tenantId}/item-master-config?version=${version}` ); if (!response.ok) throw new Error('설정 조회 실패'); return response.json(); }, // 전체 설정 저장 async saveConfig(tenantId: number, config: ItemMasterConfig) { // ✅ number 타입 const response = await fetch( `/api/tenants/${tenantId}/item-master-config`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) } ); if (!response.ok) throw new Error('설정 저장 실패'); return response.json(); }, // 페이지 조회 async getPage(tenantId: number, pageId: string) { // ✅ number 타입 const response = await fetch( `/api/tenants/${tenantId}/item-master-config/pages/${pageId}` ); if (!response.ok) throw new Error('페이지 조회 실패'); return response.json(); }, // 규격 마스터 추가 async addSpecification(tenantId: number, spec: SpecificationMaster) { // ✅ number 타입 const response = await fetch( `/api/tenants/${tenantId}/item-master-config/masters/specifications`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(spec) } ); if (!response.ok) throw new Error('규격 추가 실패'); return response.json(); } }; ``` **사용 예시**: ```typescript // AuthContext에서 tenant.id를 추출하여 사용 const { user } = useAuth(); const tenantId = user.tenant.id; // number 타입 (예: 282) // API 호출 const config = await ItemMasterConfigAPI.getConfig(tenantId); ``` ### 2. Context 통합 ```typescript // ItemMasterContext.tsx // 서버 동기화 함수 추가 const syncWithServer = async () => { try { const { data } = await ItemMasterConfigAPI.getConfig(tenantId); // 모든 상태 업데이트 setItemPages(data.config.pages); setSectionTemplates(data.config.sectionTemplates); // ... 나머지 데이터 // localStorage 캐시 localStorage.setItem('mes-itemMasterConfig', JSON.stringify(data)); localStorage.setItem('mes-itemMasterConfig-lastSync', new Date().toISOString()); } catch (error) { console.error('서버 동기화 실패:', error); // localStorage 폴백 } }; // 저장 함수 추가 const saveToServer = async () => { try { const configData = { version: currentVersion, comment: saveComment, config: { pages: itemPages, sectionTemplates: sectionTemplates, itemMasterFields: itemMasterFields, masters: { specifications: specificationMasters, materialNames: materialItemNames, // ... 나머지 } } }; await ItemMasterConfigAPI.saveConfig(tenantId, configData); } catch (error) { console.error('저장 실패:', error); throw error; } }; ``` --- ## 다음 단계 ### Phase 1: API 모킹 (현재) 1. ✅ API 구조 설계 완료 2. ⏳ Mock API 서버 구현 (MSW 또는 json-server) 3. ⏳ 프론트엔드 API 클라이언트 구현 4. ⏳ Context와 API 통합 ### Phase 2: 백엔드 구현 1. ⏳ 데이터베이스 스키마 설계 2. ⏳ API 엔드포인트 구현 3. ⏳ 인증/권한 처리 4. ⏳ 버전 관리 로직 구현 ### Phase 3: 품목관리 페이지 동적 생성 1. ⏳ 설정 기반 폼 렌더러 구현 2. ⏳ 조건부 표시 로직 구현 3. ⏳ 유효성 검증 구현 4. ⏳ 실제 품목 데이터 저장 API 연동 --- ## 부록 ### A. localStorage 키 규칙 **❌ 기존 (tenant.id 없음 - 데이터 오염 위험)**: ```typescript // 테넌트 ID가 없어서 테넌트 전환 시 데이터 오염 발생! 'mes-itemMasterConfig' 'mes-specificationMasters' ``` **✅ 권장 (tenant.id 포함 - 완전한 격리)**: ```typescript // 설정 데이터 (tenant.id 포함) `mes-${tenantId}-itemMasterConfig` // 예: 'mes-282-itemMasterConfig' `mes-${tenantId}-itemMasterConfig-version` `mes-${tenantId}-itemMasterConfig-lastSync` // 개별 마스터 데이터 (tenant.id + 버전 포함) `mes-${tenantId}-specificationMasters` // 예: 'mes-282-specificationMasters' `mes-${tenantId}-specificationMasters-version` `mes-${tenantId}-materialItemNames` `mes-${tenantId}-materialItemNames-version` `mes-${tenantId}-itemCategories` `mes-${tenantId}-itemUnits` `mes-${tenantId}-itemMaterials` `mes-${tenantId}-surfaceTreatments` `mes-${tenantId}-partTypeOptions` `mes-${tenantId}-partUsageOptions` `mes-${tenantId}-guideRailOptions` ``` **구현 예시**: ```typescript // TenantAwareCache 클래스 사용 (권장) // 자세한 구현은 [REF-2025-11-19] multi-tenancy-implementation.md 참조 const cache = new TenantAwareCache(user.tenant.id); cache.set('itemMasterConfig', configData); // 또는 직접 구현 const key = `mes-${user.tenant.id}-itemMasterConfig`; // 'mes-282-itemMasterConfig' localStorage.setItem(key, JSON.stringify(configData)); ``` **테넌트 전환 시 캐시 삭제**: ```typescript // 로그아웃 또는 테넌트 전환 시 function clearTenantCache(tenantId: number) { const keys = Object.keys(localStorage); const prefix = `mes-${tenantId}-`; keys.forEach(key => { if (key.startsWith(prefix)) { localStorage.removeItem(key); } }); } ``` ### B. 타입 정의 파일 위치 ``` src/ ├─ types/ │ ├─ itemMaster.ts # 품목 관련 타입 │ ├─ itemMasterConfig.ts # 설정 관련 타입 │ └─ api.ts # API 응답 타입 ├─ lib/ │ └─ api/ │ └─ itemMasterConfigApi.ts # API 클라이언트 └─ contexts/ └─ ItemMasterContext.tsx # Context (기존) ``` --- **문서 버전**: 1.0 **마지막 업데이트**: 2025-11-18