바이어 리스트 업로드 기능은 현재 두 개의 병렬 구현이 공존합니다. 하나(smart-import)는 모든 진입 동선이 연결된 현행 경로, 다른 하나(lead-import)는 화면 진입점이 사라진 고아 코드입니다. 본 문서는 두 구조와 공유 의존, 그리고 삭제 시 함정을 정리합니다.
같은 "바이어 업로드" 기능이 두 벌. 사용자가 실제 닿는 건 하나뿐.
현행 smart-import
/leads/add → /api/v1/smart-import/*
import_jobs.unresolvedData로 보존(복구 가능)고아 lead-import
/lead-import → /api/v1/admin/lead-import/*
/lead-import 흐름은 화면상 데드코드지만, 서비스 파일 일부(parseUploadedFile·bulkImportLeads·verifyImportEmails)는 현행 smart-import이 빌려 쓰므로 파일 통삭제 불가. 게다가 GDPR 동의 입증이 이 경로에만 있어 단순 청소가 아닌 제품/컴플라이언스 의사결정 사안.
/leads/add)3단계 마법사(그룹 → 업로드 → 매핑) + SSE 파이프라인.
| 파일 | 역할 |
|---|---|
pages/leads/AddBuyersPage.tsx | 오케스트레이터. group→upload→mapping state machine. URL의 groupId/wsId/mode/newGroup 파라미터 해석 |
add-buyers/StepGroupSelect.tsx | 기존 그룹 선택 / 새 그룹 생성 (발송 단위 결정) |
add-buyers/StepUpload.tsx | dropzone(50MB·확장자 검증) → useAnalyzeFile 호출. 템플릿 CSV/XLSX 다운로드 |
add-buyers/StepMapping.tsx | AI 매핑 표시·수정 → 제외 미리보기 → startImportPipeline(SSE 콜백) |
add-buyers/StepExclusion.tsx · ImportStatusDialog.tsx | 제외 행 확인 모달 / 진행·완료 표시 |
lib/api/services/smart-import.ts · hooks/smart-import.ts | analyzeFile, startPipeline(SSE), useUnresolvedRows 등 |
| 위치 | 역할 |
|---|---|
routes/smart-import.routes.ts | 4 endpoint: POST /analyze · POST /start(SSE) · GET /:jobId · GET /:jobId/unresolved |
services/smart-import.service.ts (868줄) | analyzeFileColumns · deduplicateRecords · runSmartImportPipelineLocked · mapRecordToLeadData |
utils/dedup-normalize.ts | email/url/회사명 정규화 (lead-import과 공유) |
lib/import-lock.ts · db/schema/import-jobs.ts | 워크스페이스 동시실행 락 / job·unresolved 저장 |
FE 액션과 BE 처리의 짝. SSE로 단계가 실시간 스트리밍됩니다.
// FE: /leads/add StepGroupSelect ─▶ StepUpload ─▶ StepMapping ──(SSE)──▶ 완료 모달 │ │ POST /analyze POST /start ▼ ▼ // BE: runSmartImportPipelineLocked() [워크스페이스당 1실행 락] 0 preflight 식별자(회사명/URL/이메일) ≥1 매핑 필수, 아니면 400 1 parse 파싱 + 빈 행 제거 → emptyRowsSkipped 2 dedup email → url → 회사명 매칭 → {unique, duplicates} └ duplicates 를 import_jobs.unresolvedData 에 저장 2.5 verify MillionVerifier — undeliverable·risky(≤20) 이메일 제거 3 import bulkImportLeads(unique) → 그룹 멤버 추가 3.5 group DB매칭 중복(matchedLeadId)을 그룹에 편입 ✓ complete imported / duplicates / addedToGroup / existingAddedToGroup ...
duplicates로 빼고 continue. ← 스페로네 이슈 발생 지점.bulkImportLeads로 생성. DB매칭 중복(matchedLeadId 보유)은 기존 리드를 그룹에 새로 편입./lead-import)서버에 마운트돼 있고 라우트도 등록돼 있지만, 화면에서 도달할 길이 없습니다.
navigate("/lead-import") · <Link to="/lead-import"> · 사이드바 메뉴 항목 0건. 존재하는 참조는 라우트 등록(dashboard-admin-routes.tsx:198), 브레드크럼 맵(DashboardLayout.tsx:607), 권한 맵(permission/constants.ts:60)뿐 — 모두 "도달 시 표시"용일 뿐 진입 동선이 아님.
| 레이어 | 파일 / 심볼 | 상태 |
|---|---|---|
| FE 페이지 | pages/lead-import/index.tsx, LeadImportAttestationSection.tsx | 고아 |
| FE API | lib/api/hooks/lead-import.ts, services/lead-import.ts | 고아 |
| FE 라우트 | dashboard-admin-routes.tsx:198, lazy-imports.ts:88 | 등록만 |
| BE 라우트 | routes/lead-import.routes.ts (upload·sheet-names·preview) | 고아 |
| BE 서비스(전용) | importLeadsStream, importLeadsBatch, analyzeLeadPreviewWithAI, getSheetNames, validateFileExtension, validateNonEmptyData, importSingleLead, checkDuplicate | lead-import 전용 |
* "전용" = lead-import.routes.ts 외부에서 호출 0건 (정적 검색 기준).
두 경로가 같은 dedup 로직을 공유. 16명 업로드가 4 리스트로 줄어든 메커니즘.
업로드 파일의 website_url이 비어 있어 URL 매칭이 꺼지고, 이메일은 전부 달라 통과 → 정규화 회사명이 판정축이 됨. normalizeCompanyName이 악센트·접미사(Inc/Ltd…)·부호를 제거하므로 표기가 달라도 같은 키로 수렴.
| 업로드 회사명 | 정규화 키 | 인원 | 결과 |
|---|---|---|---|
| Gap Inc. | gap | 8 | 기존 5월 리드와 충돌 → 0 생성, contact 미병합 |
| Banana Republic / Gap Inc. | banana republic / gap | 6 | 1 생성, 5 탈락 |
| Banana Republic Factory / Gap Inc. | banana republic factory / gap | 1 | 1 생성 |
| Gap Inc. / Banana Republic | gap inc / banana republic | 1 | 1 생성 |
→ 신규 3 + 기존 Gap 1 = 그룹 멤버 4 ("4개 리스트"). beta DB(스페로네_0430)에서 실제 재현 확인됨.
unresolvedData에 남겨 수동 복구 가능.
POST /lead-import/upload은 consentLegalBasis · consentSource · consentEvidenceText를 필수로 받음(routes:138-154). 반면 POST /smart-import/start body에는 consent 필드가 전무. 즉 현행 경로는 GDPR Art.6/7 동의 입증 없이 리드를 적재 중일 수 있음.
따라서 lead-import 제거는 두 갈래 중 하나의 결정을 강제합니다:
parseUploadedFile·bulkImportLeads·verifyImportEmails → services/import-helpers.ts로 이동, import 갱신.lead-import.routes.ts + 전용 서비스 함수 8종 삭제. app.ts의 .use(leadImportRoutes) 제거.