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.
This commit is contained in:
337
docs/smart-excel-upload.md
Normal file
337
docs/smart-excel-upload.md
Normal file
@@ -0,0 +1,337 @@
|
||||
# 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)으로 범위 조절 가능
|
||||
Reference in New Issue
Block a user