diff --git a/CLAUDE.md b/CLAUDE.md index 18adf690..785d508b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -593,6 +593,73 @@ import { FormField } from '@/components/molecules/FormField'; --- +## Module Separation Architecture +**Priority**: πŸ”΄ + +### κ°œμš” +λ©€ν‹°ν…Œλ„ŒνŠΈ λͺ¨λ“ˆ 뢄리 μ•„ν‚€ν…μ²˜. ν…Œλ„ŒνŠΈλ³„λ‘œ ν•„μš”ν•œ λͺ¨λ“ˆλ§Œ ν™œμ„±ν™”ν•˜μ—¬ λΆˆν•„μš”ν•œ κΈ°λŠ₯ μˆ¨κΉ€. +`tenant.options.industry` λ―Έμ„€μ • μ‹œ **λͺ¨λ“  λͺ¨λ“ˆ ν™œμ„±ν™”** (κΈ°μ‘΄ λ™μž‘ 100% μœ μ§€). + +### 핡심 νŒ¨ν„΄: moduleAware μ•ˆμ „ μž₯치 +```typescript +const { isEnabled, tenantIndustry } = useModules(); +const moduleAware = !!tenantIndustry; // industry λ―Έμ„€μ • β†’ false β†’ μ „λΆ€ ν—ˆμš© + +if (!moduleAware) return allData; // κΈ°μ‘΄κ³Ό 동일 +return filteredData; // λͺ¨λ“ˆ 기반 필터링 +``` + +### λͺ¨λ“ˆ ꡬ쑰 +``` +src/modules/ +β”œβ”€β”€ types.ts # ModuleId, TenantIndustry, MODULE_REGISTRY +β”œβ”€β”€ config.ts # INDUSTRY_MODULES (업쒅별 ν™œμ„± λͺ¨λ“ˆ λ§€ν•‘) +β”œβ”€β”€ ModuleGuard.tsx # 라우트 기반 μ ‘κ·Ό μ œμ–΄ (layout.tsxμ—μ„œ μ‚¬μš©) +└── ModuleProvider.tsx # React Context (tenant API β†’ enabledModules 계산) + +src/hooks/useModules.ts # { isEnabled, tenantIndustry, enabledModules, ... } +``` + +### λͺ¨λ“ˆ ID λͺ©λ‘ +| ModuleId | μ„€λͺ… | ν…Œλ„ŒνŠΈ | +|----------|------|--------| +| `production` | 생산관리 | 경동 | +| `quality` | ν’ˆμ§ˆκ΄€λ¦¬ | 경동 | +| `construction` | μ‹œκ³΅κ΄€λ¦¬ | 주일 | +| `vehicle-management` | μ°¨λŸ‰κ΄€λ¦¬ | 선택적 | + +### 라우트 κ°€λ“œ vs λͺ…μ‹œμ  κ°€λ“œ +- **라우트 κ°€λ“œ (ModuleGuard)**: `/production/*`, `/quality/*` λ“± μ „μš© 라우트 +- **λͺ…μ‹œμ  κ°€λ“œ (useModules)**: 곡톡 라우트 λ‚΄ λͺ¨λ“ˆ 의쑴 νŽ˜μ΄μ§€ (예: `/sales/*/production-orders`) + +```typescript +// 곡톡 라우트 λ‚΄ λͺ¨λ“ˆ 의쑴 νŽ˜μ΄μ§€ β€” λͺ…μ‹œμ  κ°€λ“œ ν•„μˆ˜ +if (tenantIndustry && !isEnabled('production')) { + return
생산관리 λͺ¨λ“ˆμ΄ ν™œμ„±ν™”λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
; +} +``` + +### 크둜슀 λͺ¨λ“ˆ μž„ν¬νŠΈ κ·œμΉ™ +- **Common β†’ Tenant 직접 import κΈˆμ§€** (검증 슀크립트: `scripts/verify-module-separation.sh`) +- **ν—ˆμš© μ˜ˆμ™Έ**: `// MODULE_SEPARATION_OK` 주석 + `src/lib/api/` 곡유 래퍼 +- **Tenant β†’ Common import**: 자유 +- **Tenant β†’ Tenant import**: κΈˆμ§€ (dynamic import만 ν—ˆμš©) + +### MODULE.md 경계 마컀 +각 ν…Œλ„ŒνŠΈ λͺ¨λ“ˆ 디렉토리에 `MODULE.md` 파일둜 λͺ¨λ“ˆ 경계 λ¬Έμ„œν™”: +- `src/components/production/MODULE.md` +- `src/components/quality/MODULE.md` +- `src/components/business/construction/MODULE.md` +- `src/components/vehicle-management/MODULE.md` + +### Path Aliases +```json +// tsconfig.json +"@modules/*": ["./src/modules/*"] +``` + +--- + ## User Environment **Priority**: 🟒 diff --git a/claudedocs/architecture/module-separation-guide.md b/claudedocs/architecture/module-separation-guide.md new file mode 100644 index 00000000..eab259f8 --- /dev/null +++ b/claudedocs/architecture/module-separation-guide.md @@ -0,0 +1,234 @@ +# SAM ERP λ©€ν‹°ν…Œλ„ŒνŠΈ λͺ¨λ“ˆ 뢄리 μ•„ν‚€ν…μ²˜ + +> μž‘μ„±μΌ: 2026-03-18 +> μƒνƒœ: ν”„λ‘ νŠΈμ—”λ“œ Phase 0~3 μ™„λ£Œ / λ°±μ—”λ“œ μž‘μ—… ν•„μš” + +--- + +## 1. κ°œμš” + +### λͺ©ν‘œ +ν•˜λ‚˜μ˜ SAM ERP μ½”λ“œλ² μ΄μŠ€μ—μ„œ **ν…Œλ„ŒνŠΈ(νšŒμ‚¬)λ³„λ‘œ ν•„μš”ν•œ λͺ¨λ“ˆλ§Œ ν™œμ„±ν™”**ν•˜μ—¬, +λΆˆν•„μš”ν•œ λ©”λ‰΄Β·νŽ˜μ΄μ§€Β·λŒ€μ‹œλ³΄λ“œ μ„Ήμ…˜μ„ μˆ¨κΈ°λŠ” ꡬ쑰. + +### ν˜„μž¬ ν…Œλ„ŒνŠΈλ³„ λͺ¨λ“ˆ ꡬ성 +| μ—…μ’… μ½”λ“œ | ν…Œλ„ŒνŠΈ μ˜ˆμ‹œ | ν™œμ„± λͺ¨λ“ˆ | +|-----------|------------|-----------| +| `shutter_mes` | 경동 | 생산관리, ν’ˆμ§ˆκ΄€λ¦¬, μ°¨λŸ‰κ΄€λ¦¬ | +| `construction` | 주일 | μ‹œκ³΅κ΄€λ¦¬, μ°¨λŸ‰κ΄€λ¦¬ | +| (λ―Έμ„€μ •) | 기타 λͺ¨λ“  ν…Œλ„ŒνŠΈ | **전체 λͺ¨λ“ˆ ν™œμ„±ν™” (κΈ°μ‘΄κ³Ό 동일)** | + +### μ•ˆμ „ 원칙 +``` +tenant.options.industryκ°€ μ„€μ •λ˜μ§€ μ•Šμ€ ν…Œλ„ŒνŠΈ β†’ λͺ¨λ“  κΈ°λŠ₯ κ·ΈλŒ€λ‘œ μ‚¬μš© κ°€λŠ₯ += κΈ°μ‘΄ λ™μž‘ 100% μœ μ§€, λΆ€μž‘μš© 제둜 +``` + +--- + +## 2. ν”„λ‘ νŠΈμ—”λ“œ ꡬ쑰 (μ™„λ£Œ) + +### 파일 ꡬ쑰 +``` +src/modules/ +β”œβ”€β”€ types.ts # ModuleId νƒ€μž… μ •μ˜ +β”œβ”€β”€ tenant-config.ts # μ—…μ’…β†’λͺ¨λ“ˆ λ§€ν•‘ (resolveEnabledModules) +└── index.ts # λͺ¨λ“ˆ λ ˆμ§€μŠ€νŠΈλ¦¬ (라우트 λ§€ν•‘, λŒ€μ‹œλ³΄λ“œ μ„Ήμ…˜) + +src/hooks/ +└── useModules.ts # React ν›…: isEnabled(), isRouteAllowed(), tenantIndustry +``` + +### λͺ¨λ“ˆ ID λͺ©λ‘ +| ModuleId | 이름 | μ†Œμœ  라우트 | λŒ€μ‹œλ³΄λ“œ μ„Ήμ…˜ | +|----------|------|------------|--------------| +| `common` | 곡톡 ERP | /dashboard, /accounting, /sales, /hr, /approval, /settings λ“± | μ „λΆ€ | +| `production` | 생산관리 | /production | dailyProduction, unshipped | +| `quality` | ν’ˆμ§ˆκ΄€λ¦¬ | /quality | - | +| `construction` | μ‹œκ³΅κ΄€λ¦¬ | /construction | construction | +| `vehicle-management` | μ°¨λŸ‰κ΄€λ¦¬ | /vehicle-management, /vehicle | - | + +### ν”„λ‘ νŠΈμ—”λ“œ λ™μž‘ 흐름 +``` +1. 둜그인 β†’ authStore에 tenant 정보 μ €μž₯ +2. useModules() 훅이 tenant.options.industry 읽음 +3. industry κ°’μœΌλ‘œ INDUSTRY_MODULE_MAP 쑰회 β†’ ν™œμ„± λͺ¨λ“ˆ λͺ©λ‘ κ²°μ • +4. 각 μ»΄ν¬λ„ŒνŠΈμ—μ„œ isEnabled('production') λ“±μœΌλ‘œ λΆ„κΈ° +``` + +### 적용된 μ˜μ—­ + +#### A. CEO λŒ€μ‹œλ³΄λ“œ +- **μ„Ήμ…˜ 필터링**: λΉ„ν™œμ„± λͺ¨λ“ˆμ˜ λŒ€μ‹œλ³΄λ“œ μ„Ήμ…˜ μžλ™ μ œμ™Έ +- **API 호좜 차단**: λΉ„ν™œμ„± λͺ¨λ“ˆμ˜ APIλŠ” ν˜ΈμΆœν•˜μ§€ μ•ŠμŒ (null endpoint) +- **μ„€μ • νŒμ—…**: λΉ„ν™œμ„± λͺ¨λ“ˆ μ„Ήμ…˜μ€ μ„€μ •μ—μ„œλ„ μ•ˆ λ³΄μž„ +- **μΊ˜λ¦°λ”**: λΉ„ν™œμ„± λͺ¨λ“ˆμ˜ 일정 μœ ν˜• ν•„ν„° μˆ¨κΉ€ +- **μš”μ•½ λ„€λΉ„**: λΉ„ν™œμ„± μ„Ήμ…˜ μžλ™ μ œμ™Έ + +#### B. 라우트 μ ‘κ·Ό μ œμ–΄ +- `/production/*`, `/quality/*`, `/construction/*` λ“± μ „μš© λΌμš°νŠΈλŠ” λͺ¨λ“ˆ λΉ„ν™œμ„± μ‹œ μ ‘κ·Ό 차단 +- `/sales/*/production-orders` 같은 곡톡 라우트 λ‚΄ λͺ¨λ“ˆ 의쑴 νŽ˜μ΄μ§€λŠ” λͺ…μ‹œμ  κ°€λ“œ 적용 + +#### C. μ‚¬μ΄λ“œλ°” 메뉴 +- λΉ„ν™œμ„± λͺ¨λ“ˆμ˜ 메뉴 ν•­λͺ© μˆ¨κΉ€ (isRouteAllowed 기반) + +--- + +## 3. λ°±μ—”λ“œ ν•„μš” μž‘μ—… + +### 3.1 tenants ν…Œμ΄λΈ” options ν•„λ“œμ— industry μΆ”κ°€ +**μš°μ„ μˆœμœ„: πŸ”΄ ν•„μˆ˜** + +ν˜„μž¬ ν”„λ‘ νŠΈμ—”λ“œλŠ” `tenant.options.industry` 값을 μ½μ–΄μ„œ λͺ¨λ“ˆμ„ κ²°μ •ν•©λ‹ˆλ‹€. +이 값이 λ°±μ—”λ“œμ—μ„œ 내렀와야 μ‹€μ œλ‘œ λ™μž‘ν•©λ‹ˆλ‹€. + +```php +// tenants ν…Œμ΄λΈ”μ˜ options JSON μ»¬λŸΌμ— industry μΆ”κ°€ +// μ˜ˆμ‹œ 데이터: +{ + "industry": "shutter_mes" // 경동: μ…”ν„° MES +} +{ + "industry": "construction" // 주일: 건섀 +} +// λ‹€λ₯Έ ν…Œλ„ŒνŠΈ: industry ν‚€ μ—†μŒ β†’ ν”„λ‘ νŠΈμ—μ„œ 전체 λͺ¨λ“ˆ ν™œμ„±ν™” +``` + +**μž‘μ—… λ‚΄μš©:** +1. `tenants` ν…Œμ΄λΈ”μ˜ `options` JSON μ»¬λŸΌμ— `industry` ν‚€ μΆ”κ°€ (λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ λΆˆν•„μš”, JSONμ΄λ―€λ‘œ) +2. 경동 ν…Œλ„ŒνŠΈ: `options->industry = 'shutter_mes'` +3. 주일 ν…Œλ„ŒνŠΈ: `options->industry = 'construction'` +4. ν…Œλ„ŒνŠΈ 정보 API 응닡에 `options.industry` 포함 확인 + +**확인 포인트:** +- ν”„λ‘ νŠΈμ—”λ“œμ—μ„œ `authStore.currentUser.tenant.options.industry`둜 μ ‘κ·Ό +- ν˜„μž¬ 둜그인 API(`/api/v1/auth/me` λ˜λŠ” μœ μ‚¬)의 μ‘λ‹΅μ—μ„œ tenant.optionsκ°€ ν¬ν•¨λ˜λŠ”μ§€ 확인 +- 포함 μ•ˆ 되면 응닡에 μΆ”κ°€ ν•„μš” + +### 3.2 (선택) ν…Œλ„ŒνŠΈ 관리 ν™”λ©΄μ—μ„œ industry μ„€μ • UI +**μš°μ„ μˆœμœ„: 🟑 선택** + +κ΄€λ¦¬μžκ°€ ν…Œλ„ŒνŠΈλ³„ 업쒅을 μ„€μ •ν•  수 μžˆλŠ” UI. κΈ‰ν•˜μ§€ μ•ŠμŒ β€” DB 직접 μˆ˜μ •μœΌλ‘œ μΆ©λΆ„. + +### 3.3 (Phase 2 μ˜ˆμ •) λͺ…μ‹œμ  λͺ¨λ“ˆ λͺ©λ‘ API +**μš°μ„ μˆœμœ„: 🟒 ν–₯ν›„** + +ν˜„μž¬λŠ” `industry` β†’ ν”„λ‘ νŠΈμ—”λ“œ ν•˜λ“œμ½”λ”© λ§€ν•‘μœΌλ‘œ λͺ¨λ“ˆ κ²°μ •. +ν–₯ν›„ λ°±μ—”λ“œμ—μ„œ 직접 λͺ¨λ“ˆ λͺ©λ‘μ„ λ‚΄λ €μ£Όλ©΄ 더 μœ μ—°ν•΄μ§. + +```php +// tenant.options μ˜ˆμ‹œ (Phase 2) +{ + "industry": "shutter_mes", + "modules": ["production", "quality", "vehicle-management"] // λͺ…μ‹œμ  λͺ©λ‘ +} +``` + +ν”„λ‘ νŠΈμ—”λ“œλŠ” 이미 이 ꡬ쑰λ₯Ό μ§€μ›ν•˜λ„λ‘ μ€€λΉ„λ˜μ–΄ 있음: +```typescript +// src/modules/tenant-config.ts +export function resolveEnabledModules(options) { + // Phase 2: λ°±μ—”λ“œκ°€ λͺ…μ‹œμ  λͺ¨λ“ˆ λͺ©λ‘ 제곡 β†’ μš°μ„  μ‚¬μš© + if (explicitModules && explicitModules.length > 0) { + return explicitModules; + } + // Phase 1: industry 기반 κΈ°λ³Έκ°’ (ν˜„μž¬) + if (industry) { + return INDUSTRY_MODULE_MAP[industry] ?? []; + } + return []; +} +``` + +--- + +## 4. 업쒅별 λͺ¨λ“ˆ λ§€ν•‘ (ν”„λ‘ νŠΈμ—”λ“œ ν•˜λ“œμ½”λ”©) + +```typescript +// src/modules/tenant-config.ts +const INDUSTRY_MODULE_MAP: Record = { + shutter_mes: ['production', 'quality', 'vehicle-management'], + construction: ['construction', 'vehicle-management'], +}; +``` + +μƒˆλ‘œμš΄ μ—…μ’… μΆ”κ°€ μ‹œ: +1. 여기에 λ§€ν•‘ μΆ”κ°€ +2. ν•„μš”ν•˜λ©΄ `ModuleId` νƒ€μž…μ— μƒˆ λͺ¨λ“ˆ ID μΆ”κ°€ +3. `MODULE_REGISTRY` (src/modules/index.ts)에 라우트/λŒ€μ‹œλ³΄λ“œ μ„Ήμ…˜ 등둝 + +--- + +## 5. 핡심 μ½”λ“œ νŒ¨ν„΄ + +### κΈ°λ³Έ μ‚¬μš©λ²• +```typescript +import { useModules } from '@/hooks/useModules'; + +function MyComponent() { + const { isEnabled, tenantIndustry } = useModules(); + + // μ•ˆμ „ μž₯치: industry 미섀정이면 λͺ¨λ“  κΈ°λŠ₯ ν™œμ„± + const moduleAware = !!tenantIndustry; + + if (moduleAware && !isEnabled('production')) { + return
생산관리 λͺ¨λ“ˆμ΄ λΉ„ν™œμ„±ν™”λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.
; + } + + // 생산관리 κΈ°λŠ₯ λ Œλ”λ§... +} +``` + +### 크둜슀 λͺ¨λ“ˆ μž„ν¬νŠΈ κ·œμΉ™ +``` +βœ… Common β†’ Common (자유) +βœ… Tenant β†’ Common (자유) +βœ… Common β†’ Tenant (래퍼 경유) (src/lib/api/μ—μ„œ MODULE_SEPARATION_OK 주석과 ν•¨κ»˜) +❌ Common β†’ Tenant (직접) (scripts/verify-module-separation.shκ°€ κ²€μΆœ) +❌ Tenant β†’ Tenant (κΈˆμ§€, dynamic import만 ν—ˆμš©) +``` + +--- + +## 6. κ΅¬ν˜„ 이λ ₯ + +| Phase | λ‚΄μš© | 컀밋 | μƒνƒœ | +|-------|------|------|------| +| Phase 0 | 크둜슀 λͺ¨λ“ˆ μ˜μ‘΄μ„± ν•΄μ†Œ | `a99c3b39` | βœ… μ™„λ£Œ | +| Phase 1 | λͺ¨λ“ˆ λ ˆμ§€μŠ€νŠΈλ¦¬ + 라우트 κ°€λ“œ | `0a65609e` | βœ… μ™„λ£Œ | +| Phase 2 | CEO λŒ€μ‹œλ³΄λ“œ λͺ¨λ“ˆ λ””μ»€ν”Œλ§ | `46501214` | βœ… μ™„λ£Œ | +| Phase 3 | 물리적 뢄리 (경계 마컀, 검증, κ°€λ“œ, λ¬Έμ„œ) | (미컀밋) | βœ… μ™„λ£Œ | + +--- + +## 7. ν…ŒμŠ€νŠΈ μ‹œλ‚˜λ¦¬μ˜€ + +### ν…ŒμŠ€νŠΈ 방법 +λ°±μ—”λ“œμ—μ„œ `tenant.options.industry`λ₯Ό μ„€μ •ν•œ ν›„: + +| μ‹œλ‚˜λ¦¬μ˜€ | μ˜ˆμƒ κ²°κ³Ό | +|----------|----------| +| industry λ―Έμ„€μ • ν…Œλ„ŒνŠΈ 둜그인 | κΈ°μ‘΄κ³Ό μ™„μ „ 동일 (λͺ¨λ“  메뉴/κΈ°λŠ₯ ν‘œμ‹œ) | +| `shutter_mes` ν…Œλ„ŒνŠΈ 둜그인 | μ‹œκ³΅κ΄€λ¦¬ 메뉴 μˆ¨κΉ€, λŒ€μ‹œλ³΄λ“œ μ‹œκ³΅ μ„Ήμ…˜ μ•ˆ λ³΄μž„ | +| `construction` ν…Œλ„ŒνŠΈ 둜그인 | 생산/ν’ˆμ§ˆ 메뉴 μˆ¨κΉ€, λŒ€μ‹œλ³΄λ“œ 생산 μ„Ήμ…˜ μ•ˆ λ³΄μž„ | +| `shutter_mes`μ—μ„œ `/construction` 직접 μ ‘κ·Ό | μ ‘κ·Ό 차단 λ©”μ‹œμ§€ ν‘œμ‹œ | +| `construction`μ—μ„œ `/production` 직접 μ ‘κ·Ό | μ ‘κ·Ό 차단 λ©”μ‹œμ§€ ν‘œμ‹œ | + +### λ‘€λ°± 방법 +문제 λ°œμƒ μ‹œ DBμ—μ„œ `tenant.options.industry` κ°’λ§Œ μ œκ±°ν•˜λ©΄ μ¦‰μ‹œ 원볡. +ν”„λ‘ νŠΈμ—”λ“œ μ½”λ“œ λ³€κ²½ λΆˆν•„μš”. + +--- + +## 8. ν–₯ν›„ λ‘œλ“œλ§΅ + +``` +ν˜„μž¬ (Phase 1) ν–₯ν›„ (Phase 2) μ΅œμ’… (Phase 3) +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ industry ν•˜λ“œμ½”λ”© β”‚ β†’ β”‚ λ°±μ—”λ“œ modules λͺ©λ‘ β”‚ β†’ β”‚ JSON μŠ€ν‚€λ§ˆ 기반 β”‚ +β”‚ λ§€ν•‘μœΌλ‘œ λͺ¨λ“ˆ κ²°μ • β”‚ β”‚ APIμ—μ„œ 직접 μˆ˜μ‹  β”‚ β”‚ 동적 νŽ˜μ΄μ§€ 쑰립 β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +- **Phase 2**: `tenant.options.modules = ["production", "quality"]` ν˜•νƒœλ‘œ λ°±μ—”λ“œμ—μ„œ λͺ…μ‹œμ  λͺ¨λ“ˆ λͺ©λ‘ 전달 β†’ μ—…μ’… λ§€ν•‘ ν…Œμ΄λΈ” λΆˆν•„μš” +- **Phase 3**: 각 λͺ¨λ“ˆμ˜ νŽ˜μ΄μ§€ ꡬ성을 JSON μŠ€ν‚€λ§ˆλ‘œ μ •μ˜ β†’ μ½”λ“œ λ³€κ²½ 없이 ν…Œλ„ŒνŠΈλ³„ ν™”λ©΄ μ»€μŠ€ν„°λ§ˆμ΄μ§• diff --git a/scripts/verify-module-separation.sh b/scripts/verify-module-separation.sh new file mode 100755 index 00000000..df268a26 --- /dev/null +++ b/scripts/verify-module-separation.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# =================================================================== +# Module Separation Verification Script +# +# 곡톡 ERP β†’ ν…Œλ„ŒνŠΈ λͺ¨λ“ˆ κ°„ κΈˆμ§€λœ 정적 importλ₯Ό κ²€μ‚¬ν•©λ‹ˆλ‹€. +# Phase 0μ—μ„œ ν•΄μ†Œν•œ μ˜μ‘΄μ„±μ΄ λ‹€μ‹œ λ°œμƒν•˜μ§€ μ•Šλ„λ‘ CIμ—μ„œ μ‹€ν–‰ κ°€λŠ₯. +# +# μ‚¬μš©λ²•: bash scripts/verify-module-separation.sh +# μ’…λ£Œμ½”λ“œ: 0 = 톡과, 1 = μœ„λ°˜ 발견 +# =================================================================== + +set -euo pipefail + +echo "=================================================" +echo " Module Separation Verification" +echo "=================================================" +echo "" + +# ν…Œλ„ŒνŠΈ μ „μš© 경둜 νŒ¨ν„΄ (from 'xxx' λ˜λŠ” from "xxx" ν˜•νƒœλ‘œ 검색) +TENANT_PATHS=( + "@/components/production/" + "@/components/quality/" + "@/components/business/construction/" + "@/components/vehicle-management/" +) + +# 곡톡 ERP μ†ŒμŠ€ 디렉토리 (ν…Œλ„ŒνŠΈ νŽ˜μ΄μ§€ μ œμ™Έ) +COMMON_DIRS=( + "src/components/approval" + "src/components/accounting" + "src/components/auth" + "src/components/atoms" + "src/components/board" + "src/components/business/CEODashboard" + "src/components/business/DashboardSwitcher.tsx" + "src/components/clients" + "src/components/common" + "src/components/customer-center" + "src/components/document-system" + "src/components/hr" + "src/components/items" + "src/components/layout" + "src/components/material" + "src/components/molecules" + "src/components/organisms" + "src/components/orders" + "src/components/outbound" + "src/components/pricing" + "src/components/providers" + "src/components/reports" + "src/components/settings" + "src/components/stocks" + "src/components/templates" + "src/components/ui" + "src/lib" + "src/hooks" + "src/stores" + "src/contexts" +) + +VIOLATIONS=0 + +for dir in "${COMMON_DIRS[@]}"; do + # 디렉토리/파일이 μ—†μœΌλ©΄ μŠ€ν‚΅ + [ -e "$dir" ] || continue + + for tenant_path in "${TENANT_PATHS[@]}"; do + # 정적 import 검색 (dynamic importλŠ” ν—ˆμš©) + found=$(grep -rn "from ['\"]${tenant_path}" "$dir" \ + --include="*.ts" --include="*.tsx" 2>/dev/null \ + | grep -v "dynamic(" \ + | grep -v "// MODULE_SEPARATION_OK" \ + || true) + + if [ -n "$found" ]; then + echo "VIOLATION: $dir β†’ $tenant_path" + echo "$found" + echo "" + VIOLATIONS=$((VIOLATIONS + 1)) + fi + done +done + +echo "=================================================" +if [ $VIOLATIONS -eq 0 ]; then + echo " PASSED: No forbidden imports found." + exit 0 +else + echo " FAILED: Found $VIOLATIONS forbidden import(s)." + echo "" + echo " ν•΄κ²° 방법:" + echo " - dynamic import (next/dynamic)둜 ꡐ체" + echo " - @/lib/api/ λ˜λŠ” @/interfaces/둜 νƒ€μž… 이동" + echo " - @/components/document-system/으둜 곡유 λͺ¨λ‹¬ 이동" + echo " - λΆˆκ°€ν”Όν•œ 경우 // MODULE_SEPARATION_OK 주석 μΆ”κ°€" + exit 1 +fi diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx index b7e20aba..bf39bcda 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx @@ -16,6 +16,7 @@ import { useState, useEffect, useCallback } from "react"; import { useRouter, useParams } from "next/navigation"; +import { useModules } from "@/hooks/useModules"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { @@ -345,6 +346,24 @@ export default function ProductionOrderCreatePage() { const router = useRouter(); const params = useParams(); const orderId = params.id as string; + const { isEnabled, tenantIndustry } = useModules(); + + // 생산 λͺ¨λ“ˆ λΉ„ν™œμ„± μ‹œ μ ‘κ·Ό 차단 (tenantIndustry λ―Έμ„€μ • μ‹œ μ „λΆ€ ν—ˆμš©) + if (tenantIndustry && !isEnabled('production')) { + return ( + +
+

생산관리 λͺ¨λ“ˆμ΄ ν™œμ„±ν™”λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

+ +
+
+ ); + } const [loading, setLoading] = useState(true); const [error, setError] = useState(null); diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/[id]/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/[id]/page.tsx index 0e0c97f8..1b0ec878 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/[id]/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/[id]/page.tsx @@ -11,6 +11,7 @@ import { useState, useEffect } from "react"; import { useRouter, useParams } from "next/navigation"; +import { useModules } from "@/hooks/useModules"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; @@ -194,6 +195,24 @@ export default function ProductionOrderDetailPage() { const router = useRouter(); const params = useParams(); const orderId = params.id as string; + const { isEnabled, tenantIndustry } = useModules(); + + // 생산 λͺ¨λ“ˆ λΉ„ν™œμ„± μ‹œ μ ‘κ·Ό 차단 (tenantIndustry λ―Έμ„€μ • μ‹œ μ „λΆ€ ν—ˆμš©) + if (tenantIndustry && !isEnabled('production')) { + return ( + +
+

생산관리 λͺ¨λ“ˆμ΄ ν™œμ„±ν™”λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

+ +
+
+ ); + } const [detail, setDetail] = useState(null); const [loading, setLoading] = useState(true); diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/page.tsx index da143646..1ac16634 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/page.tsx @@ -12,6 +12,7 @@ import { useState, useCallback } from "react"; import { useRouter } from "next/navigation"; +import { useModules } from "@/hooks/useModules"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; @@ -174,6 +175,23 @@ const TABLE_COLUMNS: TableColumn[] = [ export default function ProductionOrdersListPage() { const router = useRouter(); + const { isEnabled, tenantIndustry } = useModules(); + + // 생산 λͺ¨λ“ˆ λΉ„ν™œμ„± μ‹œ μ ‘κ·Ό 차단 (tenantIndustry λ―Έμ„€μ • μ‹œ μ „λΆ€ ν—ˆμš©) + if (tenantIndustry && !isEnabled('production')) { + return ( +
+

생산관리 λͺ¨λ“ˆμ΄ ν™œμ„±ν™”λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

+ +
+ ); + } + const [stats, setStats] = useState({ total: 0, waiting: 0, diff --git a/src/components/business/construction/MODULE.md b/src/components/business/construction/MODULE.md new file mode 100644 index 00000000..b1498417 --- /dev/null +++ b/src/components/business/construction/MODULE.md @@ -0,0 +1,32 @@ +# Construction Module (건섀관리) + +**Module ID**: `construction` +**Tenant**: Juil (주일건섀) +**Route Prefixes**: `/construction` +**Component Count**: 161 files + +## Dependencies on Common ERP +- `@/lib/api/*` β€” Server actions, API client +- `@/components/ui/*` β€” UI primitives (shadcn/ui) +- `@/components/templates/*` β€” IntegratedListTemplateV2 λ“± +- `@/components/organisms/*` β€” PageLayout, PageHeader +- `@/hooks/*` β€” usePermission, useModules λ“± +- `@/stores/authStore` β€” Tenant 정보 +- `@/components/common/*` β€” 곡톡 μ»΄ν¬λ„ŒνŠΈ + +## Exports to Common ERP +**NONE** β€” 건섀 λͺ¨λ“ˆμ€ λ…λ¦½μ μœΌλ‘œ μž‘λ™. + +## Related Dashboard Sections +- `construction` (μ‹œκ³΅ ν˜„ν™©) + +## Subdirectories +- `bidding/` β€” μž…μ°° 관리 +- `contract/` β€” 계약 관리 +- `estimates/` β€” 견적 관리 +- `progress-billing/` β€” κΈ°μ„± 관리 +- `site-management/` β€” ν˜„μž₯ 관리 +- `labor-management/` β€” 노무 관리 +- `item-management/` β€” 자재 관리 +- `partners/` β€” ν˜‘λ ₯업체 관리 +- 기타 20개 ν•˜μœ„ 도메인 diff --git a/src/components/production/MODULE.md b/src/components/production/MODULE.md new file mode 100644 index 00000000..98e75450 --- /dev/null +++ b/src/components/production/MODULE.md @@ -0,0 +1,25 @@ +# Production Module (생산관리) + +**Module ID**: `production` +**Tenant**: Kyungdong (경동 μ…”ν„° MES) +**Route Prefixes**: `/production` +**Component Count**: 56 files + +## Dependencies on Common ERP +- `@/lib/api/*` β€” Server actions, API client +- `@/components/ui/*` β€” UI primitives (shadcn/ui) +- `@/components/templates/*` β€” IntegratedListTemplateV2 λ“± +- `@/components/organisms/*` β€” PageLayout, PageHeader +- `@/hooks/*` β€” usePermission, useModules λ“± +- `@/stores/authStore` β€” Tenant 정보 +- `@/stores/menuStore` β€” μ‚¬μ΄λ“œλ°” μƒνƒœ + +## Exports to Common ERP +**NONE** β€” Phase 0μ—μ„œ λͺ¨λ“  ꡐ차 μ°Έμ‘° ν•΄μ†Œ μ™„λ£Œ. +- νƒ€μž…: `@/lib/api/production-orders/types.ts` (re-export) +- μ„œλ²„ μ•‘μ…˜: `@/lib/api/production-orders/actions.ts` (async wrapper) +- λͺ¨λ‹¬: `@/components/document-system/modals/` (dynamic import wrapper) + +## Related Dashboard Sections +- `production` (생산 ν˜„ν™©) +- `shipment` (좜고 ν˜„ν™©) diff --git a/src/components/quality/MODULE.md b/src/components/quality/MODULE.md new file mode 100644 index 00000000..539e77cf --- /dev/null +++ b/src/components/quality/MODULE.md @@ -0,0 +1,21 @@ +# Quality Module (ν’ˆμ§ˆκ΄€λ¦¬) + +**Module ID**: `quality` +**Tenant**: Kyungdong (경동 μ…”ν„° MES) +**Route Prefixes**: `/quality` +**Component Count**: 35 files + +## Dependencies on Common ERP +- `@/lib/api/*` β€” Server actions, API client +- `@/components/ui/*` β€” UI primitives (shadcn/ui) +- `@/components/templates/*` β€” IntegratedListTemplateV2 λ“± +- `@/components/organisms/*` β€” PageLayout, PageHeader +- `@/hooks/*` β€” usePermission, useModules λ“± +- `@/stores/authStore` β€” Tenant 정보 + +## Exports to Common ERP +**NONE** β€” Phase 0μ—μ„œ ꡐ차 μ°Έμ‘° ν•΄μ†Œ μ™„λ£Œ. +- λͺ¨λ‹¬: `@/components/document-system/modals/` (WorkLogModal β€” dynamic import) + +## Related Dashboard Sections +μ—†μŒ (ν’ˆμ§ˆ λŒ€μ‹œλ³΄λ“œ μ„Ήμ…˜μ€ 아직 λ―Έκ΅¬ν˜„) diff --git a/src/components/vehicle-management/MODULE.md b/src/components/vehicle-management/MODULE.md new file mode 100644 index 00000000..cce7d21d --- /dev/null +++ b/src/components/vehicle-management/MODULE.md @@ -0,0 +1,20 @@ +# Vehicle Management Module (μ°¨λŸ‰κ΄€λ¦¬) + +**Module ID**: `vehicle-management` +**Tenant**: Optional (경동 + 주일 곡톡 선택) +**Route Prefixes**: `/vehicle-management`, `/vehicle` +**Component Count**: 13 files + +## Dependencies on Common ERP +- `@/lib/api/*` β€” Server actions, API client +- `@/components/ui/*` β€” UI primitives (shadcn/ui) +- `@/components/templates/*` β€” IntegratedListTemplateV2 λ“± +- `@/components/organisms/*` β€” PageLayout, PageHeader +- `@/hooks/*` β€” usePermission, useModules λ“± +- `@/stores/authStore` β€” Tenant 정보 + +## Exports to Common ERP +**NONE** + +## Related Dashboard Sections +μ—†μŒ diff --git a/src/lib/api/production-orders/actions.ts b/src/lib/api/production-orders/actions.ts index e863b576..307927a2 100644 --- a/src/lib/api/production-orders/actions.ts +++ b/src/lib/api/production-orders/actions.ts @@ -11,7 +11,7 @@ import { getProductionOrders as _getProductionOrders, getProductionOrderStats as _getProductionOrderStats, getProductionOrderDetail as _getProductionOrderDetail, -} from '@/components/production/ProductionOrders/actions'; +} from '@/components/production/ProductionOrders/actions'; // MODULE_SEPARATION_OK β€” 곡유 μ•‘μ…˜ 래퍼 (Phase 0) import type { ProductionOrderListParams } from './types'; export async function getProductionOrders(params: ProductionOrderListParams) { diff --git a/src/lib/api/production-orders/types.ts b/src/lib/api/production-orders/types.ts index 95c61370..2ed15da6 100644 --- a/src/lib/api/production-orders/types.ts +++ b/src/lib/api/production-orders/types.ts @@ -19,4 +19,4 @@ export type { ApiProductionWorkOrder, ApiBomProcessGroup, ApiBomItem, -} from '@/components/production/ProductionOrders/types'; +} from '@/components/production/ProductionOrders/types'; // MODULE_SEPARATION_OK β€” 곡유 μΈν„°νŽ˜μ΄μŠ€ (Phase 0) diff --git a/tsconfig.json b/tsconfig.json index 50296bbe..417c2930 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,6 +25,9 @@ "paths": { "@/*": [ "./src/*" + ], + "@modules/*": [ + "./src/modules/*" ] } },