[feat] negodata: 초청 메일 헤더에 회사 브랜딩(settings.branding.email_header) 배선

This commit is contained in:
Mina Choi 2026-07-22 15:54:05 +09:00
parent bb576af346
commit 0242ecff09
4 changed files with 29 additions and 2 deletions

View File

@ -8,7 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from common.database.db_session_manager import DB_SESSION_MNG
from common.database.model.models import (
quotations, sessions, chats, nego_cards, wild_cards, items, suppliers, quotation_settings,
version_nego_cards, version_wild_cards, users, supplier_items,
version_nego_cards, version_wild_cards, users, supplier_items, companies,
)
from common.enums import CloseReason, ErrorType, QuotationStatus, SessionStatus
from common.logger import LOG
@ -384,6 +384,25 @@ class QuotationCRUD(IQuotationCRUD):
LOG.e_no_callstack(ex)
return ErrorType.DB_RUN_FAILED, {}
async def get_email_header(self, cdb: AsyncSession, user_id):
# 견적 소유 유저 → 회사 → companies.settings.branding.email_header. 초청 메일 헤더 브랜딩용. 없으면 None.
try:
query = (
select(companies.settings)
.select_from(users)
.join(companies, companies.company_id == users.company_id)
.where(users.user_id == user_id, companies.deleted == False) # noqa: E712
.limit(1)
)
err, rows = await DB_SESSION_MNG.execute(cdb, query)
if err != ErrorType.SUCCESS or not rows:
return None
settings = rows[0] or {}
return (settings.get("branding") or {}).get("email_header")
except Exception as ex:
LOG.e_no_callstack(ex)
return None
async def get_item_companies(self, cdb: AsyncSession, item_ids) -> Tuple[ErrorType, dict]:
"""item_id -> company_id(소유 회사) 매핑. 앵커링 칸(회사×유형×가격구간) 해석 입력."""
try:

View File

@ -96,6 +96,7 @@ def build_invite_email(
qt_number: str,
end_time: datetime | None,
chat_url: str,
email_header: str | None = None,
) -> tuple[str, str, str]:
"""협상 초청 메일 (제목/HTML/텍스트) 생성.
@ -113,6 +114,7 @@ def build_invite_email(
qt_number=escape(qt_number),
deadline=escape(deadline),
chat_url=escape(chat_url),
email_header=escape(email_header or "NEGODATA"),
)
text = (

View File

@ -6,7 +6,7 @@
<!-- 헤더 바 -->
<tr>
<td style="background-color:#2563eb;padding:18px 28px;">
<span style="color:#ffffff;font-size:16px;font-weight:700;letter-spacing:1px;">NEGODATA</span>
<span style="color:#ffffff;font-size:16px;font-weight:700;letter-spacing:1px;">$email_header</span>
</td>
</tr>

View File

@ -962,6 +962,11 @@ class QuotationService:
async def _send_invites(self, quotation, targets: list, res: Res_NotifySessions) -> list:
"""targets [(session, supplier_name, email)] 에 초청 메일 발송. res.sent/failed 를 채우고
성공한 session_id 목록을 반환. ACS/SMTP 미설정이면 첫 발송에서 중단(EMAIL_NOT_CONFIGURED)."""
# 회사 브랜딩(초청 메일 헤더) 한 번 조회 — 견적당 동일.
email_header = await DB_SESSION_MNG.execute_lambda(
quotations.DBType(), DBWRType.DB_READ.value,
lambda s: self.quotation_crud.get_email_header(s, quotation.user_id),
)
sent_ids = []
for sess, sp_name, email in targets:
subject, html, text = build_invite_email(
@ -970,6 +975,7 @@ class QuotationService:
qt_number=quotation.number,
end_time=quotation.end_time,
chat_url=self._session_chat_url(sess.session_id),
email_header=email_header,
)
try:
await send_email(email, subject, html, text)