# [맥락노트] 품번 수동 접두어 채번 - 접두어별 독립 순번 생성

> 관련 문서: [계획서](./MPN[계획]-품번-수동접두어채번.md) | [체크리스트](./MPN[체크]-품번-수동접두어채번.md)

---

## 왜 이 작업을 하는가

- 기준정보 - 품목정보 등록 모달에서 품번 인풋에 사용자가 값을 입력해도 무시되고 "BULK1"로 저장됨
- 서로 다른 접두어("ㅁㅁㅁ", "ㅇㅇㅇ")를 입력해도 전부 같은 시퀀스 카운터를 공유함
- 카테고리 미선택 시 `--제발-015-003` 처럼 연속 구분자가 발생함
- 사용자 입력이 반영되고, 접두어별로 독립된 순번이 부여되어야 함

---

## 핵심 결정 사항과 근거

### 1. 수동 값 추출을 buildPrefixKey 전으로 이동

- **결정**: `allocateCode` 내부에서 수동 값 추출 → buildPrefixKey 순서로 변경
- **근거**: 기존에는 buildPrefixKey(L1306)가 먼저 실행된 후 수동 값 추출(L1332)이 진행됨. 수동 값이 prefix_key에 포함되려면 추출이 먼저 되어야 함
- **대안 검토**: buildPrefixKey 내부에서 직접 추출 → 기각 (역할 분리 위반, previewCode 호출에도 영향)

### 2. buildPrefixKey에 수동 파트 값 포함

- **결정**: `manualValues` optional 파라미터 추가, 전달되면 prefix_key에 포함
- **근거**: 기존 `continue`(L85-87)로 수동 파트가 prefix_key에서 제외되어 모든 접두어가 같은 시퀀스를 공유함
- **하위호환**: optional 파라미터이므로 `previewCode`(L1091) 등 기존 호출부는 영향 없음

### 3. 템플릿 파싱 실패 시 userInputCode 전체를 수동 값으로 사용

- **결정**: 수동 파트가 1개이고 템플릿 기반 추출이 실패하면 `userInputCode` 전체를 수동 값으로 사용
- **근거**: 사용자가 "ㅁㅁㅁ"처럼 접두어 부분만 입력하면 템플릿 "카테고리값-____-XXX"와 불일치. `startsWith` 조건 실패로 추출이 안 됨. 이 경우 입력 전체가 수동 값임
- **제한**: 수동 파트가 2개 이상이면 이 폴백 불가 (어디서 분리할지 알 수 없음)

### 4. 코드 조합에서 manualConfig.value 폴백 제거

- **결정**: `extractedManualValues[i] || part.manualConfig?.value || ""` → `extractedManualValues[i] || ""`
- **근거**: `manualConfig.value`는 UI에서 입력/편집할 수 없는 유령 필드. `ManualConfigPanel.tsx`에 `value` 입력란이 없어 DB에 한번 저장되면 스프레드 연산자로 계속 보존됨
- **이중 조치**: 코드에서 폴백 제거 + DB 마이그레이션으로 기존 "BULK1" 값 정리

### 5. DB 마이그레이션은 BULK1만 타겟팅

- **결정**: `manual_config->>'value' = 'BULK1'` 조건으로 한정
- **근거**: 다른 value가 의도적으로 설정된 경우가 있을 수 있음. 확인된 문제("BULK1")만 정리하여 부작용 방지
- **대안 검토**: 전체 `manual_config.value` 키 제거 → 보류 (운영 판단 필요)

### 6. extractManualValuesFromInput 헬퍼 분리

- **결정**: 기존 `allocateCode` 내부의 수동 값 추출 로직(L1332-1442)을 별도 private 메서드로 추출
- **근거**: 추출 로직이 약 110줄로 `allocateCode`가 과도하게 비대함. 헬퍼로 분리하면 순서 변경도 자연스러움
- **원칙**: 로직 자체는 변경 없음, 위치만 이동 (구조적 변경과 행위적 변경 분리)

### 7. 프론트엔드 변경 불필요

- **결정**: 프론트엔드 코드 수정 없음
- **근거**: `_numberingRuleId`가 사용자 입력 시에도 유지되고 있음 확인. `buttonActions.ts`가 정상적으로 `allocateCode`를 호출함. 문제는 백엔드 로직에만 있음

### 8. joinPartsWithSeparators 연속 구분자 방지

- **결정**: 빈 파트 뒤에 이미 같은 구분자가 있으면 중복 추가하지 않음
- **근거**: 카테고리가 비면 파트 값 `""` + 구분자 `-`가 반복되어 `--` 발생. 구분자 구조(`-ㅁㅁㅁ-001`)는 유지하되 연속(`--`)만 방지
- **조건**: `if (val || !result.endsWith(sep))` — 값이 있으면 항상 추가, 값이 없으면 이미 같은 구분자로 끝나면 스킵

### 9. 템플릿 카테고리/참조 플레이스홀더를 실제값으로 변경

- **결정**: `extractManualValuesFromInput` 내부의 카테고리/참조 빈 값 반환을 `"CATEGORY"`/`"REF"` → `""`로 변경
- **근거**: 실제 코드 생성에서 빈 카테고리는 `""`인데 템플릿에서 `"CATEGORY"`를 쓰면 구조 불일치로 추출 실패. 로그로 확인: `userInputCode=-제발-015, previewTemplate=CATEGORY-____-XXX, extractedManualValues=[]`
- **카테고리 있을 때**: `catMapping2?.format` 반환은 수정 전후 동일하여 영향 없음

---

## 관련 파일 위치

| 구분 | 파일 경로 | 설명 |
|------|----------|------|
| 수정 대상 | `backend-node/src/services/numberingRuleService.ts` | joinPartsWithSeparators(L36), buildPrefixKey(L75), extractManualValuesFromInput(신규), allocateCode(L1296) |
| 신규 생성 | `db/migrations/1053_remove_bulk1_manual_config_value.sql` | BULK1 유령 값 정리 마이그레이션 |
| 변경 없음 | `frontend/components/screen/widgets/TextInputComponent.tsx` | _numberingRuleId 유지 확인 완료 |
| 변경 없음 | `frontend/lib/registry/components/numbering-rule/config.ts` | 채번 설정 레지스트리 |
| 변경 없음 | `frontend/components/screen/config-panels/NumberConfigPanel.tsx` | 채번 규칙 설정 패널 |
| 참고 | `backend-node/src/controllers/numberingRuleController.ts` | allocateNumberingCode 컨트롤러 |

---

## 기술 참고

### allocateCode 실행 순서 (변경 전 → 후)

```
변경 전: buildPrefixKey(L1306) → 시퀀스 할당 → 수동 값 추출(L1332) → 코드 조합
변경 후: 수동 값 추출 → buildPrefixKey(수동 값 포함) → 시퀀스 할당 → 코드 조합
```

### prefix_key 구성 (변경 전 → 후)

```
변경 전: "카테고리값"        (수동 파트 무시, 모든 접두어가 같은 키)
변경 후: "카테고리값|ㅁㅁㅁ"  (수동 파트 포함, 접두어별 독립 키)
```

### 폴백 체인 (변경 전 → 후)

```
변경 전: extractedManualValues[i] || manualConfig.value || ""
변경 후: extractedManualValues[i] || ""
```

### joinPartsWithSeparators 연속 구분자 방지 (변경 전 → 후)

```
변경 전: "" + "-" + "" + "-" + "ㅁㅁㅁ" → "--ㅁㅁㅁ"
변경 후: "" + "-" (이미 "-"로 끝남, 스킵) + "ㅁㅁㅁ" → "-ㅁㅁㅁ"
```

### 템플릿 정합성 (변경 전 → 후)

```
변경 전: 카테고리 비었을 때 템플릿 = "CATEGORY-____-XXX" / 입력 = "-제발-015" → 불일치 → 추출 실패
변경 후: 카테고리 비었을 때 템플릿 = "-____-XXX"        / 입력 = "-제발-015" → 일치 → 추출 성공
```

### 10. 실시간 순번 미리보기 구현 방식

- **결정**: V2Input에서 `manualInputValue` 변경 시 디바운스(300ms)로 preview API를 재호출하여 suffix(순번)를 갱신
- **근거**: 기존 preview API는 `manualInputValue` 없이 호출되어 모든 접두어가 같은 기본 순번을 표시함. 접두어별 정확한 순번을 보여주려면 preview 시점에도 수동 값을 전달하여 해당 prefix_key의 시퀀스를 조회해야 함
- **대안 검토**: 프론트엔드에서 카운트 API를 별도 호출 → 기각 (기존 `previewCode` 흐름 재사용이 프로젝트 관행에 부합)
- **디바운스 300ms**: 사용자 타이핑 중 과도한 API 호출 방지. 프로젝트 기존 패턴(검색 디바운스 등)과 동일

### 11. previewCode에 manualInputValue 전달

- **결정**: `previewCode` 시그니처에 `manualInputValue?: string` 추가, `buildPrefixKey`에 `[manualInputValue]`로 전달
- **근거**: `buildPrefixKey`가 이미 `manualValues` optional 파라미터를 지원하므로 자연스럽게 확장 가능. 순번 조회 시 접두어별 독립 시퀀스를 정확히 반영함
- **하위호환**: optional 파라미터이므로 기존 호출(`formData`만 전달)에 영향 없음

### 12. 초기 상태에서 레거시 시퀀스 조회 방지

- **결정**: `previewCode`에서 수동 파트가 있는데 `manualInputValue`가 없으면 시퀀스 조회를 건너뛰고 `currentSeq = 0` 사용
- **근거**: 수정 전에는 모든 할당이 수동 파트 없는 공용 prefix_key를 사용했으므로 레거시 시퀀스가 누적되어 있음(예: 16). 모달 초기 상태에서 이 공용 키를 조회하면 `-016`이 표시됨. 아직 어떤 접두어인지 모르는 상태이므로 `startFrom` 기본값을 보여주는 것이 정확함
- **`currentSeq = 0` + `startFrom`**: `nextSequence = 0 + startFrom(5) = 5` → `-005` 표시. 사용자가 입력하면 디바운스 preview가 해당 접두어의 실제 시퀀스를 조회

### 13. 카테고리 변경 시 수동 입력값 포함하여 순번 재조회

- **결정**: 초기 useEffect(카테고리 변경 트리거)에서 `previewNumberingCode` 호출 시 현재 `manualInputValue`도 함께 전달
- **근거**: 카테고리를 바꾸거나 삭제하면 prefix_key가 달라지므로 순번도 달라져야 함. 기존에는 입력값 변경과 카테고리 변경이 별도 트리거여서 카테고리 변경 시 수동 값이 누락됨
- **빈 입력값 처리**: `manualInputValue || undefined`로 처리하여 빈 문자열일 때는 기존처럼 `skipSequenceLookup` 작동

### 14. 카테고리 해석 로직 resolveCategoryFormat 헬퍼 통합

- **결정**: `previewCode`, `allocateCode`, `extractManualValuesFromInput` 3곳에 복붙된 카테고리 매핑 해석 로직을 `resolveCategoryFormat` private 메서드로 추출
- **근거**: 동일 로직 약 50줄이 3곳에 복사되어 있었음 (변수명만 pool2/ct2/cc2 등으로 다름). 한 곳을 수정하면 나머지도 동일하게 수정해야 하는 유지보수 위험
- **원칙**: 구조적 변경만 수행 (로직 변경 없음)

### BULK1이 DB에 남아있는 이유

```
ManualConfigPanel.tsx: placeholder 입력란만 존재 (value 입력란 없음)
플레이스홀더 수정 시: { ...existingConfig, placeholder: newValue }
→ 기존 config에 value: "BULK1"이 있으면 스프레드로 계속 보존됨
→ UI에서 제거 불가능한 유령 값
```
