fad458b2a7
- UIUX 文档:填充 19 个缺口(多主体画像/健康度/AI+看板/增长域/洞察域/创始人端/OODA/助推/商密) - UIUX 文档:插入 6 个新章节(十四~十九),旧章节重编号为二十~三十一,更新目录和交叉引用 - 作业指导书 x5:导航改为 6 域分组,新增 Context Bar/工作模式/Insight Rail/决策线程/多工作区等 UI 概念 - 新建 docs/2-task-uiux.md:50 个代码落地开发任务,按 P0-P6 分优先级 + 8 Sprint 规划 - 后端/前端:大量新增模型、路由、组件(来自之前 Phase 开发)
298 lines
10 KiB
Python
298 lines
10 KiB
Python
"""企业档案路由:CRUD + 列表分页。"""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.database import get_db
|
|
from app.core.dependencies import get_current_user
|
|
from app.models.company import Company
|
|
from app.models.user import User
|
|
from app.schemas.common import ApiResponse, success
|
|
from app.schemas.company import (
|
|
CompanyCreate,
|
|
CompanyDetailResponse,
|
|
CompanyListResponse,
|
|
CompanyResponse,
|
|
CompanyUpdate,
|
|
AgreementBrief,
|
|
BoardMeetingBrief,
|
|
HealthScoreBrief,
|
|
ReportBrief,
|
|
RiskBrief,
|
|
WeakSignalBrief,
|
|
)
|
|
from app.models.agreement import InvestmentAgreement
|
|
from app.models.board import BoardMeeting
|
|
from app.models.financial_data import FinancialData
|
|
from app.models.health_score import HealthScore
|
|
from app.models.report import MonthlyReport
|
|
from app.models.risk import RiskEvent
|
|
from app.models.weak_signal import WeakSignal
|
|
|
|
router = APIRouter(prefix="/companies", tags=["companies"])
|
|
|
|
|
|
@router.get("", response_model=ApiResponse[CompanyListResponse])
|
|
async def list_companies(
|
|
page: int = Query(default=1, ge=1),
|
|
page_size: int = Query(default=20, ge=1, le=100),
|
|
keyword: str | None = Query(default=None, description="按名称搜索"),
|
|
industry: str | None = Query(default=None, description="按行业筛选"),
|
|
stage: str | None = Query(default=None, description="按融资阶段筛选"),
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
"""获取企业列表(分页 + 筛选)。"""
|
|
query = select(Company).where(Company.tenant_id == user.tenant_id)
|
|
|
|
if keyword:
|
|
query = query.where(Company.name.ilike(f"%{keyword}%"))
|
|
if industry:
|
|
query = query.where(Company.industry == industry)
|
|
if stage:
|
|
query = query.where(Company.stage == stage)
|
|
|
|
# 总数
|
|
count_query = select(func.count()).select_from(query.subquery())
|
|
total_result = await db.execute(count_query)
|
|
total = total_result.scalar_one()
|
|
|
|
# 分页
|
|
offset = (page - 1) * page_size
|
|
query = query.order_by(Company.created_at.desc()).offset(offset).limit(page_size)
|
|
result = await db.execute(query)
|
|
companies = result.scalars().all()
|
|
|
|
return success(
|
|
data=CompanyListResponse(
|
|
items=[CompanyResponse.model_validate(c, from_attributes=True) for c in companies],
|
|
total=total,
|
|
page=page,
|
|
page_size=page_size,
|
|
)
|
|
)
|
|
|
|
|
|
@router.get("/{company_id}", response_model=ApiResponse[CompanyResponse])
|
|
async def get_company(
|
|
company_id: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
"""获取企业详情。"""
|
|
result = await db.execute(
|
|
select(Company).where(Company.id == company_id, Company.tenant_id == user.tenant_id)
|
|
)
|
|
company = result.scalar_one_or_none()
|
|
if not company:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="企业不存在")
|
|
return success(data=CompanyResponse.model_validate(company, from_attributes=True))
|
|
|
|
|
|
@router.get("/{company_id}/detail", response_model=ApiResponse[CompanyDetailResponse])
|
|
async def get_company_detail(
|
|
company_id: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
"""获取企业详情聚合数据 — 工作台使用。
|
|
|
|
聚合:企业基本信息 + 最新健康度 + 最近月报 + 未解决风险 + 弱信号 + 活跃协议 + 董事会会议 + 财务数据。
|
|
"""
|
|
# 企业基本信息
|
|
result = await db.execute(
|
|
select(Company).where(Company.id == company_id, Company.tenant_id == user.tenant_id)
|
|
)
|
|
company = result.scalar_one_or_none()
|
|
if not company:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="企业不存在")
|
|
|
|
# 最新健康度评分
|
|
health_result = await db.execute(
|
|
select(HealthScore)
|
|
.where(HealthScore.company_id == company_id)
|
|
.order_by(HealthScore.calculated_at.desc())
|
|
.limit(1)
|
|
)
|
|
health = health_result.scalar_one_or_none()
|
|
health_brief = HealthScoreBrief(
|
|
total_score=health.total_score,
|
|
financial_score=health.financial_score,
|
|
operational_score=health.operational_score,
|
|
ai_commercial_score=health.ai_commercial_score,
|
|
ai_cost_score=health.ai_cost_score,
|
|
org_talent_score=getattr(health, "org_talent_score", None),
|
|
product_tech_score=getattr(health, "product_tech_score", None),
|
|
market_compete_score=getattr(health, "market_compete_score", None),
|
|
governance_score=getattr(health, "governance_score", None),
|
|
financing_score=getattr(health, "financing_score", None),
|
|
synergy_score=getattr(health, "synergy_score", None),
|
|
ai_model_product_score=getattr(health, "ai_model_product_score", None),
|
|
data_compliance_score=getattr(health, "data_compliance_score", None),
|
|
team_tech_score=getattr(health, "team_tech_score", None),
|
|
customer_success_score=getattr(health, "customer_success_score", None),
|
|
trend=health.trend,
|
|
calculated_at=health.calculated_at,
|
|
) if health else None
|
|
|
|
# 最近 5 条月报
|
|
reports_result = await db.execute(
|
|
select(MonthlyReport)
|
|
.where(MonthlyReport.company_id == company_id)
|
|
.order_by(MonthlyReport.period_year.desc(), MonthlyReport.period_month.desc())
|
|
.limit(5)
|
|
)
|
|
reports = reports_result.scalars().all()
|
|
report_briefs = [
|
|
ReportBrief(
|
|
id=r.id, period_year=r.period_year, period_month=r.period_month,
|
|
status=r.status, ai_summary=r.ai_summary, submitted_at=r.submitted_at,
|
|
) for r in reports
|
|
]
|
|
|
|
# 未解决风险
|
|
risks_result = await db.execute(
|
|
select(RiskEvent)
|
|
.where(RiskEvent.company_id == company_id, RiskEvent.status.in_(["open", "assigned", "in_progress"]))
|
|
.order_by(RiskEvent.identified_at.desc())
|
|
.limit(10)
|
|
)
|
|
risks = risks_result.scalars().all()
|
|
risk_briefs = [
|
|
RiskBrief(
|
|
id=r.id, type=r.type, severity=r.severity, status=r.status,
|
|
title=r.title, identified_at=r.identified_at,
|
|
) for r in risks
|
|
]
|
|
|
|
# 最近弱信号
|
|
signals_result = await db.execute(
|
|
select(WeakSignal)
|
|
.where(WeakSignal.company_id == company_id)
|
|
.order_by(WeakSignal.detected_at.desc())
|
|
.limit(10)
|
|
)
|
|
signals = signals_result.scalars().all()
|
|
signal_briefs = [
|
|
WeakSignalBrief(
|
|
id=s.id, signal_type=s.signal_type, content=s.content,
|
|
confidence=s.confidence, risk_probability=s.risk_probability,
|
|
status=s.status, detected_at=s.detected_at,
|
|
) for s in signals
|
|
]
|
|
|
|
# 活跃协议
|
|
agreements_result = await db.execute(
|
|
select(InvestmentAgreement)
|
|
.where(InvestmentAgreement.company_id == company_id, InvestmentAgreement.status == "active")
|
|
.order_by(InvestmentAgreement.created_at.desc())
|
|
)
|
|
agreements = agreements_result.scalars().all()
|
|
agreement_briefs = [
|
|
AgreementBrief(id=a.id, title=a.title, status=a.status, signed_at=a.signed_at)
|
|
for a in agreements
|
|
]
|
|
|
|
# 最近董事会会议
|
|
board_result = await db.execute(
|
|
select(BoardMeeting)
|
|
.where(BoardMeeting.company_id == company_id)
|
|
.order_by(BoardMeeting.created_at.desc())
|
|
.limit(5)
|
|
)
|
|
meetings = board_result.scalars().all()
|
|
meeting_briefs = [
|
|
BoardMeetingBrief(id=m.id, title=m.title, status=m.status, meeting_at=m.meeting_at)
|
|
for m in meetings
|
|
]
|
|
|
|
# 财务数据统计
|
|
fin_count_result = await db.execute(
|
|
select(func.count()).select_from(
|
|
select(FinancialData).where(FinancialData.company_id == company_id).subquery()
|
|
)
|
|
)
|
|
fin_count = fin_count_result.scalar_one()
|
|
|
|
latest_fin_result = await db.execute(
|
|
select(FinancialData)
|
|
.where(FinancialData.company_id == company_id)
|
|
.order_by(FinancialData.period_year.desc(), FinancialData.period_month.desc())
|
|
.limit(1)
|
|
)
|
|
latest_fin = latest_fin_result.scalar_one_or_none()
|
|
latest_financial = latest_fin.data_json if latest_fin else None
|
|
|
|
return success(data=CompanyDetailResponse(
|
|
company=CompanyResponse.model_validate(company, from_attributes=True),
|
|
health_score=health_brief,
|
|
recent_reports=report_briefs,
|
|
open_risks=risk_briefs,
|
|
recent_weak_signals=signal_briefs,
|
|
active_agreements=agreement_briefs,
|
|
recent_board_meetings=meeting_briefs,
|
|
financial_data_count=fin_count,
|
|
latest_financial=latest_financial,
|
|
))
|
|
|
|
|
|
@router.post("", response_model=ApiResponse[CompanyResponse], status_code=status.HTTP_201_CREATED)
|
|
async def create_company(
|
|
req: CompanyCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
"""创建企业。"""
|
|
company = Company(tenant_id=user.tenant_id, **req.model_dump())
|
|
db.add(company)
|
|
await db.flush()
|
|
return success(
|
|
data=CompanyResponse.model_validate(company, from_attributes=True),
|
|
message="创建成功",
|
|
)
|
|
|
|
|
|
@router.put("/{company_id}", response_model=ApiResponse[CompanyResponse])
|
|
async def update_company(
|
|
company_id: str,
|
|
req: CompanyUpdate,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
"""更新企业信息。"""
|
|
result = await db.execute(
|
|
select(Company).where(Company.id == company_id, Company.tenant_id == user.tenant_id)
|
|
)
|
|
company = result.scalar_one_or_none()
|
|
if not company:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="企业不存在")
|
|
|
|
update_data = req.model_dump(exclude_unset=True)
|
|
for key, value in update_data.items():
|
|
setattr(company, key, value)
|
|
|
|
await db.flush()
|
|
return success(
|
|
data=CompanyResponse.model_validate(company, from_attributes=True),
|
|
message="更新成功",
|
|
)
|
|
|
|
|
|
@router.delete("/{company_id}", response_model=ApiResponse[None])
|
|
async def delete_company(
|
|
company_id: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
"""删除企业。"""
|
|
result = await db.execute(
|
|
select(Company).where(Company.id == company_id, Company.tenant_id == user.tenant_id)
|
|
)
|
|
company = result.scalar_one_or_none()
|
|
if not company:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="企业不存在")
|
|
|
|
await db.delete(company)
|
|
return success(message="删除成功")
|