# 협상 QA 체크리스트 (260727 요청 검증) > 대상: **negodata 어드민**(:3000) + **negosium 공급사 포털**(:3300). 두 앱을 함께 돌려야 하는 플로우라 루트 `docs/`에 둔다. > 형식: 케이스별 `~하면 → ~나와야 한다`. 근거는 코드 검증(2026-07-30) 기준. ⚠=현재 알려진 이슈. > 런타임 주의: negosium front/backend는 이미지 빌드라 코드 수정은 **재빌드해야 반영**된다. --- ## 0. 요청사항 반영 현황 (코드 검증 결과, Excel 상태열 아님) | # | 요청 | 판정 | 비고 | |---|------|------|------| | 2 | 견적관리 리스트+히스토리·상세 | ✅ | /quotation + `?detail=` 시트(협상현황/대화/카드, 라운드 타임라인) | | 3 | 재협상 리스트·심사 | ✅ | /renegotiation 대기/승인/반려 | | 4 | MD→구매담당자·판매가 미노출·위치 | ✅ | | | 5/23 | 목표가=매입가×(1−네고율) 자동 | ✅ | 재/재견적 한정, 네고율=견적세팅 목표마진 | | 6 | 공급가=매입가 일원화 | ⚙ 설정 | 코드 지원됨(`hideCls`). IMK `hidden_fields`에 `"price"` 추가하면 매입가만 남음 | | 7 | 최저가 크롤 + VAT산식 | ⚠ 인프라 / 논이슈 | VAT는 설정·아이템별 처리(논이슈). 단 dev의 lps-worker 크래시+수집테이블 0행 → 수집 파이프라인 확인 필요 | | 8 | 신규 상품 공급사 입력칸 | ✅ | | | 9 | SG명/유통레벨 콤보·취급상품 삭제 | ❌ 미구현 | §9 참조 | | 10 | 리드타임→표준납기 | ✅ 수정 | Summary.tsx 하드코딩 라벨 회사설정 반영으로 수정(2026-07-30) | | 11 | 단가 VAT별도 | ✅ | 회사설정 `item_vat_yn` 동적(IMK는 vat_yn hidden) | | 12 | 앵커 10원 반올림 | ✅ | `calc_anchoring_price` + 카드 카운터(`compute_counter`) + 와일드카드 1%(2026-07-31 수정) 모두 10원 반올림 | | 13 | 절충안 계산식 | ✅ | `compute_counter` 점검, 버그 없음 | | 14/17 | 결렬 희망가 입력란 통일 | ✅ | 단일 RejectForm | | 15 | 마무리 배송 콤보/기타의견/투찰요약 | ✅ | ExtraInfoBar가 session_fields select 렌더 → 콤보 [직납/IMK배송/IMK집배송] | | 16 | 제안가 갭·반올림 | ✅ | 갭은 정상(목표가=후보 min×수수료). 봇 제안가(카운터·와일드카드 1%)도 10원 반올림 반영(2026-07-31) | | 18/22 | 성공률 기준·결렬 무관 | ✅ | 게이지는 참고용, 판정과 무관(§D). 타결=목표가 아래로/봇 카운터 수락, 결렬=카드3+최종제안 소진까지 목표가 위 | | 19/25 | 종료 의견 단계 | ✅ | 타결 ExtraInfoBar(custom.opinion)+결렬 RejectForm 의견 | | 20 | 결렬사유 상시노출·필수 | ✅ 검증 | 결렬 세션 9/9 사유 존재. §20 참조 | | 21 | 카드 3회 초과 | ✅ 정상 | 3회=협상카드 한도. 와일드카드·최종제안은 별도로 붙는 구조라 보이는 카드가 3장을 넘을 수 있음(§21) | | 24 | 목표가 필드 1→3.낙찰기준 이동 | ✅ | | --- ## A. 견적 생성 (어드민) - **A1** 재/재견적에서 매입가 10,000·네고율(견적세팅 목표마진) 2% 입력 → 목표가(구매담당자 제시가) 필드에 **9,800 자동 채움**(10원 반올림) - **A2** 목표가 직접 안 건드리고 생성 → `md_price=null`로 전송, 서버가 후보 최솟값으로 재산정 - **A3** **신규**협상/신규견적 → 매입가 후보 빠지고 인터넷최저가만 사용 → 자동 목표가가 매입가 기반이 아님(A1과 다른 게 정상) - **A4** 1.기본정보엔 목표가 없어야 / **3.낙찰기준** 스텝에 목표가+산정후보 나와야 - **A5** 판매가 입력칸 없어야 / 라벨 "구매담당자 제시가" ## B. 앵커·제안가 - **B1** 협상 시작 → 세션 앵커가 = 목표가×(1−1%), **끝자리 0** (예 목표 7,650 → 앵커 7,570) - **B2** 봇 재제안가 전부 **끝자리 0** — 카드 카운터(절충가·목표가 제시)와 **와일드카드 1% 인하가** 포함 (예 제시 15,555 → 1% 인하가 15,400). 1원 단위가 보이면 불량 ## C. 협상 진행 (실시간 챗, 공급사 포털) - **C1** 제시가 **≤ 앵커가** → **즉시 협상완료(투찰 확정)**, 성공률 99 - **C2** 앵커가 < 제시가 ≤ 앵커×1.02 → **와일드카드 1% 인하 요청**(세션 1회), 성공률 99~80 - **C3** 앵커×1.02 초과 → **일반 협상카드로 재제안(최대 3번)**, 성공률 표시(제시가 낮을수록↑) - **C4** 카드 소진 또는 재제안 3번 후에도 앵커 밑 못 내림 → **협상 실패(결렬, 투찰 없음)** - **C5** 카드 사용한도 3은 **협상카드에만** 적용. **와일드카드·최종제안은 별도로 붙어** 공급사가 보는 카드가 3장을 넘을 수 있음(정상, §21) - **C6** 단가는 **VAT별도** 표기(IMK는 VAT 라벨 자체 숨김) ## D. 타결/결렬 판정 & 성공률 게이지 - **판정 규칙**: 유저가 **목표가 아래로 내리거나** 봇이 되받은 제시가(카운터)를 **수락하면 타결**. **카드(3장)+최종제안**까지 다 소진될 때까지 **목표가 위에서 버티면 결렬**. - **D1** 성공률 게이지는 **참고용일 뿐 판정과 무관** — 제시가 낮을수록 게이지↑(상한 99, **100% 도달 안 함**). - **D2** 성공률 낮아도 무조건 결렬 아님. 최종 마감(낙찰/개찰)은 별도 기준(§G). ## E. 협상 타결(완료) 마무리 - **E1** 협상완료 → **부가정보 입력 단계 등장** (표준납기·최소주문수량·발주배수·배송유형·의견) - **E2** 배송유형 → **콤보 [직납/IMK배송/IMK집배송]**로 떠야 (자유입력·다른 라벨이면 불량) - **E3** 의견(선택) 작성 → `custom.opinion` 저장돼야 - **E4** 입력 완료 → **투찰결과 요약 갱신 후 최종 안내**, 요약에 부가정보·의견 반영 - **E5** 어드민 상세(협상현황/요약)에서 부가정보 **표시+잠금(읽기전용)** ## F. 협상 결렬(실패) - **F1** 결렬 → **결렬 사유 입력 단계 항상 노출 + 필수**(견적/유형 무관 동일 폼) - **F2** 사유 프리셋(단가인상/수량/단종/품절)+기타, **기타 선택 시 사유 텍스트 필수** - **F3** 희망가격 입력 = **단일 폼**(RSP/CM 동일) - **F4** 의견(선택) 작성 가능 - **F5** 검증: 결렬(협상거부) 세션은 **100% 사유가 기록**돼야 (사유 없는 결렬 세션 = 불량). §20 ## G. 마감 · 낙찰/개찰 - **G1** 투찰가 ≤ 앵커가 → **낙찰** - **G2** 앵커~목표가 → 1:1은 **사용자 지정**(mid_action), 1:N은 낙찰 - **G3** 목표가 초과 → 1:1 **사용자 지정**(over_action, 관례 개찰), 1:N 낙찰 - **G4** 동가(최저 2곳+)·전원 미응찰·협상거부 → **개찰**(낙찰자 미정, 결렬 아님) / 마감사유 라벨 정확히 ## H. 재협상 요청·심사 (공급사→어드민) - **H1** 개찰 마감 + 본인 마지막 라운드 → **재협상 요청 가능**(사유+희망가), 담당자 알림 1건 - **H2** 낙찰건/남의 세션 요청 → **거부** - **H3** 같은 세션 2회 요청 → 2번째 **거부**; 철회 후 재요청 가능 - **H4** 어드민 /renegotiation 대기 탭 접수 → **승인 시 다음 차수 견적 생성**, 반려 시 사유 필수 --- ## 알려진 이슈 상세 ### §9. 협력사 SG명/유통레벨 콤보·취급상품 삭제 — 미구현 요청 4가지 모두 현재 구조와 다르다: 1. **분류카테고리 → SG명**: 지금 '분류카테고리'는 **입력 필드가 아니라 취급상품에서 파생 집계**되는 읽기전용 값(`SupplierItemsManager.tsx`). 회사 라벨(labels.category)로 "SG명" 표기만 되지, 직접 고르는 필드가 아님. 2. **선택콤보(취급 SG명)**: 등록된 SG명 중 선택하는 콤보 없음. 3. **유통레벨 콤보(제조/총판/대리점/일반유통)**: 없음. 유사한 건 취급상품별 **공급유형** 콤보인데 값이 다름(유통/제조/총판/없음). '대리점'·'일반유통' 값 자체가 코드에 없음. IMK 설정의 `supplier_fields.distribution_level`은 콤보가 아니라 **텍스트** 커스텀 필드. 4. **취급상품 칸 삭제**: 삭제 안 됨(등록/수정폼·엑셀업로드에 그대로). - 근본: IMK는 SG명·유통레벨을 **협력사 레벨 속성(콤보)**으로 두고 취급상품 목록은 없애길 원함. 현재는 반대로 **취급상품(supplier_items)에서 카테고리·공급유형을 파생**하는 모델. → 데이터 모델 전환이 필요한 작업. ### §20. 결렬 사유 상시 노출·필수 — 검증 완료 - agent의 모든 실패 경로가 단일 step `"협상실패"`(chat_end)로 수렴 → backend가 rejectRSP/CM로 무조건 매핑 → 프론트 통일 RejectForm(사유 필수). 세션 상태 분기 없음. - 실데이터: 협상거부(status=5) 9건 **전부 reject_reason 존재**, 사유 없는 결렬 0건. - 원 버그의 "견적마다 상이"는 *대화 중 결렬*(사유 있음) vs *미참여/마감 자동종료*(대화 자체 없음 → 사유 없음)의 구분이었을 뿐, 결렬 플로우는 일관됨. ### §21. 카드 3회 초과 — 정상 동작(버그 아님) `card_count=3`은 **협상카드 사용 한도**다. 와일드카드와 최종제안(종결)은 이 한도와 **별개로 각각 붙는** 구조라, 공급사가 보는 카드가 3장을 넘을 수 있다. 1. **와일드카드 1장**(1% 인하/동적 카운터) — 협상카드 카운트와 별개. 2. **최종제안 1장**(중간값 절충/최후통첩) — 카드 소진 후 마지막 국면에 별도로 붙음. → 협상카드 3 + 와일드 1 + 최종제안 1 = 최대 5장 노출(예: EST-202607-4DB5). - 참고(엣지): 세션에 견적세팅(card_count)이 연결 안 되면 한도가 안 걸릴 수 있으니, 설정이 항상 세션에 물리는지만 확인.