[견적·목표가] - 목표가 산정 KTC 기준 정비(MD 우선, 없으면 인터넷최저가·매입가·판매가 중 최저 / 신규는 인터넷최저가만) - 인터넷 평균 수수료 0.078 상수화(견적설정 컬럼 제거) - 앵커링가 세션 저장 + 목표가 클릭 시 산정내역 모달 - 재생성 시 목표가·앵커링가 재계산 없이 직전 값 상속(KTC) - MD 제시가·협력사 유형(유통·제조·총판·기타) 입력 [협상카드] - 사용 구분(공통/신규견적전용/재견적전용) [협상 진행] - 협력사 초청 메일 발송(일괄/개별·재발송, 발송상태 표시) [기타] - 견적상세 창 닫기 버그 수정, 불필요한 컬럼 주석 정리 - DB: 기존 DB는 postgres-init/04-alter.sql 적용 필요(자동 마이그레이션 없음) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
183 lines
6.5 KiB
Python
183 lines
6.5 KiB
Python
import uuid
|
||
from datetime import datetime
|
||
from typing import Any, Optional
|
||
|
||
from pydantic import ConfigDict
|
||
|
||
from common.enums import CardType, ChatSender, DeliveryType, QuotationStatus, QuotationType, SessionStatus, SupplierType
|
||
from common.models.gmodel import Res_PageProtocol, Res_WebPacketProtocol, WebPacketProtocol
|
||
|
||
|
||
class QuotationProtocol(WebPacketProtocol):
|
||
pass
|
||
|
||
|
||
class Req_CreateQuotation(QuotationProtocol):
|
||
qt_setting_id: uuid.UUID
|
||
version_id: Optional[uuid.UUID] = None # 미지정 시 기본 전략 버전(card.versions 시드)으로 채움
|
||
name: str = ""
|
||
type: int = 0
|
||
status: int = 0
|
||
start_time: Optional[datetime] = None # 미지정 시 생성 시각(UTC)
|
||
end_time: datetime
|
||
round: int = 1
|
||
manager_name: Optional[str] = None
|
||
manager_email: Optional[str] = None
|
||
manager_contact_number: Optional[str] = None
|
||
memo: Optional[str] = None
|
||
md_price: Optional[int] = None # MD 제시가(원). 세션 목표가 산정 최우선값
|
||
supplier_type: Optional[int] = None # 협력사 유형(SupplierType). 재견적 1:1 → 견적에 기록
|
||
item_ids: list[uuid.UUID] = [] # 협상 대상 상품. item×supplier 조합마다 세션 1개 생성
|
||
supplier_ids: list[uuid.UUID] = [] # 협상 초청 공급사
|
||
card_ids: list[uuid.UUID] = [] # 선택 협상카드. 버전을 만들어 묶고 quotation.version_id 로 연결
|
||
|
||
|
||
class Req_RegenerateQuotation(QuotationProtocol):
|
||
supplier_ids: list[uuid.UUID] = [] # 다음 라운드에 부를 공급사(프론트 선택). 상품·기간·번호는 원 견적에서 이어받음
|
||
|
||
|
||
class QuotationData(WebPacketProtocol):
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
qt_id: uuid.UUID
|
||
user_id: uuid.UUID
|
||
qt_setting_id: uuid.UUID
|
||
version_id: uuid.UUID
|
||
name: str
|
||
number: str
|
||
type: QuotationType
|
||
round: int = 1
|
||
status: QuotationStatus
|
||
start_time: datetime
|
||
end_time: datetime
|
||
manager_name: Optional[str] = None
|
||
manager_email: Optional[str] = None
|
||
manager_contact_number: Optional[str] = None
|
||
memo: Optional[str] = None
|
||
md_price: Optional[int] = None
|
||
supplier_type: Optional[SupplierType] = None
|
||
iteration: int = 0
|
||
preferred_sp_yn: Optional[bool] = None
|
||
preferred_sp_id: Optional[uuid.UUID] = None
|
||
preferred_sp_name: Optional[str] = None
|
||
equal_bid_yn: Optional[bool] = None
|
||
equal_bid_data: Optional[Any] = None
|
||
participation_count: int = 0 # 견적별 참여 협력사 수(세션 distinct supplier). 목록 집계로 채움.
|
||
item_id: Optional[uuid.UUID] = None # 대표 상품 id(세션의 첫 item). 목록 조인으로 채움.
|
||
item_name: Optional[str] = None # 대표 상품명. 목록 조인으로 채움.
|
||
created_at: Optional[datetime] = None
|
||
updated_at: Optional[datetime] = None
|
||
|
||
|
||
class Res_Quotation(Res_WebPacketProtocol):
|
||
quotation: Optional[QuotationData] = None
|
||
|
||
|
||
class Res_QuotationList(Res_PageProtocol):
|
||
quotations: list[QuotationData] = []
|
||
|
||
|
||
class Res_DeleteQuotation(Res_WebPacketProtocol):
|
||
pass
|
||
|
||
|
||
class SessionData(WebPacketProtocol):
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
session_id: uuid.UUID
|
||
qt_id: uuid.UUID
|
||
supplier_id: uuid.UUID
|
||
item_id: uuid.UUID
|
||
qt_number: str
|
||
qt_round: int
|
||
qt_type: QuotationType
|
||
target_price: int
|
||
target_anchoring_price: Optional[int] = None # 앵커링가(원). 목표가×(1−앵커링율)
|
||
status: SessionStatus
|
||
bid_price: Optional[int] = None
|
||
bid_at: Optional[datetime] = None
|
||
end_time: datetime
|
||
reject_reason: Optional[str] = None
|
||
reject_price: Optional[int] = None
|
||
reject_delivery_type: Optional[DeliveryType] = None
|
||
email_sent_at: Optional[datetime] = None # 협상 초청 메일 발송 시각(None=미발송). 프론트 발송배지/재발송 판단
|
||
url: str = "" # 세션 chat 실행 URL(공급사 협상 프론트). DB 미저장 — session_id 로 구성
|
||
|
||
|
||
class Res_CreateQuotation(Res_WebPacketProtocol):
|
||
qt_id: Optional[uuid.UUID] = None # 생성된 견적 id(프론트가 상세 오버레이 오픈에 사용)
|
||
session_count: int = 0 # 함께 생성된 협상 세션 수(토스트 표시용). 세션 풀바디는 별도 GET 으로 조회
|
||
|
||
|
||
class Res_NotifySessions(Res_WebPacketProtocol):
|
||
sent: int = 0 # 발송 성공 세션 수
|
||
failed: int = 0 # 발송 시도했으나 실패한 세션 수
|
||
skipped: int = 0 # 담당자 이메일이 없어 건너뛴 세션 수
|
||
total: int = 0 # 대상 세션 총수
|
||
|
||
|
||
class Res_QuotationStatus(Res_WebPacketProtocol):
|
||
qt_id: Optional[uuid.UUID] = None
|
||
job_status: int = 0
|
||
message: str = ""
|
||
|
||
|
||
class Res_QuotationSessions(Res_WebPacketProtocol):
|
||
qt_id: Optional[uuid.UUID] = None
|
||
sessions: list[SessionData] = []
|
||
total: int = 0
|
||
|
||
|
||
class ChatMessageData(WebPacketProtocol):
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
chat_id: uuid.UUID
|
||
session_id: uuid.UUID
|
||
card_id: Optional[uuid.UUID] = None
|
||
index: int
|
||
sender: ChatSender
|
||
target_price: int
|
||
card_used_yn: Optional[bool] = None
|
||
indicator_value: Optional[float] = None
|
||
card_type: Optional[CardType] = None
|
||
script: Optional[str] = None
|
||
step: Optional[str] = None
|
||
|
||
|
||
class Res_SessionChat(Res_WebPacketProtocol):
|
||
session_id: Optional[uuid.UUID] = None
|
||
messages: list[ChatMessageData] = []
|
||
|
||
|
||
class Res_QuotationResult(Res_WebPacketProtocol):
|
||
qt_id: Optional[uuid.UUID] = None
|
||
winner_supplier_id: Optional[uuid.UUID] = None
|
||
winner_supplier_name: Optional[str] = None
|
||
is_equal_bid: Optional[bool] = None
|
||
equal_bid_data: Optional[Any] = None
|
||
result_count: int = 0
|
||
|
||
|
||
class QuotationCardData(WebPacketProtocol):
|
||
session_card_id: uuid.UUID
|
||
qt_id: Optional[uuid.UUID] = None
|
||
nego_card_id: Optional[uuid.UUID] = None
|
||
wild_card_id: Optional[uuid.UUID] = None
|
||
type: Optional[CardType] = None
|
||
number: Optional[str] = None
|
||
name: Optional[str] = None
|
||
script: Optional[str] = None # 협상 멘트(평문)
|
||
edit_script: Optional[Any] = None # 협상 멘트(Slate 서식본)
|
||
condition: Optional[str] = None # 와일드카드 전용: 사용 조건(트리거)
|
||
memo: Optional[str] = None # 와일드카드 전용: 메모
|
||
|
||
|
||
class Res_QuotationCards(Res_WebPacketProtocol):
|
||
qt_id: Optional[uuid.UUID] = None
|
||
cards: list[QuotationCardData] = []
|
||
|
||
|
||
class Res_LastSupplierType(Res_WebPacketProtocol):
|
||
supplier_type: Optional[SupplierType] = None # 협력사 직전 견적의 유형(없으면 None)
|
||
qt_number: Optional[str] = None # 그 견적의 번호(이전 견적 값임을 표시용)
|