공급사 상품 등록 · v13 → v2 · 개발(ERD·기술) 문서
공급사 상품 등록 — 조건값 자동매칭 데이터 모델
공급사가 올린 상품을 MD 검수 전에 시스템이 기존 상품(그룹)과 자동 매칭하는 구조. 매칭 키를 조건값(상품명·용량/중량·보관·단위)으로 바꾸고, 완전일치=후보1개=자동확정을 앞단으로 뺍니다. 매칭이 잡히면 그룹 상품값을 자동으로 채워(승계), 공급사는 net공급가·유통사 상품명/코드만 입력합니다. 화면은 공급사 포털 ↔ 어드민(MD)로 권한 자동 분기. 회의(음성) 9피드백 + 실제 엑셀 양식 반영.
바로가기 개요ERDDDL매칭엑셀·단위이미지상태인덱스미정
product/manage와 공존) / 공급사 포털 › 상품 등록한눈 결론
product/manage는 그대로 공존.seller_product·product_group도 컬럼명·존재 여부는 가정이며, 배포 전 실제 스키마와 대조가 필요합니다. 임의 설계 지점은 본문에 🟠 미정 / 가정으로 표시했습니다.개요 — v13은 무엇을, v2는 왜 바꾸나
공급사는 자기 상품이 오더히어로에 이미 있는지 모른다. 매칭을 시스템이 대신한다.
공급사가 상품을 등록하지만, 그 상품이 이미 판매중인 상품인지 신규인지 공급사는 알 수 없습니다. v13은 히어로코드 단건 매칭으로 "같은 상품인지"를 확인하고, 매칭 결과를 자동통과/확인필요/신규/오류로 나눈 뒤 MD 검수(보류·반려·승인·수정후승인)로 넘겼습니다. 문제는 매칭 정확도와 분기 수였습니다.
진입 · 화면 분리 · 권한 분기
공급사 화면과 MD(어드민) 화면은 완전히 별개입니다. 데모용 토글이 아니라 로그인 권한(공급사 / MD)에 따라 서버가 자동으로 다른 화면을 내려줍니다. 프로토타입에서는 두 화면을 각각 보여주지만, 실제로는 권한 자동 분기입니다.
| 화면 | 권한 | 진입 경로 | 하는 일 |
|---|---|---|---|
| 공급사 포털 | 공급사(seller) | 공급사 포털 › 상품 등록 | 자기 상품 리스트 열람 · 신규 등록(단건/엑셀) · net공급가·유통사명/코드 입력 |
| 어드민(MD) | MD/내부 | 어드민 › 상품 관리 › 신규 공급사 상품 관리 | 업로드 요청 검수 · 후보군 선택 · 신규 확정 · 오류 반려 |
product/manage)는 그대로 유지하고, 신규 공급사 상품 관리(이 프로젝트)를 별도 메뉴로 추가합니다. 둘은 공존하며 기존 메뉴를 대체하지 않습니다.공급사 상품 리스트 — 기존 product/manage와 동일 구성
공급사가 로그인하면 해당 공급사의 모든 상품이 리스트로 노출됩니다(자기 상품만, WHERE seller_id = 로그인공급사). 상품 노출·구분(컬럼)은 기존 공급사 상품 관리 리스트와 동일하게 맞춥니다.
| # | 컬럼 | 출처 | 비고 |
|---|---|---|---|
| 1 | 그룹 ID | product_group.group_code | 그룹 식별 |
| 2 | 그룹핑 갯수 | 집계 | 그룹당 상품 수 |
| 3 | 상품 이미지 | 그룹 승계 | 매칭 시 그룹 이미지 |
| 4 | 그룹 상품명 | 그룹 | 양식 "그룹 상품명" |
| 5 | 상품명 | 단품 | 양식 "상품명" |
| 6 | 카테고리 | 그룹/분류 | 대·중·소분류 |
| 7 | 총 중량 | 파생 | 중량 × 수량 |
| 8 | 중량 | 양식 | 조건값 |
| 9 | 수량 | 양식(선택) | 입수 |
| 10 | 원산지 | 양식 | — |
| 11 | 제조사 | 양식(선택) | — |
| 12 | 브랜드 | 양식 | — |
| 13 | 특성 | 양식 | — |
| 14 | 등급 | 양식 | — |
| 15 | 크기 | 양식(선택) | — |
| 16 | 색상 | 양식(선택) | — |
동일 컬럼 구성은 기존 리스트 화면 기준(가정)이며, 실제 product/manage의 컬럼·집계 쿼리와 대조가 필요합니다(🟠 실측 없음).
데이터 모델 (ERD)
신규 3테이블(요청 · 항목 · 후보) + 기존 참조(그룹 · 상품 · 분류). 조건값 매칭 컬럼 강조(보라 hl).
product_group의 실제 컬럼 구성은 미확인(가정) — 조건값이 상품명 문자열에만 섞여 있다면 선행 정규화 마이그레이션이 필요합니다(§8·§9).DDL (Postgres 초안)
단위 enum · 그룹코드 FK · 조건값 컬럼. 🆕 = v2 신규 · 🟠 = 미정 기본값.
-- 단위 enum (양식 픽스 · 변환/분기 기준) 🟠 목록은 확정 필요 CREATE TYPE unit_kind AS ENUM ('ML','L','G','KG','EA'); -- 용량계(ML,L)·중량계(G,KG)·수량계(EA). L↔ML, KG↔G 만 상호변환 가능. CREATE TYPE storage_kind AS ENUM ('ROOM','COLD','FROZEN'); -- 상온/냉장/냉동 CREATE TYPE match_state AS ENUM ('auto','check','new','error'); -- 업로드 요청 배치 (엑셀 1파일 / 수기 1건) CREATE TABLE seller_product_request ( id BIGINT PRIMARY KEY, seller_id BIGINT NOT NULL, -- 공급사(히어로) source VARCHAR(8) NOT NULL DEFAULT 'excel', -- excel/manual file_name VARCHAR(255), total_rows INT DEFAULT 0, matched_rows INT DEFAULT 0, -- 🆕 진행률 = matched_rows/total_rows (비동기 배치) status VARCHAR(12) NOT NULL DEFAULT 'queued', -- 🆕 queued/matching/matched/confirmed (비동기 잡) created_by BIGINT, created_at TIMESTAMP DEFAULT now(), matched_at TIMESTAMP -- 🆕 배치 완료 시각(완료 알림 트리거) ); -- 요청 항목 (엑셀 1행 = 1건) · 조건값 + 매칭 결과 스냅샷 CREATE TABLE seller_product_request_item ( id BIGINT PRIMARY KEY, request_id BIGINT NOT NULL REFERENCES seller_product_request(id), row_no INT, -- 엑셀 원본 행번호(오류 표시용) -- ── 매칭 조건값(양식 필수) ────────────────────────────── group_prod_name VARCHAR(200), -- 🆕 그룹 상품명(예: '감귤(10~11과),1kg,국산') product_name VARCHAR(200) NOT NULL, -- 🆕 조건값: 상품명(예: 감귤) volume_value NUMERIC(12,3), -- 🆕 조건값: 중량 수치(예: 1) volume_unit unit_kind, -- 🆕 조건값: 단위(KG 등, 픽스). NULL=오류 storage_type storage_kind, -- 🆕 조건값: 보관(냉장/냉동/상온) pack_qty INT, -- 🆕 조건값: 개수(입수) norm_key VARCHAR(300), -- 🆕 정규화 매칭키(§4). 검색 인덱스 matched_group_code VARCHAR(20) REFERENCES product_group(group_code), -- auto/check 확정시 match_state match_state NOT NULL DEFAULT 'new', error_code VARCHAR(24), -- unit_missing/required_missing/bad_group 등 -- ── 공급사 입력(매칭돼도 이 3개만 항목이 보유) ────────── net_supply_price NUMERIC(12,2), -- 🆕⭐ net공급가(예: 400) — 공급사 필수 입력 dist_product_code VARCHAR(64), -- 🆕 유통사 상품코드 — 공급사 입력 dist_product_name VARCHAR(200), -- 🆕 유통사 상품명 — 공급사 입력 -- ── 신규(match_state='new')일 때만 직접 입력 ──────────── new_payload JSONB, -- 🆕 신규행 나머지 양식값(과세·분류·원산지·브랜드·등급·배송유형·외부적재·최소주문·라벨수량단위 …) 🟠 image_url VARCHAR(300), -- 신규(new)일 때만 직접 업로드(매칭 시 그룹 이미지 승계) created_at TIMESTAMP DEFAULT now() ); -- 매칭 성공 항목의 과세·중량·분류·원산지·브랜드·등급·배송유형 등은 컬럼 복제 대신 -- matched_group_code 조인으로 product_group에서 조회(=자동채움 승계). 스냅샷 저장 여부는 🟠(§6). -- 매칭 후보 (🟡 check 상태에서만 N행) · 자동확정(auto)은 미저장/미노출 CREATE TABLE seller_product_request_match_candidate ( id BIGINT PRIMARY KEY, item_id BIGINT NOT NULL REFERENCES seller_product_request_item(id), group_code VARCHAR(20) NOT NULL REFERENCES product_group(group_code), match_score NUMERIC(5,2), -- 일치 조건 수/가중 점수 matched_fields JSONB, -- {name:true,volume:true,storage:false,...} rank INT );
🟠 미정 — enum 목록(개수 단위·묶음 단위 추가 여부)·norm_key 문자열 규칙·match_score 산식은 §4·§9에서 다룹니다. FK는 product_group.group_code가 실제 PK/유니크라는 가정 위에 있습니다.
매칭 파이프라인
조건값 정규화 → 그룹 검색 → 후보 수로 분기(완전일치=자동 / 부분=후보 / 0=신규). 임계 🟠.
- 조건값 추출·정규화 — 각 행에서 상품명·용량/중량·단위·보관·개수를 뽑아 표준형으로. 단위 변환(L→ML, KG→G), 상품명 트림/소문자/공백·특수문자 정리, 보관상태 enum 매핑. 결과를
norm_key로 합성. - 그룹 검색(좁은 후보) — "요쿠르트 전체"가 아니라 조건을 다 걸어
product_group을 조회. 예: 상품명 유사 AND volume=1000ML AND storage=COLD. 조건이 다 걸리면 후보가 몇 개 안 나옴. - 후보 수로 분기 — 아래 규칙. 임계(어떤 조건이 몇 개 일치해야 자동인가)는 🟠 미정.
match_state='auto' + matched_group_code 세팅. 후보를 사용자에게 노출하지 않고 내부 자동확정. 후보 테이블에 저장 안 함(또는 감사용 1행). MD가 후속 수정.match_state='check' + 후보 N행 저장(rank·score). 이때만 사람이 후보 중 선택.match_state='new'. 기존 그룹 없음 → 공급사가 이미지·분류·상세 직접 입력. 신규 그룹 생성 후보.match_state='error' + error_code. 매칭 시도 전 제외 → 재업로드.매칭 성공 시 자동채움(승계) — 핵심
공급사가 단건 등록할 때 필수값을 입력하거나 상단 검색에 입력하면 시스템이 동일 그룹 상품을 추천합니다(§4 조건값 검색과 같은 로직). 추천을 선택하면 이미지·카테고리·중량·보관·원산지·브랜드·등급·배송유형 등 나머지 값이 그룹에서 그대로 채워지고, 공급사는 아래 3개만 입력합니다.
matched_group_code 조인으로 참조.request_item에 저장.new_payload)하고 이미지 직접 업로드. 신규 그룹 생성 후보.matched_group_code 세팅 + 3개 입력. 신규 = match_state='new' + new_payload 전체. 엑셀 대량 등록도 같은 규칙을 행 단위로 적용하되, 즉시 매칭이 아니라 비동기 배치(§5·§9).완전일치 판정(초안 🟠)
부분일치 스코어(초안 🟠)
엑셀 파싱 · 실제 양식 · 비동기 배치
양식 = 상품 등록 양식_필수,선택_260901.xlsx(실측). 임의 필드 금지. 대량 업로드는 동기 즉시 로딩 대신 비동기 배치.
필수 열 (실제 양식)
| 열 | 타입 | 정규화 / 비고 | 실패 시 |
|---|---|---|---|
| 유통사 상품코드 | text | 공급사 입력 → dist_product_code | — |
| 유통사 상품명 | text | 공급사 입력 → dist_product_name | — |
| 그룹 상품명 | text | 예 "감귤(10~11과),1kg,국산" · 매칭 검색 대상 | 빈값=⛔ required_missing |
| 상품명 | text | 예 "감귤" · trim·공백·특수문자 정리(비교용) | 빈값=⛔ required_missing |
| 과세여부 | enum | 면세/과세 → tax_type | 미매핑=⛔ bad_tax |
| 보관상태 | enum | 냉장/냉동/상온 → COLD/FROZEN/ROOM | 미매핑=⛔ bad_storage |
| 중량 | number+단위 | 예 "1KG" · 값+단위 분해 → volume_value/unit | 단위없음=⛔ unit_missing |
| net공급가 ⭐ | number | 예 400 · 원 단위 → net_supply_price(공급사 입력) | 비숫자=⛔ bad_number |
| 배송예정일수 | text | 예 "D-1" · 리드타임 | 미매핑=⛔ bad_lead |
| 대분류 · 중분류 · 소분류 | text×3 | 분류 계층 → category | 매칭 시 그룹 승계 |
| 특성 · 원산지 · 브랜드 · 등급 | text×4 | 예 원산지 "국내산" · 매칭 시 그룹 승계 | — |
| 주문 배수 단위 | int | 주문 배수 | 0/음수=⛔ bad_qty |
| 배송유형 | enum | MFC / 허브 | 미매핑=⛔ bad_delivery |
| 외부 적재 | enum | 가능 / 불가능 | — |
| 최소 주문 수량 | int | MOQ | — |
| 라벨용 엑셀 다운로드 수량 단위 | int | 라벨 출력 수량 단위 | — |
선택 열 (실제 양식)
| 선택 필드 — 없으면 빈값 허용, 매칭 시 그룹 승계 | |||
|---|---|---|---|
| 제조사 | 수량 | 할인기준가 | 상품상세 |
| 원산지 세부사항 | 색상 | 상품단위 | 크기 |
| 수치 | 일시품절 | 장기품절 | 재고수량 |
| 수량제한 | 단위당 상품 개수 | 총량피킹 | — |
예시행: 감귤(10~11과),1kg,국산 / 감귤 / 면세 / 냉장 / 1KG / net 400 / D-1 / 국내산 / 배송 MFC · 단감(4~5과),1kg,국산 / net 700 / 배송 허브.
대량 업로드 = 비동기 배치 매칭 (🟠 검토 필요)
- 업로드·접수 — 파일 저장 +
seller_product_request(status=queued, total_rows) 생성 후 즉시 응답(요청 id 반환). 전건 매칭을 기다리지 않음. - 비동기 매칭 워커 — 잡큐가 배치를 집어 status=
matching. 행을 청크로 조건값 검색·분기(§4)하며matched_rows를 증가 → 진행률 = matched_rows/total_rows. - 완료 알림 — 전건 끝나면 status=
matched+matched_at. 공급사/MD에 완료 알림(결과는 나중에 확인). 화면은 진행률 폴링 또는 푸시. - 결과 확인·검수 — 상태별(🟡check·🔴new·⛔error) 목록 조회. 오류행만 재업로드.
파싱·매칭은 배치 단위. 행별 error_code로 오류행만 재업로드. 양식은 오더히어로가 배포하므로 열 순서·헤더 표준화 가능. 동기 즉시 로딩은 지양(🟠 §9).
이미지 자동 승계 로직
그룹에 이미 있으면 그룹 이미지를 자동으로 따온다. 신규만 직접 업로드.
- 매칭 결과 분기 —
match_state ∈ {auto, check→확정}이고matched_group_code가 정해지면 이미지 승계 대상. - 그룹 이미지 조회 —
product_image WHERE group_code = matched_group_code ORDER BY sort. 메인/서브를 그룹에서 가져옴. 공급사가 이미지를 안 올려도 됨. - 승계 방식 — 하위(공급사 상품/히어로)는 그룹 이미지를 참조 또는 복사. 그룹 선택 시 하위는 자동으로 들어옴. 참조 vs 복사 정책 🟠 미정.
- 신규(매칭 0)만 직접 —
match_state='new'이면 그룹이 없으므로 이미지 직접 업로드 또는 Zip 일괄. 신규 그룹 생성 시 그 이미지가 그룹 대표가 됨.
상태 전이
항목(item) 상태 = 매칭 판정 4종. 배치(request)는 파싱→매칭→확정. v13 다분기를 앞단으로 축소.
항목 상태(match_state)
| 상태 | 진입 조건 | 사람 개입 | 다음 |
|---|---|---|---|
| 🟢 auto | 후보 1개(완전일치) | 없음(미노출·내부확정) | MD 후속 수정 → 확정 |
| 🟡 check | 후보 ≥ 2(덜한 필터) | 후보 중 선택 | 선택 → 확정 / 없음 → new 전환 |
| 🔴 new | 후보 0 | 정보·이미지 입력 | 신규 그룹 생성 → 확정 |
| ⛔ error | 단위 없음·필수 누락·그룹ID 오류 | 수정 | 제외 → 재업로드 |
배치 상태(request.status) — 비동기
업로드는 queued로 즉시 응답, 워커가 matching으로 배치 처리하며 matched_rows/total_rows 진행률 노출, 완료 시 matched + 알림(§5).
인덱스 · 마이그레이션
매칭 조회 핫패스 인덱스 + 선행 정규화 마이그레이션.
인덱스(초안)
| 테이블 | 인덱스 | 용도 |
|---|---|---|
| product_group | (storage_type, volume_unit, volume_value) + rep_name trigram | 조건값 다중 AND 후보 검색 핫패스 |
| request_item | (request_id, match_state) | 검수 화면 상태별 카운트/목록 |
| request_item | norm_key | 동일 정규화키 중복·재조회 |
| match_candidate | (item_id, rank) | 후보군 정렬 노출 |
pg_trgm(trigram) 또는 정규화 후 동등비교가 필요. 정규화 후 동등비교를 1차로 쓰고, 미스가 많으면 trigram 유사도를 후보 확장에 🟠.마이그레이션 순서
- 선행 — 그룹 조건값 정규화(관건).
product_group에 조건값이 정규화 컬럼으로 없으면(상품명 문자열에만 섞여 있으면) 이 매칭은 성립하지 않음. 용량·단위·보관을 컬럼으로 추출·백필하는 마이그레이션이 먼저. 실측으로 존재 여부 확인 필요(현재 가정). - enum 생성 — unit_kind·storage_kind·match_state. 목록 확정(🟠) 후.
- 신규 3테이블 — request · item · match_candidate. FK는 product_group.group_code 유니크 전제.
- 인덱스 — 위 표. 대용량 백필 뒤 CONCURRENTLY.
- 양식 배포 — 픽스된 엑셀 템플릿 공급사 배포(열·단위 표준).
미정(🟠) · 오픈 이슈
회의에서 "조사 필요"로 남긴 항목 + 설계상 확정 안 된 값. 문서 전반의 🟠 집합.
| 항목 | 내용 | 상태 |
|---|---|---|
| 그룹 생성 기준 | 같은 "냉동 시금치 1KG 냉장"인데 그룹이 나뉘는 케이스 존재(안 보이는 값 차이). 어떤 값이 같아야 한 그룹인가 정리 필요. | 조사 |
| 자동 매칭 임계 | 조건 몇 개 일치 시 auto vs check. 완전일치 판정에 pack_qty 포함 여부·상품명 유사도 컷. | 미정 |
| 확정 시점 · MD 수정 범위 | "확정"이 언제 잠기는지, MD 후속 수정이 어디까지(가격·분류·이미지·그룹 재지정). | 미정 |
| 그룹 조건값 존재 | product_group에 정규화된 용량·단위·보관 컬럼이 실제 있는지. 매칭 성립의 전제(§8-1). | 실측필요 |
| 단위 enum 목록 | ML·L·G·KG·EA 외 묶음/박스/팩 단위 필요 여부. | 확정 |
| 이미지 승계 형태 | 참조(FK) vs 복사(스냅샷). §6. | 미정 |
| match_score 산식 | 필드 가중치·자동/후보 컷. §4 초안은 예시. | 미정 |
| 엑셀 대량 성능 방식 | 100~1000건 동기 즉시 매칭은 로딩 부담. 비동기 배치+진행률+완료 알림 방향은 제안, 잡큐 종류·워커·청크·폴링 vs 푸시는 미정. | 검토필요 |
| 자동채움 저장 형태 | 매칭 항목의 승계값을 조인 참조만 할지 스냅샷 컬럼에 복제할지(§6 이미지와 동일 축). | 미정 |
| 신규행 new_payload 구조 | 매칭 0건 신규의 나머지 양식값을 JSONB로 둘지 정규 컬럼으로 펼칠지. | 미정 |
| 화면 분리 · 신규 메뉴 | 공급사 포털 ↔ 어드민(MD) 권한 분기 · 어드민 신규 메뉴 공존은 방향 확정, 실제 라우팅·권한코드·기존 리스트 컬럼/쿼리 대조는 실측. | 실측필요 |
참고 v13 프로토: md-supplier-product.pages.dev · DB 실측 없음(요구사항 기반 초안)