from decimal import Decimal from sqlalchemy import func, select from sqlalchemy.orm import Session from app.modules.business.models import Expense, FundAccount, Procurement, Project, WorkTask from app.modules.feishu.service import FeishuService from app.modules.risk.service import RiskService def _money(value: Decimal | int | float | None) -> str: """Format a numeric value as a two-decimal money string.""" amount = Decimal(value or 0) return f"{amount:,.2f}" class ReportService: """Build operational reports and push them through Feishu.""" def __init__(self, db: Session): self.db = db self.risks = RiskService(db) def daily_brief(self) -> dict: project_count = int( self.db.execute(select(func.count()).select_from(Project)).scalar() or 0 ) task_count = int(self.db.execute(select(func.count()).select_from(WorkTask)).scalar() or 0) procurement_pending = int( self.db.execute( select(func.count()) .select_from(Procurement) .where(Procurement.approval_status.in_(["草稿", "审批中", "待审批"])) ).scalar() or 0 ) expense_pending = int( self.db.execute( select(func.count()) .select_from(Expense) .where(Expense.approval_status.in_(["草稿", "审批中", "待审批"])) ).scalar() or 0 ) fund_total = ( self.db.execute(select(func.sum(FundAccount.current_balance))).scalar() or Decimal("0") ) risk_summary = self.risks.summary() lines = [ f"- 项目总数:{project_count}", f"- 任务总数:{task_count}", f"- 待处理采购:{procurement_pending}", f"- 待处理费用:{expense_pending}", f"- 当前账户总余额:{_money(fund_total)}", f"- 逾期任务:{len(risk_summary['overdue_tasks'])}", f"- 延期项目:{len(risk_summary['delayed_projects'])}", f"- 超预算项目:{len(risk_summary['over_budget_projects'])}", f"- 资金风险账户:{len(risk_summary['fund_risks'])}", f"- 综合风险等级:{risk_summary['risk_level']}", ] return {"title": "每日经营晨报", "lines": lines, "content": "\n".join(lines)} def project_weekly(self) -> dict: active = int( self.db.execute( select(func.count()) .select_from(Project) .where(Project.status.notin_(["验收", "已完成", "复盘", "归档", "关闭"])) ).scalar() or 0 ) delayed = self.risks.delayed_projects() over_budget = self.risks.over_budget_projects() lines = [ f"- 活跃项目:{active}", f"- 延期项目:{len(delayed)}", f"- 超预算项目:{len(over_budget)}", "- 需要管理层关注:", ] for item in delayed[:10]: lines.append(f" - 延期:{item.get('code')} {item.get('name')},负责人 {item.get('owner')}") for item in over_budget[:10]: lines.append(f" - 超预算:{item.get('code')} {item.get('name')}") return {"title": "项目周报", "lines": lines, "content": "\n".join(lines)} def push_report( self, report: dict, receive_id: str | None, receive_id_type: str, actor: str, ) -> dict: card = FeishuService.build_basic_card(report["title"], report["lines"]) return FeishuService(self.db).send_card(card, receive_id, receive_id_type, actor)