협력사가 단종·품절을 대화 도중 알아채도 봇의 결렬 선언을 기다려야 했고, 거부로 끝난 협상은 가격을 남겨도 낙찰 후보에서 빠져 계약으로 이어지지 않았다. negosium - 채팅 액션바에 협상 거부 진입점 — 대화가 끝나지 않고 입력을 기다리는 동안만 노출, 주 CTA 와 붙지 않게 넓은 화면은 우측 끝 고정·좁은 화면은 wrap - 거부 팝업은 목록 거부 팝업과 같은 어휘·규격, 대화 중이라 공급 희망 가격·의견을 더 받는다 - /reject 에 reject_price·opinion 추가 — sessions.reject_price 저장, 의견은 custom 병합 - 화면 문구 '거절' → '거부' 통일 (버튼·배지·탭·토스트·안내 팝업) negodata - 개찰 견적 직접 낙찰 후보 = 가격을 써낸 세션 — 투찰한 협상완료 + 공급 희망가를 남긴 협상거부 - 계약가 파생 _award_price/awardPrice — coalesce(투찰가, 거부 시 공급 희망가) - 통계 낙찰 세션 조인도 같은 기준 — 안 고치면 거부가로 낙찰한 건이 절감 집계에서 빠진다 - 세션 상태 탭 라벨 '거절사유/거절가격/거절배송방식' → '거부…' 자동 마감 판정(close_and_decide)은 그대로 — 자동 낙찰은 투찰가만 본다.
64 lines
3.7 KiB
Python
64 lines
3.7 KiB
Python
from typing import Optional
|
|
|
|
from pydantic import Field
|
|
|
|
from common.models.gmodel import Res_WebPacketProtocol, WebPacketProtocol
|
|
|
|
|
|
# 협상 세션 목록 행. status/qt_type 은 정수 코드로 내려가고 라벨 매핑은 프론트가 한다.
|
|
class ListItem(WebPacketProtocol):
|
|
session_id: str = Field("", description="협상 세션 uuid (채팅 진입용)")
|
|
session_status: int = Field(0, description="세션 상태 코드 (SessionStatus: 1=생성 2=진행중 3=완료 4=미참여 5=거부)")
|
|
qt_type: int = Field(0, description="견적 종류 코드 (1=재협상, 2=재견적, 3=신규협상, 4=신규견적)")
|
|
qt_number: str = Field("", description="견적 번호")
|
|
qt_end_time: str = Field("", description="견적 마감 시각 (ISO 8601)")
|
|
item_code: str = Field("", description="상품 코드")
|
|
item_name: str = Field("", description="상품명")
|
|
model_name: str = Field("", description="모델명")
|
|
maker_name: str = Field("", description="제조사")
|
|
custom: dict = Field(default_factory=dict, description="협상완료 부가정보 값(sessions.custom). 미입력이면 빈 dict")
|
|
renegotiable: bool = Field(False, description="재협상 요청 가능 여부 — 낙찰 없이 마감(개찰)된 마지막 차수이고 대기 중 요청이 없을 때만 True")
|
|
renegotiation_status: int = Field(0, description="현재 재협상 요청 상태(RenegotiationStatus). 요청 이력이 없으면 0")
|
|
renegotiation_memo: str = Field("", description="담당자 심사 메모(반려 사유). 없으면 빈 문자열")
|
|
result: int = Field(0, description="공급사 관점 협상 결과(SessionResult): 0=미정 1=낙찰 2=미낙찰 3=결렬(개찰, 재협상 대상)")
|
|
has_chat: bool = Field(False, description="대화 이력 존재 여부 — 종료된 협상(미참여·거부)의 '결과 보기' 노출 판단용")
|
|
|
|
|
|
class Res_SessionList(Res_WebPacketProtocol):
|
|
items: list[ListItem] = Field(default_factory=list, description="협상 세션 목록")
|
|
total: int = Field(0, description="필터 조건에 맞는 전체 건수(페이지네이션용)")
|
|
page: int = Field(0, description="현재 페이지 (1부터)")
|
|
page_size: int = Field(0, description="페이지당 건수")
|
|
|
|
|
|
class Res_Participate(Res_WebPacketProtocol):
|
|
session_id: str = Field("", description="참여 성공한 세션 uuid (채팅 진입용)")
|
|
|
|
|
|
class Req_Reject(WebPacketProtocol):
|
|
reject_reason: str = Field("", max_length=255, description="거부 사유 (단종/품절 프리셋 라벨 또는 직접 입력)")
|
|
reject_price: Optional[int] = Field(None, description="공급 희망 가격(원). 채팅 중 거부에서만 들어온다 — 목록 거부엔 없음")
|
|
opinion: Optional[str] = Field(None, max_length=255, description="추가 의견 — sessions.custom.opinion 에 병합")
|
|
|
|
|
|
class Res_Reject(Res_WebPacketProtocol):
|
|
session_id: str = Field("", description="거부 처리된 세션 uuid")
|
|
|
|
|
|
class Req_ExtraInfo(WebPacketProtocol):
|
|
custom: dict = Field(default_factory=dict, description="협상완료 부가정보 값 {key: value} (회사 정의 session_fields 대로)")
|
|
|
|
|
|
class Res_ExtraInfo(Res_WebPacketProtocol):
|
|
session_id: str = Field("", description="부가정보 저장된 세션 uuid")
|
|
|
|
|
|
class Req_Renegotiation(WebPacketProtocol):
|
|
reason: str = Field("", max_length=255, description="재협상 요청 사유(프리셋 라벨 또는 직접 입력)")
|
|
desired_price: Optional[int] = Field(None, description="희망 공급가(원). 담당자 판단 근거로만 쓰인다")
|
|
|
|
|
|
class Res_Renegotiation(Res_WebPacketProtocol):
|
|
session_id: str = Field("", description="요청이 기록된 세션 uuid")
|
|
status: int = Field(0, description="요청 상태(RenegotiationStatus): 1=심사중 2=승인 3=반려 4=철회")
|