336b60b3af
- 亮灯表从自然月改为30天滚动窗口(30/60天阈值),解决月底覆盖断崖 - 系统设置新增「定时通报时间」选择器,支局长可配置每日企微通报时刻 - 新增 SystemConfig 键值表 + scheduler_manager APScheduler 运行时重调度 - 拜访记录/导入模板/弹窗表单统一「同访人员」→「相关人员」 - 仪表盘周报数据补全:companion_names_resolved、visitor_name/phone 等字段 - 新增 weekly_report_template.xlsx 模板文件 Co-Authored-By: Claude <noreply@anthropic.com>
110 lines
4.3 KiB
Python
110 lines
4.3 KiB
Python
from contextlib import asynccontextmanager
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from sqlalchemy import select
|
|
from app.config import settings
|
|
from app.database import engine, Base, async_session
|
|
from app.api import router as api_router
|
|
from app.api import auth, users, customers, visits, work_plans, mini_business, key_visits
|
|
from app.api import dashboard, upload, export, import_data, wecom, daily_notes, ai_summary, system_config
|
|
from app.models.system_config import SystemConfig
|
|
from app.services.scheduler_manager import start_scheduler, shutdown_scheduler
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
# Startup: create tables if not exists (for dev convenience)
|
|
async with engine.begin() as conn:
|
|
await conn.run_sync(Base.metadata.create_all)
|
|
# Add columns that may be missing from older tables
|
|
await conn.run_sync(lambda c: c.exec_driver_sql(
|
|
"ALTER TABLE customers ADD COLUMN IF NOT EXISTS remarks TEXT DEFAULT ''"
|
|
))
|
|
await conn.run_sync(lambda c: c.exec_driver_sql(
|
|
"ALTER TABLE visits ADD COLUMN IF NOT EXISTS visitor_name VARCHAR(50) DEFAULT ''"
|
|
))
|
|
await conn.run_sync(lambda c: c.exec_driver_sql(
|
|
"ALTER TABLE visits ADD COLUMN IF NOT EXISTS visitor_phone VARCHAR(20) DEFAULT ''"
|
|
))
|
|
# edit_log columns for change tracking
|
|
for tbl in ["visits", "daily_notes", "work_plans", "mini_business", "key_visits"]:
|
|
await conn.run_sync(lambda c, t=tbl: c.exec_driver_sql(
|
|
f"ALTER TABLE {t} ADD COLUMN IF NOT EXISTS edit_log JSONB DEFAULT '[]'"
|
|
))
|
|
# New columns for v0.3
|
|
await conn.run_sync(lambda c: c.exec_driver_sql(
|
|
"ALTER TABLE users ADD COLUMN IF NOT EXISTS require_report BOOLEAN DEFAULT TRUE"
|
|
))
|
|
await conn.run_sync(lambda c: c.exec_driver_sql(
|
|
"ALTER TABLE customers ADD COLUMN IF NOT EXISTS last_visit_date DATE"
|
|
))
|
|
await conn.run_sync(lambda c: c.exec_driver_sql(
|
|
"ALTER TABLE customers ADD COLUMN IF NOT EXISTS last_visit_manager_id UUID"
|
|
))
|
|
# New columns for v0.4
|
|
await conn.run_sync(lambda c: c.exec_driver_sql(
|
|
"ALTER TABLE visits ADD COLUMN IF NOT EXISTS companion_names TEXT[] DEFAULT '{}'"
|
|
))
|
|
# system_config table for v0.5
|
|
await conn.run_sync(lambda c: c.exec_driver_sql(
|
|
"CREATE TABLE IF NOT EXISTS system_config (key VARCHAR(64) PRIMARY KEY, value TEXT DEFAULT '')"
|
|
))
|
|
|
|
# Read notification_time from DB (or use default 17:30)
|
|
notification_hour, notification_minute = 17, 30
|
|
async with async_session() as db:
|
|
row = await db.get(SystemConfig, "notification_time")
|
|
if row and row.value:
|
|
try:
|
|
parts = row.value.strip().split(":")
|
|
notification_hour, notification_minute = int(parts[0]), int(parts[1])
|
|
except (ValueError, IndexError):
|
|
pass
|
|
|
|
# Start daily reporting scheduler
|
|
start_scheduler(notification_hour, notification_minute)
|
|
|
|
yield
|
|
|
|
# Shutdown
|
|
shutdown_scheduler()
|
|
await engine.dispose()
|
|
|
|
|
|
app = FastAPI(
|
|
title=settings.APP_NAME,
|
|
version="0.1.0",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
# CORS
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.CORS_ORIGINS,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Mount all routers
|
|
app.include_router(auth.router, prefix="/api")
|
|
app.include_router(users.router, prefix="/api")
|
|
app.include_router(customers.router, prefix="/api")
|
|
app.include_router(visits.router, prefix="/api")
|
|
app.include_router(work_plans.router, prefix="/api")
|
|
app.include_router(mini_business.router, prefix="/api")
|
|
app.include_router(key_visits.router, prefix="/api")
|
|
app.include_router(dashboard.router, prefix="/api")
|
|
app.include_router(upload.router, prefix="/api")
|
|
app.include_router(export.router, prefix="/api")
|
|
app.include_router(import_data.router, prefix="/api")
|
|
app.include_router(wecom.router, prefix="/api")
|
|
app.include_router(daily_notes.router, prefix="/api")
|
|
app.include_router(ai_summary.router, prefix="/api")
|
|
app.include_router(system_config.router, prefix="/api")
|
|
|
|
|
|
@app.get("/health")
|
|
async def health_check():
|
|
return {"status": "ok", "app": settings.APP_NAME}
|