@@ -220,10 +258,11 @@ const customHeaderActions =
| 조건 | 결과 |
|------|------|
| new 모드 | 버튼 미표시 (품목 미저장) |
-| view/edit + `hasInspectionTemplate=false` | 버튼 미표시 |
+| view/edit + 템플릿 없고 검사결과 없음 | 버튼 미표시 |
| view/edit + `hasInspectionTemplate=true` | **두 버튼 모두 표시** |
+| view/edit + 검사결과 또는 검사일 있음 | **두 버튼 모두 표시** (합격 후에도 유지) |
-> **핵심**: MNG에서 해당 품목의 수입검사 템플릿에 `linked_item_ids`를 설정하면 버튼이 자동 표시된다.
+> **핵심**: 템플릿이 있거나, 이미 검사가 수행된 경우 버튼이 표시된다.
---
@@ -236,4 +275,4 @@ const customHeaderActions =
---
-**최종 업데이트**: 2026-03-17
+**최종 업데이트**: 2026-03-20
diff --git a/dev/guides/file-storage-guide.md b/dev/guides/file-storage-guide.md
index 0b034fa..7f20e44 100644
--- a/dev/guides/file-storage-guide.md
+++ b/dev/guides/file-storage-guide.md
@@ -1045,17 +1045,47 @@ export default function StorageQuotaBar({ used, limit }) {
- 대시보드 추가
- 차트 라이브러리 (Chart.js, Recharts)
-### Phase 3 (1년 후)
-1. **Object Storage 전환**
- - AWS S3 / Naver Cloud Object Storage
- - Laravel Flysystem 드라이버 변경
- - 기존 파일 마이그레이션
+### Phase 3 — ✅ 완료 (2026-03-20)
+1. **Object Storage → Cloudflare R2** 전환 완료
+2. **이미지 서빙 → R2 Presigned URL** 방식 적용
+3. **CDN** — 미적용 (Cloudflare DNS 등록 필요)
-2. **CDN 연동**
- - CloudFront / CloudFlare
- - 이미지 썸네일 자동 생성
+---
-3. **고급 기능**
- - 파일 버전 관리
- - 협업 편집
- - 파일 잠금
+## ☁️ R2 파일 서빙 정책 (2026-03-20~)
+
+### 핵심 원칙
+
+- **파일 저장**: Cloudflare R2 (S3 호환)
+- **이미지 서빙**: API Resource에서 `image_url` (presigned URL) 반환 → 브라우저가 R2 직접 로드
+- **프록시 금지**: MNG/React에서 API를 경유한 바이너리 스트리밍 방식 사용하지 않음
+- **공개 라우트 금지**: 인증 없는 파일 접근 라우트 생성 금지
+
+### 파일 접근 방식
+
+```
+Browser → R2 presigned URL 직접 로드 (1홉)
+```
+
+### API Resource 규칙
+
+`image_file_id`를 반환하는 모든 API Resource는 `image_url`도 함께 반환한다.
+
+- `File::presignedUrl()` 메서드 사용 (30분 유효)
+- `temporaryUrl()`은 로컬 서명 생성만 수행 (R2 네트워크 호출 없음)
+
+### 소비자(MNG, React) 규칙
+
+- API 응답의 `image_url` 필드를 `
![]()
`에 직접 사용
+- `image_url`이 없으면 `route('files.view', $id)` fallback (MNG FileViewController redirect)
+
+### 보안
+
+- 서명 기반 접근: 30분 만료, 만료 후 403
+- 테넌트 격리: BelongsToTenant 스코프 적용 후 URL 발급
+- 인증 없는 파일 공개 라우트 금지
+
+### 향후 개선
+
+- **R2 Custom Domain**: 도메인을 Cloudflare DNS에 등록하면 CDN 캐시 적용 가능 (10~30ms)
+- **확대 적용**: 품목, 문서 등 `image_file_id`가 있는 모든 API Resource에 `image_url` 추가
diff --git a/dev/guides/r2-image-proxy-guide.md b/dev/guides/r2-image-proxy-guide.md
new file mode 100644
index 0000000..104b40a
--- /dev/null
+++ b/dev/guides/r2-image-proxy-guide.md
@@ -0,0 +1,215 @@
+# R2 이미지 프록시 가이드
+
+> **작성일**: 2026-03-21
+> **상태**: 운영 중
+
+---
+
+## 1. 개요
+
+SAM 프로젝트는 파일 저장소로 **Cloudflare R2** (S3 호환)를 사용한다. MNG에서 R2 이미지를 표시할 때 환경(Docker/서버)과 용도(일반 표시/Canvas 편집/미리보기)에 따라 다른 접근 방식이 필요하다.
+
+### 핵심 문제
+
+```
+브라우저 → R2 직접 접근: CORS 차단 (Canvas에서 사용 불가)
+Docker 내부 → api.sam.kr: DNS 해석 불가 (500 에러)
+브라우저 JS → https://nginx: Docker 내부 URL 접근 불가
+```
+
+---
+
+## 2. 이미지 접근 경로 3가지
+
+### 2.1 일반 `
![]()
` 표시 — redirect 방식
+
+```
+브라우저 → /files/{id}/view → MNG FileViewController → API presigned URL → 302 redirect → R2
+```
+
+- **용도**: 목록/상세 화면의 이미지 표시
+- **라우트**: `GET /files/{id}/view` → `FileViewController@show`
+- **동작**: API에서 presigned URL을 받아 브라우저를 R2로 redirect
+- **장점**: 빠름 (서버에서 이미지 다운로드 안 함)
+- **제한**: Canvas에서 사용 불가 (redirect 후 cross-origin → tainted canvas)
+
+### 2.2 Canvas 편집기 — streaming 프록시
+
+```
+브라우저 → /files/{id}/proxy → MNG FileViewController → R2 다운로드 → 이미지 스트리밍
+```
+
+- **용도**: 절곡품 전개도 Canvas 편집기 (`fabric.Image.fromURL`)
+- **라우트**: `GET /files/{id}/proxy` → `FileViewController@proxy`
+- **동작**: MNG 서버가 R2에서 이미지를 다운로드하여 같은 도메인으로 스트리밍
+- **장점**: CORS 문제 없음, `toDataURL()` 정상 동작
+- **제한**: `file_id`가 필요 (image_path만 있으면 사용 불가)
+
+### 2.3 미리보기 모달 — MNG API 프록시
+
+```
+브라우저 JS → /api/admin/document-templates/presigned-url-by-path → MNG API → API 서버 → R2 presigned URL 반환
+```
+
+- **용도**: 문서양식 미리보기에서 섹션 이미지 (`image_path`만 있는 경우)
+- **라우트**: `POST /api/admin/document-templates/presigned-url-by-path`
+- **동작**: 브라우저 JS가 MNG API를 호출 → MNG가 API 서버에 presigned URL 요청 → URL 반환
+- **장점**: `file_id` 없이 `image_path`로 접근 가능
+- **주의**: 동기 XHR 사용 (미리보기 렌더링 시 순차 처리)
+
+---
+
+## 3. 환경별 설정
+
+### 3.1 Docker (로컬)
+
+```env
+# api/.env
+R2_ACCESS_KEY_ID=cecd4d4c...
+R2_SECRET_ACCESS_KEY=f20136ec...
+R2_BUCKET=sam
+R2_ENDPOINT=https://caf8dcb2c4ea443018ee5e7a7421db0e.r2.cloudflarestorage.com
+R2_REGION=auto
+```
+
+```env
+# mng/.env (Docker 내부 통신)
+API_BASE_URL=https://api.sam.kr
+API_INTERNAL_URL=https://nginx
+```
+
+### 3.2 서버 (개발/운영)
+
+```env
+# api/.env — R2 설정 동일
+# mng/.env
+API_BASE_URL=https://api.dev.codebridge-x.com
+# API_INTERNAL_URL 미설정 (직접 접근)
+```
+
+---
+
+## 4. MNG → API 호출 시 필수 패턴
+
+MNG에서 API를 호출할 때 `API_INTERNAL_URL` 분기 처리가 **필수**이다.
+
+```php
+$baseUrl = config('services.api.base_url', 'https://api.sam.kr');
+$internalUrl = config('services.api.internal_url');
+
+$headers = [
+ 'X-API-KEY' => config('services.api.key'),
+ 'X-TENANT-ID' => session('selected_tenant_id', 1),
+];
+
+// Docker: nginx 컨테이너 경유, Host 헤더로 서버 블록 라우팅
+if ($internalUrl) {
+ $headers['Host'] = parse_url($baseUrl, PHP_URL_HOST) ?: 'api.sam.kr';
+ $baseUrl = $internalUrl;
+}
+
+$response = Http::baseUrl($baseUrl)
+ ->withoutVerifying()
+ ->withHeaders($headers)
+ ->timeout(10)
+ ->get('/api/v1/...');
+```
+
+> 참조 구현: `FormulaApiService::resolveApiConnection()`
+
+---
+
+## 5. API 화이트리스트
+
+MNG에서 Bearer 토큰 없이 호출하는 API는 `ApiKeyMiddleware`의 `allowWithoutAuth`에 등록 필요:
+
+```
+api/v1/bending-items 절곡 기초관리
+api/v1/bending-items/* 절곡 기초관리 상세
+api/v1/guiderail-models 가이드레일 모델
+api/v1/guiderail-models/* 가이드레일 모델 상세
+api/v1/items/*/files 품목 파일
+api/v1/files/*/presigned-url 파일 presigned URL
+api/v1/files/presigned-url-by-path 경로 기반 presigned URL
+```
+
+**파일 위치**: `api/app/Http/Middleware/ApiKeyMiddleware.php`
+
+---
+
+## 6. 트러블슈팅
+
+### 이미지가 404로 나올 때
+
+1. **R2 설정 확인**: API `.env`에 `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET`, `R2_ENDPOINT` 존재 여부
+2. **API 캐시 클리어**: `docker exec sam-api-1 php artisan config:clear`
+3. **R2 파일 존재 확인**: `Storage::disk('r2')->exists('경로')`
+
+### 이미지가 401로 나올 때
+
+1. **화이트리스트 확인**: `ApiKeyMiddleware`의 `allowWithoutAuth`에 해당 라우트 등록 여부
+2. **X-API-KEY 확인**: `config('services.api.key')` 값이 `api_keys` 테이블에 존재하는지
+3. **X-TENANT-ID 확인**: `session('selected_tenant_id')` 값
+
+### Canvas에서 tainted canvas 에러
+
+1. **프록시 사용 확인**: `/files/{id}/view`(redirect) 대신 `/files/{id}/proxy`(streaming) 사용
+2. **`data-proxy-url` 속성**: `
![]()
` 태그에 `data-proxy-url="{{ route('files.proxy', $fileId) }}"` 추가
+3. **JS에서 프록시 URL 우선**: `current.dataset.proxyUrl || current.src`
+
+### Docker에서 api.sam.kr 연결 실패 (cURL error 7)
+
+1. **`API_INTERNAL_URL` 설정**: MNG `.env`에 `API_INTERNAL_URL=https://nginx`
+2. **Host 헤더 추가**: `$headers['Host'] = parse_url($baseUrl, PHP_URL_HOST)`
+3. **참조**: `BendingBaseController::api()`, `FileViewController`, `DocumentTemplateController`
+
+### 미리보기에서 섹션 이미지 안 나올 때
+
+1. **MNG API 프록시 확인**: `/api/admin/document-templates/presigned-url-by-path` 라우트 존재 여부
+2. **`image_url` 캐시**: `_previewImageUrl` 함수에서 한 번 조회 후 `section.image_url`에 캐시
+3. **브라우저 콘솔**: XHR 요청 상태 확인 (200이면 정상, 401이면 화이트리스트, 500이면 R2 설정)
+
+---
+
+## 7. 관련 파일
+
+| 파일 | 역할 |
+|------|------|
+| `mng/app/Http/Controllers/FileViewController.php` | `show`(redirect), `proxy`(streaming) |
+| `mng/routes/web.php` | `/files/{id}/view`, `/files/{id}/proxy` |
+| `mng/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php` | `presignedUrlByPath` (미리보기용) |
+| `mng/resources/views/document-templates/partials/preview-modal.blade.php` | `_previewImageUrl` 함수 |
+| `mng/app/Http/Controllers/BendingBaseController.php` | `api()` 메서드 (internal_url 패턴) |
+| `mng/app/Http/Controllers/DocumentTemplateController.php` | `getPresignedUrlFromApi`, `getPresignedUrlByPath` |
+| `api/app/Http/Middleware/ApiKeyMiddleware.php` | `allowWithoutAuth` 화이트리스트 |
+| `api/config/filesystems.php` | R2 디스크 설정 (`disks.r2`) |
+
+---
+
+## 8. 요약 다이어그램
+
+```
+ ┌──────────────────────────────────┐
+ │ Cloudflare R2 │
+ │ (S3 호환 파일 저장소) │
+ └──────────┬───────────────────────┘
+ │ presigned URL
+ ┌──────────┴───────────────────────┐
+ │ API 서버 (Laravel) │
+ │ /api/v1/files/{id}/presigned-url │
+ │ /api/v1/files/presigned-url-by-path │
+ └──────────┬───────────────────────┘
+ │
+ ┌────────────────────┼────────────────────┐
+ │ │ │
+ ┌──────────┴──────────┐ ┌──────┴──────┐ ┌──────────┴──────────┐
+ │ /files/{id}/view │ │ /files/{id} │ │ /api/admin/doc-tmpl │
+ │ (redirect → R2) │ │ /proxy │ │ /presigned-url-by- │
+ │ │ │ (streaming) │ │ path (MNG API) │
+ │ 일반
![]()
표시 │ │ Canvas 편집 │ │ 미리보기 모달 │
+ └─────────────────────┘ └─────────────┘ └─────────────────────┘
+```
+
+---
+
+**최종 업데이트**: 2026-03-21
diff --git a/features/bending/README.md b/features/bending/README.md
new file mode 100644
index 0000000..96db6eb
--- /dev/null
+++ b/features/bending/README.md
@@ -0,0 +1,336 @@
+# 절곡 바라시 기초자료
+
+> **작성일**: 2026-03-21
+> **상태**: 운영 중
+
+---
+
+## 1. 개요
+
+### 1.1 목적
+
+절곡 바라시 기초자료는 **원자재(SUS, EGI 등)를 절곡(바라시) 가공하여 만드는 1차 가공물(부품)의 형상을 정의**하는 마스터 데이터이다.
+
+### 1.2 2계층 생산 구조
+
+```
+원자재 (SUS 1.2T, EGI 1.55T 등)
+ ↓ 절곡 가공 (바라시)
+1차 가공물: bending_items (기초자료) — 절곡 형상 정의
+ ↓ 조립
+2차 가공물: bending_models (가이드레일/케이스/하단마감재) — 부품을 조합한 완성품
+```
+
+- **기초자료** = 1차 가공물 = 절곡 형상 (길이 무관)
+- **가이드레일/케이스** = 2차 가공물 = 1차 가공물을 조립한 형상
+
+### 1.3 재공품(WIP)과의 관계
+
+재공품은 **유휴 시간을 활용하여 가장 많이 나가는 정형화된 부품을 미리 생산**하는 것이다.
+
+- 모든 절곡 형상을 재공품으로 관리하는 것이 아님
+- 수요가 높은 **표준 형상**만 재공품 코드로 관리
+- 기초자료는 표준 형상 + 주문에 따른 **변형 형상**도 관리
+
+---
+
+## 2. 품목코드 체계
+
+### 2.1 기초자료 코드 (형상 정의, 길이 무관)
+
+```
+BD-{분류코드}.{변형번호}
+```
+
+| 세그먼트 | 설명 | 예시 |
+|----------|------|------|
+| `BD` | Bending 접두사 (고정) | `BD` |
+| 분류코드 | 부품 종류 + 재질 계열 (2자리) | `CL`, `RS`, `CP` |
+| `.nnn` | 변형 번호 (점 구분자, 3자리) | `.001`, `.027` |
+
+**규칙:**
+- `.001` = **대표(표준) 형상** — 재공품의 기준이 되는 절곡도
+- `.002`~ = 표준 대비 **변형** — 주문에 따라 수정된 절곡도
+- 최대 `.999` (999종 변형 수용)
+
+**예시:**
+```
+BD-CL.001 케이스 린텔 — 대표(표준) 형상
+BD-CL.002 케이스 린텔 — 변형 1
+BD-RS.001 가이드레일 SUS 마감재 — 대표(표준)
+BD-RS.025 가이드레일 SUS 마감재 — 변형 25
+```
+
+### 2.2 재공품 코드 (형상 + 길이, 고유 품목)
+
+```
+BD-{분류코드}-{길이코드}
+```
+
+| 세그먼트 | 설명 | 예시 |
+|----------|------|------|
+| `BD` | Bending 접두사 (기초자료와 공통) | `BD` |
+| 분류코드 | 기초자료와 동일 (2자리) | `CL`, `RS` |
+| `-길이코드` | 원자재 길이 (하이픈 구분자) | `-30` (3000mm) |
+
+**예시:**
+```
+BD-CL-30 케이스 린텔 3000mm (고유 품목코드)
+BD-RS-24 가이드레일 SUS 마감재 2438mm
+```
+
+### 2.3 기초자료 vs 재공품 코드 구분
+
+| 구분 | 형식 | 구분자 | 의미 |
+|------|------|:------:|------|
+| 기초자료 (표준) | `BD-CL.001` | 점 (`.`) | 절곡 형상만 (길이 무관) |
+| 기초자료 (변형) | `BD-CL.002` | 점 (`.`) | 주문 수정 형상 |
+| 재공품 | `BD-CL-30` | 하이픈 (`-`) | 표준 형상 + 길이 (고유 품목) |
+
+> `BD-CL`까지 공통, 그 뒤 구분자(`.` vs `-`)로 구분. 기초자료는 길이 정보를 담지 않는다.
+
+### 2.4 LOT 번호
+
+```
+{제품}{종류}{날짜코드}-{길이코드}
+```
+
+| 예시 | 의미 |
+|------|------|
+| `CL6318-30` | 케이스 린텔, 2026-03-18 생산, 3000mm |
+
+> LOT 번호는 생산 이력 추적용이며, 품목코드/기초자료 코드와는 별개 체계이다.
+
+---
+
+## 3. 분류코드 접두사 정의
+
+### 3.1 가이드레일 부품 (R 계열)
+
+| 코드 | 부품명 | 주재질 |
+|------|--------|--------|
+| `RS` | SUS 마감재 | SUS 1.2T |
+| `RM` | 본체/보강 | EGI 1.55T |
+| `RC` | C형 | EGI 1.55T |
+| `RD` | D형 | EGI 1.55T |
+| `RE` | 측면 마감재 | EGI/SUS |
+| `RT` | 절단판 | — |
+| `RH` | 뒷보강 | — |
+| `RN` | 비인정 | — |
+
+### 3.2 케이스 부품 (C 계열)
+
+| 코드 | 부품명 | 주재질 |
+|------|--------|--------|
+| `CP` | 밑면판/점검구 | EGI 1.55T |
+| `CF` | 전면판 | EGI 1.55T |
+| `CB` | 후면 코너/후면부 | EGI 1.55T |
+| `CL` | 린텔 | EGI 1.55T |
+| `CX` | 상부 덮개 | EGI 1.55T |
+
+### 3.3 하단마감재 부품 (B/T 계열)
+
+| 코드 | 부품명 | 주재질 |
+|------|--------|--------|
+| `BS` | 하장바 SUS | SUS 1.5T |
+| `BE` | 하장바 EGI | EGI 1.55T |
+| `BH` | 보강평철 | EGI 1.15T |
+| `TS` | 철재 하장바 SUS | SUS |
+| `TE` | 철재 하장바 EGI | EGI |
+
+### 3.4 기타
+
+| 코드 | 부품명 |
+|------|--------|
+| `XE` | 마구리 |
+| `LE` | L-BAR |
+| `ZP` | 특수 밑면/점검구 |
+| `ZF` | 특수 전면판 |
+| `ZB` | 특수 후면 |
+
+---
+
+## 4. 데이터 모델
+
+### 4.1 테이블
+
+| 테이블 | 역할 | 프로젝트 |
+|--------|------|----------|
+| `bending_items` | 1차 가공물 (부품 형상) | API |
+| `bending_models` | 2차 가공물 (조립품) | API |
+
+### 4.2 bending_items 주요 필드
+
+| 필드 | 설명 |
+|------|------|
+| `code` | 품목코드 (고유, `BD-XX.nnn`) |
+| `item_name` | 부품명 |
+| `item_sep` | 대분류 (스크린/철재) |
+| `item_bending` | 분류 (가이드레일/케이스/하단마감재/마구리) |
+| `material` | 원자재 (SUS 1.2T, EGI 1.55T 등) |
+| `bending_data` | 전개도 JSON `[{input, rate, sum, color, aAngle}]` |
+| `legacy_code` | 이전 코드 (마이그레이션 이력 보존) |
+
+### 4.3 2차 가공물 매핑
+
+`bending_models.components` JSON 배열에서 `sam_item_id`로 `bending_items.id`를 참조한다.
+
+```
+가이드레일 KSS01 (GR-3)
+├── BD-RS.001 (SUS 마감재, 수량2)
+├── BD-RM.001 (본체)
+├── BD-RC.001 (C형)
+└── BD-RD.001 (D형)
+```
+
+---
+
+## 5. MNG 화면 기능
+
+### 5.1 메뉴 구조
+
+```
+절곡품 관리
+├── 기초관리 ← 이 문서의 대상
+├── 가이드레일
+├── 케이스
+└── 하단마감재
+```
+
+### 5.2 기초관리 CRUD
+
+| 기능 | 설명 |
+|------|------|
+| **목록** | 대분류/분류/재질/인정여부 필터, 검색, 페이지네이션 |
+| **등록** | 분류코드 드롭다운 선택 → 순번(`.nnn`) 자동 채번 |
+| **조회** | 코드/기본정보/전개도/이미지 표시 |
+| **수정** | 코드 편집 가능 (저장 시 중복 검사) |
+| **복사** | 현재 항목을 복제 — 코드 자동 채번 + 이미지 복사 |
+| **삭제** | Soft delete |
+
+### 5.3 코드 관련 동작
+
+#### 등록 시
+
+1. 분류코드 드롭다운에서 `BD-XX` 선택
+2. 저장 시 API가 해당 분류의 마지막 번호 +1 자동 채번
+3. 결과: `BD-CL.028` (CL 분류에 .027까지 있었다면)
+
+#### 수정 시
+
+1. 코드 필드 편집 가능
+2. 저장 시 API가 변경된 코드의 **중복 검사** 수행
+3. 중복이면 422 에러 + "코드 'BD-XX.nnn'는 이미 사용 중입니다" 메시지
+4. 중복 아니면 정상 저장
+
+#### 코드 변경 불가 케이스
+
+- 같은 코드 유지 → 중복 검사 건너뜀
+- 자기 자신은 중복에서 제외
+
+#### 복사 시
+
+1. 수정/조회 화면에서 **복사** 버튼 클릭
+2. 확인 다이얼로그 후 API 호출 (`POST /api/v1/bending-items/{id}/duplicate`)
+3. **같은 분류코드의 다음 번호 자동 채번** (예: `.027`까지 있으면 → `.028`)
+4. **R2 이미지 파일도 함께 복사** (R2 내 파일 복사 + 새 File 레코드 생성)
+5. 복사 완료 후 새 항목의 **수정 화면**으로 자동 이동
+6. "복사 완료 — 새 코드: BD-CL.028" 성공 메시지 표시
+
+복사되는 항목:
+- 기본정보 (품명, 대분류, 분류, 재질, 규격 등)
+- 전개도 데이터 (`bending_data` JSON)
+- options (검색어, 메모 등)
+- 전개도 이미지 (R2 파일 복사)
+
+### 5.4 2차 가공물 부품 코드 표시
+
+가이드레일/케이스/하단마감재 상세 화면의 '절곡 부품 조합' 섹션에 각 부품의 **기초자료 품목코드**가 표시된다.
+
+```
+순서:1 BD-RS.011 1번(마감제) 재질: SUS 1.2T 수량: 2
+순서:2 BD-RM.009 2번(본체) 재질: EGI 1.55T
+순서:3 BD-RC.005 3번(벽면형-C) 재질: EGI 1.55T
+순서:4 BD-RD.004 4번(벽면형-D) 재질: EGI 1.55T
+```
+
+- `components[].sam_item_id` → `bending_items.id` → `bending_items.code` 매핑
+- API Resource에서 `item_code` 필드로 자동 주입
+- 조회/수정 모드 모두 회색 배지로 표시
+
+---
+
+## 6. API 엔드포인트
+
+| Method | Path | 설명 | 화이트리스트 |
+|--------|------|------|:----------:|
+| GET | `/api/v1/bending-items` | 목록 (페이지네이션) | 필수 |
+| GET | `/api/v1/bending-items/filters` | 필터 옵션 | 필수 |
+| GET | `/api/v1/bending-items/prefixes` | 분류코드 접두사 목록 | 필수 |
+| POST | `/api/v1/bending-items` | 등록 (자동 채번) | 필수 |
+| GET | `/api/v1/bending-items/{id}` | 상세 조회 | 필수 |
+| PUT | `/api/v1/bending-items/{id}` | 수정 (코드 중복 검사) | 필수 |
+| POST | `/api/v1/bending-items/{id}/duplicate` | 복사 (자동 채번 + 이미지 복사) | 필수 |
+| DELETE | `/api/v1/bending-items/{id}` | 삭제 | 필수 |
+
+> 화이트리스트: `ApiKeyMiddleware`의 `allowWithoutAuth`에 등록 필요 (Bearer 토큰 없이 API Key + X-TENANT-ID로 접근)
+
+---
+
+## 7. MNG→API 통신 규칙
+
+### 7.1 Docker 환경 (로컬)
+
+```php
+$baseUrl = config('services.api.internal_url'); // https://nginx
+$headers['Host'] = 'api.sam.kr'; // nginx 서버 블록 라우팅
+```
+
+### 7.2 서버 환경 (개발/운영)
+
+```php
+$baseUrl = config('services.api.base_url'); // https://api.dev.codebridge-x.com
+// Host 헤더 불필요
+```
+
+### 7.3 이미지 표시
+
+| 용도 | 라우트 | 방식 |
+|------|--------|------|
+| 일반 `
![]()
` 표시 | `/files/{id}/view` | R2 presigned URL로 redirect |
+| Canvas 편집기 | `/files/{id}/proxy` | MNG 서버가 R2에서 다운로드 후 스트리밍 (CORS 우회) |
+
+---
+
+## 8. 마이그레이션 이력
+
+| 날짜 | 작업 |
+|------|------|
+| 2026-03-21 (1차) | 기존 코드(날짜 포함 LOT 형식) → `BD-XX-nn` 변환 (265건) |
+| 2026-03-21 (2차) | `BD-XX-nn` → `BD-XX.nn` 변환 (재공품과 구분자 분리) |
+| 2026-03-21 (3차) | `BD-XX.nn` → `BD-XX.nnn` 3자리 변형번호 (999종 수용) |
+| 2026-03-21 (4차) | 순번 없는 항목 → `.001` 통일 (대표 번호) |
+
+```
+❌ CX250722-06 (날짜 포함) — LOT 번호와 혼동
+❌ BD-CX-06 (하이픈) — 재공품 BD-CX-30과 혼동
+❌ BD-CX.06 (2자리) — 변형 수용량 99종 한계
+✅ BD-CX.006 (점 + 3자리) — 변형 999종, 재공품과 구분 명확
+```
+
+기존 코드는 `legacy_code` 필드에 보존되어 있다.
+
+---
+
+## 관련 문서
+
+- [bending-item-code-policy.md](../../standards/bending-item-code-policy.md) — 품목코드 체계 정책
+- [20260321_bending_api_internal_url_fix.md](../../changes/20260321_bending_api_internal_url_fix.md) — API 연동 수정 이력
+- `api/app/Models/BendingItem.php` — 1차 가공물 모델
+- `api/app/Models/BendingModel.php` — 2차 가공물 모델
+- `api/app/Services/BendingItemService.php` — 자동 채번/중복 검사 로직
+- `api/app/Services/BendingCodeService.php` — 재공품 코드맵/LOT 채번
+
+---
+
+**최종 업데이트**: 2026-03-21
diff --git a/features/documents/mng-document-template.md b/features/documents/mng-document-template.md
index 5570865..22e6b56 100644
--- a/features/documents/mng-document-template.md
+++ b/features/documents/mng-document-template.md
@@ -815,12 +815,69 @@ GET /v1/document-templates/{id} → show (상세)
---
+## 재공품 문서양식 구성 (2026-03-21)
+
+### 개요
+
+재공품 공정(P-004~P-007)별로 **작업일지**와 **중간검사 성적서** 양식을 분리 구성했다. 원본(#67, #68)을 복사하여 공정별 전용 양식을 생성하고, 검사 기준서 항목을 해당 공정 부품만 남도록 분류했다.
+
+### 양식 목록
+
+| 공정 | 작업일지 | 중간검사 성적서 |
+|------|---------|---------------|
+| P-004 가이드레일 | #69 재공품 가이드레일 작업일지 | #70 재공품 가이드레일 중간검사 성적서 |
+| P-005 케이스 | #71 재공품 케이스 작업일지 | #72 재공품 케이스 중간검사 성적서 |
+| P-006 하단마감재 | #73 재공품 하단마감재 작업일지 | #74 재공품 하단마감재 중간검사 성적서 |
+| P-007 기타 | #75 재공품 기타 작업일지 | #76 재공품 기타 중간검사 성적서 |
+
+> 원본 #67(중간검사), #68(작업일지)는 soft delete 상태.
+
+### 검사 기준서 항목 분류
+
+원본 #67의 "중간검사 DATA" 섹션 10개 항목을 `category` 기준으로 공정별 분배:
+
+| 양식 | 검사항목 |
+|------|---------|
+| #70 가이드레일 | 가이드레일/겉모양(절곡상태), 가이드레일/치수(길이/너비/간격) — 4건 |
+| #72 케이스 | 케이스/겉모양(절곡상태), 케이스/치수(높이/하단/너비차/위치) — 2건 |
+| #74 하단마감재 | 하단마감재/겉모양(절곡상태), 하단마감재/치수(너비) — 2건 |
+| #76 기타 | 연기차단재/치수(가이드레일용), 연기차단재/치수(케이스용) — 2건 |
+
+### 기본필드 연동키 매핑
+
+문서 생성 시 자동으로 값을 채우는 연동키 설정 (8개 양식 60건 일괄 적용):
+
+| 필드 라벨 | 연동키 (field_key) | 자동 입력 데이터 |
+|----------|-------------------|-----------------|
+| 부품명 | `product_name` | 품목명 |
+| 부품코드 | `product_code` | 품목코드 |
+| 재질/규격 | `specification` | 규격 정보 |
+| 수주/제품 LOT NO | `lot_no` | LOT 번호 |
+| 로트크기 | `lot_size` | 로트 크기 |
+| 발주처/수주처 | `client` | 거래처명 |
+| 현장명 | `site_name` | 현장명 |
+| 검사일자 | `inspection_date` | 검사 날짜 |
+| 검사자 | `inspector` | 검사자명 |
+
+**미매핑 필드** (연동키 미존재, 수동 입력):
+- 수주일, 작업일자, 생산담당자, 출고예정일
+
+### UI 개선 사항 (2026-03-21)
+
+- **복사 버튼**: 문서양식 편집 화면에 추가. 전체 탭 내용(기본정보, 결재라인, 검사 기준서, 테이블 컬럼) 복제
+- **상단 sticky 고정**: 양식명/문서제목/분류/회사명 + 탭 네비게이션이 스크롤 시 상단 고정. 기본정보 탭에서는 중복 방지를 위해 숨김
+- **Legacy 양식 fallback**: `section_fields`가 없는 legacy 양식에서 고정 컬럼(분류/검사항목/검사기준/검사방식/측정방법/측정수)으로 자동 렌더링
+- **sub_labels 안전 처리**: `Array.isArray()` 체크 추가 (문자열로 저장된 경우 TypeError 방지)
+
+---
+
## 관련 문서
- [README.md](README.md) — 문서관리 시스템 개요 (API 중심)
- [MNG 문서관리](mng-document-system.md) — 문서 생성/편집/결재 (서식을 사용하는 측)
- [DB 스키마 — 문서](../../system/database/documents.md)
+- [절곡 바라시 기초자료](../bending/README.md) — 재공품 공정 구조, 품목코드 체계
---
-**최종 업데이트**: 2026-03-06
+**최종 업데이트**: 2026-03-21
diff --git a/frontend/_index.md b/frontend/_index.md
index 468b4a2..52fe3bf 100644
--- a/frontend/_index.md
+++ b/frontend/_index.md
@@ -1,7 +1,7 @@
# SAM ERP Frontend Documentation
> **프로젝트**: SAM ERP Next.js 프론트엔드
-> **최종 갱신**: 2026-03-10
+> **최종 갱신**: 2026-03-18
> **현재 문서 버전**: v1 (운영 중) / v2 (설계 중)
---
@@ -26,6 +26,7 @@ frontend/
| # | 문서 | 버전 | 최종 수정 | 담당 | 대상 | 설명 |
|---|------|------|----------|------|------|------|
+| 00 | [onboarding](v1/00-onboarding.md) | 1.0.0 | 2026-03-20 | Frontend | 전체 | 신규 합류자 온보딩 (시스템 개요, 도메인 맵, 읽기 순서) |
| 01 | [architecture](v1/01-architecture.md) | 1.0.0 | 2026-03-09 | Frontend | 전체 | 프로젝트 구조, 기술 스택, 디렉토리 설계 |
| 02 | [api-pattern](v1/02-api-pattern.md) | 1.0.0 | 2026-03-09 | Frontend | FE/BE | API 통신 패턴 (프록시, Server Action, buildApiUrl) |
| 03 | [component-design](v1/03-component-design.md) | 1.0.0 | 2026-03-09 | Frontend | FE/기획 | 컴포넌트 계층 (atoms → templates), 페이지 유형 |
@@ -37,12 +38,13 @@ frontend/
| 09 | [conventions](v1/09-conventions.md) | 1.0.0 | 2026-03-09 | Frontend | FE | 네이밍, import, 파일 배치, Git 규칙 |
| 10 | [document-api-integration](v1/10-document-api-integration.md) | 1.0.0 | 2026-02-05 | API Team | FE/BE | 문서 관리 API 연동 (검사 성적서 resolve/upsert) |
| 11 | [browser-navigation-rules](v1/11-browser-navigation-rules.md) | 1.0.0 | 2026-03-10 | Frontend | AI/QA | 브라우저 네비게이션 규칙 (URL 추측 금지, 메뉴 클릭 필수) |
+| 12 | [permission-whitelist](v1/12-permission-whitelist.md) | 1.0.0 | 2026-03-20 | Frontend | FE/BE | 권한 기반 페이지 접근 제어 (화이트리스트, 바이패스, 모듈 역할 분담) |
### v2 — 동적 멀티테넌트 시스템 (설계 중)
| # | 문서 | 버전 | 최종 수정 | 담당 | 대상 | 설명 |
|---|------|------|----------|------|------|------|
-| 01 | [dynamic-multi-tenant-page-system](v2/01-dynamic-multi-tenant-page-system.md) | 1.1.0 | 2026-03-11 | FE/BE | 전체 | 동적 멀티테넌트 페이지 시스템 설계 (17개 규칙, JSON config, 동적 라우팅, 권한 통합) |
+| 01 | [dynamic-multi-tenant-page-system](v2/01-dynamic-multi-tenant-page-system.md) | 1.3.0 | 2026-03-18 | FE/BE | 전체 | 동적 멀티테넌트 페이지 시스템 설계 (17개 규칙, JSON config, 동적 라우팅, 권한 통합, 테넌트 분류, 선결과제) |
> **v2 상태**: 초안 — 백엔드 회의 후 협의 항목 확정 예정
@@ -62,6 +64,7 @@ frontend/
| 날짜 | 문서 | 변경 | 버전 |
|------|------|------|------|
| 2026-03-11 | 01 | 동적 멀티테넌트 페이지 시스템 설계 초안 작성 | 1.1.0 |
+| 2026-03-18 | 01 | JSONB 저장 확정, 테넌트 분류(3종), 선결과제 4개, 의존성 위반 목록 추가 | 1.3.0 |
### v1 (2026-03-09 ~)
@@ -70,6 +73,8 @@ frontend/
| 2026-03-09 | 01~09 | 초기 작성 | 1.0.0 |
| 2026-02-05 | 10 | 문서 API 연동 가이드 작성 (api-specs에서 이관) | 1.0.0 |
| 2026-03-10 | 11 | 브라우저 네비게이션 규칙 추가 (AI/E2E URL 추측 금지) | 1.0.0 |
+| 2026-03-20 | 12 | 권한 기반 페이지 접근 제어 — PermissionGate 화이트리스트 전환, 바이패스 경로, 모듈 시스템 역할 분담 | 1.0.0 |
+| 2026-03-20 | 00 | 신규 합류자 온보딩 가이드 — 시스템 개요, 테넌트 구조, 도메인 맵, 상태관리, 라우팅 패턴, 읽기 순서 | 1.0.0 |
---
@@ -104,6 +109,7 @@ PATCH: 오탈자, 코드 예시 수정, 사소한 수정
| 할 일 | 읽을 문서 |
|-------|----------|
+| **프로젝트에 처음 합류** | **v1/00-onboarding (여기서 시작)** |
| 프로젝트 전체 구조 이해 | v1/01-architecture |
| API 호출 방법 알기 | v1/02-api-pattern |
| 새 리스트 페이지 만들기 | v1/03-component-design → v1/04-common-components |
@@ -114,4 +120,5 @@ PATCH: 오탈자, 코드 예시 수정, 사소한 수정
| 코딩 컨벤션 확인 | v1/09-conventions |
| 문서 관리 API 연동 | v1/10-document-api-integration |
| AI/E2E 페이지 이동 규칙 | v1/11-browser-navigation-rules |
+| 권한 기반 접근 제어 이해 | v1/12-permission-whitelist |
| **동적 멀티테넌트 설계** | **v2/01-dynamic-multi-tenant-page-system** |
diff --git a/frontend/v1/00-onboarding.md b/frontend/v1/00-onboarding.md
new file mode 100644
index 0000000..7f0c8ef
--- /dev/null
+++ b/frontend/v1/00-onboarding.md
@@ -0,0 +1,386 @@
+# 00. 신규 합류자 온보딩 가이드
+
+> **대상**: 프로젝트에 새로 합류하는 개발자 (시니어 포함)
+> **버전**: 1.0.0
+> **최종 수정**: 2026-03-20
+> **읽는 시간**: 15분
+
+---
+
+## 1. SAM ERP 한눈에 보기
+
+SAM은 **멀티테넌트 폐쇄형 ERP** 시스템입니다.
+인증된 사용자만 접근 가능하며, 테넌트(고객사)별로 필요한 모듈만 활성화됩니다.
+
+```
+┌─────────────────────────────────────────────────────────┐
+│ SAM ERP Platform │
+│ │
+│ ┌─────────────────────────────────────────────────┐ │
+│ │ 공통 ERP (~165 페이지) │ │
+│ │ 회계 | 영업 | 인사 | 결재 | 게시판 | 설정 │ │
+│ │ 고객센터 | 기준정보 | 자재/재고 | 출고/배송 │ │
+│ └─────────────────────────────────────────────────┘ │
+│ │
+│ ┌──────────────────┐ ┌──────────────────────────┐ │
+│ │ 경동 MES (~27p) │ │ 주일 건설 (~48p) │ │
+│ │ 생산관리 │ │ 시공/프로젝트 │ │
+│ │ 품질관리 │ │ 입찰/계약 │ │
+│ └──────────────────┘ │ 기성관리 │ │
+│ └──────────────────────────┘ │
+│ ┌──────────────────┐ │
+│ │ 옵션 모듈 │ │
+│ │ 차량관리 │ │
+│ └──────────────────┘ │
+└─────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 2. 테넌트 구조
+
+현재 3개 테넌트가 운영됩니다.
+
+| 테넌트 | 업종 | 전용 모듈 | 특징 |
+|--------|------|-----------|------|
+| 경동 | 셔터 제조 (MES) | 생산, 품질 | 작업지시/실적, 설비관리, QMS |
+| 주일 | 건설 시공 | 건설/프로젝트 | 현장관리, 입찰, 기성 |
+| (신규) | 일반 | 공통만 | 공통 ERP 기능만 사용 |
+
+### 테넌트별 접근 제어
+
+```
+로그인 → 백엔드가 해당 테넌트의 메뉴 목록 반환
+ → PermissionGate가 화이트리스트로 접근 제어
+ → 메뉴에 없는 페이지 = 접근 불가 (URL 직접 입력도 차단)
+
+경동 유저: 회계, 영업, 생산, 품질 등 접근 가능 / 건설 차단
+주일 유저: 회계, 영업, 건설 등 접근 가능 / 생산, 품질 차단
+```
+
+상세: [v1/12-permission-whitelist.md](12-permission-whitelist.md)
+
+---
+
+## 3. 기술 스택
+
+| 영역 | 기술 |
+|------|------|
+| 프레임워크 | Next.js 15 (App Router) |
+| 런타임 | React 19 |
+| 언어 | TypeScript (strict) |
+| UI | shadcn/ui (Radix UI) + Tailwind CSS 4 |
+| 상태관리 | Zustand |
+| 폼 | react-hook-form + Zod |
+| 백엔드 | PHP Laravel 12 (별도 프로젝트) |
+| 모바일 | Capacitor (하이브리드 앱) |
+
+### 핵심 제약
+
+- **모든 페이지 Client Component**: `'use client'` 필수 (HttpOnly 쿠키 인증 때문)
+- **Server Component 사용 금지**: SEO 불필요 + 쿠키 수정 불가
+- **API 호출은 반드시 프록시**: `/api/proxy/` 또는 Server Action 경유
+
+---
+
+## 4. 프로젝트 구조
+
+```
+sam_project/
+├── sam-next/sam-react-prod/ ← 프론트엔드 (현재 프로젝트)
+├── sam-api/sam-api/ ← 백엔드 (PHP Laravel)
+├── sam-design/sam-design/ ← 디자인 시스템
+└── sam-docs/ ← 프로젝트 문서
+```
+
+### 프론트엔드 디렉토리
+
+```
+src/
+├── app/[locale]/(protected)/ # 라우트 (도메인별 폴더)
+│ ├── accounting/ # 회계
+│ ├── sales/ # 영업
+│ ├── hr/ # 인사
+│ ├── approval/ # 결재
+│ ├── production/ # 생산 (경동 전용)
+│ ├── quality/ # 품질 (경동 전용)
+│ ├── construction/ # 건설 (주일 전용)
+│ ├── dashboard/ # CEO 대시보드
+│ └── settings/ # 설정
+│
+├── components/ # 컴포넌트 (계층 구조)
+│ ├── ui/ # atoms (shadcn/ui)
+│ ├── molecules/ # molecules (FormField, DateRangeSelector 등)
+│ ├── organisms/ # organisms (PageLayout, IntegratedListTemplateV2 등)
+│ ├── templates/ # templates (UniversalListPage 등)
+│ ├── {domain}/ # 도메인별 비즈니스 컴포넌트
+│ └── document-system/ # 모듈 경계 넘는 공유 컴포넌트
+│
+├── stores/ # Zustand 전역 상태
+├── hooks/ # 커스텀 훅
+├── lib/ # 유틸리티, API 래퍼
+├── modules/ # 모듈 시스템 (테넌트 분리)
+└── contexts/ # React Context (Permission 등)
+```
+
+---
+
+## 5. 전역 상태 (Zustand Stores)
+
+| 스토어 | 역할 | 지속성 |
+|--------|------|--------|
+| `authStore` | 로그인 유저, 테넌트, 역할 정보 | localStorage |
+| `menuStore` | 사이드바 메뉴 목록, 활성 메뉴, 접힘 상태 | localStorage |
+| `permissionStore` | 메뉴별 권한 매트릭스 (view/create/update/delete) | 메모리 |
+| `masterDataStore` | 기준정보 캐시 (품목, 공정 등) | 메모리 |
+| `themeStore` | 테마 설정 | localStorage |
+| `useUIStore` | UI 상태 (사이드바 너비 등) | 메모리 |
+| `useItemMasterStore` | 품목 마스터 폼 상태 | 메모리 |
+| `favoritesStore` | 즐겨찾기 메뉴 | localStorage |
+| `useCalendarScheduleStore` | 캘린더 일정 | 메모리 |
+| `useTableColumnStore` | 테이블 컬럼 설정 (표시/숨김) | localStorage |
+
+---
+
+## 6. 라우팅 패턴
+
+### 페이지 모드 (mode 쿼리파라미터)
+
+```
+/sales/order-management → 목록 (기본)
+/sales/order-management?mode=new → 등록 폼
+/sales/order-management/123 → 상세 (view)
+/sales/order-management/123?mode=edit → 수정 폼
+```
+
+- 별도 `/new`, `/edit` 경로 사용 금지
+- 목록과 등록을 같은 page.tsx에서 mode로 분기
+
+### 페이지 유형
+
+| 유형 | 컴포넌트 | 특징 |
+|------|----------|------|
+| 목록 | `UniversalListPage` | 검색, 페이지네이션, 컬럼 설정, 모바일 카드 |
+| 상세/폼 | `Card` + `FormField` | sticky 하단 액션 바, 모드별 분기 |
+| 대시보드 | 섹션 기반 | 모듈별 조건부 렌더링 |
+
+---
+
+## 7. 도메인 맵
+
+### 공통 ERP (모든 테넌트)
+
+```
+회계 (accounting/)
+├── 매출/매입 관리
+├── 입출금 관리
+├── 세금계산서
+├── 거래처 원장
+├── 경조사비/접대비
+└── 일보/결산
+
+영업 (sales/)
+├── 견적/수주 관리
+├── 단가 관리
+├── 생산지시 (공유 API)
+└── 거래처 관리
+
+인사 (hr/)
+├── 직원 관리
+├── 근태/출결
+├── 급여/휴가
+└── 인사 이력
+
+결재 (approval/)
+├── 기안/수신/참조
+└── 결재 양식 관리
+
+기준정보 (master-data/)
+├── 품목 마스터
+├── 공정 관리
+└── 단가 테이블
+
+자재/재고 (material/, stocks/)
+├── 입고 관리
+├── 재고 현황
+└── 재고 생산
+
+출고/배송 (outbound/)
+├── 출하 관리
+└── 차량 배차
+```
+
+### 경동 전용 (셔터 제조 MES)
+
+```
+생산 (production/)
+├── 작업지시
+├── 작업실적
+├── 작업자 화면
+└── 생산 대시보드
+
+품질 (quality/)
+├── 설비 관리/점검/수리
+├── 검사 관리
+├── QMS (문서관리)
+└── 성적서/작업일지
+```
+
+### 주일 전용 (건설 시공)
+
+```
+건설 (construction/)
+├── 수주/현장 관리
+├── 프로젝트 관리
+│ ├── 계약/실행예산
+│ ├── 입찰 관리
+│ ├── 시공 관리
+│ └── 인력 현황
+└── 기성 관리
+```
+
+---
+
+## 8. 데이터 흐름
+
+```
+[컴포넌트] → Server Action (또는 fetch /api/proxy/...)
+ │
+ ↓
+ [authenticatedFetch]
+ │
+ ├── 정상 → 데이터 반환
+ ├── 401 → 자동 토큰 갱신 → 재시도
+ └── 실패 → 로그인 페이지 이동
+
+Server Action 위치: src/components/{domain}/actions.ts
+URL 빌더: buildApiUrl('/api/v1/path', { search, page })
+```
+
+---
+
+## 9. 모듈 분리 현황
+
+코드 아키텍처 레벨에서 공통 ERP와 테넌트 전용 코드의 경계를 관리합니다.
+
+| 단계 | 상태 | 내용 |
+|------|------|------|
+| Phase 0 | 완료 | 공통 -> 테넌트 import 의존성 해소 |
+| Phase 1 | 완료 | 모듈 레지스트리 + useModules() 훅 |
+| Phase 2 | 완료 | CEO 대시보드 모듈화 (섹션/API 최적화) |
+| Phase 3 | 완료 | 검증 스크립트 + 경계 문서 (MODULE.md) |
+| 화이트리스트 | 완료 | PermissionGate 화이트리스트 전환 |
+
+### 모듈 경계 규칙
+
+```
+허용: 테넌트 → 공통 import (production → ui/)
+금지: 공통 → 테넌트 import (approval → production/)
+금지: 테넌트 간 import (production → construction/)
+
+공유 필요 시: document-system/ 또는 lib/api/ 래퍼 경유
+```
+
+검증: `scripts/verify-module-separation.sh`
+
+---
+
+## 10. 향후 로드맵
+
+```
+v1 (현재) ──── 모듈 분리 완료, 권한 화이트리스트
+ │
+v2 (진행) ──── 백엔드에서 모듈/페이지 정보 JSON API 제공
+ │ useModules() 내부를 API 호출로 교체
+ │
+v3 (목표) ──── JSON 스키마 기반 동적 페이지 조립
+ 테넌트 추가 = 어드민 설정만 → 코드 변경 0줄
+```
+
+상세: [v2/01-dynamic-multi-tenant-page-system.md](../v2/01-dynamic-multi-tenant-page-system.md)
+
+---
+
+## 11. 문서 읽기 순서
+
+### 첫째 날: 전체 구조 파악
+
+| 순서 | 문서 | 핵심 |
+|------|------|------|
+| 1 | 이 문서 (00-onboarding) | 시스템 전체 그림 |
+| 2 | [01-architecture](01-architecture.md) | 기술 스택, 디렉토리 구조 |
+| 3 | [07-auth-flow](07-auth-flow.md) | 인증/토큰 흐름 |
+| 4 | [12-permission-whitelist](12-permission-whitelist.md) | 접근 제어 |
+
+### 둘째 날: 개발 패턴 익히기
+
+| 순서 | 문서 | 핵심 |
+|------|------|------|
+| 5 | [02-api-pattern](02-api-pattern.md) | API 호출 방법 |
+| 6 | [03-component-design](03-component-design.md) | 컴포넌트 계층 |
+| 7 | [04-common-components](04-common-components.md) | UniversalListPage 등 사용법 |
+| 8 | [05-form-pattern](05-form-pattern.md) | 폼 패턴 (Zod, FormField) |
+
+### 셋째 날: 세부 규칙
+
+| 순서 | 문서 | 핵심 |
+|------|------|------|
+| 9 | [06-styling-guide](06-styling-guide.md) | Tailwind, 색상 |
+| 10 | [08-dashboard-system](08-dashboard-system.md) | 대시보드 아키텍처 |
+| 11 | [09-conventions](09-conventions.md) | 네이밍, Git 규칙 |
+
+---
+
+## 12. Git 브랜치 전략
+
+```
+main ────── 배포용 (검증된 것만, 기능별 squash merge)
+ │
+develop ─── 평소 작업 (자유롭게 커밋)
+ │
+feature/* ─ 큰 기능/실험적 작업 시 사용
+```
+
+- develop에서 자유롭게 작업
+- main에는 기능별 squash merge (cherry-pick + 정리)
+- main 직접 push 금지
+
+---
+
+## 13. 개발 환경 셋업
+
+```bash
+# 1. 의존성 설치
+npm install
+
+# 2. 환경변수 (.env.local)
+# 팀원에게 받거나 sam-docs 참고
+
+# 3. 개발 서버
+npm run dev
+
+# 4. 접속
+http://localhost:3000
+```
+
+### 주요 명령어
+
+| 명령어 | 용도 |
+|--------|------|
+| `npm run dev` | 개발 서버 |
+| `npm run build` | 프로덕션 빌드 |
+| `npx tsc --noEmit` | 타입 체크 |
+
+---
+
+## 14. 자주 하는 실수
+
+| 실수 | 올바른 방법 |
+|------|-------------|
+| Server Component 사용 | `'use client'` 필수 |
+| localStorage 직접 접근 | `typeof window` 가드 필요 |
+| API 직접 fetch | Server Action 또는 `/api/proxy/` 사용 |
+| `/new`, `/edit` 라우트 생성 | `?mode=new`, `?mode=edit` 쿼리 사용 |
+| `DatePicker` 2개 직접 조합 | `DateRangeSelector` 사용 |
+| `Label + Input` 수동 조합 | `FormField` molecule 사용 |
+| 공통에서 테넌트 코드 import | `document-system/` 래퍼 경유 |
\ No newline at end of file
diff --git a/frontend/v1/12-permission-whitelist.md b/frontend/v1/12-permission-whitelist.md
new file mode 100644
index 0000000..50bf68b
--- /dev/null
+++ b/frontend/v1/12-permission-whitelist.md
@@ -0,0 +1,134 @@
+# 12. 권한 기반 페이지 접근 제어 (화이트리스트)
+
+> **대상**: 프론트엔드/백엔드 개발자
+> **버전**: 1.0.0
+> **최종 수정**: 2026-03-20
+
+---
+
+## 1. 개요
+
+PermissionGate가 **화이트리스트 방식**으로 동작합니다.
+메뉴 권한에 등록되지 않은 페이지는 접근이 차단됩니다.
+
+```
+메뉴 권한에 있음 + view: true → 허용
+메뉴 권한에 있음 + view: false → 차단 (AccessDenied)
+메뉴 권한에 없음 → 차단 (AccessDenied)
+```
+
+---
+
+## 2. 왜 화이트리스트?
+
+| 방식 | 동작 | 문제 |
+|------|------|------|
+| 블랙리스트 (이전) | 권한에 없으면 허용 | 메뉴 미할당 페이지에 URL 직접 접근 가능 |
+| **화이트리스트 (현재)** | 권한에 없으면 차단 | 메뉴 등록된 페이지만 접근 가능 |
+
+기존 정책과 일치:
+```
+테넌트 생성 → 글로벌 메뉴 동기화 → 역할 생성 → 메뉴 권한 설정 → 접근 가능
+```
+
+메뉴 권한이 설정되지 않은 페이지는 접근할 수 없는 것이 정상 동작입니다.
+
+---
+
+## 3. 바이패스 경로
+
+메뉴 권한 없이도 항상 접근 가능한 시스템 페이지:
+
+| 경로 | 이유 |
+|------|------|
+| `/settings/permissions` | 자기 잠금 방지 (권한 설정 페이지) |
+| `/settings/account-info` | 내 계정 설정 |
+| `/dashboard` | 대시보드 (모든 유저 필수, `startsWith`로 type2~5 포함) |
+| `/company-info` | 회사 정보 |
+| `/subscription` | 구독 관리 |
+| `/dev`, `/test` | 개발 도구 (개발 환경에서만 활성화) |
+
+### 바이패스 추가/관리
+
+파일: `src/contexts/PermissionContext.tsx`
+
+```typescript
+const BYPASS_PATHS = [
+ '/settings/permissions',
+ '/settings/account-info',
+ '/dashboard',
+ '/company-info',
+ '/subscription',
+ ...(process.env.NODE_ENV === 'development' ? ['/dev', '/test'] : []),
+];
+```
+
+- `startsWith` 매칭: `/dashboard` → `/dashboard_type2`, `/dashboard_type3` 등 모두 포함
+- 비즈니스 페이지는 바이패스에 추가하지 않음 (권한으로 제어)
+
+---
+
+## 4. 접근 제어 흐름
+
+```
+유저가 페이지 접근
+ │
+ ├─ 권한 로딩 중 → 빈 화면 (로딩)
+ │
+ ├─ permissionMap 없음 (로딩 실패) → 통과 (안전장치)
+ │
+ ├─ BYPASS_PATHS 매칭 → 통과
+ │
+ └─ findMatchingUrl (longest prefix match)
+ ├─ 매칭됨 + view: true → 허용
+ ├─ 매칭됨 + view: false → AccessDenied
+ └─ 매칭 없음 → AccessDenied
+```
+
+### findMatchingUrl 동작
+
+```
+접근: /accounting/deposits/123
+permissionMap: { "/accounting/deposits": { view: true, ... } }
+
+1. 정확히 매칭: /accounting/deposits/123 → 없음
+2. Prefix 매칭: /accounting/deposits → 있음! → view 확인
+```
+
+상위 경로에 권한이 있으면 하위 경로도 접근 가능합니다.
+
+---
+
+## 5. 프론트엔드 개발자 체크리스트
+
+| 항목 | 설명 |
+|------|------|
+| 새 페이지 추가 시 | 백엔드 메뉴 등록 필수 (미등록 = 접근 불가) |
+| 시스템 페이지 추가 시 | BYPASS_PATHS에 추가 검토 |
+| 개발 중 접근 차단 시 | 해당 메뉴의 권한 설정 확인 |
+| 테넌트별 접근 제어 | 메뉴 권한으로 제어 (프론트 코드 변경 불필요) |
+
+---
+
+## 6. 백엔드 개발자 참고
+
+| 항목 | 설명 |
+|------|------|
+| 새 메뉴 추가 시 | 글로벌 메뉴 등록 → 테넌트 동기화 → 역할별 권한 설정 |
+| 테넌트별 페이지 차단 | 해당 테넌트에 메뉴 미할당 또는 view: false |
+| industry 기반 프리셋 (향후) | 테넌트 생성 시 업종별 메뉴 프리셋 자동 적용 |
+
+---
+
+## 7. 모듈 시스템과의 역할 분담
+
+| 역할 | PermissionGate | 모듈 시스템 (useModules) |
+|------|---|---|
+| 페이지 접근 차단 | O | X (제거 검토) |
+| 메뉴 표시/숨김 | O (백엔드 메뉴 응답) | X |
+| 대시보드 섹션 ON/OFF | X | O |
+| 대시보드 API 호출 최적화 | X | O |
+| JSON 동적 페이지 초석 | X | O |
+
+- **PermissionGate**: 접근 제어 ("누가 어디에 들어갈 수 있는가")
+- **모듈 시스템**: 화면 구성 ("들어간 페이지에 뭘 보여줄 것인가")
\ No newline at end of file
diff --git a/frontend/v2/01-dynamic-multi-tenant-page-system.md b/frontend/v2/01-dynamic-multi-tenant-page-system.md
index 862c910..6751ab1 100644
--- a/frontend/v2/01-dynamic-multi-tenant-page-system.md
+++ b/frontend/v2/01-dynamic-multi-tenant-page-system.md
@@ -1,11 +1,16 @@
# 동적 멀티테넌트 페이지 시스템 설계
> 작성일: 2026-03-11
-> 상태: 초안 (백엔드 논의 필요)
+> 최종 업데이트: 2026-03-20
+> 상태: 초안 (백엔드 논의 진행 중)
> 관련 문서:
> - `[VISION-2026-02-19] dynamic-rendering-platform-strategy.md`
> - `[PLAN-2026-02-06] multi-tenancy-optimization-roadmap.md`
> - `[DESIGN-2026-02-11] dynamic-field-type-extension.md`
+> - `[ANALYSIS-2026-03-17] tenant-module-separation-dependency-audit.md`
+> - `[PLAN-2026-03-17] tenant-module-separation-plan.md` — Phase 0~3 실행 계획
+> - `[IMPL-2026-03-20] permission-whitelist-gate.md` — PermissionGate 화이트리스트 전환
+> - `sam-docs/frontend/v1/12-permission-whitelist.md` — 권한 기반 접근 제어 가이드
---
@@ -761,9 +766,61 @@ DynamicItemForm의 ComputedField → computed 타입으로 범용화
### 규칙 17: 점진적 마이그레이션 전략
+#### 17-1. 3단계 아키텍처 방향 (2026-03-17 확인)
+
+```
+1단계: 현재 → 모듈 분리
+ - 공통 ERP / 테넌트별 모듈 물리적 분리
+ - 선결과제 해소 (아래 17-2 참조)
+
+2단계: 모듈 분리 → JSON 동적 조립
+ - 테넌트 모듈을 manifest/JSON 기반으로 전환
+ - 동적 페이지 렌더러 도입
+
+3단계: 최종 — 빈 페이지 셸 + 백엔드 JSON으로 페이지 자동 조립
+ - 이 문서의 최종 목표
+```
+
+#### 17-2. 선결과제 (모듈 분리 전 해결 필수)
+
+| # | 과제 | 내용 | 예상 |
+|---|------|------|------|
+| 1 | CEO 대시보드 테넌트 의존성 해소 | 생산/건설 섹션 직접 import → 동적 로딩 전환 | - |
+| 2 | 공유 컴포넌트 추출 | 결재/영업(공통)이 생산(경동) 코드 직접 import | - |
+| 3 | 라우트 가드 추가 | 테넌트 미보유 모듈 URL 직접 접근 차단 | - |
+| 4 | dashboard-invalidation 동적화 | production/construction 도메인 키 하드코딩 제거 | - |
+
+> 선결과제 해소 예상: 3~4일, 이후 모듈 분리 본작업은 별도 산정
+
+**핵심 의존성 위반 (공통 → 테넌트 방향, 수정 필요)**:
+```
+ApprovalBox → production/InspectionReportModal
+Sales/production-orders → production/ProductionOrders (actions+types+UI)
+Sales → router.push("/production/work-orders") 하드코딩
+CEODashboard → DailyProductionSection, ConstructionSection 직접 import
+dashboard-invalidation.ts → production/construction 도메인 키
+```
+
+**안전한 부분**:
+- 테넌트 간 교차 의존성 없음 (생산↔건설 = 0)
+- 건설(주일) 모듈 완전 독립 → 바로 분리 가능
+- Zustand 스토어, API 프록시, 메뉴 시스템은 무관
+
+#### 17-3. 테넌트별 페이지 현황 (2026-03-17 분석)
+
+| 테넌트 | 업종 | 전용 모듈 | 페이지 수 |
+|--------|------|----------|:---:|
+| 공통 ERP | 전 업종 | 회계, 인사, 결재, 게시판, 설정, 고객센터 등 | ~165 |
+| 경동 | 셔터 제조 (MES) | 생산, 품질관리 | ~27 |
+| 주일 | 건설 시공 | 건설/프로젝트, 입찰, 기성 | ~48 |
+| (옵션) | - | 차량관리 | ~13 |
+
+#### 17-4. 마이그레이션 Phase
+
| Phase | 범위 | 예상 기간 | 상태 |
|-------|------|----------|------|
-| **Phase 0** | 인프라 구축 | 2-3주 | ⏳ 준비 |
+| **선결과제** | 의존성 해소 (17-2) | 3-4일 | ⏳ 준비 |
+| **Phase 0** | 인프라 구축 | 2-3주 | ⏳ |
| | - catch-all 라우터 | | |
| | - pageConfigStore | | |
| | - DynamicListPage/FormPage 렌더러 | | |
@@ -776,13 +833,13 @@ DynamicItemForm의 ComputedField → computed 타입으로 범용화
| | - 거래처관리, 설비관리 등 | | |
| **Phase 3** | 복잡한 비즈니스 페이지 전환 | 6-8주 | ⏳ |
| | - 견적, 수주, 생산 등 로직 있는 페이지 | | |
-| | - 로직 블록 구축 병행 | | |
| **Phase 4** | 기존 정적 → 동적 완전 전환 | 지속적 | ⏳ |
| | - 남은 하드코딩 페이지 점진적 전환 | | |
```
전환 판단 기준:
+[선행] 선결과제 해소 (의존성 분리) → 선결과제 Phase
[쉬움] 순수 CRUD (리스트+폼) → Phase 2에서 전환
[보통] CRUD + 단순 계산 → Phase 2~3
[어려움] 복잡한 비즈니스 로직 → Phase 3
@@ -886,9 +943,10 @@ DynamicItemForm의 ComputedField → computed 타입으로 범용화
| 동적 필드 타입 설계 | `claudedocs/architecture/[DESIGN-2026-02-11]` | 4-Level 구조, 14종 필드 |
| 동적 필드 구현 현황 | `claudedocs/architecture/[IMPL-2026-02-11]` | Phase 1~3 프론트 구현 완료 |
| 백엔드 API 스펙 | `claudedocs/item-master/[API-REQUEST-2026-02-12]` | 동적 필드 타입 백엔드 요청서 |
+| 테넌트 모듈 의존성 분석 | `claudedocs/architecture/[ANALYSIS-2026-03-17]` | 3테넌트 분리, 선결과제 4개, 의존성 위반 목록 |
---
-**문서 버전**: 1.2
-**마지막 업데이트**: 2026-03-11
+**문서 버전**: 1.3
+**마지막 업데이트**: 2026-03-18
**다음 단계**: 백엔드 회의 → 협의 필요 항목 확정 → v2.0 작성 → `sam-docs/frontend/v2/`에 최종본 등록
diff --git a/patent-attorney-briefing-compact.md b/patent-attorney-briefing-compact.md
new file mode 100644
index 0000000..826beb8
--- /dev/null
+++ b/patent-attorney-briefing-compact.md
@@ -0,0 +1,149 @@
+# SAM 특허 출원 브리핑 — 변리사 제출용 (축약본)
+
+> **출원인**: (주)코드브릿지엑스
+> **대상**: SAM (Smart Automation Management) — 중소 제조업 맞춤형 ERP/MES 자동 구성 플랫폼
+> **작성일**: 2026-03-20
+> **미팅일**: 2026-03-19
+
+---
+
+## Slide 1: 표지
+
+**SAM 특허 출원 브리핑**
+
+- 출원인: (주)코드브릿지엑스
+- 대상: SAM — 고객사별 맞춤형 ERP/MES 자동 구성 방법
+- 2026-03-20
+
+---
+
+## Slide 2: 특허 전략 — 2축 구조
+
+### 축1: 플랫폼 방법특허 (최우선)
+
+"고객사 업무 데이터를 AI로 분석하여 맞춤형 ERP/MES를 자동 구성하는 방법"
+
+| 핵심 구성 요소 | 설명 |
+|-------------|------|
+| 비대면 3채널 수집 | 견적서 파일 + 체크리스트 설문 + 음성 인터뷰 |
+| AI 패턴 분석 | 업종·규모·공정 자동 도출 (LLM + 20만줄 도메인 지식) |
+| 레시피 기반 자동 초기화 | TenantBootstrapper 4단계 멱등적 파이프라인 |
+| 동적 필드 3계층 | page→section→field 구조, 코드 배포 없이 커스터마이징 |
+| Zero-Config 수식 라우팅 | 파일 존재만으로 테넌트별 계산 엔진 자동 발견 |
+
+### 축2: 개별 알고리즘 특허 (후순위)
+
+| 후보 | 핵심 |
+|------|------|
+| BOM 수식 엔진 (1,948줄) | 10단계 견적 자동화 파이프라인 |
+| 절곡 공정 최적 배분 (1,171줄) | 원자재 절재 낭비 15%→5% |
+| 건기원 실적신고 자동화 | 품질검사→엑셀 자동 생성 |
+| 신용도 이중 평가 | 외부(쿠콘) + 내부(거래 이력) 종합 등급 |
+
+### 선행기술 리스크
+
+- 젠소프트 "AI 기반 하이퍼 오토메이션" 특허가 유사 선행기술
+- **대응**: 컨셉이 아닌 구체적 알고리즘·처리 흐름 + 산업 특화로 차별화
+
+---
+
+## Slide 3: 5단계 자동화 파이프라인 (청구항 핵심)
+
+```
+[단계 1] [단계 2] [단계 3] [단계 4] [단계 5]
+비대면 3채널 수집 → 전처리·정규화 → AI 패턴 분석 → 테넌트 자동 생성 → 동적 커스터마이징
+ + 시스템 초기화
+━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━ ━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━
+• 견적서 파일 • 파일 파싱 • 업종 분류 • 한글 초성→코드 • 동적 필드 3계층
+• 체크리스트 설문 • 응답 정규화 • 규모 판별 • 메뉴 계층 복제 • 전역/테넌트 오버레이
+• 음성 인터뷰 • STT+키워드 추출 • 필요 모듈 매칭 • 역할/권한 자동 • Zero-Config 수식
+ (1~2일) (자동) • BOM 패턴 추론 • 레시피 4Step • 행 수준 데이터 격리
+ (수분) (수초) (267모델 자동)
+```
+
+### 청구항 독립항 1 (요약)
+
+> (a) 복수 채널 비대면 데이터 수집 → (b) 정규화·통합 → (c) AI 업종·공정 패턴 도출 → (d) 테넌트 코드 생성 + 레시피 기반 자동 초기화 → (e) 필드·메뉴·권한 동적 조정 + 행 수준 격리
+
+---
+
+## Slide 4: As-Is / To-Be 핵심 비교
+
+| 항목 | 기존 방식 | SAM | 개선률 |
+|------|:--------:|:---:|:------:|
+| ERP 초기 구축 | 6개월~1년 | **1주일** | 31배 |
+| 견적 1건 작성 | 3~5시간 | **10초** | 1,300배 |
+| 신규 업체 온보딩 | 2주 (현장 방문) | **1일 (비대면)** | 14배 |
+| 업종 커스터마이징 | 코드 포크 (수개월) | **설정 변경** | ∞ |
+| 도입 비용 | 5,800만~1.5억원 | **월 과금** | 10배+ |
+| 도입 실패율 | 40~60% | **0%** (2사 실증) | ∞ |
+
+### 연간 비용 절감 (1개사 기준)
+
+| 항목 | 기존 | SAM 적용 후 | 절감 |
+|------|:----:|:----------:|:----:|
+| 견적 인건비 | 3,750만 | 0 | 3,750만 |
+| 절곡 오류 재작업 | 600만 | 0 | 600만 |
+| 원자재 낭비 | 2,400만 | 600만 | 1,800만 |
+| 단가·품질 관리 | 1,680만 | 0 | 1,680만 |
+| **합계** | **8,430만/년** | **600만/년** | **7,830만/년** |
+
+### 실증 사례: 경동기업(2025-12~), 주일기업(2025-09~) 운영 중
+
+---
+
+## Slide 5: 선행기술 대비 독창성
+
+| 기술 요소 | 젠소프트 | 이카운트 | **SAM** |
+|----------|:-------:|:-------:|:------:|
+| 비대면 3채널 수집 | ❌ | ❌ | ✅ |
+| 한글 초성 코드 생성 | ❌ | ❌ | ✅ |
+| 레시피 기반 멱등적 초기화 | ❌ | ❌ | ✅ |
+| 동적 필드 3계층 (page→section→field) | △ 고정 | △ 고정 | ✅ |
+| DB 수준 조건부 렌더링 | ❌ | ❌ | ✅ |
+| Zero-Config 테넌트 수식 라우팅 | ❌ | ❌ | ✅ |
+| 행 수준 자동 격리 (267모델) | △ DB분리 | ❌ 단일 | ✅ |
+| 4계층 AI 프롬프트 엔진 | ❌ | ❌ | ✅ |
+| 자연어 트리거 배포 자동화 | ❌ | ❌ | ✅ |
+
+### 프롬프트 엔지니어링 — 핵심 차별점
+
+- **4계층 규칙 상속**: 전역→SAM→서비스별→문서 (합산 3,000줄+)
+- **20만줄 도메인 지식 베이스**: AI가 산업 특화 규칙을 자동 참조
+- **45개 도메인 스킬 + 12개 전문 에이전트**: 코드 품질·보안·배포를 AI가 자율 관리
+- **L1/L2/L3 3단계 서버 안전 제어**: 실제 사고(502) 경험에서 도출된 AI 권한 체계
+
+---
+
+## Slide 6: 실행 로드맵
+
+| Phase | 기간 | 핵심 액션 |
+|:-----:|------|---------|
+| **1** | 3~4월 | 변리사 제출 자료 완성 (흐름도·비교표·청구항 초안·선행기술 분석) |
+| **2** | 4월 | 보완 개발 (신용도 내부 평가, AI 동적 설문, 업종 확장 3건+) |
+| **3** | 5월~ | 축1 플랫폼 방법특허 출원 → 축2 알고리즘 특허 필요성 재검토 |
+
+### 업종 확장 가능성 (범용 설계 합의)
+
+| 확장 업종 | 추가 개발 범위 |
+|----------|:------------:|
+| 가구 제조 | 핸들러 1개 |
+| 철강 가공 | 규격 테이블 교체 |
+| 시공업 | 공정 유형 추가 |
+| 유통업 | 유통 워크플로우 |
+
+> 기술적 근거: Zero-Config Factory + 동적 필드 + 레시피 패턴으로 파일/설정 추가만으로 업종 확장
+
+### 시스템 규모 (실증 데이터, 2026-03-20 기준)
+
+| 지표 | 수치 |
+|------|:----:|
+| Eloquent 모델 | 267개 |
+| API 라우트 | 1,327개 |
+| 서비스 클래스 | 332개 |
+| DB 마이그레이션 | 551개 |
+| 운영 테넌트 | 2개 |
+
+---
+
+**최종 업데이트**: 2026-03-20
diff --git a/patent-attorney-briefing-compact.pdf b/patent-attorney-briefing-compact.pdf
new file mode 100644
index 0000000..5eb17c6
Binary files /dev/null and b/patent-attorney-briefing-compact.pdf differ
diff --git a/patent-attorney-briefing-compact.pptx b/patent-attorney-briefing-compact.pptx
new file mode 100644
index 0000000..91acaeb
Binary files /dev/null and b/patent-attorney-briefing-compact.pptx differ
diff --git a/patent-compact-convert.cjs b/patent-compact-convert.cjs
new file mode 100644
index 0000000..f53244e
--- /dev/null
+++ b/patent-compact-convert.cjs
@@ -0,0 +1,454 @@
+const path = require('path');
+module.paths.unshift(path.join(require('os').homedir(), '.claude/skills/pptx-skill/scripts/node_modules'));
+const PptxGenJS = require('pptxgenjs');
+
+async function main() {
+ const pres = new PptxGenJS();
+ pres.defineLayout({ name: 'CUSTOM_16x9', width: 10, height: 5.625 });
+ pres.layout = 'CUSTOM_16x9';
+
+ const C = {
+ bgDark: '0D1B2A',
+ bgCard: '1B2838',
+ bgCardAlt: '152232',
+ primary: '00BFA5',
+ primaryLight: '1A3D4A',
+ accent: 'FF6F61',
+ accentYellow: 'FFB74D',
+ white: 'FFFFFF',
+ gray: '8899AA',
+ grayLight: 'B0BEC5',
+ line: '2A3E52',
+ blue: '42A5F5',
+ green: '66BB6A',
+ red: 'EF5350',
+ };
+ const F = 'Arial';
+
+ // ═══════════════════════════════════════════════════
+ // SLIDE 1: 표지
+ // ═══════════════════════════════════════════════════
+ const s1 = pres.addSlide();
+ s1.background = { fill: C.bgDark };
+
+ // 상단 얇은 악센트 라인
+ s1.addShape(pres.ShapeType.rect, { x: 0, y: 0, w: 10, h: 0.04, fill: { color: C.primary } });
+
+ // 좌측 포인트 바
+ s1.addShape(pres.ShapeType.rect, { x: 0.8, y: 1.5, w: 0.06, h: 1.8, fill: { color: C.primary } });
+
+ // SAM BI 로고
+ s1.addImage({ path: '/home/aweso/sam/docs/assets/bi/sam_bi_white.png', x: 0.8, y: 0.5, w: 1.2, h: 0.6 });
+
+ // 메인 타이틀
+ s1.addText('특허 출원 브리핑', {
+ x: 1.1, y: 1.55, w: 7, h: 0.7,
+ fontSize: 34, bold: true, color: C.white, fontFace: F
+ });
+ s1.addText('고객사별 맞춤형 ERP/MES 자동 구성 방법', {
+ x: 1.1, y: 2.2, w: 7, h: 0.5,
+ fontSize: 16, color: C.primary, fontFace: F
+ });
+ s1.addText('변리사 제출용 기술 자료 (축약본)', {
+ x: 1.1, y: 2.75, w: 7, h: 0.4,
+ fontSize: 12, color: C.gray, fontFace: F
+ });
+
+ // 하단 정보
+ s1.addShape(pres.ShapeType.rect, { x: 0, y: 4.7, w: 10, h: 0.02, fill: { color: C.line } });
+ s1.addText('(주)코드브릿지엑스', {
+ x: 0.8, y: 4.85, w: 3, h: 0.35,
+ fontSize: 12, bold: true, color: C.grayLight, fontFace: F
+ });
+ s1.addText('2026-03-20', {
+ x: 6, y: 4.85, w: 3.2, h: 0.35,
+ fontSize: 11, color: C.gray, align: 'right', fontFace: F
+ });
+
+ // 대외비
+ addConfidential(s1, pres);
+
+ // ═══════════════════════════════════════════════════
+ // SLIDE 2: 특허 전략 — 2축 구조
+ // ═══════════════════════════════════════════════════
+ const s2 = pres.addSlide();
+ s2.background = { fill: C.bgDark };
+ addHeader(s2, pres, '특허 전략 — 2축 구조', C);
+ addConfidential(s2, pres);
+ addFooter(s2, pres, C, '2 / 6');
+
+ // 축1 카드
+ const ax1x = 0.4, ax1y = 1.05, ax1w = 4.5, ax1h = 3.65;
+ s2.addShape(pres.ShapeType.roundRect, { x: ax1x, y: ax1y, w: ax1w, h: ax1h, rectRadius: 0.12, fill: { color: '0D2818' }, line: { color: '1B5E20', width: 1.2 } });
+ // 축1 헤더
+ s2.addShape(pres.ShapeType.roundRect, { x: ax1x, y: ax1y, w: ax1w, h: 0.45, rectRadius: 0.12, fill: { color: '1B5E20' } });
+ s2.addShape(pres.ShapeType.rect, { x: ax1x, y: ax1y + 0.3, w: ax1w, h: 0.15, fill: { color: '1B5E20' } });
+ s2.addText([
+ { text: ' AXIS 1 ', options: { fontSize: 9, bold: true, color: 'A5D6A7' } },
+ { text: ' 플랫폼 방법특허 (BM 발명)', options: { fontSize: 11, bold: true, color: C.white } },
+ ], { x: ax1x + 0.1, y: ax1y + 0.02, w: ax1w - 0.2, h: 0.4, fontFace: F });
+
+ // 축1 설명
+ s2.addText('"고객사 업무 데이터를 AI로 분석하여\n맞춤형 ERP/MES를 자동 구성하는 방법"', {
+ x: ax1x + 0.2, y: ax1y + 0.55, w: ax1w - 0.4, h: 0.55,
+ fontSize: 9.5, italic: true, color: C.primary, fontFace: F, lineSpacingMultiple: 1.3
+ });
+
+ const axis1Items = [
+ ['비대면 3채널 수집', '파일 + 설문 + 음성 인터뷰'],
+ ['AI 패턴 분석', 'LLM + 20만줄 도메인 지식'],
+ ['레시피 기반 자동 초기화', 'TenantBootstrapper 4단계'],
+ ['동적 필드 3계층', 'page → section → field'],
+ ['Zero-Config 수식 라우팅', '파일 존재만으로 자동 발견'],
+ ];
+ axis1Items.forEach((item, i) => {
+ const iy = ax1y + 1.2 + i * 0.48;
+ s2.addShape(pres.ShapeType.roundRect, { x: ax1x + 0.15, y: iy, w: ax1w - 0.3, h: 0.4, rectRadius: 0.06, fill: { color: '0F2D1A' } });
+ s2.addShape(pres.ShapeType.ellipse, { x: ax1x + 0.25, y: iy + 0.08, w: 0.24, h: 0.24, fill: { color: '2E7D32' } });
+ s2.addText(String(i + 1), { x: ax1x + 0.25, y: iy + 0.08, w: 0.24, h: 0.24, fontSize: 8, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: F });
+ s2.addText(item[0], { x: ax1x + 0.6, y: iy + 0.02, w: 2, h: 0.18, fontSize: 9, bold: true, color: C.white, fontFace: F });
+ s2.addText(item[1], { x: ax1x + 0.6, y: iy + 0.2, w: 3.6, h: 0.16, fontSize: 7.5, color: C.gray, fontFace: F });
+ });
+
+ // 최우선 배지
+ s2.addShape(pres.ShapeType.roundRect, { x: ax1x + ax1w - 1.2, y: ax1y + 0.55, w: 1, h: 0.28, rectRadius: 0.04, fill: { color: C.accent } });
+ s2.addText('최우선', { x: ax1x + ax1w - 1.2, y: ax1y + 0.55, w: 1, h: 0.28, fontSize: 9, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: F });
+
+ // 축2 카드
+ const ax2x = 5.1, ax2y = 1.05, ax2w = 4.5, ax2h = 2.5;
+ s2.addShape(pres.ShapeType.roundRect, { x: ax2x, y: ax2y, w: ax2w, h: ax2h, rectRadius: 0.12, fill: { color: '1A1A2E' }, line: { color: '37474F', width: 1 } });
+ s2.addShape(pres.ShapeType.roundRect, { x: ax2x, y: ax2y, w: ax2w, h: 0.45, rectRadius: 0.12, fill: { color: '37474F' } });
+ s2.addShape(pres.ShapeType.rect, { x: ax2x, y: ax2y + 0.3, w: ax2w, h: 0.15, fill: { color: '37474F' } });
+ s2.addText([
+ { text: ' AXIS 2 ', options: { fontSize: 9, bold: true, color: C.grayLight } },
+ { text: ' 개별 알고리즘 특허', options: { fontSize: 11, bold: true, color: C.white } },
+ ], { x: ax2x + 0.1, y: ax2y + 0.02, w: ax2w - 0.2, h: 0.4, fontFace: F });
+
+ const axis2Items = [
+ ['BOM 수식 엔진', '1,948줄 · 10단계 견적 자동화'],
+ ['절곡 공정 최적 배분', '1,171줄 · 낭비 15%→5%'],
+ ['건기원 실적신고', '품질검사→엑셀 자동 생성'],
+ ['신용도 이중 평가', '외부(쿠콘)+내부(거래이력)'],
+ ];
+ axis2Items.forEach((item, i) => {
+ const iy = ax2y + 0.55 + i * 0.45;
+ s2.addText('•', { x: ax2x + 0.25, y: iy, w: 0.2, h: 0.35, fontSize: 10, color: C.blue, fontFace: F });
+ s2.addText(item[0], { x: ax2x + 0.45, y: iy + 0.02, w: 2, h: 0.16, fontSize: 9, bold: true, color: C.grayLight, fontFace: F });
+ s2.addText(item[1], { x: ax2x + 0.45, y: iy + 0.18, w: 3.8, h: 0.14, fontSize: 7.5, color: C.gray, fontFace: F });
+ });
+
+ // 선행기술 리스크 박스
+ const riskY = 3.7;
+ s2.addShape(pres.ShapeType.roundRect, { x: ax2x, y: riskY, w: ax2w, h: 1, rectRadius: 0.1, fill: { color: '2D1515' }, line: { color: '5D3030', width: 0.8 } });
+ s2.addText('선행기술 리스크', { x: ax2x + 0.2, y: riskY + 0.06, w: 2.5, h: 0.28, fontSize: 9, bold: true, color: C.accent, fontFace: F });
+ s2.addText('젠소프트 "AI 기반 하이퍼 오토메이션" 특허가 유사', {
+ x: ax2x + 0.2, y: riskY + 0.35, w: ax2w - 0.4, h: 0.2, fontSize: 8, color: C.grayLight, fontFace: F
+ });
+ s2.addText('대응: 컨셉이 아닌 구체적 처리 흐름 + 산업 특화로 차별화', {
+ x: ax2x + 0.2, y: riskY + 0.6, w: ax2w - 0.4, h: 0.2, fontSize: 8, bold: true, color: C.accentYellow, fontFace: F
+ });
+
+ // ═══════════════════════════════════════════════════
+ // SLIDE 3: 5단계 자동화 파이프라인
+ // ═══════════════════════════════════════════════════
+ const s3 = pres.addSlide();
+ s3.background = { fill: C.bgDark };
+ addHeader(s3, pres, '5단계 자동화 파이프라인 — 청구항 핵심', C);
+ addConfidential(s3, pres);
+ addFooter(s3, pres, C, '3 / 6');
+
+ const steps = [
+ { num: '1', title: '로우데이터\n수집', sub: '비대면 3채널', detail: '견적서 파일\n설문 응답\n음성 인터뷰', time: '1~2일', color: '1565C0' },
+ { num: '2', title: '전처리\n정규화', sub: '데이터 변환', detail: '파일 파싱\n응답 정규화\nSTT 키워드', time: '자동', color: '00838F' },
+ { num: '3', title: 'AI 분석\n패턴 도출', sub: 'LLM 엔진', detail: '업종 분류\n규모 판별\nBOM 추론', time: '수분', color: '2E7D32' },
+ { num: '4', title: '테넌트 생성\n자동 초기화', sub: 'Bootstrapper', detail: '코드 생성\n메뉴 복제\n레시피 4Step', time: '수초', color: 'E65100' },
+ { num: '5', title: '동적\n커스터마이징', sub: '맞춤 적용', detail: '필드 3계층\n수식 라우팅\n데이터 격리', time: '즉시', color: '6A1B9A' },
+ ];
+
+ const stepW = 1.65, stepGap = 0.18, startX = 0.42;
+ steps.forEach((st, i) => {
+ const sx = startX + i * (stepW + stepGap);
+ const sy = 1.1;
+
+ // 카드 배경
+ s3.addShape(pres.ShapeType.roundRect, { x: sx, y: sy, w: stepW, h: 3.35, rectRadius: 0.1, fill: { color: C.bgCard }, line: { color: C.line, width: 0.5 } });
+
+ // 넘버 서클
+ s3.addShape(pres.ShapeType.ellipse, { x: sx + stepW / 2 - 0.22, y: sy + 0.12, w: 0.44, h: 0.44, fill: { color: st.color } });
+ s3.addText(st.num, { x: sx + stepW / 2 - 0.22, y: sy + 0.12, w: 0.44, h: 0.44, fontSize: 16, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: F });
+
+ // 제목
+ s3.addText(st.title, { x: sx + 0.05, y: sy + 0.65, w: stepW - 0.1, h: 0.55, fontSize: 10, bold: true, color: C.white, align: 'center', valign: 'middle', lineSpacingMultiple: 1.1, fontFace: F });
+
+ // 서브 태그
+ s3.addShape(pres.ShapeType.roundRect, { x: sx + 0.2, y: sy + 1.28, w: stepW - 0.4, h: 0.25, rectRadius: 0.04, fill: { color: C.bgCardAlt } });
+ s3.addText(st.sub, { x: sx + 0.2, y: sy + 1.28, w: stepW - 0.4, h: 0.25, fontSize: 7.5, color: C.grayLight, align: 'center', valign: 'middle', fontFace: F });
+
+ // 구분선
+ s3.addShape(pres.ShapeType.rect, { x: sx + 0.15, y: sy + 1.65, w: stepW - 0.3, h: 0.01, fill: { color: C.line } });
+
+ // 상세 항목
+ s3.addText(st.detail, { x: sx + 0.15, y: sy + 1.75, w: stepW - 0.3, h: 1.0, fontSize: 8, color: C.grayLight, lineSpacingMultiple: 1.5, fontFace: F });
+
+ // 소요 시간 배지
+ s3.addShape(pres.ShapeType.roundRect, { x: sx + 0.25, y: sy + 2.9, w: stepW - 0.5, h: 0.3, rectRadius: 0.04, fill: { color: st.color } });
+ s3.addText(st.time, { x: sx + 0.25, y: sy + 2.9, w: stepW - 0.5, h: 0.3, fontSize: 9, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: F });
+
+ // 화살표 (마지막 제외)
+ if (i < 4) {
+ s3.addText('>', { x: sx + stepW, y: sy + 0.85, w: stepGap, h: 0.5, fontSize: 16, bold: true, color: C.primary, align: 'center', valign: 'middle', fontFace: F });
+ }
+ });
+
+ // 하단 청구항 요약
+ s3.addShape(pres.ShapeType.roundRect, { x: 0.42, y: 4.55, w: 9.16, h: 0.45, rectRadius: 0.06, fill: { color: C.primaryLight }, line: { color: C.primary, width: 0.5 } });
+ s3.addText([
+ { text: '청구항 핵심: ', options: { fontSize: 8, bold: true, color: C.primary } },
+ { text: '(a) 3채널 수집 → (b) 정규화 → (c) AI 패턴 도출 → (d) 레시피 기반 초기화 → (e) 동적 조정 + 행 수준 격리', options: { fontSize: 8, color: C.grayLight } },
+ ], { x: 0.6, y: 4.55, w: 8.8, h: 0.45, valign: 'middle', fontFace: F });
+
+ // ═══════════════════════════════════════════════════
+ // SLIDE 4: As-Is / To-Be 핵심 비교
+ // ═══════════════════════════════════════════════════
+ const s4 = pres.addSlide();
+ s4.background = { fill: C.bgDark };
+ addHeader(s4, pres, 'As-Is / To-Be 핵심 비교', C);
+ addConfidential(s4, pres);
+ addFooter(s4, pres, C, '4 / 6');
+
+ // 왼쪽: 지표 대조표
+ const tblX = 0.35, tblY = 1.05, tblW = 5.9;
+
+ // 테이블 헤더
+ s4.addShape(pres.ShapeType.roundRect, { x: tblX, y: tblY, w: tblW, h: 0.38, rectRadius: 0.08, fill: { color: '1B3A5C' } });
+ s4.addText('항목', { x: tblX + 0.1, y: tblY, w: 1.6, h: 0.38, fontSize: 8, bold: true, color: C.grayLight, valign: 'middle', fontFace: F });
+ s4.addText('기존 방식', { x: tblX + 1.7, y: tblY, w: 1.6, h: 0.38, fontSize: 8, bold: true, color: C.accent, align: 'center', valign: 'middle', fontFace: F });
+ s4.addText('SAM', { x: tblX + 3.3, y: tblY, w: 1.4, h: 0.38, fontSize: 8, bold: true, color: C.primary, align: 'center', valign: 'middle', fontFace: F });
+ s4.addText('개선률', { x: tblX + 4.7, y: tblY, w: 1.1, h: 0.38, fontSize: 8, bold: true, color: C.accentYellow, align: 'center', valign: 'middle', fontFace: F });
+
+ const rows = [
+ ['ERP 초기 구축', '6개월~1년', '1주일', '31배'],
+ ['견적 1건 작성', '3~5시간', '10초', '1,300배'],
+ ['신규 업체 온보딩', '2주 (현장방문)', '1일 (비대면)', '14배'],
+ ['업종 커스터마이징', '코드포크 (수개월)', '설정 변경', '∞'],
+ ['도입 비용', '5,800만~1.5억', '월 과금', '10배+'],
+ ['도입 실패율', '40~60%', '0% (2사 실증)', '∞'],
+ ];
+
+ rows.forEach((row, i) => {
+ const ry = tblY + 0.42 + i * 0.42;
+ const bgColor = i % 2 === 0 ? C.bgCard : C.bgCardAlt;
+ s4.addShape(pres.ShapeType.rect, { x: tblX, y: ry, w: tblW, h: 0.4, fill: { color: bgColor } });
+ s4.addText(row[0], { x: tblX + 0.1, y: ry, w: 1.6, h: 0.4, fontSize: 8.5, color: C.white, valign: 'middle', fontFace: F });
+ s4.addText(row[1], { x: tblX + 1.7, y: ry, w: 1.6, h: 0.4, fontSize: 8.5, color: C.grayLight, align: 'center', valign: 'middle', fontFace: F });
+ s4.addText(row[2], { x: tblX + 3.3, y: ry, w: 1.4, h: 0.4, fontSize: 8.5, bold: true, color: C.primary, align: 'center', valign: 'middle', fontFace: F });
+ s4.addText(row[3], { x: tblX + 4.7, y: ry, w: 1.1, h: 0.4, fontSize: 9, bold: true, color: C.accentYellow, align: 'center', valign: 'middle', fontFace: F });
+ });
+
+ // 오른쪽: 비용 절감 카드
+ const costX = 6.5, costY = 1.05, costW = 3.2, costH = 3.7;
+ s4.addShape(pres.ShapeType.roundRect, { x: costX, y: costY, w: costW, h: costH, rectRadius: 0.12, fill: { color: C.bgCard }, line: { color: C.primary, width: 1 } });
+
+ // 비용 카드 헤더
+ s4.addText('연간 비용 절감 (1개사)', { x: costX + 0.15, y: costY + 0.1, w: costW - 0.3, h: 0.3, fontSize: 9, bold: true, color: C.primary, fontFace: F });
+ s4.addShape(pres.ShapeType.rect, { x: costX + 0.15, y: costY + 0.42, w: costW - 0.3, h: 0.01, fill: { color: C.line } });
+
+ const costItems = [
+ ['견적 인건비', '3,750만'],
+ ['절곡 오류 재작업', '600만'],
+ ['원자재 낭비', '1,800만'],
+ ['단가/품질 관리', '1,680만'],
+ ];
+ costItems.forEach((ci, i) => {
+ const cy = costY + 0.5 + i * 0.45;
+ s4.addShape(pres.ShapeType.roundRect, { x: costX + 0.15, y: cy, w: costW - 0.3, h: 0.38, rectRadius: 0.05, fill: { color: C.bgCardAlt } });
+ s4.addText(ci[0], { x: costX + 0.25, y: cy, w: 1.5, h: 0.38, fontSize: 8, color: C.grayLight, valign: 'middle', fontFace: F });
+ s4.addText(ci[1], { x: costX + 1.8, y: cy, w: 1.2, h: 0.38, fontSize: 9, bold: true, color: C.green, align: 'right', valign: 'middle', fontFace: F });
+ });
+
+ // 합계
+ s4.addShape(pres.ShapeType.rect, { x: costX + 0.15, y: costY + 2.35, w: costW - 0.3, h: 0.01, fill: { color: C.primary } });
+ s4.addShape(pres.ShapeType.roundRect, { x: costX + 0.15, y: costY + 2.45, w: costW - 0.3, h: 0.55, rectRadius: 0.08, fill: { color: C.primaryLight } });
+ s4.addText('연간 절감 합계', { x: costX + 0.25, y: costY + 2.48, w: 1.5, h: 0.22, fontSize: 8, color: C.grayLight, fontFace: F });
+ s4.addText('7,830만원/년', { x: costX + 0.25, y: costY + 2.68, w: costW - 0.5, h: 0.28, fontSize: 16, bold: true, color: C.primary, fontFace: F });
+
+ // 실증 사례 배지
+ s4.addShape(pres.ShapeType.roundRect, { x: costX + 0.15, y: costY + 3.15, w: costW - 0.3, h: 0.4, rectRadius: 0.06, fill: { color: '1A2E1A' }, line: { color: '2E7D32', width: 0.5 } });
+ s4.addText([
+ { text: '실증: ', options: { fontSize: 8, bold: true, color: C.green } },
+ { text: '경동기업(2025-12~)\n주일기업(2025-09~) 운영 중', options: { fontSize: 7.5, color: C.grayLight } },
+ ], { x: costX + 0.25, y: costY + 3.15, w: costW - 0.5, h: 0.4, valign: 'middle', fontFace: F });
+
+ // ═══════════════════════════════════════════════════
+ // SLIDE 5: 선행기술 대비 독창성
+ // ═══════════════════════════════════════════════════
+ const s5 = pres.addSlide();
+ s5.background = { fill: C.bgDark };
+ addHeader(s5, pres, '선행기술 대비 독창성', C);
+ addConfidential(s5, pres);
+ addFooter(s5, pres, C, '5 / 6');
+
+ // 좌측: 비교표
+ const cmpX = 0.35, cmpY = 1.05, cmpW = 5.3;
+
+ // 비교표 헤더
+ s5.addShape(pres.ShapeType.roundRect, { x: cmpX, y: cmpY, w: cmpW, h: 0.38, rectRadius: 0.08, fill: { color: '1B3A5C' } });
+ s5.addText('기술 요소', { x: cmpX + 0.1, y: cmpY, w: 2, h: 0.38, fontSize: 8, bold: true, color: C.grayLight, valign: 'middle', fontFace: F });
+ s5.addText('젠소프트', { x: cmpX + 2.2, y: cmpY, w: 0.9, h: 0.38, fontSize: 7.5, bold: true, color: C.grayLight, align: 'center', valign: 'middle', fontFace: F });
+ s5.addText('이카운트', { x: cmpX + 3.1, y: cmpY, w: 0.9, h: 0.38, fontSize: 7.5, bold: true, color: C.grayLight, align: 'center', valign: 'middle', fontFace: F });
+ s5.addText('SAM', { x: cmpX + 4.05, y: cmpY, w: 1.1, h: 0.38, fontSize: 8, bold: true, color: C.primary, align: 'center', valign: 'middle', fontFace: F });
+
+ const compRows = [
+ ['비대면 3채널 수집', 'X', 'X', 'O'],
+ ['한글 초성 코드 생성', 'X', 'X', 'O'],
+ ['레시피 멱등적 초기화', 'X', 'X', 'O'],
+ ['동적 필드 3계층', '△', '△', 'O'],
+ ['DB 수준 조건부 렌더링', 'X', 'X', 'O'],
+ ['Zero-Config 수식 라우팅', 'X', 'X', 'O'],
+ ['행 수준 자동 격리 (267모델)', '△', 'X', 'O'],
+ ['4계층 AI 프롬프트 엔진', 'X', 'X', 'O'],
+ ['자연어 트리거 배포 자동화', 'X', 'X', 'O'],
+ ];
+
+ compRows.forEach((row, i) => {
+ const ry = cmpY + 0.42 + i * 0.36;
+ const bgColor = i % 2 === 0 ? C.bgCard : C.bgCardAlt;
+ s5.addShape(pres.ShapeType.rect, { x: cmpX, y: ry, w: cmpW, h: 0.34, fill: { color: bgColor } });
+ s5.addText(row[0], { x: cmpX + 0.1, y: ry, w: 2.1, h: 0.34, fontSize: 7.5, color: C.white, valign: 'middle', fontFace: F });
+
+ [1, 2, 3].forEach((col) => {
+ const cx = cmpX + 2.2 + (col - 1) * 0.9 + (col === 3 ? 0.15 : 0);
+ const cw = col === 3 ? 1.1 : 0.9;
+ const val = row[col];
+ let tc, tf;
+ if (val === 'O') { tc = C.primary; tf = true; }
+ else if (val === '△') { tc = C.accentYellow; tf = false; }
+ else { tc = C.gray; tf = false; }
+ s5.addText(val === 'O' ? '✓' : val === 'X' ? '—' : '△', { x: cx, y: ry, w: cw, h: 0.34, fontSize: 9, bold: tf, color: tc, align: 'center', valign: 'middle', fontFace: F });
+ });
+ });
+
+ // 우측: 프롬프트 엔진 차별점
+ const peX = 5.85, peY = 1.05, peW = 3.85;
+ s5.addShape(pres.ShapeType.roundRect, { x: peX, y: peY, w: peW, h: 3.9, rectRadius: 0.12, fill: { color: C.bgCard }, line: { color: C.blue, width: 1 } });
+ s5.addText('프롬프트 엔지니어링 핵심', { x: peX + 0.15, y: peY + 0.1, w: peW - 0.3, h: 0.3, fontSize: 9.5, bold: true, color: C.blue, fontFace: F });
+ s5.addShape(pres.ShapeType.rect, { x: peX + 0.15, y: peY + 0.42, w: peW - 0.3, h: 0.01, fill: { color: C.line } });
+
+ const peItems = [
+ { icon: '4', label: '계층 규칙 상속', desc: '전역→SAM→서비스→문서\n합산 3,000줄+ 규칙', color: '1565C0' },
+ { icon: '20만', label: '도메인 지식 베이스', desc: '산업 특화 규칙을\nAI가 자동 참조', color: '00838F' },
+ { icon: '57', label: '스킬+에이전트', desc: '45 스킬 + 12 에이전트\n코드 품질·보안·배포 자율 관리', color: '6A1B9A' },
+ { icon: 'L3', label: '서버 안전 제어', desc: 'L1(읽기)·L2(승인)·L3(금지)\n실제 502 사고에서 도출', color: 'C62828' },
+ ];
+
+ peItems.forEach((pe, i) => {
+ const py = peY + 0.5 + i * 0.85;
+ // 아이콘 서클
+ s5.addShape(pres.ShapeType.ellipse, { x: peX + 0.2, y: py + 0.1, w: 0.5, h: 0.5, fill: { color: pe.color } });
+ s5.addText(pe.icon, { x: peX + 0.2, y: py + 0.1, w: 0.5, h: 0.5, fontSize: pe.icon.length > 2 ? 7 : 12, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: F });
+ // 라벨
+ s5.addText(pe.label, { x: peX + 0.8, y: py + 0.05, w: 2.8, h: 0.22, fontSize: 9, bold: true, color: C.white, fontFace: F });
+ // 설명
+ s5.addText(pe.desc, { x: peX + 0.8, y: py + 0.3, w: 2.8, h: 0.4, fontSize: 7.5, color: C.gray, lineSpacingMultiple: 1.3, fontFace: F });
+ });
+
+ // ═══════════════════════════════════════════════════
+ // SLIDE 6: 실행 로드맵
+ // ═══════════════════════════════════════════════════
+ const s6 = pres.addSlide();
+ s6.background = { fill: C.bgDark };
+ addHeader(s6, pres, '실행 로드맵', C);
+ addConfidential(s6, pres);
+ addFooter(s6, pres, C, '6 / 6');
+
+ // 타임라인 바
+ s6.addShape(pres.ShapeType.rect, { x: 0.7, y: 1.45, w: 8.6, h: 0.04, fill: { color: C.line } });
+
+ const phases = [
+ { x: 0.7, w: 3.0, num: '1', period: '3~4월', title: '변리사 제출 자료 완성', color: '1565C0',
+ items: ['온보딩 자동화 흐름도', 'As-Is/To-Be 비교표', '청구항 초안 공동 작성', '선행기술 벤치마킹'] },
+ { x: 3.9, w: 2.6, num: '2', period: '4월', title: '보완 개발', color: '00838F',
+ items: ['신용도 내부 평가 구현', 'AI 동적 설문 프로토타입', '업종 확장 사례 3건+'] },
+ { x: 6.7, w: 2.6, num: '3', period: '5월~', title: '특허 출원', color: '2E7D32',
+ items: ['축1 플랫폼 방법특허 출원', '축2 알고리즘 특허 재검토', '명세서 보강 (실사용 데이터)'] },
+ ];
+
+ phases.forEach((ph) => {
+ // 타임라인 위 노드
+ s6.addShape(pres.ShapeType.ellipse, { x: ph.x + ph.w / 2 - 0.15, y: 1.32, w: 0.3, h: 0.3, fill: { color: ph.color } });
+ s6.addText(ph.num, { x: ph.x + ph.w / 2 - 0.15, y: 1.32, w: 0.3, h: 0.3, fontSize: 11, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: F });
+
+ // 카드
+ const cardY = 1.75;
+ s6.addShape(pres.ShapeType.roundRect, { x: ph.x, y: cardY, w: ph.w, h: 2.6, rectRadius: 0.1, fill: { color: C.bgCard }, line: { color: ph.color, width: 1 } });
+
+ // 기간 배지
+ s6.addShape(pres.ShapeType.roundRect, { x: ph.x + 0.12, y: cardY + 0.1, w: 0.9, h: 0.28, rectRadius: 0.04, fill: { color: ph.color } });
+ s6.addText(ph.period, { x: ph.x + 0.12, y: cardY + 0.1, w: 0.9, h: 0.28, fontSize: 8, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: F });
+
+ // 제목
+ s6.addText(ph.title, { x: ph.x + 1.1, y: cardY + 0.08, w: ph.w - 1.3, h: 0.3, fontSize: 10, bold: true, color: C.white, fontFace: F });
+
+ // 구분선
+ s6.addShape(pres.ShapeType.rect, { x: ph.x + 0.12, y: cardY + 0.48, w: ph.w - 0.24, h: 0.01, fill: { color: C.line } });
+
+ // 액션 아이템
+ ph.items.forEach((item, j) => {
+ const iy = cardY + 0.58 + j * 0.45;
+ s6.addShape(pres.ShapeType.roundRect, { x: ph.x + 0.12, y: iy, w: ph.w - 0.24, h: 0.38, rectRadius: 0.05, fill: { color: C.bgCardAlt } });
+ s6.addShape(pres.ShapeType.rect, { x: ph.x + 0.12, y: iy, w: 0.04, h: 0.38, fill: { color: ph.color } });
+ s6.addText(item, { x: ph.x + 0.28, y: iy, w: ph.w - 0.52, h: 0.38, fontSize: 8, color: C.grayLight, valign: 'middle', fontFace: F });
+ });
+ });
+
+ // 하단: 시스템 규모 + 업종 확장
+ const btmY = 4.55;
+ s6.addShape(pres.ShapeType.roundRect, { x: 0.4, y: btmY, w: 9.2, h: 0.6, rectRadius: 0.08, fill: { color: C.primaryLight }, line: { color: C.primary, width: 0.5 } });
+
+ const stats = [
+ ['모델 267개', '라우트 1,327개', '서비스 332개', '마이그레이션 551개', '운영 2사'],
+ ];
+ s6.addText([
+ { text: '시스템 규모 ', options: { fontSize: 8, bold: true, color: C.primary } },
+ { text: '모델 267개 | 라우트 1,327개 | 서비스 332개 | 마이그레이션 551개 | 운영 테넌트 2사', options: { fontSize: 8, color: C.grayLight } },
+ ], { x: 0.55, y: btmY, w: 5.5, h: 0.6, valign: 'middle', fontFace: F });
+
+ s6.addText([
+ { text: '확장 가능: ', options: { fontSize: 8, bold: true, color: C.accentYellow } },
+ { text: '가구·철강·시공·유통·식품 (파일/설정 추가만으로)', options: { fontSize: 8, color: C.grayLight } },
+ ], { x: 6.1, y: btmY, w: 3.4, h: 0.6, valign: 'middle', align: 'right', fontFace: F });
+
+ // ═══════════════════════════════════════════════════
+ // 저장
+ // ═══════════════════════════════════════════════════
+ const outPath = '/home/aweso/sam/docs/patent-attorney-briefing-compact.pptx';
+ await pres.writeFile({ fileName: outPath });
+ console.log('PPTX created: ' + outPath);
+}
+
+// ─── Helper Functions ──────────────────────────────────
+function addHeader(slide, pres, title, C) {
+ slide.addShape(pres.ShapeType.rect, { x: 0, y: 0, w: 10, h: 0.04, fill: { color: C.primary } });
+ slide.addShape(pres.ShapeType.rect, { x: 0.35, y: 0.35, w: 0.06, h: 0.38, fill: { color: C.primary } });
+ slide.addText(title, { x: 0.55, y: 0.28, w: 6, h: 0.5, fontSize: 18, bold: true, color: C.white, fontFace: 'Arial' });
+}
+
+function addFooter(slide, pres, C, pageNum) {
+ slide.addShape(pres.ShapeType.rect, { x: 0, y: 5.33, w: 10, h: 0.01, fill: { color: C.line } });
+ slide.addText('(주)코드브릿지엑스 | SAM 특허 출원 브리핑', {
+ x: 0.35, y: 5.35, w: 5, h: 0.25,
+ fontSize: 7, color: C.gray, fontFace: 'Arial'
+ });
+ slide.addText(pageNum, {
+ x: 7, y: 5.35, w: 2.7, h: 0.25,
+ fontSize: 7, color: C.gray, align: 'right', fontFace: 'Arial'
+ });
+}
+
+function addConfidential(slide, pres) {
+ slide.addShape(pres.ShapeType.roundRect, { x: 8.3, y: 0.15, w: 1.4, h: 0.35, rectRadius: 0.04, fill: { color: 'D32F2F' } });
+ slide.addText('CONFIDENTIAL', { x: 8.3, y: 0.12, w: 1.4, h: 0.22, fontSize: 7, bold: true, color: 'FFFFFF', align: 'center', fontFace: 'Arial' });
+ slide.addText('대 외 비', { x: 8.3, y: 0.28, w: 1.4, h: 0.22, fontSize: 8, bold: true, color: 'FFCDD2', align: 'center', fontFace: 'Arial' });
+}
+
+main().catch(console.error);
diff --git a/patent-method-patent-briefing.pdf b/patent-method-patent-briefing.pdf
new file mode 100644
index 0000000..b283735
Binary files /dev/null and b/patent-method-patent-briefing.pdf differ
diff --git a/patent-method-patent-briefing.pptx b/patent-method-patent-briefing.pptx
index b2fdcc3..9a78896 100644
Binary files a/patent-method-patent-briefing.pptx and b/patent-method-patent-briefing.pptx differ
diff --git a/patent-technical-documents.pdf b/patent-technical-documents.pdf
new file mode 100644
index 0000000..cec0759
Binary files /dev/null and b/patent-technical-documents.pdf differ
diff --git a/patent-technical-documents.pptx b/patent-technical-documents.pptx
index 4f8810b..50c9ca0 100644
Binary files a/patent-technical-documents.pptx and b/patent-technical-documents.pptx differ
diff --git a/plans/optimal-stock-management-plan.md b/plans/optimal-stock-management-plan.md
new file mode 100644
index 0000000..3d9615c
--- /dev/null
+++ b/plans/optimal-stock-management-plan.md
@@ -0,0 +1,169 @@
+# 적정재고 관리 기능 기획
+
+> **작성일**: 2026-03-20
+> **상태**: 기획
+> **담당**: R&D실
+
+---
+
+## 1. 개요
+
+### 1.1 목적
+
+재고 상세 화면에 **적정재고** 개념을 추가하여, 단순 최소치 확보(안전재고)를 넘어 **범위 기반 재고 관리**를 가능하게 한다.
+
+### 1.2 핵심 개념
+
+| 구분 | 안전재고 (Safety Stock) | 적정재고 (Optimal Stock) |
+|------|----------------------|------------------------|
+| **정의** | 결품 방지를 위한 **최소 보유량** | 효율적 운영을 위한 **범위 (최소~최대)** |
+| **관리 기준** | 단일 값 (최소치) | 최소 ~ 최대 범위 |
+| **역할** | "이 이하로 떨어지면 안 된다" → 발주 트리거 | "이 범위 안에 있어야 정상이다" → 과잉재고 방지 + 결품 방지 |
+| **알림** | 재고 < 안전재고 → `low` 상태 | 재고 > 최대재고 → `over` 상태 |
+
+### 1.3 개선 범위
+
+- `stocks` 테이블에 `max_stock` 컬럼 추가
+- `Stock` 모델 상태 계산 로직 확장 (`over` 상태 추가)
+- React 재고 상세 화면에 적정재고(최소/최대) 입력 UI 추가
+- 재고 목록에서 `over` 상태 표시
+
+---
+
+## 2. 현재 상태
+
+### 2.1 DB 스키마 (stocks 테이블)
+
+```sql
+safety_stock DECIMAL(15,3) DEFAULT 0 COMMENT '안전 재고'
+-- max_stock 컬럼 없음
+```
+
+### 2.2 상태 계산 로직 (`Stock::calculateStatus()`)
+
+```php
+if ($this->stock_qty <= 0) return 'out'; // 재고 없음
+if ($this->stock_qty < $this->safety_stock) return 'low'; // 부족
+return 'normal'; // 정상
+```
+
+### 2.3 React 재고 상세 화면
+
+- **수정 가능**: 안전재고 (Input), 상태 (Select)
+- **적정재고(최대재고) 입력 필드 없음**
+
+---
+
+## 3. 변경 사항
+
+### 3.1 DB 마이그레이션
+
+```php
+// API 마이그레이션 (stocks 테이블에 max_stock 추가)
+Schema::table('stocks', function (Blueprint $table) {
+ $table->decimal('max_stock', 15, 3)->default(0)
+ ->comment('최대 재고 (적정재고 상한)')
+ ->after('safety_stock');
+});
+```
+
+### 3.2 Stock 모델 변경
+
+```php
+// fillable 추가
+'max_stock',
+
+// casts 추가
+'max_stock' => 'decimal:3',
+
+// calculateStatus() 확장
+public function calculateStatus(): string
+{
+ if ($this->stock_qty <= 0) {
+ return 'out'; // 재고 없음
+ }
+ if ($this->stock_qty < $this->safety_stock) {
+ return 'low'; // 안전재고 미달
+ }
+ if ($this->max_stock > 0 && $this->stock_qty > $this->max_stock) {
+ return 'over'; // 최대재고 초과
+ }
+ return 'normal'; // 정상 범위
+}
+```
+
+> `max_stock = 0`이면 최대재고 미설정으로 간주 → 기존 동작과 동일 (하위 호환)
+
+### 3.3 StockService 변경
+
+`update()` 메서드에서 `max_stock` 필드 저장 추가.
+
+### 3.4 API 응답 변경
+
+`GET /api/v1/stocks/{id}` 응답에 `max_stock` 필드 추가.
+
+`PUT /api/v1/stocks/{id}` 요청에 `max_stock` 필드 허용.
+
+### 3.5 React 재고 상세 화면 변경
+
+**수정 모드 기본 정보 카드 - Row 2 변경:**
+
+| 현재 (4열) | 변경 후 (4열) |
+|-----------|-------------|
+| 규격 / 단위 / 재고량 / 안전재고 | 규격 / 단위 / 재고량 / 안전재고 |
+| — | **Row 3 추가**: 최대재고 / 재공품 / 상태 / (빈칸) |
+
+안전재고 라벨을 **"안전재고 (최소)"**, 최대재고는 **"최대재고"** 로 표시하여 적정재고 범위임을 직관적으로 전달.
+
+**보기 모드도 동일하게 최대재고 필드 추가.**
+
+### 3.6 재고 상태 표시 확장
+
+| 상태 | 조건 | 라벨 | 색상 |
+|------|------|------|------|
+| `out` | stock_qty <= 0 | 없음 | 빨강 |
+| `low` | stock_qty < safety_stock | 부족 | 주황 |
+| `normal` | 범위 내 | 정상 | 초록 |
+| `over` | stock_qty > max_stock (max_stock > 0) | 초과 | 보라 |
+
+---
+
+## 4. 검증 규칙
+
+- `safety_stock >= 0` (필수)
+- `max_stock >= 0` (필수, 0 = 미설정)
+- `max_stock > 0`일 때 `max_stock >= safety_stock` (최대 >= 최소)
+- 위반 시 저장 차단 + 에러 메시지
+
+---
+
+## 5. 영향 범위
+
+| 대상 | 파일 | 변경 |
+|------|------|------|
+| DB | 마이그레이션 신규 | `max_stock` 컬럼 추가 |
+| API 모델 | `Stock.php` | fillable, casts, calculateStatus |
+| API 서비스 | `StockService.php` | update 로직에 max_stock 추가 |
+| React 타입 | `types.ts` | `maxStock` 필드 추가 |
+| React 액션 | `actions.ts` | updateStock에 max_stock 전달 |
+| React UI | `StockStatusDetail.tsx` | 최대재고 입력 필드 + 상태 표시 |
+| React 목록 | `StockStatusList.tsx` | `over` 상태 뱃지 추가 |
+
+---
+
+## 6. 하위 호환성
+
+- `max_stock` 기본값 `0` → 미설정 시 기존 로직과 100% 동일
+- 기존 `normal`/`low`/`out` 상태 그대로 유지
+- `over` 상태는 `max_stock > 0`인 품목에서만 발생
+
+---
+
+## 관련 문서
+
+- 재고 조정 위치 이동 요청: `plans/stock-detail-inventory-adjustment-request.md`
+- 재고생산관리: `features/sales/stock-production.md`
+
+---
+
+**최종 업데이트**: 2026-03-20
diff --git a/plans/qa-bugfix-plan-v2.md b/plans/qa-bugfix-plan-v2.md
index 5b3cb44..b8e29fa 100644
--- a/plans/qa-bugfix-plan-v2.md
+++ b/plans/qa-bugfix-plan-v2.md
@@ -14,11 +14,11 @@
|------|------|
| **총 이슈** | 43건 (신규 26 + V1이관 17) |
| **중요도 분포** | Critical 6, Major 22, Minor 9, 확인필요 4, 보류/패스 2 |
-| **완료** | 16건 (코드수정 12 + 코드정상/자동해소 4) |
-| **미착수** | 27건 (재검증 2, 프론트이관 5, 별도세션 11, 기획확인 5, 보류 4) |
-| **진행률** | 16/43 (37%) |
-| **다음 작업** | Phase 5 신규기능 또는 별도 세션 이슈 |
-| **마지막 업데이트** | 2026-03-18 |
+| **완료** | 31건 (코드수정 18 + 정상/자동해소 4 + 디펙아님 7 + 준비중 1 + 패스 1) |
+| **미착수** | 12건 (프론트이관 3, 신규기능 3, 별도세션 2, 패스/보류 4) |
+| **진행률** | 31/43 (72%) |
+| **다음 작업** | V1#37 작업지시 수량 수정, V1#40 미터단위, V1#41 재고조정 (신규기능) |
+| **마지막 업데이트** | 2026-03-19 |
---
@@ -71,12 +71,12 @@
### 2.0 Phase 0: 확인필요 (4건) — 정책 결정 후 배정
-| # | 출처 | 모듈 | 이슈 | 확인 사항 |
-|---|------|------|------|----------|
-| 4 | 신규 | 수주관리 | 출고예정일 빈값 → 납품요청일 복사 | 의도된 동작인지 확인 |
-| 21 | 신규 | 제품검사 | 검사 완료 후 수정 가능 | 완료 후 수정 차단 필요 여부 |
-| V1#10 | V1이관 | 견적관리 | 목록 작업 컬럼 빈값 | 어떤 데이터가 들어가야 하는지 기획 확인 |
-| V1#12 | V1이관 | 견적관리 | 연락처 필수값 안내 시점 | UX 정책: 저장 시 vs 실시간 검증 |
+| # | 출처 | 모듈 | 이슈 | 확인 사항 | 상태 |
+|---|------|------|------|----------|------|
+| 4 | 신규 | 수주관리 | 출고예정일 빈값 → 납품요청일 복사 | 의도된 동작인지 확인 | ✅ 디펙아님 (정상 동작) |
+| 21 | 신규 | 제품검사 | 검사 완료 후 수정 가능 | 완료 후 수정 차단 필요 여부 | ✅ 수정완료 (완료 후 차단) |
+| V1#10 | V1이관 | 견적관리 | 목록 작업 컬럼 빈값 | 어떤 데이터가 들어가야 하는지 기획 확인 | 보류 (셀렉트 시 액션 표시됨) |
+| V1#12 | V1이관 | 견적관리 | 연락처 필수값 안내 시점 | UX 정책: 저장 시 vs 실시간 검증 | 패스 (현재 동작 유지) |
---
@@ -91,7 +91,7 @@
| 항목 | 내용 |
|------|------|
| **현상** | 수주확정 후 생산지시 생성 클릭 시 `[500] Undefined variable $process` 에러 |
-| **경로** | `/sales/order-management-sales/[id]` → 생산지시 생성 |
+| **경로** | https://dev.codebridge-x.com/ko/sales/order-management-sales/[id] → 생산지시 생성 |
| **영향** | **생산→출고→매출 전체 흐름 차단** |
| **비고** | 3/13 정상이었으나 3/16부터 에러 발생. 예시: KD-SS-260316-02 |
@@ -112,7 +112,7 @@
| 항목 | 내용 |
|------|------|
| **현상** | 제품검사 등록 성공 알럿 → 목록 이동 → 데이터 없음. 요약카드만 +1 |
-| **경로** | `/quality/inspections?mode=new` |
+| **경로** | https://dev.codebridge-x.com/ko/quality/inspections?mode=new |
| **영향** | **품질관리 흐름 검증 차단** |
**수정 대상:**
@@ -128,7 +128,7 @@
| 항목 | 내용 |
|------|------|
| **현상** | 출고 완료되어도 매출금액 0원. SL202603~ 목록/상세 모두 0원 |
-| **경로** | `/accounting/sales`, `/accounting/sales/[id]?mode=view` |
+| **경로** | https://dev.codebridge-x.com/ko/accounting/sales , https://dev.codebridge-x.com/ko/accounting/sales/[id]?mode=view |
| **선행** | #16 해결 필요 |
**수정 대상:**
@@ -144,7 +144,7 @@
| 항목 | 내용 |
|------|------|
| **현상** | 실적신고 미확정 상태 데이터가 품질관리서 목록에 표시됨 |
-| **경로** | `/quality/qms` |
+| **경로** | https://dev.codebridge-x.com/ko/quality/qms |
| **선행** | #20 해결 필요 |
**수정 대상:**
@@ -160,7 +160,7 @@
| 항목 | 내용 |
|------|------|
| **현상** | 부가세 포함 선택 후 저장 → 상세에서 부가세 미노출 |
-| **경로** | `/sales/quote-management/new` |
+| **경로** | https://dev.codebridge-x.com/ko/sales/quote-management/new |
| **코드 검증** | Quote 모델 fillable에 `tax_amount` 있으나, 프론트에서 tax_amount 미전송. 계산/저장 로직 자체 미구현 |
**수정 대상:**
@@ -177,7 +177,7 @@
| 항목 | 내용 |
|------|------|
| **현상** | 단가 수정 시 저장되지 않고 0원으로 저장 |
-| **경로** | `/sales/pricing-management/create?itemId=` |
+| **경로** | https://dev.codebridge-x.com/ko/sales/pricing-management/create?itemId= |
| **코드 검증** | `PricingService.php` → `getItemPrice()` 메서드가 TODO 상태, `return ['price' => 0]` 하드코딩 |
**수정 대상:**
@@ -199,7 +199,7 @@
| 항목 | 내용 |
|------|------|
| **현상** | 로트번호 컬럼에 출고번호(SHP-*)가 표시됨. 상세에서는 `-` 표기 |
-| **경로** | `/outbound/shipments` |
+| **경로** | https://dev.codebridge-x.com/ko/outbound/shipments |
---
@@ -208,16 +208,17 @@
| 항목 | 내용 |
|------|------|
| **현상** | 전체 출고건 "등록된 제품이 없다"고 표시 |
-| **경로** | `/outbound/shipments/[id]?mode=view` |
+| **경로** | https://dev.codebridge-x.com/ko/outbound/shipments/[id]?mode=view |
---
-#### #25 출고 수동등록 로트번호 미로드 `Major` `신규`
+#### #25 출고 수동등록 로트번호 미로드 `Major` `신규` ✅ 디펙아님 (기능 제거)
| 항목 | 내용 |
|------|------|
| **현상** | 생산완료 후 수동 등록 시 로트번호 목록 미로드 |
-| **경로** | `/outbound/shipments?mode=new` |
+| **경로** | https://dev.codebridge-x.com/ko/outbound/shipments?mode=new |
+| **점검 결과** | 출고 수동등록 기능은 의도적으로 제거됨. 제품 없이 수동 등록 불가하므로 수주→생산→출고(자동생성) 경로만 지원 |
---
@@ -226,25 +227,28 @@
| 항목 | 내용 |
|------|------|
| **현상** | 카드 출고완료 0건 vs 목록 출고완료 2건 |
-| **경로** | `/outbound/shipments` |
+| **경로** | https://dev.codebridge-x.com/ko/outbound/shipments |
---
-#### #27 매출관리 흐름 검증 불가 `Major` `신규`
+#### #27 매출관리 흐름 검증 불가 `Major` `신규` ✅ 디펙아님 (#25 해소)
| 항목 | 내용 |
|------|------|
| **현상** | 출고 수동등록 불가로 매출 등록 확인 불가 |
| **선행** | #25 + #16 해결 시 자동 해소 가능 |
+| **점검 결과** | #25가 디펙아님(수동등록 기능 제거). 매출 흐름은 수주→생산→출고(자동)→매출 경로로 검증 가능. #16(생산지시 500) 기수정 완료로 정상 흐름 확보됨 |
---
-#### #29 엑셀 업로드 구버전 제품명 산출 안됨 `Major` `신규`
+#### #29 엑셀 업로드 구버전 제품명 산출 안됨 `Major` `신규` ⏳ 준비중 처리
| 항목 | 내용 |
|------|------|
| **현상** | 엑셀 업로드 시 구버전 제품명으로 견적 산출 안됨 |
-| **경로** | `/sales/quote-management/new?mode=new` |
+| **경로** | https://dev.codebridge-x.com/ko/sales/quote-management?mode=new |
+| **점검 결과** | 엑셀 제품코드 매칭이 `item_code === productCode` 완전 일치만 지원. 구버전 제품명/별칭 필드 없음. 양식 재설계 필요 |
+| **처리** | 양식다운로드 + 업로드 버튼 → "준비중입니다." toast로 임시 비활성화. `LocationListPanel.tsx` 수정 |
---
@@ -259,7 +263,7 @@
| 항목 | 내용 |
|------|------|
| **현상** | 수주 선택 시 모두 "불일치" 표시. "미검사" 워딩과도 불일치 |
-| **경로** | `/quality/inspections?mode=new` |
+| **경로** | https://dev.codebridge-x.com/ko/quality/inspections?mode=new |
---
@@ -268,7 +272,7 @@
| 항목 | 내용 |
|------|------|
| **현상** | 품질관리서 목록에 품목명이 표시되지 않음 |
-| **경로** | `/quality/qms` |
+| **경로** | https://dev.codebridge-x.com/ko/quality/qms |
---
@@ -277,7 +281,7 @@
| 항목 | 내용 |
|------|------|
| **현상** | 기존 데이터로는 표시됨, 신규 검증 불가 (#20 선행) |
-| **경로** | `/quality/qms` |
+| **경로** | https://dev.codebridge-x.com/ko/quality/qms |
---
@@ -286,7 +290,7 @@
| 항목 | 내용 |
|------|------|
| **현상** | 관련서류 탭 수주서/작업일지/검사서 검증 불가 (#20 선행) |
-| **경로** | `/quality/qms` |
+| **경로** | https://dev.codebridge-x.com/ko/quality/qms |
---
@@ -299,7 +303,7 @@
| 항목 | 내용 |
|------|------|
| **현상** | 신규 등록 거래처가 견적 등록 시 수주처 목록에 미표시 |
-| **경로** | `/sales/quote-management/new?mode=new` |
+| **경로** | https://dev.codebridge-x.com/ko/sales/quote-management?mode=new |
---
@@ -308,64 +312,72 @@
| 항목 | 내용 |
|------|------|
| **현상** | 활성 6건 + 비활성 14건 = 20건 전부 목록 표시 |
-| **경로** | `/sales/quote-management/new?mode=new` |
+| **경로** | https://dev.codebridge-x.com/ko/sales/quote-management?mode=new |
> **참고**: V1 #25(수주관리 `getClients({ only_active: true })`)와 동일 패턴. 견적관리 쪽도 적용 필요.
---
-#### #13 단가 신규 등록 기능 없음 `Major` `신규`
+#### #13 단가 신규 등록 기능 없음 `Major` `신규` ✅ 디펙아님 (의도된 설계)
| 항목 | 내용 |
|------|------|
| **현상** | 품목마스터 동기화 버튼만 존재. 수동 단가 등록 기능 없음 |
-| **경로** | `/sales/pricing-management` |
+| **경로** | https://dev.codebridge-x.com/ko/sales/pricing-management |
| **연관** | V1#32, V1#33 (PricingService 통합 구현) |
+| **점검 결과** | 등록 기능은 구현됨 (미등록 품목 행 클릭 → create 페이지). 품목마스터에 없는 제품의 단가 등록은 의도적으로 불가. 설계 의도대로 정상 |
---
-#### #14 배차차량 상태 전환 방법 없음 `Major` `신규`
+#### #14 배차차량 상태 전환 방법 없음 `Major` `신규` ✅ 완료
| 항목 | 내용 |
|------|------|
| **현상** | "작성대기"에서 전환 불가. 배차정보 입력해도 상태 변경 없음 |
-| **경로** | `/outbound/vehicle-dispatches/[id]?mode=view` |
+| **경로** | https://dev.codebridge-x.com/ko/outbound/vehicle-dispatches/[id]?mode=view |
+| **점검 결과** | 백엔드 update()에서 options.status를 저장하지 않음. 프론트는 조회 시 freight_cost_type으로 가상 계산만 |
+| **처리** | 백엔드 update() 시 freight_cost_type 유무로 status 자동 결정(completed/draft). 상차/직접배차는 금액 0 허용. 프론트는 options.status 우선 사용 + 폴백 유지 |
---
-#### #15 생산지시 상세 개소 값 불일치 `Major` `신규`
+#### #15 생산지시 상세 개소 값 불일치 `Major` `신규` ✅ 완료
| 항목 | 내용 |
|------|------|
| **현상** | 작업지시서 개소 컬럼에 품목 합 표시. 납품 개소 값이어야 함 |
-| **경로** | `/sales/order-management-sales/production-orders/[id]?mode=view` |
+| **경로** | https://dev.codebridge-x.com/ko/sales/order-management-sales/production-orders/[id]?mode=view |
---
-#### #17 수주 목록 상태/카드 불일치 `Major` `신규`
+#### #17 수주 목록 상태/카드 불일치 `Major` `신규` ✅ 완료
| 항목 | 내용 |
|------|------|
-| **현상** | 카드 생산지시 대기 7건 vs 목록 수주확정 5건 |
-| **경로** | `/sales/order-management-sales` |
+| **현상** | 카드 생산지시 대기 7건 vs 목록 수주확정 5건. 추가로 3월 21건인데 11건만 표시 |
+| **경로** | https://dev.codebridge-x.com/ko/sales/order-management-sales |
+| **점검 결과** | ① 카드는 복합 상태 합산, 필터는 3개만 존재 ② received_at 전부 NULL로 API 날짜 필터 미작동 |
+| **처리** | 백엔드 날짜 필터를 COALESCE(DATE(received_at), DATE(created_at)) 폴백으로 수정. 프론트 필터/카드는 현행 유지 |
---
-#### V1#6 거래처 등록 시 회계관리 미노출 `Major` `V1이관`
+#### V1#6 거래처 등록 시 회계관리 미노출 `Major` `V1이관` 패스 (mng 별도)
| 항목 | 내용 |
|------|------|
| **현상** | 신규 거래처 등록 시 회계관리 거래처 목록에 미노출 |
-| **코드 검증** | ClientService에 mng DB 동기화 로직 없음 |
+| **코드 검증** | DB 동기화 문제가 아님. 같은 DB(samdb)의 같은 `clients` 테이블(41건) 공유. **mng 회계 화면이 `trading_partners`(34건) 테이블을 별도 조회**하는 구조적 차이. mng 회계 코드 확인 필요 |
+| **처리** | mng 영역이므로 패스. 별도 세션에서 처리 |
---
-#### V1#11 견적 접수일 날짜 하루 밀림 `Major` `V1이관`
+#### V1#11 견적 접수일 날짜 하루 밀림 `Major` `V1이관` ✅ 완료
| 항목 | 내용 |
|------|------|
| **현상** | 접수 날짜 오늘(3/13) 설정 → 저장 후 목록에 3/12로 노출 |
| **코드 검증** | QuoteRegistration.tsx에서 UTC 타임존 변환 처리 없음 |
+| **점검 결과** | DB 저장은 정상(`2026-03-19`). Laravel `date` 캐스트가 JSON 직렬화 시 UTC 변환(`2026-03-18T15:00:00.000000Z`) → 프론트에서 -1일 표시 |
+| **처리** | Quote 모델의 date 캐스트를 `date:Y-m-d` 형식으로 변경 → JSON 직렬화 시 `"2026-03-19"` 문자열로 반환 |
---
@@ -384,13 +396,15 @@
---
-#### V1#32 단가 등록 시 품목코드/품목명 변경 `Major` `V1이관`
+#### V1#32 단가 등록 시 품목코드/품목명 변경 `Major` `V1이관` ✅ 완료
| 항목 | 내용 |
|------|------|
| **현상** | 단가 등록 시 품목코드가 ITEM-15893, 품목명이 "없음"으로 변경됨 |
| **코드 검증** | PricingService.php store() 미구현 (TODO 상태) |
| **연관** | V1#33, 신규#13과 통합 처리 |
+| **점검 결과** | Price 모델에 `item()` 관계 미정의 → API 응답에 품목 정보 없음 → 프론트 폴백값(`ITEM-{id}`, `품목명 없음`) 표시 |
+| **처리** | ① Price 모델에 `item()` BelongsTo 관계 추가 ② index/show에서 `item:id,code,name,item_category` eager load ③ 프론트 변환 함수에서 item 데이터 우선 사용 |
---
@@ -450,11 +464,11 @@
| # | 출처 | 모듈 | 이슈 | 상세 | 상태 |
|---|------|------|------|------|------|
-| 18 | 신규 | 재고현황 | 재공품 필드 0 표시 | 목록 0 노출, 상세 "사용" 표시 | ⏳ 기획 확인 |
+| 18 | 신규 | 재고현황 | 재공품 필드 0 표시 | 목록 0 노출, 상세 "사용" 표시 | 패스 (다른 개발자 진행중) |
| 19 | 신규 | 제품검사 | 상태/카드 불일치 | 카드 완료5 vs 목록 완료2 | ✅ #20에서 해소 |
| 24 | 신규 | 출고관리 | 상태 워딩 | "출고완료**으로**" → "출고완료**로**" | ✅ 완료 |
| 32 | 신규 | 견적관리 | 날짜필터 활성화 스타일 | 선택 버튼 활성화 스타일 없음 | → 프론트이관 |
-| V1#13 | V1이관 | 견적관리 | 수동 품목 단가 0원 | 수동 추가 시 단가 입력 불가 | ⏳ 기획 확인 |
+| V1#13 | V1이관 | 견적관리 | 수동 품목 단가 0원 | 수동 추가 시 단가 입력 불가 | ✅ 디펙아님 (단가 데이터 미등록) |
| V1#17 | V1이관 | 견적관리 | PDF 생성 안됨 | DomPDF 미구현 | ⏳ 별도 세션 |
| V1#24 | V1이관 | 견적관리 | 견적상태 3곳 불일치 | 상태/뱃지/금액요약 워딩 통일 | → 프론트이관 |
| V1#34 | V1이관 | 품목관리 | 품목 규격 (나머지) | WO items에 spec 미적용 | ⏳ 별도 세션 |
@@ -586,6 +600,17 @@ Phase 6 (Minor) ── 모두 독립
| 2026-03-18 | Phase 3 | #7 #10 완료, #5 정상, #9 자동해소 |
| 2026-03-18 | Phase 4 | V1#29 완료, #11 #12 코드정상(재검증), 나머지 별도 처리 |
| 2026-03-18 | Phase 6 | #24 완료, #19 자동해소, #32 V1#24 V1#42 프론트이관 |
+| 2026-03-18 | Phase 4 | #15 개소값 수정, #17 카드 재구성+stats 상태 추가, 공정그룹 한글화 |
+| 2026-03-18 | Phase 0 | #4 디펙아님, #21 수정완료, V1#10 보류, V1#12 패스 |
+| 2026-03-18 | Phase 6 | V1#13 디펙아님(단가 미등록), #18 패스(다른 개발자) |
+| 2026-03-18 | 기타 | V1#6 구조확인(clients vs trading_partners), 개발서버 CLI timezone 수정 |
+| 2026-03-19 | Phase 2 | #25 디펙아님(수동등록 기능 의도적 제거), #27 디펙아님(#25 해소, 자동 흐름으로 검증 가능) |
+| 2026-03-19 | Phase 2 | #29 엑셀 업로드 "준비중" 처리 (양식다운로드+업로드 버튼 toast 비활성화) |
+| 2026-03-19 | 문서 | 전체 경로를 full URL(https://dev.codebridge-x.com/ko/...)로 업데이트 |
+| 2026-03-19 | Phase 4 | #17 완료 — 필터 전체 상태 추가 + 카드 필터 연동 + API 서버사이드 날짜 필터(COALESCE) + 건수 누락 해결 |
+| 2026-03-19 | Phase 4 | #14 완료 — 백엔드 update() 시 freight_cost_type 유무로 options.status 자동 저장 |
+| 2026-03-19 | Phase 4 | V1#11 완료 — Quote 모델 date 캐스트를 `date:Y-m-d` 형식으로 변경 (UTC 직렬화 방지) |
+| 2026-03-19 | Phase 4 | #13 디펙아님(의도된 설계), V1#6 패스(mng 별도), V1#32 완료 — Price 모델 item() 관계 추가 + eager load |
---
diff --git a/rules/leave-promotion-policy.md b/rules/leave-promotion-policy.md
new file mode 100644
index 0000000..3a3b205
--- /dev/null
+++ b/rules/leave-promotion-policy.md
@@ -0,0 +1,148 @@
+# 연차촉진 관리 정책
+
+> **작성일**: 2026-03-20
+> **상태**: 적용 완료
+> **근거**: 근로기준법 제61조 (연차 유급휴가의 사용 촉진)
+
+---
+
+## 1. 개요
+
+### 1.1 목적
+
+연차유급휴가 사용촉진 통지서 발송 시기와 촉진기간 계산 공식을 정의한다.
+근로기준법 제61조에 따라 **1년 이상 근로자(제1항)**와 **1년 미만 근로자(제2항)**의 계산 공식이 다르다.
+
+### 1.2 핵심 원칙
+
+- 1년 이상/미만 여부는 **입사일 기준**으로 자동 판별
+- 1차·2차 촉진 통지를 모두 이행하면 **미사용 수당 지급 의무 면제**
+- 미이행 시 미사용 연차에 대한 **수당 지급 의무 발생**
+
+---
+
+## 2. 근로자 구분 기준
+
+### 2.1 판별 공식
+
+```php
+$firstAnniversary = $hireDate->copy()->addYear();
+
+if ($firstAnniversary > Carbon::create($year, 1, 1)) {
+ // 1년 미만 근로자 (제61조 제2항)
+} else {
+ // 1년 이상 근로자 (제61조 제1항)
+}
+```
+
+### 2.2 판별 예시
+
+| 입사일 | 조회 연도 | 1주년 | 구분 | 근거 |
+|--------|:---------:|-------|------|------|
+| 2025-06-15 | 2026 | 2026-06-15 | **1년 미만** | 1주년 > 2026-01-01 |
+| 2025-11-01 | 2026 | 2026-11-01 | **1년 미만** | 1주년 > 2026-01-01 |
+| 2024-03-01 | 2026 | 2025-03-01 | **1년 이상** | 1주년 < 2026-01-01 |
+| 2023-08-20 | 2026 | 2024-08-20 | **1년 이상** | 1주년 < 2026-01-01 |
+
+---
+
+## 3. 촉진기간 계산 공식
+
+### 3.1 1년 이상 근로자 (제61조 제1항)
+
+| 항목 | 공식 | 설명 |
+|------|------|------|
+| **만료일** | 입사기념일(`$year + 1`) 전날 | 연차가 소멸되는 날 |
+| **1차 촉진 시작** | 만료일 - **6개월** | 미사용 일수 서면 통보 |
+| **1차 촉진 종료** | 1차 시작 + **10일** | 사용 시기 제출 기한 |
+| **2차 촉진 마감** | 만료일 - **2개월** | 회사 지정 사용 시기 통보 |
+
+**계산 예시** (입사일: 2023-05-10, 조회 연도: 2026)
+
+```
+만료일 = 2027-05-10 - 1일 = 2027-05-09
+1차 촉진 시작 = 2027-05-09 - 6개월 = 2026-11-09
+1차 촉진 종료 = 2026-11-09 + 10일 = 2026-11-19
+2차 촉진 마감 = 2027-05-09 - 2개월 = 2027-03-09
+```
+
+### 3.2 1년 미만 근로자 (제61조 제2항)
+
+| 항목 | 공식 | 설명 |
+|------|------|------|
+| **만료일** | 입사 **1주년** 전날 | 월차가 소멸되는 날 |
+| **1차 촉진 시작** | 만료일 - **3개월** | 미사용 일수 서면 통보 |
+| **1차 촉진 종료** | 1차 시작 + **10일** | 사용 시기 제출 기한 |
+| **2차 촉진 마감** | 만료일 - **1개월** | 회사 지정 사용 시기 통보 |
+
+**계산 예시** (입사일: 2025-08-01, 조회 연도: 2026)
+
+```
+만료일 = 2026-08-01 - 1일 = 2026-07-31
+1차 촉진 시작 = 2026-07-31 - 3개월 = 2026-04-30
+1차 촉진 종료 = 2026-04-30 + 10일 = 2026-05-10
+2차 촉진 마감 = 2026-07-31 - 1개월 = 2026-06-30
+```
+
+### 3.3 비교 요약
+
+| 구분 | 연차 유형 | 만료일 | 1차 촉진 | 2차 촉진 |
+|------|----------|--------|----------|----------|
+| 1년 이상 | 연차 (15일~) | 입사기념일(year+1) 전날 | 만료 **6개월** 전 | 만료 **2개월** 전 |
+| 1년 미만 | 월차 (최대 11일) | 입사 **1주년** 전날 | 만료 **3개월** 전 | 만료 **1개월** 전 |
+
+---
+
+## 4. 통지서 구성
+
+### 4.1 1차 촉진 통지서
+
+- 수신자 정보 (성명, 부서, 직급)
+- 연차 현황 (발생/사용/잔여일수)
+- 사용계획 제출기한
+- 근로기준법 제61조 법적 문구
+
+### 4.2 2차 촉진 통지서
+
+- 수신자 정보 (성명, 부서, 직급)
+- 잔여 연차일수
+- 회사 지정 휴가일 목록
+- 근로기준법 제61조 법적 문구
+
+### 4.3 발송 흐름
+
+```
+1차 촉진 통지 발송
+ ↓
+근로자 10일 이내 사용 시기 제출?
+ ├─ Yes → 종료 (근로자 계획대로 사용)
+ └─ No → 2차 촉진 통지 발송
+ ↓
+ 회사가 사용 시기 지정 통보
+ ↓
+ 미사용 수당 지급 의무 면제
+```
+
+---
+
+## 5. 구현 위치
+
+| 구분 | 파일 |
+|------|------|
+| 촉진기간 계산 | `mng/app/Services/HR/LeaveService.php` → `getPromotionCandidates()` |
+| 통지서 발송 | `mng/app/Services/HR/LeaveService.php` → `sendPromotionNotices()` |
+| 관리 화면 | `mng/resources/views/hr/leave-promotions/index.blade.php` |
+| 컨트롤러 | `mng/app/Http/Controllers/HR/LeavePromotionController.php` |
+| 1차 통지서 양식 | `mng/resources/views/approvals/partials/_leave-promotion-1st-form.blade.php` |
+| 2차 통지서 양식 | `mng/resources/views/approvals/partials/_leave-promotion-2nd-form.blade.php` |
+
+---
+
+## 관련 문서
+
+- `rules/attendance-api.md` — 근태 API 규칙
+- `dev/dev_plans/leave-management-plan.md` — 휴가관리 모듈 개발 계획
+
+---
+
+**최종 업데이트**: 2026-03-20
diff --git a/standards/bending-item-code-policy.md b/standards/bending-item-code-policy.md
new file mode 100644
index 0000000..0102f25
--- /dev/null
+++ b/standards/bending-item-code-policy.md
@@ -0,0 +1,231 @@
+# 절곡 재공품 품목코드 체계
+
+> **작성일**: 2026-03-21
+> **상태**: 설계 확정
+
+---
+
+## 1. 개요
+
+### 1.1 목적
+
+절곡 바라시 기초자료(1차 가공물)에 **의미 기반 고유 품목코드**를 부여하여, 재공품 관리 및 2차 가공물(가이드레일/케이스 등)과의 매핑을 체계화한다.
+
+### 1.2 재공품과의 관계
+
+재공품(WIP)은 유휴 시간을 활용하여 **가장 많이 나가는 정형화된 부품**을 미리 생산하는 것이다. 모든 절곡 형상을 재공품으로 관리하는 것이 아니라, 수요가 높은 **표준 형상**만 재공품 코드로 관리한다.
+
+기초자료는 이 표준 형상을 정의하는 동시에, 주문에 따라 **표준을 수정한 변형 형상**도 관리한다.
+
+### 1.3 핵심 원칙
+
+- 품목코드는 **고유키(Unique Key)** — 중복 불가
+- 날짜 정보 미포함 — LOT 번호와 명확히 구분
+- 코드만으로 분류(종류+재질 계열) 식별 가능
+- `BD-XX`까지 재공품과 공통, 그 뒤 구분자(`.` vs `-`)로 구분
+
+---
+
+## 2. 코드 형식
+
+### 2.1 기초자료 (1차 가공물 — 형상 정의)
+
+```
+BD-{분류코드} ← 해당 분류에 1건일 때
+BD-{분류코드}.{순번} ← 해당 분류에 다수일 때
+```
+
+| 세그먼트 | 설명 | 예시 |
+|----------|------|------|
+| `BD` | Bending 접두사 (고정) | `BD` |
+| 분류코드 | 부품 종류+재질 계열 (2자리) | `RS`, `CP`, `CX` |
+| `.nnn` | 변형 번호 (점 구분자, 3자리, 001~999) | `.001`, `.027` |
+
+**예시:**
+```
+BD-CL 케이스 린텔 — 표준 형상 (재공품 BD-CL-30 등의 기준)
+BD-CL.001 케이스 린텔 — 변형 1번 (주문 수정)
+BD-CL.002 케이스 린텔 — 변형 2번
+BD-RS 가이드레일 SUS 마감재 — 표준 형상
+BD-RS.001 가이드레일 SUS 마감재 — 변형 1번
+```
+
+> **표준 형상** (`BD-XX`): 재공품으로 미리 생산하는 가장 많이 나가는 정형화된 부품
+> **변형** (`BD-XX.nnn`): 주문에 따라 표준 형상을 수정한 절곡도 (최대 999종)
+
+### 2.2 재공품 (WIP 품목 — 길이 포함)
+
+```
+BD-{분류코드}-{길이코드}
+```
+
+| 세그먼트 | 설명 | 예시 |
+|----------|------|------|
+| `BD` | Bending 접두사 (기초자료와 공통) | `BD` |
+| 분류코드 | 기초자료와 동일 (2자리) | `CL`, `RS` |
+| `-길이코드` | 원자재 길이 (하이픈 구분자) | `-30` (3000mm) |
+
+**예시:**
+```
+BD-CL-30 케이스 린텔 3000mm
+BD-RS-24 가이드레일 SUS 마감재 2438mm
+```
+
+### 2.3 기초자료 vs 재공품 코드 구분
+
+| 구분 | 구분자 | 형식 | 의미 |
+|------|:------:|------|------|
+| 기초자료 (표준) | 없음 | `BD-CL` | 표준 절곡 형상 (길이 무관) |
+| 기초자료 (변형) | 점 (`.`) | `BD-CL.001` | 주문 수정 형상 (최대 999종) |
+| 재공품 | 하이픈 (`-`) | `BD-CL-30` | 표준 형상 + 길이 (고유 품목) |
+
+> **핵심**: `BD-CL`까지 공통, 그 뒤 구분자가 다름.
+> - 기초자료는 **절곡 형상만** 정의 (길이 정보 없음)
+> - 재공품은 **형상 + 길이**로 고유 품목 (변종 시 새 코드 부여)
+> - 기초자료 `BD-CL` = 재공품 `BD-CL-30`, `BD-CL-24` 등의 **기준 형상**
+
+---
+
+## 3. 분류코드 정의
+
+### 3.1 가이드레일 부품 (R 계열)
+
+| 코드 | 부품명 | 주재질 | 현재 건수 |
+|------|--------|--------|:---------:|
+| `RS` | SUS 마감재 | SUS 1.2T | 25 |
+| `RM` | 본체/보강 | EGI 1.55T | 12 |
+| `RC` | C형 | EGI 1.55T | 11 |
+| `RD` | D형 | EGI 1.55T | 7 |
+| `RE` | 측면 마감재 | EGI/SUS | 11 |
+| `RT` | 절단판 | — | 3 |
+| `RH` | 뒷보강 | — | 1 |
+| `RN` | 비인정 | — | 1 |
+
+### 3.2 케이스 부품 (C 계열)
+
+| 코드 | 부품명 | 주재질 | 현재 건수 |
+|------|--------|--------|:---------:|
+| `CP` | 밑면판/점검구 | EGI 1.55T | 44 |
+| `CF` | 전면판 | EGI 1.55T | 34 |
+| `CB` | 후면 코너/후면부 | EGI 1.55T | 28 |
+| `CL` | 린텔 | EGI 1.55T | 27 |
+| `CX` | 상부 덮개 | EGI 1.55T | 27 |
+
+### 3.3 하단마감재 부품 (B/T 계열)
+
+| 코드 | 부품명 | 주재질 | 현재 건수 |
+|------|--------|--------|:---------:|
+| `BS` | 하장바 (SUS) | SUS 1.5T | 4 |
+| `BE` | 하장바 (EGI) | EGI 1.55T | 1 |
+| `BH` | 보강평철 | EGI 1.15T | 1 |
+| `TS` | 철재 하장바 (SUS) | SUS | 3 |
+| `TE` | 철재 하장바 (EGI) | EGI | 2 |
+
+### 3.4 기타
+
+| 코드 | 부품명 | 현재 건수 |
+|------|--------|:---------:|
+| `XE` | 마구리 | 12 |
+| `LE` | L-BAR | 2 |
+| `ZP` | 특수 밑면/점검구 | 5 |
+| `ZF` | 특수 전면판 | 3 |
+| `ZB` | 특수 후면 | 1 |
+
+---
+
+## 4. 2계층 구조
+
+절곡품은 **원자재 → 1차 가공물 → 2차 가공물** 2계층 구조를 따른다.
+
+```
+원자재 (SUS 1.2T, EGI 1.55T 등)
+ ↓ 절곡 가공 (바라시)
+1차 가공물: bending_items (기초자료) — BD-XX-nn 코드
+ ↓ 조립
+2차 가공물: bending_models (가이드레일/케이스/하단마감재) — GR-nn 코드
+```
+
+### 4.1 테이블 관계
+
+| 테이블 | 역할 | 코드 형식 | 건수 (2026-03) |
+|--------|------|----------|:--------------:|
+| `bending_items` | 1차 가공물 (부품) | `BD-XX-nn` | 265 |
+| `bending_models` | 2차 가공물 (조립품) | `GR-nn` 등 | 21+ |
+
+### 4.2 매핑 방식
+
+`bending_models.components` JSON 배열에서 `sam_item_id`로 `bending_items.id`를 참조한다.
+
+```json
+// bending_models.components 예시 (가이드레일 KSS01)
+[
+ {"orderNumber": 1, "sam_item_id": 1968, "itemName": "1번(마감제)", "material": "SUS 1.2T", "quantity": 2},
+ {"orderNumber": 2, "sam_item_id": 1976, "itemName": "2번(본체)", "material": "EGI 1.55T"},
+ {"orderNumber": 3, "sam_item_id": 1966, "itemName": "3번(벽면형-C)", "material": "EGI 1.55T"},
+ {"orderNumber": 4, "sam_item_id": 1965, "itemName": "4번(벽면형-D)", "material": "EGI 1.55T"}
+]
+```
+
+---
+
+## 5. 코드와 LOT 번호의 차이
+
+| 항목 | 기초자료 코드 | 재공품 코드 | LOT 번호 |
+|------|-------------|------------|----------|
+| 목적 | 절곡 형상 정의 | 품목 식별 (형상+길이) | 생산 이력 추적 |
+| 길이 정보 | 없음 | 포함 | 포함 |
+| 날짜 정보 | 없음 | 없음 | 포함 |
+| 구분자 | 점 (`.`) | 하이픈 (`-`) | — |
+| 예시 | `BD-CL.03` | `BD-CL-30` | `CL6318-30` |
+| 변경 여부 | 변경 불가 | 변경 불가 (고유) | 생산 시 생성 |
+
+---
+
+## 6. 신규 부품 등록 시 코드 부여 규칙
+
+API가 분류코드(`BD-XX`)를 받으면 자동으로 채번한다:
+
+1. **해당 분류 첫 등록**: `BD-XX` — 표준 형상
+2. **변형 등록**: `BD-XX.001` — 표준 대비 수정된 형상
+3. **이후 변형**: `BD-XX.002`, `BD-XX.003`, ... (마지막 +1 자동)
+4. **최대 999종**: `BD-XX.999`까지
+
+```
+표준 등록: BD-CL (표준 형상, 재공품 기준)
+변형 1: BD-CL.001 (주문 수정)
+변형 2: BD-CL.002 (또 다른 수정)
+```
+
+> 표준 형상 `BD-XX`는 이미 존재할 때, 변형만 추가 등록된다. 표준 자체가 변경되지 않는다.
+
+---
+
+## 7. 마이그레이션 이력
+
+| 날짜 | 작업 | 비고 |
+|------|------|------|
+| 2026-03-21 (1차) | 기존 코드(날짜 포함) → `BD-XX-nn` 형식 변환 | 265건 |
+| 2026-03-21 (2차) | `BD-XX-nn` → `BD-XX.nn` 형식 변환 | 재공품 코드와 구분자 분리 |
+| 2026-03-21 (3차) | `BD-XX.nn` → `BD-XX.nnn` 3자리 변형번호 | 999종 변형 수용 |
+
+### 이전 코드 형식 (폐기)
+
+```
+❌ CX250722-06 (접두사 + 날짜 + 순번) — LOT 번호와 혼동
+❌ BD-CX-06 (하이픈 구분 2자리) — 재공품 BD-CX-30과 혼동
+❌ BD-CX.06 (점 구분 2자리) — 변형 수용량 99종 한계
+✅ BD-CX.006 (점 구분 3자리) — 변형 999종, 재공품과 구분 명확
+✅ BD-CX (표준 형상) — 재공품 BD-CX-길이의 기준
+```
+
+---
+
+## 관련 문서
+
+- [changes/20260321_bending_api_internal_url_fix.md](../changes/20260321_bending_api_internal_url_fix.md) — 절곡품 API 연동 수정
+- `api/app/Models/BendingItem.php` — 1차 가공물 모델
+- `api/app/Models/BendingModel.php` — 2차 가공물 모델
+
+---
+
+**최종 업데이트**: 2026-03-21