[feat] negodata/backend: 견적 협상대화에 사용 협상카드 내용 노출
QuotationCardData 에 number/edit_script/condition/memo 추가, card 카탈로그(nego/wild) 라이브 조인으로 채움. DB 컬럼 변경 없음(기존 카드 테이블 조인만 확장). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3280cab088
commit
df68609c49
@ -59,8 +59,8 @@ class users(MainTableMixin, MAIN_BASE):
|
||||
email = Column(String(255), nullable=True)
|
||||
contact_number = Column(String(20), nullable=True)
|
||||
last_accessed_at = Column(DateTime, nullable=False, server_default=_utc_now_sql())
|
||||
status = Column(SmallInteger, nullable=False, default=UserStatus.ACTIVE.value) # UserStatus
|
||||
role = Column(SmallInteger, nullable=False, default=UserRole.USER.value) # UserRole
|
||||
status = Column(SmallInteger, nullable=False, default=UserStatus.ACTIVE.value)
|
||||
role = Column(SmallInteger, nullable=False, default=UserRole.USER.value)
|
||||
|
||||
|
||||
class items(MainTableMixin, MAIN_BASE):
|
||||
|
||||
@ -48,6 +48,10 @@ class IQuotationCRUD(ABC):
|
||||
async def list_used_cards(self, cdb: AsyncSession, qt_id) -> Tuple[ErrorType, list]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def session_counts(self, cdb: AsyncSession, qt_ids) -> Tuple[ErrorType, dict]:
|
||||
pass
|
||||
|
||||
|
||||
class QuotationCRUD(IQuotationCRUD):
|
||||
async def search(
|
||||
@ -88,6 +92,24 @@ class QuotationCRUD(IQuotationCRUD):
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, [], 0
|
||||
|
||||
async def session_counts(self, cdb: AsyncSession, qt_ids) -> Tuple[ErrorType, dict]:
|
||||
"""견적 id 목록에 대해 참여 협력사 수(distinct supplier)를 한 번에 센다. {qt_id: count}."""
|
||||
try:
|
||||
if not qt_ids:
|
||||
return ErrorType.SUCCESS, {}
|
||||
query = (
|
||||
select(sessions.quotation_id, func.count(func.distinct(sessions.supplier_id)))
|
||||
.where(sessions.quotation_id.in_(qt_ids), sessions.deleted == False) # noqa: E712
|
||||
.group_by(sessions.quotation_id)
|
||||
)
|
||||
err_type, rows = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, {}
|
||||
return ErrorType.SUCCESS, {r[0]: int(r[1] or 0) for r in rows}
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, {}
|
||||
|
||||
async def get_by_id(self, cdb: AsyncSession, qt_id) -> Tuple[ErrorType, quotations]:
|
||||
try:
|
||||
query = select(quotations).where(quotations.qt_id == qt_id, quotations.deleted == False).limit(1) # noqa: E712
|
||||
@ -159,16 +181,21 @@ class QuotationCRUD(IQuotationCRUD):
|
||||
|
||||
async def list_used_cards(self, cdb: AsyncSession, qt_id) -> Tuple[ErrorType, list]:
|
||||
"""견적의 세션들에서 실제 사용된 카드(chats.card_used_yn)를 카드 카탈로그와 조인.
|
||||
반환: [(chat_row, card_id, name, script), ...].
|
||||
반환: [(chat_row, card_id, number, name, script, edit_script, condition, memo), ...].
|
||||
card_type 1=nego_cards / 2=wild_cards 양쪽을 LEFT JOIN 해서 어느 쪽이든 잡는다.
|
||||
condition/memo 는 wild_cards 에만 있는 컬럼이라 nego 카드면 NULL 로 나온다.
|
||||
"""
|
||||
try:
|
||||
query = (
|
||||
select(
|
||||
chats,
|
||||
func.coalesce(nego_cards.nego_card_id, wild_cards.wild_card_id).label("card_pk"),
|
||||
func.coalesce(nego_cards.number, wild_cards.number).label("card_number"),
|
||||
func.coalesce(nego_cards.name, wild_cards.name).label("card_name"),
|
||||
func.coalesce(nego_cards.script, wild_cards.script).label("card_script"),
|
||||
func.coalesce(nego_cards.edit_script, wild_cards.edit_script).label("card_edit_script"),
|
||||
wild_cards.condition.label("card_condition"),
|
||||
wild_cards.memo.label("card_memo"),
|
||||
)
|
||||
.join(sessions, sessions.session_id == chats.session_id)
|
||||
.outerjoin(nego_cards, and_(nego_cards.nego_card_id == chats.card_id, chats.card_type == 1))
|
||||
|
||||
@ -33,7 +33,7 @@ class Req_UpdateCard(CardProtocol):
|
||||
memo: Optional[str] = None
|
||||
|
||||
|
||||
# 통합 카드 표현(nego_cards + wild_cards 공통). nego_card_id 는 출처 테이블의 PK 를 그대로 담는다.
|
||||
# 통합 카드 표현(nego_cards + wild_cards 공통).
|
||||
class CardData(WebPacketProtocol):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
@ -51,6 +51,7 @@ class QuotationData(WebPacketProtocol):
|
||||
preferred_sp_name: Optional[str] = None
|
||||
equal_bid_yn: Optional[bool] = None
|
||||
equal_bid_data: Optional[Any] = None
|
||||
participation_count: int = 0 # 견적별 참여 협력사 수(세션 distinct supplier). 목록 집계로 채움.
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
@ -143,8 +144,12 @@ class QuotationCardData(WebPacketProtocol):
|
||||
nego_card_id: Optional[uuid.UUID] = None
|
||||
wild_card_id: Optional[uuid.UUID] = None
|
||||
type: Optional[int] = None
|
||||
number: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
script: 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):
|
||||
|
||||
@ -57,6 +57,21 @@ class QuotationService:
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
# 참여 협력사 수(세션 distinct supplier)를 이 페이지 견적들에 대해 한 방으로 세서 합친다(메인 쿼리 비건드림).
|
||||
qt_ids = [r.qt_id for r in rows]
|
||||
counts = {}
|
||||
if qt_ids:
|
||||
cnt_err, got = await DB_SESSION_MNG.execute_lambda(
|
||||
quotations.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.quotation_crud.session_counts(s, qt_ids),
|
||||
)
|
||||
if cnt_err == ErrorType.SUCCESS:
|
||||
counts = got
|
||||
for r in rows:
|
||||
r.participation_count = counts.get(r.qt_id, 0)
|
||||
|
||||
res.quotations = [QuotationData.model_validate(r) for r in rows]
|
||||
res.total = total
|
||||
return res
|
||||
@ -247,9 +262,10 @@ class QuotationService:
|
||||
return res
|
||||
|
||||
res.qt_id = quotation.qt_id
|
||||
# rows = [(chat_row, nego_card_id, name, script), ...]. nego/wild 구분은 chats.card_type.
|
||||
# rows = [(chat_row, card_id, number, name, script, edit_script, condition, memo), ...].
|
||||
# nego/wild 구분은 chats.card_type. condition/memo 는 와일드카드에만 존재.
|
||||
cards = []
|
||||
for chat_row, nc_id, nc_name, nc_script in rows:
|
||||
for chat_row, nc_id, nc_number, nc_name, nc_script, nc_edit, wc_condition, wc_memo in rows:
|
||||
is_wild = chat_row.card_type == 2
|
||||
cards.append(
|
||||
QuotationCardData(
|
||||
@ -258,8 +274,12 @@ class QuotationService:
|
||||
nego_card_id=None if is_wild else nc_id,
|
||||
wild_card_id=nc_id if is_wild else None,
|
||||
type=chat_row.card_type if chat_row.card_type is not None else 1,
|
||||
number=nc_number,
|
||||
name=nc_name,
|
||||
script=nc_script,
|
||||
edit_script=nc_edit,
|
||||
condition=wc_condition if is_wild else None,
|
||||
memo=wc_memo if is_wild else None,
|
||||
)
|
||||
)
|
||||
res.cards = cards
|
||||
|
||||
Loading…
Reference in New Issue
Block a user