feat: batch update - gift card breakdown spec, backend APIs, miniprogram pages, ETL finance recharge, docs & migrations
This commit is contained in:
74
apps/backend/app/routers/xcx_board.py
Normal file
74
apps/backend/app/routers/xcx_board.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""
|
||||
看板路由:BOARD-1(助教)、BOARD-2(客户)、BOARD-3(财务)。
|
||||
|
||||
前缀 /api/xcx/board,由 main.py 注册。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
|
||||
from app.auth.dependencies import CurrentUser
|
||||
from app.middleware.permission import require_permission
|
||||
from app.schemas.xcx_board import (
|
||||
AreaFilterEnum,
|
||||
BoardTimeEnum,
|
||||
CoachBoardResponse,
|
||||
CoachSortEnum,
|
||||
CustomerBoardResponse,
|
||||
CustomerDimensionEnum,
|
||||
FinanceBoardResponse,
|
||||
FinanceTimeEnum,
|
||||
ProjectFilterEnum,
|
||||
SkillFilterEnum,
|
||||
)
|
||||
from app.services import board_service
|
||||
|
||||
router = APIRouter(prefix="/api/xcx/board", tags=["xcx-board"])
|
||||
|
||||
|
||||
@router.get("/coaches", response_model=CoachBoardResponse)
|
||||
async def get_coach_board(
|
||||
sort: CoachSortEnum = Query(default=CoachSortEnum.perf_desc),
|
||||
skill: SkillFilterEnum = Query(default=SkillFilterEnum.all),
|
||||
time: BoardTimeEnum = Query(default=BoardTimeEnum.month),
|
||||
user: CurrentUser = Depends(require_permission("view_board_coach")),
|
||||
):
|
||||
"""助教看板(BOARD-1)。"""
|
||||
return await board_service.get_coach_board(
|
||||
sort=sort.value, skill=skill.value, time=time.value,
|
||||
site_id=user.site_id,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/customers", response_model=CustomerBoardResponse)
|
||||
async def get_customer_board(
|
||||
dimension: CustomerDimensionEnum = Query(default=CustomerDimensionEnum.recall),
|
||||
project: ProjectFilterEnum = Query(default=ProjectFilterEnum.all),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=20, ge=1, le=100),
|
||||
user: CurrentUser = Depends(require_permission("view_board_customer")),
|
||||
):
|
||||
"""客户看板(BOARD-2)。"""
|
||||
return await board_service.get_customer_board(
|
||||
dimension=dimension.value, project=project.value,
|
||||
page=page, page_size=page_size, site_id=user.site_id,
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/finance",
|
||||
response_model=FinanceBoardResponse,
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def get_finance_board(
|
||||
time: FinanceTimeEnum = Query(default=FinanceTimeEnum.month),
|
||||
area: AreaFilterEnum = Query(default=AreaFilterEnum.all),
|
||||
compare: int = Query(default=0, ge=0, le=1),
|
||||
user: CurrentUser = Depends(require_permission("view_board_finance")),
|
||||
):
|
||||
"""财务看板(BOARD-3)。"""
|
||||
return await board_service.get_finance_board(
|
||||
time=time.value, area=area.value, compare=compare,
|
||||
site_id=user.site_id,
|
||||
)
|
||||
31
apps/backend/app/routers/xcx_coaches.py
Normal file
31
apps/backend/app/routers/xcx_coaches.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
小程序助教路由 —— 助教详情(COACH-1)。
|
||||
|
||||
端点清单:
|
||||
- GET /api/xcx/coaches/{coach_id} — 助教详情(COACH-1)
|
||||
|
||||
所有端点均需 JWT(approved 状态)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.auth.dependencies import CurrentUser
|
||||
from app.middleware.permission import require_approved
|
||||
from app.schemas.xcx_coaches import CoachDetailResponse
|
||||
from app.services import coach_service
|
||||
|
||||
router = APIRouter(prefix="/api/xcx/coaches", tags=["小程序助教"])
|
||||
|
||||
|
||||
@router.get("/{coach_id}", response_model=CoachDetailResponse)
|
||||
async def get_coach_detail(
|
||||
coach_id: int,
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
):
|
||||
"""助教详情(COACH-1)。"""
|
||||
return await coach_service.get_coach_detail(
|
||||
coach_id, user.site_id
|
||||
)
|
||||
37
apps/backend/app/routers/xcx_config.py
Normal file
37
apps/backend/app/routers/xcx_config.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
配置路由:CONFIG-1 技能类型。
|
||||
|
||||
前缀 /api/xcx/config,由 main.py 注册。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.auth.dependencies import CurrentUser
|
||||
from app.middleware.permission import require_approved
|
||||
from app.schemas.xcx_config import SkillTypeItem
|
||||
from app.services import fdw_queries
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/xcx/config", tags=["xcx-config"])
|
||||
|
||||
|
||||
@router.get("/skill-types", response_model=list[SkillTypeItem])
|
||||
async def get_skill_types(
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
):
|
||||
"""技能类型配置(CONFIG-1)。查询失败降级返回空数组。"""
|
||||
try:
|
||||
from app.database import get_connection
|
||||
conn = get_connection()
|
||||
try:
|
||||
return fdw_queries.get_skill_types(conn, user.site_id)
|
||||
finally:
|
||||
conn.close()
|
||||
except Exception:
|
||||
logger.warning("CONFIG-1 技能类型查询失败,降级为空数组", exc_info=True)
|
||||
return []
|
||||
51
apps/backend/app/routers/xcx_customers.py
Normal file
51
apps/backend/app/routers/xcx_customers.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
小程序客户路由 —— 客户详情(CUST-1)、客户服务记录(CUST-2)。
|
||||
|
||||
端点清单:
|
||||
- GET /api/xcx/customers/{customer_id} — 客户详情(CUST-1)
|
||||
- GET /api/xcx/customers/{customer_id}/records — 客户服务记录(CUST-2)
|
||||
|
||||
所有端点均需 JWT(approved 状态)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
|
||||
from app.auth.dependencies import CurrentUser
|
||||
from app.middleware.permission import require_approved
|
||||
from app.schemas.xcx_customers import (
|
||||
CustomerDetailResponse,
|
||||
CustomerRecordsResponse,
|
||||
)
|
||||
from app.services import customer_service
|
||||
|
||||
router = APIRouter(prefix="/api/xcx/customers", tags=["小程序客户"])
|
||||
|
||||
|
||||
@router.get("/{customer_id}", response_model=CustomerDetailResponse)
|
||||
async def get_customer_detail(
|
||||
customer_id: int,
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
):
|
||||
"""客户详情(CUST-1)。"""
|
||||
return await customer_service.get_customer_detail(
|
||||
customer_id, user.site_id
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{customer_id}/records", response_model=CustomerRecordsResponse)
|
||||
async def get_customer_records(
|
||||
customer_id: int,
|
||||
year: int = Query(...),
|
||||
month: int = Query(..., ge=1, le=12),
|
||||
table: str | None = Query(None),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
):
|
||||
"""客户服务记录(CUST-2)。"""
|
||||
return await customer_service.get_customer_records(
|
||||
customer_id, user.site_id, year, month, table, page, page_size
|
||||
)
|
||||
50
apps/backend/app/routers/xcx_performance.py
Normal file
50
apps/backend/app/routers/xcx_performance.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
小程序绩效路由 —— 绩效概览、绩效明细。
|
||||
|
||||
端点清单:
|
||||
- GET /api/xcx/performance — 绩效概览(PERF-1)
|
||||
- GET /api/xcx/performance/records — 绩效明细(PERF-2)
|
||||
|
||||
所有端点均需 JWT(approved 状态)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
|
||||
from app.auth.dependencies import CurrentUser
|
||||
from app.middleware.permission import require_approved
|
||||
from app.schemas.xcx_performance import (
|
||||
PerformanceOverviewResponse,
|
||||
PerformanceRecordsResponse,
|
||||
)
|
||||
from app.services import performance_service
|
||||
|
||||
router = APIRouter(prefix="/api/xcx/performance", tags=["小程序绩效"])
|
||||
|
||||
|
||||
@router.get("", response_model=PerformanceOverviewResponse)
|
||||
async def get_performance_overview(
|
||||
year: int = Query(...),
|
||||
month: int = Query(..., ge=1, le=12),
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
):
|
||||
"""绩效概览(PERF-1)。"""
|
||||
return await performance_service.get_overview(
|
||||
user.user_id, user.site_id, year, month
|
||||
)
|
||||
|
||||
|
||||
@router.get("/records", response_model=PerformanceRecordsResponse)
|
||||
async def get_performance_records(
|
||||
year: int = Query(...),
|
||||
month: int = Query(..., ge=1, le=12),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
):
|
||||
"""绩效明细(PERF-2)。"""
|
||||
return await performance_service.get_records(
|
||||
user.user_id, user.site_id, year, month, page, page_size
|
||||
)
|
||||
@@ -1,35 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
小程序任务路由 —— 任务列表、置顶、放弃、取消放弃。
|
||||
小程序任务路由 —— 任务列表、任务详情、置顶、放弃、取消放弃。
|
||||
|
||||
端点清单:
|
||||
- GET /api/xcx/tasks — 获取活跃任务列表
|
||||
- GET /api/xcx/tasks — 获取任务列表 + 绩效概览(TASK-1)
|
||||
- GET /api/xcx/tasks/{task_id} — 获取任务详情完整版(TASK-2)
|
||||
- POST /api/xcx/tasks/{id}/pin — 置顶任务
|
||||
- POST /api/xcx/tasks/{id}/unpin — 取消置顶
|
||||
- POST /api/xcx/tasks/{id}/abandon — 放弃任务
|
||||
- POST /api/xcx/tasks/{id}/cancel-abandon — 取消放弃
|
||||
- POST /api/xcx/tasks/{id}/restore — 恢复任务
|
||||
|
||||
所有端点均需 JWT(approved 状态)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
|
||||
from app.auth.dependencies import CurrentUser
|
||||
from app.middleware.permission import require_approved
|
||||
from app.schemas.xcx_tasks import AbandonRequest, TaskListItem
|
||||
from app.schemas.xcx_tasks import (
|
||||
AbandonRequest,
|
||||
TaskDetailResponse,
|
||||
TaskListResponse,
|
||||
)
|
||||
from app.services import task_manager
|
||||
|
||||
router = APIRouter(prefix="/api/xcx/tasks", tags=["小程序任务"])
|
||||
|
||||
|
||||
@router.get("", response_model=list[TaskListItem])
|
||||
@router.get("", response_model=TaskListResponse)
|
||||
async def get_tasks(
|
||||
status: str = Query("pending", pattern="^(pending|completed|abandoned)$"),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
):
|
||||
"""获取当前助教的活跃任务列表。"""
|
||||
return await task_manager.get_task_list(user.user_id, user.site_id)
|
||||
"""获取任务列表 + 绩效概览。"""
|
||||
return await task_manager.get_task_list_v2(
|
||||
user.user_id, user.site_id, status, page, page_size
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{task_id}", response_model=TaskDetailResponse)
|
||||
async def get_task_detail(
|
||||
task_id: int,
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
):
|
||||
"""获取任务详情完整版。"""
|
||||
return await task_manager.get_task_detail(
|
||||
task_id, user.user_id, user.site_id
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{task_id}/pin")
|
||||
@@ -38,7 +59,8 @@ async def pin_task(
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
):
|
||||
"""置顶任务。"""
|
||||
return await task_manager.pin_task(task_id, user.user_id, user.site_id)
|
||||
result = await task_manager.pin_task(task_id, user.user_id, user.site_id)
|
||||
return {"is_pinned": result["is_pinned"]}
|
||||
|
||||
|
||||
@router.post("/{task_id}/unpin")
|
||||
@@ -47,7 +69,8 @@ async def unpin_task(
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
):
|
||||
"""取消置顶。"""
|
||||
return await task_manager.unpin_task(task_id, user.user_id, user.site_id)
|
||||
result = await task_manager.unpin_task(task_id, user.user_id, user.site_id)
|
||||
return {"is_pinned": result["is_pinned"]}
|
||||
|
||||
|
||||
@router.post("/{task_id}/abandon")
|
||||
@@ -62,8 +85,8 @@ async def abandon_task(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{task_id}/cancel-abandon")
|
||||
async def cancel_abandon(
|
||||
@router.post("/{task_id}/restore")
|
||||
async def restore_task(
|
||||
task_id: int,
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user