feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本

包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Neo
2026-04-06 00:03:48 +08:00
parent 70324d8542
commit 6f8f12314f
515 changed files with 76604 additions and 7456 deletions

View File

@@ -25,6 +25,7 @@ from decimal import Decimal
from app.services import fdw_queries
from app.services.task_generator import compute_heart_icon
from app.trace.decorators import trace_service
logger = logging.getLogger(__name__)
@@ -39,9 +40,10 @@ LEVEL_COLOR_MAP = {
}
TASK_TYPE_MAP = {
"follow_up_visit": {"label": "回访", "class": "tag-callback"},
"high_priority_recall": {"label": "紧急召回", "class": "tag-recall"},
"priority_recall": {"label": "优先召回", "class": "tag-recall"},
"follow_up_visit": {"label": "客户回访", "class": "callback"},
"high_priority_recall": {"label": "高优先召回", "class": "high-priority"},
"priority_recall": {"label": "优先召回", "class": "priority"},
"relationship_building": {"label": "关系构建", "class": "relationship"},
}
# 头像渐变色池(循环使用)
@@ -85,6 +87,7 @@ def _format_currency(amount: float) -> str:
# ── 6.1 核心函数 ──────────────────────────────────────────
@trace_service("获取助教详情", "Get coach detail")
async def get_coach_detail(coach_id: int, site_id: int) -> dict:
"""
助教详情COACH-1
@@ -150,7 +153,13 @@ async def get_coach_detail(coach_id: int, site_id: int) -> dict:
performance = {
"monthly_hours": salary_this.get("total_hours", 0.0),
"monthly_salary": salary_this.get("total_income", 0.0),
# CHANGE 2026-03-26 | 到手 = base_income + bonus_income + bonus_money + room_incomeDWS 层已扣抽成)
"monthly_salary": (
salary_this.get("assistant_pd_money_total", 0.0)
+ salary_this.get("assistant_cx_money_total", 0.0)
+ salary_this.get("bonus_money", 0.0)
+ salary_this.get("room_income", 0.0)
),
"customer_balance": customer_balance,
"tasks_completed": tasks_completed,
"perf_current": salary_this.get("total_hours", 0.0),
@@ -287,22 +296,22 @@ def _build_income(
{
"label": "基础课时费",
"amount": f"¥{salary.get('assistant_pd_money_total', 0.0):,.0f}",
"color": "#42A5F5",
"color": "primary",
},
{
"label": "激励课时费",
"amount": f"¥{salary.get('assistant_cx_money_total', 0.0):,.0f}",
"color": "#FFA726",
"color": "success",
},
{
"label": "充值提成",
"amount": f"¥{salary.get('bonus_money', 0.0):,.0f}",
"color": "#66BB6A",
"color": "warning",
},
{
"label": "酒水提成",
"amount": f"¥{salary.get('room_income', 0.0):,.0f}",
"color": "#AB47BC",
"color": "purple",
},
]
@@ -385,17 +394,18 @@ def _build_top_customers(
balance = cust.get("customer_balance", 0.0)
consume = cust.get("total_consume", 0.0)
# CHANGE 2026-03-29 | coach-detail-500 修复 | relation_score → score对齐 TopCustomer.score Schema
result.append({
"id": mid or 0,
"name": name,
"initial": _get_initial(name),
"avatar_gradient": _get_avatar_gradient(i),
"heart_emoji": heart_emoji,
"relation_score": f"{score:.2f}",
"score": f"{score:.2f}",
"score_color": score_color,
"service_count": cust.get("service_count", 0),
"balance": _format_currency(balance),
"consume": _format_currency(consume),
"balance": float(balance) if balance else 0.0,
"consume": float(consume) if consume else 0.0,
})
return result
@@ -440,9 +450,9 @@ def _build_service_records(
"avatar_gradient": _get_avatar_gradient(i),
"type": course_type or "课程",
"type_class": type_class,
"table": str(rec.get("table_id")) if rec.get("table_id") else None,
"table": rec.get("table_name") or None,
"duration": f"{hours:.1f}h",
"income": _format_currency(income),
"income": float(income),
"date": date_str,
"perf_hours": None,
})
@@ -594,11 +604,12 @@ def _build_notes(coach_id: int, site_id: int, conn) -> list[dict]:
result = []
for r in rows:
# CHANGE 2026-03-29 | coach-detail-500 修复 | ai_score → score对齐 CoachNoteItem.score Schema
result.append({
"id": r[0],
"content": r[1] or "",
"timestamp": r[2].isoformat() if r[2] else "",
"ai_score": r[3],
"score": r[3],
"customer_name": member_name_map.get(r[5], ""),
"tag_label": r[4] or "",
"created_at": r[2].isoformat() if r[2] else "",
@@ -698,9 +709,9 @@ def _build_history_months(
result.append({
"month": month_label,
"estimated": month_str == current_month_str,
"customers": f"{customers}",
"hours": f"{hours:.1f}h",
"salary": _format_currency(salary_amount),
"customers": customers,
"hours": float(hours),
"salary": float(salary_amount),
"callback_done": callback_done,
"recall_done": recall_done,
})