86c6d24b17
统一修正3处支付相关小程序码的 page 参数(之前误写为主包 pages/ 路径, 扫码后微信找不到页面→白屏): - app/pay/service.py: 算力充值支付 → pages-extra/pay/index - app/market/service.py: 市场购买支付 → pages-extra/pay/index - app/training/main.py: 活动报名支付 → pages-extra/pay-qr/index 加上之前修复的 scan-login(auth.py + wechat.py 默认参数), 后端所有 get_wxacode 调用的 page 路径现已全部正确。
664 lines
27 KiB
Python
664 lines
27 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""创业园运营子应用接口层(app/incubator)—— 月度报告 / 共享区域 / 使用申请。
|
||
|
||
- 认证:平台 JWT(get_current_user),复用主应用登录态。
|
||
- 身份:service.resolve_identity 判定管理方 / 提交方(管理方 carrier 或权限码;
|
||
提交方=园区入驻企业成员)。
|
||
- 数据归属:管理方=全园区(按 tenant_id),提交方=本企业(按 company_id)。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException
|
||
from fastapi.responses import StreamingResponse
|
||
from pydantic import BaseModel, Field
|
||
|
||
from ..api.dependencies import Database, get_current_user, get_db
|
||
from . import service
|
||
from .service import resolve_identity
|
||
|
||
logger = logging.getLogger("incubator.api")
|
||
|
||
router = APIRouter(prefix="/incubator", tags=["incubator"])
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 请求模型
|
||
# ---------------------------------------------------------------------------
|
||
class ReportCreateRequest(BaseModel):
|
||
report_month: str = Field(..., description="报告月份,如 2026-08")
|
||
company_name: str = ""
|
||
zone: str = "" # 孵化区域
|
||
unit_no: str = "" # 单元号
|
||
park_entry_date: str = "" # 入园时间
|
||
leader_name: str = "" # 负责人姓名
|
||
registered_at: str = "" # 注册时间
|
||
registered_capital_wan: float | None = None
|
||
credit_code: str = ""
|
||
rd_invest_wan: float | None = None
|
||
month_revenue_wan: float | None = None
|
||
avg_year_revenue_wan: float | None = None
|
||
month_gross_profit_wan: float | None = None
|
||
month_tax_wan: float | None = None
|
||
avg_year_tax_wan: float | None = None
|
||
loan_startup_wan: float | None = None
|
||
loan_yunling_wan: float | None = None
|
||
contest_award_wan: float | None = None
|
||
patents: int | None = None
|
||
jobs_created: int | None = None
|
||
team: list = Field(default_factory=list)
|
||
business_note: str = ""
|
||
suggestion: str = ""
|
||
filler_name: str = ""
|
||
filler_email: str = ""
|
||
|
||
|
||
class ReportUpdateRequest(ReportCreateRequest):
|
||
_force_reopen: bool = False
|
||
|
||
|
||
class ReviewReportRequest(BaseModel):
|
||
action: str = Field(..., description="approve|reject")
|
||
comment: str = ""
|
||
|
||
|
||
class AreaCreateRequest(BaseModel):
|
||
name: str = Field(..., description="区域名称")
|
||
area_type: str = Field("meeting", description="live|meeting")
|
||
location: str = ""
|
||
capacity: int | None = None
|
||
facilities: list = Field(default_factory=list)
|
||
cover_url: str = ""
|
||
open_hour_start: str = "09:00"
|
||
open_hour_end: str = "18:00"
|
||
max_minutes: int | None = None
|
||
enabled: bool = True
|
||
status: str = Field("normal", description="normal|maintenance|closed 运营标记")
|
||
status_note: str = "" # 标记说明(如"设备检修中")
|
||
|
||
|
||
class AreaUpdateRequest(AreaCreateRequest):
|
||
pass
|
||
|
||
|
||
class ReservationCreateRequest(BaseModel):
|
||
area_id: str = Field(..., description="共享区域 id")
|
||
start_at: str = Field(..., description="使用日期起 YYYY-MM-DD HH:MM")
|
||
end_at: str = Field(..., description="使用日期止 YYYY-MM-DD HH:MM(支持跨天)")
|
||
attendees: int | None = None
|
||
purpose: str = Field("meeting", description="activity|meeting 用途(活动|会议)")
|
||
leader_name: str = ""
|
||
leader_phone: str = ""
|
||
contact_name: str = ""
|
||
contact_phone: str = ""
|
||
agreed_commitment: bool = False
|
||
signer_name: str = ""
|
||
company_name: str = ""
|
||
company_id: str = "" # 管理方代申请时指定
|
||
|
||
|
||
class ReviewReservationRequest(BaseModel):
|
||
action: str = Field(..., description="approve|reject")
|
||
comment: str = ""
|
||
record_received: bool = False # 备案:已接收备案
|
||
record_safety_notified: bool = False # 备案:已告知安全注意事项
|
||
record_handler: str = "" # 备案经办人
|
||
|
||
|
||
class SettingsUpdateRequest(BaseModel):
|
||
reservation_notice: str = "" # 共享区域申请注意事项/须知
|
||
report_notice: str = "" # 月报填报说明
|
||
open_summary: str = "" # 开放区域总览说明
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 公共:身份 / 概览
|
||
# ---------------------------------------------------------------------------
|
||
@router.get("/me", summary="当前身份(提交方/管理方/所属企业)")
|
||
async def incubator_me(
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
if not ident.is_manager and not ident.company_id:
|
||
return {"identity": None,
|
||
"message": "当前账号不属于昆明市大学生创业园:非本园入驻企业成员,也非本园管理员,请联系园区管理员绑定"}
|
||
return {"identity": ident.to_dict()}
|
||
|
||
|
||
@router.get("/overview", summary="首页概览(报告状态 / 我的申请 / 园区统计)")
|
||
async def incubator_overview(
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
service.require_identity(ident)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
from datetime import datetime
|
||
this_month = datetime.now().strftime("%Y-%m")
|
||
report_rows = await service.list_reports(db, ident, month=this_month, page_size=1)
|
||
report = report_rows["items"][0] if report_rows["items"] else None
|
||
my_res = await service.list_reservations(db, ident, page_size=5)
|
||
stats = await service.report_stats(db, ident, month=this_month) if ident.is_manager else None
|
||
return {
|
||
"identity": ident.to_dict(),
|
||
"this_month": this_month,
|
||
"my_report": report,
|
||
"my_reservations": my_res["items"],
|
||
"stats": stats,
|
||
}
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 月度报告
|
||
# ---------------------------------------------------------------------------
|
||
@router.get("/reports", summary="报告列表(管理方=全园区,提交方=本企业)")
|
||
async def incubator_reports(
|
||
month: str = "", status: str = "",
|
||
page: int = 1, page_size: int = 50,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.list_reports(db, ident, month=month, status=status,
|
||
page=page, page_size=page_size)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
|
||
|
||
@router.get("/reports/stats", summary="报告统计(管理方)")
|
||
async def incubator_report_stats(
|
||
month: str = "",
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.report_stats(db, ident, month=month)
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
|
||
|
||
@router.get("/reports/export", summary="导出报告 CSV(管理方,统计表列)")
|
||
async def incubator_reports_export(
|
||
month: str = "",
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
if not ident.is_manager:
|
||
raise HTTPException(status_code=403, detail="仅管理方可导出")
|
||
rows = (await service.list_reports(db, ident, month=month, page_size=10000))["items"]
|
||
import io
|
||
import csv
|
||
buf = io.StringIO()
|
||
w = csv.writer(buf)
|
||
w.writerow(["报告月份", "孵化区域", "单元号", "企业名称", "入园时间", "负责人",
|
||
"注册时间", "注册资金(万)", "统一信用代码", "研发当月投入(万)",
|
||
"当月营业额(万)", "年平均营业额(万)", "当月毛利润(万)", "当月缴税(万)",
|
||
"年平均缴税(万)", "创业担保贷款(万)", "云岭创业贷款(万)",
|
||
"创业大赛扶持(万)", "专利(项)", "带动就业人数", "经营状况",
|
||
"建议意见", "填表人", "填表邮箱", "状态", "审核意见"])
|
||
for r in rows:
|
||
w.writerow([r["report_month"], r["zone"], r["unit_no"], r["company_name"],
|
||
r["park_entry_date"], r["leader_name"], r["registered_at"],
|
||
r["registered_capital_wan"], r["credit_code"], r["rd_invest_wan"],
|
||
r["month_revenue_wan"], r["avg_year_revenue_wan"],
|
||
r["month_gross_profit_wan"], r["month_tax_wan"],
|
||
r["avg_year_tax_wan"], r["loan_startup_wan"], r["loan_yunling_wan"],
|
||
r["contest_award_wan"], r["patents"], r["jobs_created"],
|
||
r["business_note"], r["suggestion"], r["filler_name"],
|
||
r["filler_email"], r["status"], r["review_comment"]])
|
||
data = "\ufeff" + buf.getvalue() # BOM 便于 Excel 识别 UTF-8
|
||
return StreamingResponse(
|
||
iter([data.encode("utf-8")]),
|
||
media_type="text/csv; charset=utf-8",
|
||
headers={"Content-Disposition": f"attachment; filename=incubator_reports_{month or 'all'}.csv"},
|
||
)
|
||
|
||
|
||
@router.post("/reports", summary="新建/保存草稿(提交方)")
|
||
async def incubator_create_report(
|
||
body: ReportCreateRequest,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.create_report(db, ident, body.model_dump())
|
||
except (LookupError, PermissionError) as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||
|
||
|
||
@router.get("/reports/{report_id}", summary="报告详情")
|
||
async def incubator_report_detail(
|
||
report_id: str,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.get_report(db, ident, report_id)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
|
||
|
||
@router.put("/reports/{report_id}", summary="更新报告(draft/已提交可撤回后改)")
|
||
async def incubator_update_report(
|
||
report_id: str, body: ReportUpdateRequest,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
data = body.model_dump()
|
||
data["_force_reopen"] = body._force_reopen
|
||
try:
|
||
return await service.update_report(db, ident, report_id, data)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||
|
||
|
||
@router.post("/reports/{report_id}/submit", summary="提交审核 draft→submitted")
|
||
async def incubator_submit_report(
|
||
report_id: str,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.submit_report(db, ident, report_id)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||
|
||
|
||
@router.post("/reports/{report_id}/reopen", summary="撤回 submitted→draft")
|
||
async def incubator_reopen_report(
|
||
report_id: str,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.reopen_report(db, ident, report_id)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||
|
||
|
||
@router.post("/reports/{report_id}/review", summary="审核 approve/reject(管理方)")
|
||
async def incubator_review_report(
|
||
report_id: str, body: ReviewReportRequest,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.review_report(db, ident, report_id,
|
||
action=body.action, comment=body.comment)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 共享区域资源
|
||
# ---------------------------------------------------------------------------
|
||
@router.get("/areas", summary="共享区域列表(直播间/会议室)")
|
||
async def incubator_areas(
|
||
area_type: str = "", only_enabled: bool = True,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.list_areas(db, ident, area_type=area_type,
|
||
only_enabled=only_enabled)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
|
||
|
||
@router.get("/areas/{area_id}", summary="共享区域详情")
|
||
async def incubator_area_detail(
|
||
area_id: str,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.get_area(db, ident, area_id)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
|
||
|
||
@router.post("/areas", summary="新建共享区域(管理方)")
|
||
async def incubator_create_area(
|
||
body: AreaCreateRequest,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.create_area(db, ident, body.model_dump())
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||
|
||
|
||
@router.put("/areas/{area_id}", summary="编辑共享区域(管理方)")
|
||
async def incubator_update_area(
|
||
area_id: str, body: AreaUpdateRequest,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.update_area(db, ident, area_id, body.model_dump())
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||
|
||
|
||
@router.delete("/areas/{area_id}", summary="删除共享区域(管理方)")
|
||
async def incubator_delete_area(
|
||
area_id: str,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
ok = await service.delete_area(db, ident, area_id)
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
if not ok:
|
||
raise HTTPException(status_code=404, detail="共享区域不存在")
|
||
return {"ok": True}
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 共享区域使用申请
|
||
# ---------------------------------------------------------------------------
|
||
@router.get("/reservations", summary="申请列表(管理方=全园区,提交方=本企业)")
|
||
async def incubator_reservations(
|
||
date: str = "", status: str = "", area_id: str = "",
|
||
page: int = 1, page_size: int = 50,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.list_reservations(db, ident, date=date, status=status,
|
||
area_id=area_id, page=page, page_size=page_size)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
|
||
|
||
@router.post("/reservations", summary="发起共享区域使用申请(冲突检测)")
|
||
async def incubator_create_reservation(
|
||
body: ReservationCreateRequest,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.create_reservation(db, ident, body.model_dump())
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||
|
||
|
||
@router.post("/reservations/{rsv_id}/review", summary="审批+备案(管理方)")
|
||
async def incubator_review_reservation(
|
||
rsv_id: str, body: ReviewReservationRequest,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.review_reservation(
|
||
db, ident, rsv_id, action=body.action, comment=body.comment,
|
||
record_received=body.record_received,
|
||
record_safety_notified=body.record_safety_notified,
|
||
record_handler=body.record_handler)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||
|
||
|
||
@router.post("/reservations/{rsv_id}/cancel", summary="取消申请(发起人/管理方)")
|
||
async def incubator_cancel_reservation(
|
||
rsv_id: str,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.cancel_reservation(db, ident, rsv_id)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||
|
||
|
||
@router.post("/reservations/{rsv_id}/finish", summary="标记使用结束(管理方)")
|
||
async def incubator_finish_reservation(
|
||
rsv_id: str,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.finish_reservation(db, ident, rsv_id)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||
|
||
|
||
@router.get("/settings", summary="园区设置(注意事项/开放说明/月报说明)")
|
||
async def incubator_get_settings(
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
if not ident.is_manager and not ident.company_id:
|
||
raise HTTPException(status_code=403, detail="当前账号不属于昆明市大学生创业园,无法使用本应用")
|
||
return await service.get_settings(db, ident)
|
||
|
||
|
||
@router.put("/settings", summary="更新园区设置(管理方)")
|
||
async def incubator_update_settings(
|
||
body: SettingsUpdateRequest,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.update_settings(db, ident, body.model_dump())
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||
|
||
|
||
@router.get("/company", summary="提交方企业基本信息(表单自动识别填入)")
|
||
async def incubator_company_profile(
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
if not ident.is_manager and not ident.company_id:
|
||
raise HTTPException(status_code=403, detail="当前账号不属于昆明市大学生创业园,无法使用本应用")
|
||
return await service.get_company_profile(db, ident)
|
||
|
||
|
||
@router.get("/company/members", summary="企业绑定成员列表(联系人自动填入)")
|
||
async def incubator_company_members(
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
if not ident.is_manager and not ident.company_id:
|
||
raise HTTPException(status_code=403, detail="当前账号不属于昆明市大学生创业园,无法使用本应用")
|
||
return await service.list_company_members(db, ident)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 数据大屏 / 企业列表 / 区域时段分布 / 汇总导出
|
||
# ---------------------------------------------------------------------------
|
||
@router.get("/companies", summary="在园企业列表(数据大屏下拉 / 报表筛选)")
|
||
async def incubator_companies(
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.list_companies(db, ident)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
|
||
|
||
@router.get("/stats", summary="数据大屏统计(本月/总计/单企业,tab+下拉控制)")
|
||
async def incubator_stats(
|
||
dim: str = "month", # month|total|company
|
||
month: str = "", # dim=month 时指定月份,默认本月
|
||
company_id: str = "", # dim=company 时指定企业
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.incubator_stats(db, ident, dim=dim, month=month,
|
||
company_id=company_id)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||
|
||
|
||
@router.get("/stats/export", summary="导出汇总统计 CSV(管理方,同月报统计表格式)")
|
||
async def incubator_stats_export(
|
||
dim: str = "month",
|
||
month: str = "",
|
||
company_id: str = "",
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
if not ident.is_manager:
|
||
raise HTTPException(status_code=403, detail="仅管理方可导出汇总")
|
||
stats = await service.incubator_stats(db, ident, dim=dim, month=month,
|
||
company_id=company_id)
|
||
import io
|
||
import csv
|
||
buf = io.StringIO()
|
||
w = csv.writer(buf)
|
||
w.writerow(["昆明市大学生创业园 · 运营数据汇总", ""])
|
||
w.writerow(["统计维度", {"month": "本月", "total": "全部累计", "company": "单企业"}.get(dim, dim)])
|
||
w.writerow(["统计月份", stats.get("month") or "全部"])
|
||
w.writerow(["统计企业", stats.get("company_name") or "全园区"])
|
||
w.writerow(["生成时间", stats.get("generated_at", "")])
|
||
w.writerow([])
|
||
w.writerow(["—— 园区与报告 ——", ""])
|
||
w.writerow(["在园企业数", stats["companies"]["total"]])
|
||
w.writerow(["报告总数", stats["reports"]["total"]])
|
||
w.writerow(["已提交", stats["reports"]["submitted"]])
|
||
w.writerow(["已通过", stats["reports"]["approved"]])
|
||
w.writerow(["待审核", stats["reports"]["pending"]])
|
||
w.writerow(["提交率(%)", stats["reports"]["submit_rate"]])
|
||
w.writerow([])
|
||
w.writerow(["—— 经营指标合计(万元) ——", ""])
|
||
fin = stats["finance"]
|
||
w.writerow(["当月营业额", fin["month_revenue_wan"]])
|
||
w.writerow(["年平均营业额", fin["avg_year_revenue_wan"]])
|
||
w.writerow(["当月毛利润", fin["month_gross_profit_wan"]])
|
||
w.writerow(["当月缴税", fin["month_tax_wan"]])
|
||
w.writerow(["研发当月投入", fin["rd_invest_wan"]])
|
||
w.writerow(["创业担保贷款", fin["loan_startup_wan"]])
|
||
w.writerow(["云岭创业贷款", fin["loan_yunling_wan"]])
|
||
w.writerow(["创业大赛扶持", fin["contest_award_wan"]])
|
||
w.writerow(["获得专利(项)", fin["patents"]])
|
||
w.writerow(["带动就业(人)", fin["jobs_created"]])
|
||
w.writerow([])
|
||
w.writerow(["—— 共享区域使用 ——", ""])
|
||
rsv = stats["reservations"]
|
||
w.writerow(["申请总数", rsv["total"]])
|
||
w.writerow(["待审批", rsv["pending"]])
|
||
w.writerow(["已通过", rsv["approved"]])
|
||
w.writerow(["已拒绝", rsv["rejected"]])
|
||
w.writerow(["已取消", rsv["cancelled"]])
|
||
w.writerow(["已结束", rsv["finished"]])
|
||
w.writerow(["使用率(%)", rsv["usage_rate"]])
|
||
w.writerow([])
|
||
w.writerow(["—— 共享区域资源 ——", ""])
|
||
ar = stats["areas"]
|
||
w.writerow(["区域总数", ar["total"]])
|
||
w.writerow(["开放中", ar["enabled"]])
|
||
w.writerow(["维护中", ar["maintenance"]])
|
||
w.writerow(["已封闭", ar["closed"]])
|
||
w.writerow([])
|
||
w.writerow(["—— 月度趋势(近 12 月) ——", ""])
|
||
w.writerow(["月份", "提交报告数", "当月营业额(万)", "带动就业(人)"])
|
||
for t in stats.get("trend", []):
|
||
w.writerow([t["month"], t["reports"], t["revenue"], t["jobs"]])
|
||
data = "\ufeff" + buf.getvalue()
|
||
return StreamingResponse(
|
||
iter([data.encode("utf-8")]),
|
||
media_type="text/csv; charset=utf-8",
|
||
headers={"Content-Disposition": f"attachment; filename=kmu_stats_{dim}_{month or 'all'}.csv"},
|
||
)
|
||
|
||
|
||
@router.get("/areas/{area_id}/schedule", summary="共享区域时段分布(表格:日期×时段占用)")
|
||
async def incubator_area_schedule(
|
||
area_id: str, days: int = 7,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
ident = await resolve_identity(db, user)
|
||
try:
|
||
return await service.area_schedule(db, ident, area_id, days=days)
|
||
except LookupError as exc:
|
||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||
except PermissionError as exc:
|
||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|