- 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.
338 lines
13 KiB
Markdown
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)으로 범위 조절 가능
|