공급사 상품 등록·단가 변경 — 기술 명세서 (v1)

데이터 모델 + API 계약 · 기획문서/프로토타입 v13 + QA 어드민 실측 구조 기반 · 개발 착수용

0. 범위·전제

📦 이 문서는 「상품 등록」(퍼즈3) 기술명세입니다. 「단가·품절 업데이트」(퍼즈2)는 별도 문서 → 단가 기획 · 단가 프로토타입.
구현 성격: 신규 구축이 아니라 기존 어드민 위에 얇은 워크플로우 레이어. 상품·권한·매칭 화면/로직은 대부분 이미 존재. 새로 만드는 핵심은 ① 로그인 공급사 스코프제출 → MD 검수 큐메뉴. 코어 구조는 기존과 동일.

1. 데이터 모델

신규 테이블 5개(워크플로우) + 기존 테이블 참조. 컬럼명은 예시(실 DB 컨벤션에 맞춰 조정).

주의: 아래 테이블·컬럼은 기획 단계 제안(참고용)이다. 상품·단가 관련은 이미 운영 DB에 스키마가 존재하므로, 실제 구현은 운영 DB 스키마를 쿼리해 확정하고 여기서는 대기 큐(change_request·price_batch)·감사(audit_log) 레이어만 새로 얹는다.

1.1 submission — 제출 건(batch)

컬럼타입설명
idPK
supplier_idFK supplier제출 공급사
typeenumsingle / excel
file_namevarchar null엑셀 원본 파일명(+스토리지 경로)
statusenum(파생)pending / partial / done / rejected (item 집계)
created_at, created_byts, FK user

1.2 submission_item — 등록 상품(신규/매칭)

컬럼타입설명
idPK
submission_idFK
statusenumdraft / pending / approved / rejected / hold
name, supplier_product_namevarchar표시 상품명 / 공급사 상품명
supplier_product_codevarchar공급사 내부코드(공급사간 키 불가)
category_l/m/svarchar대/중/소
weight, qty, origin, manufacturer, brandvarchar중량·수량·원산지·제조사·브랜드
taxable, store, delivery_typeenum과세여부 / 보관(상온·냉장·냉동) / 배송유형(MFC·택배·허브)
price, net_price, discount_baseint단가 / net공급가 / 할인기준가
hero_codevarchar null매칭/발급된 히어로코드 (MD 확정 전 null 가능)
candidatesjson매칭 후보 [{hero_code, name, score}]
imagesjson{main, sub1, sub2} URL
warnsjson["대표 이미지 없음","중량 단위 모호" …]
reject_reasonvarchar null
linked_product_idFK product null승인 시 생성/연결된 실제 상품
created_at, decided_at, decided_by

1.3 change_request — 상품 정보 변경 요청 핵심

컬럼타입설명
idPK
product_idFK product대상(승인완료) 상품
supplier_idFK
sourceenumsingle (정보 변경은 단건)
diffjson[{field, label, from, to}] — 변경된 정보 필드만(name·category·weight·origin·supplier_product_name·opt …). 단가·재고·품절은 퍼즈2
apply_atts nullnull=컨펌 즉시 / 값=예약 적용일시
statusenumpending / confirmed / rejected
reasonvarchar요청 사유 / 반려 사유
created_at, created_by, decided_at, decided_by, applied_at

1.4 단가·품절 업데이트 → 퍼즈2

단가·품절 변경(승인 큐·예약·엑셀 배치·변경률 경고)은 퍼즈2 별도 문서단가 업데이트 기획 참조. 관련 실 테이블: seller_price_schedule_manage·seller_product_price_excel_raw·seller_product_price_reservation_history.

1.5 audit_log — 감사 로그

컬럼타입설명
id, atPK, ts
entity_type, entity_idvarcharsubmission_item / change_request / price_batch
actor, actionvarcharsupplier|md · 제출/컨펌/반려/보류/수정후승인 …
before, afterjson필드 단위 전/후

1.6 기존 테이블 (참조·연동)

2. 열거형·상태

열거
submission_item.statusdraft → pending → (approved | rejected | hold)
change_request.statuspending → (confirmed | rejected)
stock_status(판매상태)on_sale(판매중) / soldout_temp(일시품절) / soldout_long(장기품절)
store(보관)room / cold / frozen
delivery_typemfc / parcel / hub
warns(예외)no_image / weight_unit_ambiguous / match_none / weight_outlier / code_not_found

3. API — 공급사

베이스 /api/supplier · 인증=공급사 토큰(자기 데이터만, §6). 목록은 ?q=&status=&page=&size=.

메서드·경로설명
GET /products본인 상품 목록(검색·필터·페이지)
GET /products/{id}상세 + 변경 이력(audit) + 진행중 변경요청
GET /match?name=&weight=&origin=&category=히어로코드 매칭 후보 top-N(§5)
POST /submissions단건 등록(초안 제출). body=상품필드+hero_code?(매칭선택)
POST /submissions/bulk엑셀 등록. multipart(file) → 행별 자동매칭+버킷 결과 반환
GET /submissions등록 이력(제출 건 목록)
POST /products/{id}/change-requests상품 정보 변경 요청(diff) — 단가·품절은 퍼즈2
범위: 이 API는 상품 등록·정보 변경(퍼즈3). 단가·품절 업데이트 API는 퍼즈2단가 업데이트 기획.

예시: 변경 요청 생성 POST /products/{id}/change-requests

// request
{
  "source": "single",
  "apply_at": null,                 // null=컨펌 즉시, "2026-07-01T09:00"=예약
  "reason": "원가 변동",
  "diff": [
    {"field":"name","label":"상품명","from":"쥬시쿨 180","to":"쥬시쿨 180ML"},
    {"field":"origin","label":"원산지","from":"국산","to":"수입산"}
  ]
}
// response 201
{ "id": 9012, "status": "pending", "product_id": 4737 }

예시: 매칭 GET /match

// GET /match?name=쥬시쿨&weight=180ML&category=가공품/음료
{ "candidates": [
  {"hero_code":"HERO-00E516","name":"쥬시쿨 180ML(자두)","weight":"180ML","score":0.92,"image":"…"},
  {"hero_code":"HERO-00E517","name":"쥬시쿨 190ML","weight":"190ML","score":0.71}
]}  // 없으면 candidates:[] → 신규 플로우

4. API — MD 검수

베이스 /api/admin/supplier-review · 인증=MD role. 메뉴 "공급사 등록 상품 검수".

메서드·경로설명
GET /queue?type=submission|change&filter=risk검수 대기(제출건/변경요청). "수상한 것만" 필터
POST /items/{id}/approve승인/수정후승인. body=수정필드+hero_code(연결/발급)
POST /items/{id}/reject반려(reason)
POST /items/{id}/hold보류
POST /submissions/{id}/bulk-approve위험 플래그 없는 건 일괄 승인
POST /change-requests/{id}/confirm변경 컨펌 → diff를 product에 반영(apply_at 반영)
POST /change-requests/{id}/reject변경 반려(reason)
POST /change-requests/bulk-confirm전체 일괄 컨펌

예시: 변경 컨펌 POST /change-requests/{id}/confirm

// 처리: status=confirmed, decided_by/at 기록
//   apply_at=null → 즉시 product 필드 반영 / 값 → 스케줄러가 해당 시각에 반영
//   audit_log에 before/after(diff) 기록
{ "ok": true, "applied_at": "2026-07-01T09:00", "product_id": 4737 }

5. 매칭 API 상세

6. 인증·권한

7. 에러·예외

상황처리
필수 누락(상품명·카테고리·중량·단가·이미지[신규])400 + 누락 필드 목록
중량 단위 없음경고(warn) + 검수필요 플래그(차단 아님)
엑셀 파싱 오류 행행 단위 분리(⛔), 정상 행만 진행
중복 등록(같은 hero/상품코드)409 또는 확인 플래그
단가 일괄 — 공급사 상품코드 미매칭해당 행 제외(code_not_found), 결과에 표시
컨펌 시 히어로코드 미지정422 — 연결/발급 후 승인

8. 비기능

9. 미정 · 협의 필요