Files
vexplor_dev/docs/smart-excel-upload.md
kjs dffa16f3e5 feat: Add Smart Excel Upload functionality for item inspection
- Introduced a new SmartExcelUploadModal component to facilitate bulk item inspection uploads via Excel.
- Implemented logic for downloading templates, validating uploaded files, and parsing data for inspection criteria.
- Enhanced the item inspection page to support dynamic loading of item process mappings and reference data for improved user experience.
- Added necessary types and utility functions for template generation and parsing, ensuring robust handling of Excel data.
- These changes aim to streamline the item inspection process and improve data management across multiple company implementations.
2026-04-15 14:23:44 +09:00

338 lines
13 KiB
Markdown

# SmartExcelUpload
설정(Config) 기반 엑셀 업로드 공통 모듈. Config 객체와 데이터를 넘기면 템플릿 생성, 업로드, 검증, 미리보기까지 자동 처리된다. 화면별로 Config 정의 + 데이터 조회 + 저장 콜백만 작성하면 어디든 적용 가능.
## 기존 ExcelUploadModal과의 차이
기존 `ExcelUploadModal`은 단일 테이블의 단순 데이터를 일괄 업로드하는 용도(거래처 목록 등).
`SmartExcelUpload`는 아래와 같이 **기존 컴포넌트로 처리하기 어려운 복잡한 구조**에서 사용한다.
| 상황 | 예시 |
|------|------|
| 셀 간 연동이 필요할 때 | A 컬럼 선택 → B 컬럼 자동 입력 |
| 선택한 값에 따라 드롭다운이 달라질 때 | 마스터 데이터별로 선택 가능한 하위 항목이 다름 |
| 조건에 따라 입력 가능/불가가 바뀔 때 | 특정 유형일 때만 특정 컬럼 입력 가능 |
| 멀티 시트로 유형을 구분할 때 | 유형별 시트 분리 |
| 참조 데이터 기반 자동 검증이 필요할 때 | 기준 데이터 변경 시 템플릿 재다운로드 유도 (해시 검증) |
| 1:N 관계의 데이터를 등록할 때 | 마스터 1개에 디테일 N개, 유형 다수 |
단순 일괄 등록은 기존 `ExcelUploadModal`을 쓰면 된다.
> **참고**: 셀 간 연동 수식(VLOOKUP, INDEX/MATCH 등)은 화면마다 다르다. SmartExcelUpload가 제공하는 것은 수식 자체가 아니라 **수식을 적용하는 메커니즘**(autoFill, customFormula, INDIRECT 등)이다. 어떤 컬럼에 어떤 수식이 들어갈지는 Config에서 화면별로 정의한다.
## 파일 구조
```
SmartExcelUpload/
index.ts # export
types.ts # Config 인터페이스, 검증 타입
templateGenerator.ts # ExcelJS 기반 엑셀 템플릿 생성
templateParser.ts # 업로드 파일 파싱 + 해시 검증 + 데이터 검증
SmartExcelUploadModal.tsx # 모달 UI (다운로드 → 업로드 → 검증 → 미리보기)
```
## 핵심 개념
### Config 기반 동작
모든 동작은 `SmartExcelUploadConfig`로 결정된다.
```typescript
const config: SmartExcelUploadConfig = {
templateName: "파일명",
sheets: [...], // 시트 정의 (단일/멀티)
referenceSheet: {...}, // 참조 데이터 숨김시트 (선택)
conditionalRules: [...], // 조건부 검증 규칙 (선택)
indirectOptions: {...}, // ACC_ 동적 드롭다운 옵션 정의 (선택)
};
```
### 엑셀 템플릿 구조
```
[시트: 안내] ← Config 기반 자동 생성 (컬럼 설명, 입력 규칙, 사용법)
[시트: 데이터1] ← 사용자가 작성하는 시트 (여러 개 가능)
[시트: 데이터2]
[숨김: 참조시트] ← VLOOKUP 참조 데이터 (referenceSheet 설정 시)
[숨김: _품목공정] ← INDIRECT 이름 범위 (itemProcessMappings 설정 시)
[숨김: _품목목록] ← 마스터 드롭다운 소스 (itemProcessMappings 설정 시)
[숨김: _합격기준옵션] ← INDIRECT ACC_ 이름 범위 (indirectOptions 설정 시)
[숨김: _meta] ← 버전 해시, 생성일
```
숨김시트들은 해당 기능을 사용하는 Config일 때만 생성된다.
### 버전 해시 검증
- 템플릿 생성 시: 참조 데이터 + 드롭다운 옵션 + 매핑 데이터 → 해시 생성 → `_meta` 시트에 저장
- 업로드 시: 현재 DB 데이터로 해시 재생성 → 일치 여부 확인
- 불일치 시: "기준 데이터가 변경되었습니다. 최신 템플릿을 다시 다운로드해주세요" 경고
### 안내시트 자동 생성
Config의 컬럼 정의를 기반으로 안내시트가 자동 생성된다.
- 컬럼별 설명 (필수 여부, 자동 입력 여부, 드롭다운 유형 등)
- 조건부 규칙 설명 (conditionalRules 기반)
- 잠금 셀 목록 (autoFill/readOnly/customFormula 컬럼)
- 사용 방법 단계
---
## 컬럼 타입
### 기본 타입
| type | 설명 |
|------|------|
| `text` | 자유 텍스트 |
| `number` | 숫자 (천단위 서식 자동 적용) |
| `date` | 날짜 |
| `dropdown` | 드롭다운 선택 |
### 드롭다운 source 유형
| source | 설명 | 예시 |
|--------|------|------|
| `custom` | 고정 값 목록 | `values: ["Y", "N"]` |
| `category` | 카테고리 테이블 조회 | `tableName: "...", columnName: "..."` |
| `indirect` | 다른 셀 값에 따라 동적 변경 | `indirectKeyColumn: "...", indirectPrefix: "P_"` |
### 컬럼 속성
| 속성 | 설명 |
|------|------|
| `required` | 필수 여부 — 업로드 검증 시 빈 값 체크 |
| `readOnly` | 읽기전용 — 셀 잠금 |
| `autoFill` | 참조시트에서 VLOOKUP 자동 입력 — 셀 잠금, 회색 배경 |
| `customFormula` | 커스텀 엑셀 수식 — `{col:key}` 플레이스홀더로 같은 행 참조 |
| `enableWhen` | 조건부 활성화 — 참조시트에서 직접 조회하여 판단 (VLOOKUP 미계산 문제 없음) |
| `disableWhen` | 조건부 비활성화 — 특정 조건일 때 입력 차단 |
| `width` | 컬럼 너비 (기본 18) |
---
## 주요 기능
### 1. VLOOKUP 자동 입력 (autoFill)
참조시트의 데이터를 기반으로 다른 셀 값에 연동되어 자동 입력된다.
```typescript
{
key: "detail_column",
label: "상세정보",
readOnly: true,
autoFill: {
lookupColumn: "master_key", // 같은 행의 이 컬럼 값을 기준으로
referenceColumn: "detail", // 참조시트에서 이 컬럼 값을 가져옴
}
}
```
### 2. 커스텀 수식 (customFormula)
`{col:key}` 플레이스홀더를 사용하여 같은 행의 다른 컬럼을 참조하는 수식을 정의한다. 절대참조(`$`)는 행 치환에서 자동 보호된다.
```typescript
{
key: "code_column",
readOnly: true,
customFormula: `IFERROR(INDEX('_시트명'!$A$1:$A$9999,MATCH({col:name_column},'_시트명'!$B$1:$B$9999,0)),"")`
}
```
### 3. INDIRECT 동적 드롭다운
다른 셀 값에 따라 드롭다운 옵션이 동적으로 변경된다.
**P_ prefix (이름 범위 직접 참조):**
```typescript
{
key: "sub_item",
type: "dropdown",
dropdown: {
source: "indirect",
indirectKeyColumn: "master_code", // 이 컬럼 값을 기준으로
indirectPrefix: "P_", // P_{값} 이름 범위 참조
}
}
```
**ACC_ prefix (MATCH 인덱스 기반 참조):**
```typescript
{
key: "criteria_value",
type: "dropdown",
dropdown: {
source: "indirect",
indirectKeyColumn: "standard_key",
indirectPrefix: "ACC_", // ACC_{인덱스} — MATCH로 인덱스 조회
}
}
```
ACC_ prefix 사용 시 Config에 `indirectOptions` 설정 필요:
```typescript
indirectOptions: {
conditionColumn: "condition_type", // 참조시트에서 조건 판단할 컬럼
optionsByCondition: { "타입A": ["O", "X"] }, // 조건값별 고정 옵션
selectionOptionsColumn: "options_column", // 동적 옵션 (콤마 구분 문자열)
}
```
### 4. 조건부 활성화/비활성화
다른 컬럼 값에 따라 셀 입력 가능 여부가 결정된다. 참조시트에서 직접 조회하는 방식이라 VLOOKUP 미계산 문제가 없다.
```typescript
{ key: "value_a", type: "number", enableWhen: { column: "condition_col", equals: "특정값" } }
```
### 5. 조건부 검증 규칙
업로드 시 특정 조건에 따라 필수/무시 컬럼이 달라진다. autoFill 컬럼의 값도 참조데이터에서 직접 조회하여 조건 판단.
```typescript
conditionalRules: [
{
when: { column: "condition_col", equals: "타입A" },
require: ["required_col"], // 필수
ignore: ["optional_col"], // 무시
},
]
```
### 6. ItemProcessMapping (마스터-디테일 매핑)
마스터 항목별로 선택 가능한 하위 항목이 다를 때 사용. 벌크 API로 전체 데이터를 한 번에 조회하여 INDIRECT 이름 범위로 등록.
```typescript
itemProcessMappings: [
{ itemCode: "M-001", itemName: "마스터A", processes: [{ code: "S01", name: "하위1" }] },
{ itemCode: "M-002", itemName: "마스터B", processes: [{ code: "S02", name: "하위2" }, { code: "S03", name: "하위3" }] },
]
```
업로드 검증 시 마스터에 맞지 않는 하위 항목은 자동으로 에러 처리된다.
---
## 성능 구조
| 항목 | 방식 | 범위 |
|------|------|------|
| 드롭다운/validation | **컬럼 범위 1회** 설정 | 65,000행 |
| 수식 (VLOOKUP, customFormula) | 행별 개별 삽입 | 2,000행 (FORMULA_END) |
| 셀 보호 (잠금/해제) | 행별 개별 설정 | 2,000행 |
| 셀 스타일 (배경, 테두리) | 행별 개별 설정 | 2,000행 |
| 데이터 캐싱 | 최초 로드 후 재사용 | 페이지 세션 |
드롭다운은 범위 단위라 행 수 제한 없음. 수식/스타일은 `FORMULA_END` 상수로 조절 가능.
---
## 사용법
### 1. 기본 사용 (단순 드롭다운만)
```tsx
import { SmartExcelUploadModal } from "@/components/common/SmartExcelUpload";
import type { SmartExcelUploadConfig, ParsedSheetData } from "@/components/common/SmartExcelUpload";
const config: SmartExcelUploadConfig = {
templateName: "거래처",
sheets: [{
name: "거래처",
columns: [
{ key: "name", label: "거래처명", required: true, type: "text", width: 24 },
{ key: "division", label: "구분", type: "dropdown",
dropdown: { source: "custom", values: ["매출처", "매입처"] } },
],
}],
};
const handleUpload = async (data: ParsedSheetData[]) => {
for (const sheet of data) {
for (const row of sheet.rows) await api.create(row);
}
};
<SmartExcelUploadModal
open={open}
onOpenChange={setOpen}
config={config}
dropdownOptions={{ division: ["매출처", "매입처"] }}
onUpload={handleUpload}
/>
```
### 2. 고급 사용 (참조시트 + INDIRECT + 조건부 검증)
```tsx
<SmartExcelUploadModal
open={open}
onOpenChange={setOpen}
config={config} // 시트/컬럼/규칙 정의
referenceData={refData} // 참조시트 데이터
dropdownOptions={dropdownOpts} // 드롭다운 옵션
itemProcessMappings={processMappings} // 마스터-디테일 매핑
labelToCodeMap={labelMap} // 라벨→코드 변환
onUpload={handleUpload} // 저장 콜백
dataLoading={loading} // 로딩 상태
loadProgress={{ loaded: 100, total: 500 }} // 진행률
/>
```
### 3. Props
| prop | 타입 | 필수 | 설명 |
|------|------|------|------|
| `open` | boolean | O | 모달 열림 상태 |
| `onOpenChange` | (open: boolean) => void | O | 모달 상태 변경 |
| `config` | SmartExcelUploadConfig | O | 전체 설정 |
| `referenceData` | Record[] | | 참조시트 데이터 |
| `dropdownOptions` | Record<string, string[]> | | 드롭다운 옵션 (키: `시트명:컬럼key` 또는 `컬럼key`) |
| `itemProcessMappings` | ItemProcessMapping[] | | 마스터-디테일 매핑 데이터 |
| `labelToCodeMap` | Record<string, Record<string, string>> | | 라벨→코드 변환 |
| `extraMeta` | Record<string, string> | | _meta 시트에 추가할 정보 |
| `onUpload` | (data: ParsedSheetData[]) => Promise<void> | O | 업로드 완료 콜백 |
| `subtitle` | string | | 제목 아래 부가 설명 |
| `dataLoading` | boolean | | 외부 데이터 로딩 중 표시 |
| `loadProgress` | { loaded, total } | | 로딩 진행률 표시 |
### 4. 벌크 조회 API (백엔드)
마스터별 하위 항목을 한 번에 조회하는 API. 5,000건 단위 청크 분할로 대량 데이터 대응.
```
POST /work-instruction/routing-versions-bulk
Body: { itemCodes: ["M-001", "M-002", ...] }
Response: { success: true, data: { "M-001": [{ code, name }], "M-002": [...] } }
```
---
## 검증 흐름
```
업로드 → 메타 해시 검증 → 시트별 파싱 (사용자 입력 컬럼만 빈 행 체크)
→ 필수값 검증 (conditionalRules 적용, autoFill 값은 참조데이터에서 직접 조회)
→ 드롭다운 유효성 검증
→ INDIRECT 매핑 검증 (마스터에 맞지 않는 하위 항목 에러)
→ 에러 있으면 에러 리포트 / 없으면 미리보기 → 저장
```
---
## 확장 시 참고
- 새 화면에 적용할 때: Config 정의 + 데이터 조회 + 저장 콜백만 작성
- 단일 시트 / 멀티 시트 모두 지원 (`sheets` 배열 크기로 결정)
- 참조시트 필요 없으면 `referenceSheet` 생략 → 숨김시트 미생성
- 조건부 검증 필요 없으면 `conditionalRules` 생략 → 단순 필수값 체크만
- INDIRECT 필요 없으면 `itemProcessMappings` 생략 → 일반 드롭다운만
- ACC_ 동적 드롭다운 필요 없으면 `indirectOptions` 생략 → 해당 시트 미생성
- `FORMULA_END` (기본 2,000) / `VALIDATION_END` (기본 65,000)으로 범위 조절 가능