o2o-negosium-original/negodata/backend/services/quotation_setting_service.py
Mina Choi 019f3dbeac [feat] negodata: 견적 목표가 산정 개편 + 협상 초청메일 + 카드 사용구분
[견적·목표가]
- 목표가 산정 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>
2026-06-29 13:33:08 +09:00

121 lines
4.8 KiB
Python

import uuid
from fastapi import Depends
from common.database.db_session_manager import DB_SESSION_MNG
from common.database.model.models import quotation_settings
from common.enums import DBWRType, ErrorType
from crud.quotation_setting_crud import IQuotationSettingCRUD, QuotationSettingCRUD
from router.v1.quotation_setting.protocol import (
QuotationSettingData,
Req_CreateQuotationSetting,
Req_UpdateQuotationSetting,
Res_DeleteQuotationSetting,
Res_QuotationSetting,
Res_QuotationSettingList,
)
class QuotationSettingService:
"""견적 설정 비즈니스 로직. user_id 로 소유권을 확인한다(유저별 설정)."""
def __init__(self, qs_crud: IQuotationSettingCRUD = Depends(QuotationSettingCRUD)):
self.qs_crud = qs_crud
async def _fetch_owned(self, user_uuid: uuid.UUID, qt_setting_id: uuid.UUID):
"""설정 조회 + 소유권 확인. (ErrorType, setting|None) 반환."""
err_type, setting = await DB_SESSION_MNG.execute_lambda(
quotation_settings.DBType(),
DBWRType.DB_READ.value,
lambda s: self.qs_crud.get_by_id(s, qt_setting_id),
)
if err_type != ErrorType.SUCCESS or setting is None:
return ErrorType.QUOTATION_SETTING_NOT_FOUND, None
if setting.user_id != user_uuid:
return ErrorType.QUOTATION_SETTING_NOT_FOUND, None
return ErrorType.SUCCESS, setting
async def list_settings(self, user_id: str) -> Res_QuotationSettingList:
res = Res_QuotationSettingList()
user_uuid = uuid.UUID(user_id)
err_type, rows, total = await DB_SESSION_MNG.execute_lambda(
quotation_settings.DBType(),
DBWRType.DB_READ.value,
lambda s: self.qs_crud.list_by_user(s, user_uuid),
)
if err_type != ErrorType.SUCCESS:
res.result.SetResult(err_type)
return res
res.settings = [QuotationSettingData.model_validate(r) for r in rows]
res.total = total
return res
async def get_setting(self, user_id: str, qt_setting_id: str) -> Res_QuotationSetting:
res = Res_QuotationSetting()
err_type, setting = await self._fetch_owned(uuid.UUID(user_id), uuid.UUID(qt_setting_id))
if err_type != ErrorType.SUCCESS:
res.result.SetResult(err_type)
return res
res.setting = QuotationSettingData.model_validate(setting)
return res
async def create_setting(self, user_id: str, req: Req_CreateQuotationSetting) -> Res_QuotationSetting:
res = Res_QuotationSetting()
setting = quotation_settings(
user_id=uuid.UUID(user_id),
target_margin_rate=req.target_margin_rate,
anchoring_value=req.anchoring_value,
card_count=req.card_count,
)
err_type = await DB_SESSION_MNG.execute_lambda_run(
[quotation_settings.DBType()],
[lambda s: self.qs_crud.add_setting(s, setting)],
)
if err_type != ErrorType.SUCCESS:
res.result.SetResult(err_type)
return res
# 서버 기본값(created_at/updated_at)은 insert 후 객체에 실리지 않으므로 재조회한다.
return await self.get_setting(user_id, str(setting.qt_setting_id))
async def update_setting(self, user_id: str, qt_setting_id: str, req: Req_UpdateQuotationSetting) -> Res_QuotationSetting:
res = Res_QuotationSetting()
user_uuid = uuid.UUID(user_id)
setting_uuid = uuid.UUID(qt_setting_id)
data = req.model_dump(exclude_unset=True)
# 소유권 확인
err_type, _ = await self._fetch_owned(user_uuid, setting_uuid)
if err_type != ErrorType.SUCCESS:
res.result.SetResult(err_type)
return res
err_type = await DB_SESSION_MNG.execute_lambda_run(
[quotation_settings.DBType()],
[lambda s: self.qs_crud.update_setting(s, setting_uuid, data)],
)
if err_type != ErrorType.SUCCESS:
res.result.SetResult(err_type)
return res
# 갱신 후 재조회
return await self.get_setting(user_id, qt_setting_id)
async def delete_setting(self, user_id: str, qt_setting_id: str) -> Res_DeleteQuotationSetting:
res = Res_DeleteQuotationSetting()
user_uuid = uuid.UUID(user_id)
setting_uuid = uuid.UUID(qt_setting_id)
err_type, _ = await self._fetch_owned(user_uuid, setting_uuid)
if err_type != ErrorType.SUCCESS:
res.result.SetResult(err_type)
return res
err_type = await DB_SESSION_MNG.execute_lambda_run(
[quotation_settings.DBType()],
[lambda s: self.qs_crud.soft_delete(s, setting_uuid)],
)
if err_type != ErrorType.SUCCESS:
res.result.SetResult(err_type)
return res