MD 개발 · 그룹5 · 운영·노출 · MD-26 · PLD-1034 · 개발 기획 (정정판)
장바구니에 담긴 상품이 품절 / 장기품절 / 미노출로 바뀌었을 때 배지로 알리고, 같은 그룹의 대체 상품을 추천하고, 주문 직전에 "빠지는 상품"을 확인시킨다. 강제 교체는 하지 않는다. · rev 2 · 2026-09-03
is_original_displayed → use_yn), 앱 현황을 QA 번들 코드로 다시 확인했으며, 개발DB 행 수 인용은 전부 지웠다(구조만 남김). 바뀐 내용은 맨 아래 「변경 이력」에 있다.Sold out 오버레이는 띄우지만, [주문하기]를 누르면 그 상품을 말없이 빼고 주문한다(코드 확인). 장기품절·미노출은 API에 필드가 없어 구분 자체가 불가. 대체 추천 없음.sold_out · 장기품절 long_term_sold_out · 미노출 use_yn ≠ 'Y' 실측 — is_original_displayedopen_yngroup_id의 판매 가능 히어로를 가격 오름차순 상위 3건. 그룹 없으면 카테고리 폴백 🟠. 「담기」는 추가만, 원본 자동 삭제·강제 교체 금지.longTermSoldOut·useYn(또는 상태 enum) 필드 2개 추가 — soldOut·groupId는 이미 내려옴 ② 대체 추천 API(group_id 기준 top3) ③ (선택) 노출·전환 로그 member_cart_alert 신설.OrderRecommendView)의 상품 카드·담기 패턴.사장님은 여러 날에 걸쳐 장바구니를 유지한다. 그 사이 공급사·어드민이 상품을 품절 처리하거나(sold_out), 장기 품절로 돌리거나(long_term_sold_out), 노출을 내리면(use_yn) 담긴 상품을 그대로 주문할 수 없다.
장바구니 스토어는 주문 페이로드를 만들 때 품절 항목을 건너뛴다. 화면(CartView)도 주문 요청 직전에 같은 필터를 한 번 더 건다. 어느 쪽도 "N건이 빠졌다"는 안내를 하지 않는다.
// Cart 스토어 — 주문 페이로드 생성 computed for(let e of t.cartProductList){ if(e.soldOut||!f.value.some(t=>t.heroCode===e.heroCode))continue; … a={heroCode:e.heroCode,groupId:e.groupId??null,name:e.name, … ,parentHeroCode:e.parentHeroCode??null,issue:e.issue??null} // CartView — [주문하기] 요청 본문 e.cartProductList.filter(e=>W.value.some(t=>t.heroCode===e.heroCode)&&!e.soldOut)
Cart.E6ostIJO.js · CartView.B4gMTDcs.js (QA 번들, 2026-09-03)
soldOut·groupId·sellerId·category1/2·orderLimit·totalAvailableQuantity 존재Sold out 텍스트 오버레이(영문) + 삭제(X) 버튼 + 재입고 알림 토글(ProductRestockAlert, "알림 신청은 30일간 유지돼요")pointer-events-none, 수량 스테퍼 invisible, 합계·배송비 계산에서 제외OrderRecommendView("잊으신 식자재는 없나요?") — GET /recommend/order/product?heroProductCodes=가 상품을 돌려주면 진입, 「담기」·「N원 담고 같이 결제하기」·「주문서 작성하기」POST /cart/validate가 주문서 진입 전에 이미 호출됨longTermSoldOut·useYn 없음. 미노출 상품이 장바구니에 남아 있으면 앱은 정상처럼 보여준다| 상태 | 판정 (seller_product) | 장바구니 API 필드 | 배지 | 사용자 동작 |
|---|---|---|---|---|
| 품절 | sold_out = true 실측 | soldOut · 기존 | 품절 | 선택·수량 비활성(기존 유지) + 대체 추천(신규) + 주문 시 제외 안내(신규). 재입고 알림은 기존 그대로. |
| 장기품절 | long_term_sold_out = true 실측 | longTermSoldOut · 추가 | 장기품절 · 당분간 입고 없음 | 품절과 동일 + 대체 추천을 우선 노출(재입고 기대 낮음). |
| 미노출 | use_yn ≠ 'Y' — 어드민 상품관리의 "노출 여부"가 이 컬럼 실측 | useYn(또는 상태 enum) · 추가 | 판매 종료 | 배지 + 추천. 결제 허용 여부 🟠(경고 후 허용 권고). |
| 정상 | 위 조건 모두 아님 | — | — | 평소대로. |
use_yn 값 도메인은 Y / N / U이고 기본값은 'U'다(스키마 실측; 개발DB에서 관측된 값, 행 수 비인용). "≠ 'Y'"로 판정하면 N과 U가 모두 미노출로 잡힌다. U의 의미(미등록·대기 상태로 추정)는 확인 못 했다 — 장바구니에 U 상품이 담길 수 있는 경로가 있는지 개발에서 한 번 확인. 확인 필요| 컬럼 | 실제 의미 | 왜 아닌가 |
|---|---|---|
is_original_displayed bool, NULL 허용, 기본 true | 복제 상품(parent_hero_code)이 있을 때 원본을 노출할지 | 복제 관계가 없는 상품엔 의미가 없다. 초안의 "NULL 판정 미정(U-2)"은 질문 자체가 사라짐. |
open_yn varchar(1), 기본 'Y' | 특정 업장(mscd 전용상품) 노출 범위 — mscd_yn/mscd와 세트 | 전체 노출 여부가 아니라 "누구에게" 보이느냐다. |
컬럼 존재·타입·기본값 = app-db 스키마 실측 / 의미 = 스테이지 어드민 화면 대조(상위 세션, 2026-09-03)
품절 > 장기품절 > 미노출 — 권고 권고. 대표 배지 하나만 보이고 나머지는 카드 안 보조 문구. 대안: 장기품절을 대표로 올리는 안(정보량은 많지만 "지금 못 산다"는 신호는 품절이 더 직관적) — 결정되면 프로토 1줄 수정.
group_id의 판매 가능 히어로(use_yn='Y' ∧ sold_out=false ∧ long_term_sold_out=false)를 가격 오름차순 상위 3건. 자기 자신 제외.group_id가 NULL이거나 그룹 안에 대체재가 0건이면 카테고리 기준(category1/2/3 중 어느 레벨까지 — 미정 U-4).recommended_codes=[]로 남겨 그룹핑 품질 피드백.POST /cart/add. 원본 품절 상품은 자동 삭제하지 않는다.soldOut·추가 필드 2개)는 이미 클라이언트에 있다 → 시트는 서버 왕복 없이 띄울 수 있고, 기존 POST /cart/validate는 그 뒤에 그대로 탄다 추론(코드 흐름 기반).GET /cart요약 밴드를 탭하면 해당 카드로 스크롤. 추천 카드의 「담기」는 즉시 /cart/add, 원본은 그대로. 단계 5의 시트는 단계 6의 기존 [주문하기] 핸들러(fi) 앞에 끼워 넣는 위치다.
프로토는 기획 산출물이며 실제 앱 토큰이 아니다. 우측 설명 패널의 번호 핀이 개발 항목. 「대체 담기」·「주문하기」는 동작한다. 별도 창: md-g5-26-app.pages.dev ↗
| 컬럼 | 타입 | 비고 |
|---|---|---|
member_id PK | varchar(20) NN | FK → member.id |
hero_product_code PK | varchar(100) NN | FK → seller_product.hero_product_code ← 판정 조인 키(FK가 실제로 걸려 있음) |
product_qty | integer NN | 수량 |
product_option_json | jsonb | 옵션 |
request | text | 상품 요청사항 |
selected_delivery_date | date | 희망 납기 |
created_at | timestamp NN | KST 기본값 |
hero_product_code)| 컬럼 | 타입 | 역할 |
|---|---|---|
sold_out | boolean NN, 기본 false | 품절 판정 |
long_term_sold_out | boolean NN, 기본 false | 장기품절 판정 |
use_yn | varchar(1) NN, 기본 'U', 인덱스 있음 | 미노출 판정 — ≠ 'Y'. 값 도메인 Y/N/U |
group_id | integer NULL, 인덱스 있음 | 추천 1순위 축 — NULL이면 폴백 |
category1 · category2 · category3 | varchar(10) NULL | 폴백 축(레벨 🟠). 초안 U-7 "연결 컬럼 미확인"은 해소 — 코드 컬럼이 상품 행에 직접 있다 |
price · net_unit_price | integer NULL · bigint NULL | 추천 정렬. 어느 컬럼을 "가격"으로 볼지는 개발 시 확정(net_unit_price는 NULL 허용) |
parent_hero_code · is_original_displayed · open_yn · mscd_yn/mscd | — | 이번 판정에 쓰지 않음(02 참조) |
컬럼: id · hero_product_code · main_product_name · group_id(NN) · seller_id · change_type · sold_out · created_by(기본 'auto') · created_at. 인덱스는 created_at뿐, FK 없음.
change_type | 실측(값 도메인) | 해석 |
|---|---|---|
OUT_OF_STOCK | created_by는 항상 'auto', sold_out은 true만 실측 | 재고 소진 자동 품절(해제 방향 기록 없음 — 해제는 다른 타입으로 남는 듯) |
MANUAL | 'auto' · 공급사 자신 id · 그 외 계정 id 모두 존재, true/false 양방향 | 수동 품절/해제. "어드민" 귀속은 추론 — created_by가 어드민 계정인지 공급사 계정인지 값만으로는 못 가른다 |
ORDER_LIMIT | 'auto' · 그 외 계정 id, true/false 양방향(seller_id와 같은 created_by는 없음) | 수량제한(order_limit) 소진/해제. "공급사 계정" 귀속은 추론 |
재사용 방식: 배치안(U-1)일 때 "마지막 배치 이후 품절 상태가 바뀐 히어로" 감지 소스로 쓸 수 있다(created_at 인덱스 활용). 단 미노출(use_yn) 변경은 이 로그에 남지 않는다 — 배치안을 고르면 use_yn 변경 감지를 따로 설계해야 한다. 실시간안이면 이 로그는 어드민 "대응 로그" 화면의 원천 표시(왜 품절됐나)로만 쓴다 추론.
현재 스키마에 없다(member_cart% 테이블은 member_cart뿐). 배지·추천의 성과 추적과 중복 억제에만 필요하므로 1차 출시에서 빼도 기능은 성립한다.
| 컬럼(제안) | 타입 | 용도 |
|---|---|---|
id | bigint PK | — |
member_id | varchar(20) | 회원 |
hero_product_code | varchar(100) | 대상 상품 |
state | varchar(20) | SOLD_OUT / LONG_TERM_SOLD_OUT / HIDDEN |
recommended_codes | jsonb | 노출한 추천 히어로 코드 배열(빈 배열 = 추천 없음) |
shown_at | timestamp | 배지·추천 노출 시각 |
converted_at | timestamp NULL | 추천 「담기」 전환 시각 |
dedup_key | varchar, UNIQUE | 중복 억제 단위 🟠(U-3) — 예: member_id:hero:yyyymmdd |
기존 GET /cart(목록) · GET /cart/data?heroCodes=(선택 항목 재계산, 진입 시 항상 재조회)의 cartProductList[] 아이템에:
| 필드 | 상태 | 원천 |
|---|---|---|
soldOut | 기존 실측 | seller_product.sold_out |
groupId | 기존 실측 | seller_product.group_id (앱은 ?? null로 받음) |
longTermSoldOut | 추가 | seller_product.long_term_sold_out |
useYn | 추가 | seller_product.use_yn 원값(Y/N/U) |
대안: 서버가 판정해 cartState: AVAILABLE | SOLD_OUT | LONG_TERM_SOLD_OUT | HIDDEN enum 하나로 내려주기 — 우선순위 규칙이 서버 한 곳에 모이는 장점. 둘 중 하나로 확정. | ||
기존 soldOut 분기(오버레이·비활성·페이로드 제외)는 그대로 두고, 새 필드로 배지 문구와 시트 분류만 나눈다. 장기품절은 sold_out과 별개 컬럼이므로 "장기품절인데 soldOut=false"인 조합이 데이터상 가능하다 — 그 경우도 주문 불가로 다뤄야 하면 프론트 필터를 soldOut || longTermSoldOut으로 넓힌다 추론·확인 필요.
예시: GET /recommend/cart/substitute?heroCodes=A,B,C → 히어로별 [{heroCode, name, price, seller, arrivalDate, …}] 최대 3건. 판정식은 02의 추천 규칙 그대로(같은 group_id ∧ 판매 가능 ∧ 자기 제외 ∧ 가격 오름차순 ∧ LIMIT 3). 응답 카드 필드는 기존 인터스티셜(/recommend/order/product)의 상품 카드 스키마를 그대로 쓰면 프론트 재사용이 쉽다 권고. 장바구니 진입 시 문제 상품이 있을 때만 호출(0건이면 호출 안 함).
seller_product를 조인해 soldOut을 내려주므로 같은 쿼리에 컬럼 2개 얹는 것. 추가 부하는 사실상 없음. 최신성 문제 없음.seller_product_sold_out_log로 감지 가능하지만 미노출 변경은 로그가 없어 별도 감지 필요. 이 티켓 규모에선 과설계.[주문하기] 핸들러 순서: 시트(신규) → /recommend/order/product → (인터스티셜) → POST /cart/validate → 주문서. 시트는 클라이언트 데이터만으로 뜨고, 서버 validate가 미노출·품절을 이중으로 막을지는 U-2(미노출 결제 허용)에 따라 정한다.
권고 실시간: 기존 조회 쿼리에 컬럼 2개만 추가. 배치는 use_yn 변경 감지 설계가 따로 붙는다.
▶ 프로토 가정: 진입 시 실시간. 배치로 뒤집히면 → 캐시 테이블 + sold_out_log 폴링 + use_yn 변경 감지 3가지가 추가된다.
미노출은 "앱에서 내렸지만 상품 행은 살아 있음". 이미 담긴 건은 경고 후 허용할지, 품절처럼 제외할지.
▶ 프로토 가정: 경고 후 허용(시트에서 포함/제외 선택). 차단으로 뒤집히면 → 시트 분기 단순화 + 프론트 페이로드 필터에 useYn!=='Y' 추가 + 서버 validate에도 동일 규칙.
member_cart_alert.dedup_key같은 품절 상품에 배지·추천을 얼마나 자주 "새로" 셀지: 회원×상품×일자(권고) / 세션당 1회 / 회원×상품 1회.
▶ 프로토 가정: 회원×상품×일자 1회. 배지 자체는 항상 보이고, 억제 단위는 로그·성과 집계에만 영향 → 뒤집혀도 화면은 안 바뀐다.
같은 category3(소분류)까지만 vs category2(중분류)까지. 좁을수록 정확하고 넓을수록 "추천 없음"이 줄어든다.
▶ 프로토 가정: 소분류(category3) 동일. 중분류로 뒤집히면 → 추천 쿼리의 WHERE 한 줄 + 카드에 "비슷한 분류" 라벨.
is_original_displayed NULL 판정)·U-7(카테고리 연결 컬럼)은 실측으로 소멸, U-5(배지 우선순위)·U-6(추천 tie-break)은 권고로 흡수. MD-15 의존 해제: 현행 group_id로 바로 개발하고, 그룹3에서 그룹 정의가 바뀌면 추천 결과만 따라간다(추천 로직은 group_id 값만 읽는다).| 구분 | 항목 | 위치 |
|---|---|---|
| 프로토 | 클릭 프로토타입 v2 (탭 ①②③) | md-g5-26-app.pages.dev |
| 문서 | 이 문서 (개발 기획 정정판) | md-g5-26-doc.pages.dev |
| 지라 | PLD-1034 [MD-26] [그룹5] 장바구니 품절·미노출 대응 (앱) — 수정안 | docs/TICKET_MD26_PLD-1034_수정안.md |
| 근거 · 앱 | QA 번들 청크(읽기 전용) | Cart.E6ostIJO.js · CartView.B4gMTDcs.js · OrderRecommendView.CMDmInYs.js · ProductRestockAlert.Cj0XF_N5.js · ProductDetailsView.Bt2VIwnk.js |
| 근거 · DB | app-db 스키마(테이블·컬럼·타입·기본값·인덱스·FK · 값 도메인) | seller_product · member_cart · seller_product_sold_out_log · member_cart_alert 부재 확인 |
| 근거 · 화면 | QA 장바구니 스크린샷 / 스테이지 어드민 "노출 여부" 대조 | qa-cart.png(01에 삽입) / 상위 세션 실측 |
| rev · 일자 | 변경 |
|---|---|
| rev 1 · 2026-08-21 | 초안. 스펙 기반 정책·데이터 모델·미정 7 + MD-15 의존. |
| rev 2 · 2026-09-03 |
|