import uuid from datetime import timedelta from fastapi import Depends from common.database.db_session_manager import DB_SESSION_MNG from common.database.model.models import quotations from common.enums import DBWRType, ErrorType from common.utils.gtime import GTime from crud.dashboard_crud import DashboardCRUD, IDashboardCRUD from router.v1.dashboard.protocol import ( DashboardActionList, DashboardEmailUnsent, DashboardEmailUnsentItem, DashboardQuotationRef, DashboardScope, Res_DashboardSummary, ) class DashboardService: """대시보드 요약 집계. 회사 전체(company)와 내가 만든 견적(mine) 두 스코프를 한 응답으로 내린다. KPI 숫자는 전수 COUNT, 액션 리스트는 정렬해서 상위 N개만. 읽기 전용(사이드이펙트 없음). """ LIST_LIMIT = 5 # 액션 위젯 리스트당 표시 개수 DEADLINE_HOURS = 72 # 마감 임박 기준(3일 이내) def __init__(self, dashboard_crud: IDashboardCRUD = Depends(DashboardCRUD)): self.dashboard_crud = dashboard_crud async def get_summary(self, company_id: str, user_id: str) -> Res_DashboardSummary: res = Res_DashboardSummary() company_uuid = uuid.UUID(company_id) user_uuid = uuid.UUID(user_id) now = GTime.UTC() horizon = now + timedelta(hours=self.DEADLINE_HOURS) month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) res.company = await self._scope_summary(company_uuid, None, now, horizon, month_start) res.mine = await self._scope_summary(company_uuid, user_uuid, now, horizon, month_start) return res async def _scope_summary(self, company_uuid, owner_uuid, now, horizon, month_start) -> DashboardScope: scope = DashboardScope() scope.in_progress = await self._count( lambda s: self.dashboard_crud.count_in_progress(s, company_uuid, owner_uuid) ) scope.this_month = await self._count( lambda s: self.dashboard_crud.count_created_since(s, company_uuid, owner_uuid, month_start) ) scope.awarded_this_month = await self._count( lambda s: self.dashboard_crud.count_awarded_since(s, company_uuid, owner_uuid, month_start) ) scope.deadline_soon = await self._action( lambda s: self.dashboard_crud.deadline_soon(s, company_uuid, owner_uuid, now, horizon, self.LIST_LIMIT), with_end_time=True, ) scope.awarded = await self._action( lambda s: self.dashboard_crud.awarded(s, company_uuid, owner_uuid, self.LIST_LIMIT) ) scope.equal_bid = await self._action( lambda s: self.dashboard_crud.equal_bid(s, company_uuid, owner_uuid, self.LIST_LIMIT) ) scope.ruptured = await self._action( lambda s: self.dashboard_crud.ruptured(s, company_uuid, owner_uuid, self.LIST_LIMIT) ) scope.email_unsent = await self._email_unsent(company_uuid, owner_uuid) return scope async def _count(self, fn) -> int: err, n = await DB_SESSION_MNG.execute_lambda(quotations.DBType(), DBWRType.DB_READ.value, fn) return n if err == ErrorType.SUCCESS else 0 async def _action(self, fn, with_end_time: bool = False) -> DashboardActionList: out = DashboardActionList() err, rows, total = await DB_SESSION_MNG.execute_lambda(quotations.DBType(), DBWRType.DB_READ.value, fn) if err != ErrorType.SUCCESS: return out out.total = total out.items = [ DashboardQuotationRef(qt_id=r[0], name=r[1] or "", end_time=(r[2] if with_end_time else None)) for r in rows ] return out async def _email_unsent(self, company_uuid, owner_uuid) -> DashboardEmailUnsent: out = DashboardEmailUnsent() err, rows, total = await DB_SESSION_MNG.execute_lambda( quotations.DBType(), DBWRType.DB_READ.value, lambda s: self.dashboard_crud.email_unsent(s, company_uuid, owner_uuid, self.LIST_LIMIT), ) if err != ErrorType.SUCCESS: return out out.total = total out.quotations = [ DashboardEmailUnsentItem(qt_id=r[0], name=r[1] or "", unsent_count=int(r[2] or 0)) for r in rows ] return out