feat(scheduler): 新增多种报表推送功能并重构调度器代码

- 新增考勤汇总、风险进度、工作日报、工作周报等报表推送功能
- 重构调度器中的报表推送逻辑,提取通用的 deliver_report 函数
- 添加新的定时任务配置项用于各种报表推送
- 实现飞书推送配置检查函数 _feishu_delivery_configured
- 将重复的报表推送代码抽取为可复用的函数模式
```
This commit is contained in:
2026-07-13 09:47:29 +08:00
parent f8020cab56
commit 267b01b9f4
12 changed files with 722 additions and 191 deletions

View File

@@ -1,8 +1,12 @@
from app.core.background.task_queue.constants import (
TASK_DISPATCH_PENDING_EVENTS,
TASK_GENERATE_RISK_EVENTS,
TASK_PUSH_ATTENDANCE_SUMMARY,
TASK_PUSH_DAILY_BRIEF,
TASK_PUSH_PROJECT_WEEKLY,
TASK_PUSH_RISK_PROGRESS,
TASK_PUSH_WORK_DAILY,
TASK_PUSH_WORK_WEEKLY,
TASK_SYNC_LEGACY_PROJECTS,
TASK_SYNC_LEGACY_TASKS,
TASK_RUN_LIFECYCLE,
@@ -18,8 +22,12 @@ from app.core.background.task_queue.legacy import (
from app.core.background.task_queue.lifecycle import enqueue_lifecycle_report
from app.core.background.task_queue.market import enqueue_market_close, enqueue_market_report
from app.core.background.task_queue.reports import (
enqueue_attendance_summary_push,
enqueue_daily_brief_push,
enqueue_project_weekly_push,
enqueue_risk_progress_push,
enqueue_work_daily_push,
enqueue_work_weekly_push,
)
from app.core.background.task_queue.risk import enqueue_risk_event_generation
@@ -27,14 +35,19 @@ from app.core.background.task_queue.risk import enqueue_risk_event_generation
__all__ = [
"TASK_DISPATCH_PENDING_EVENTS",
"TASK_GENERATE_RISK_EVENTS",
"TASK_PUSH_ATTENDANCE_SUMMARY",
"TASK_PUSH_DAILY_BRIEF",
"TASK_PUSH_PROJECT_WEEKLY",
"TASK_PUSH_RISK_PROGRESS",
"TASK_PUSH_WORK_DAILY",
"TASK_PUSH_WORK_WEEKLY",
"TASK_SYNC_LEGACY_PROJECTS",
"TASK_SYNC_LEGACY_TASKS",
"TASK_RUN_LIFECYCLE",
"TASK_RUN_MARKET_CLOSE",
"TASK_RUN_MARKET_REPORT",
"dispatch_task",
"enqueue_attendance_summary_push",
"enqueue_daily_brief_push",
"enqueue_event_dispatch",
"enqueue_legacy_project_sync",
@@ -43,5 +56,8 @@ __all__ = [
"enqueue_market_close",
"enqueue_market_report",
"enqueue_project_weekly_push",
"enqueue_risk_progress_push",
"enqueue_risk_event_generation",
"enqueue_work_daily_push",
"enqueue_work_weekly_push",
]

View File

@@ -1,5 +1,9 @@
TASK_PUSH_DAILY_BRIEF = "reports.push_daily_brief"
TASK_PUSH_PROJECT_WEEKLY = "reports.push_project_weekly"
TASK_PUSH_ATTENDANCE_SUMMARY = "reports.push_attendance_summary"
TASK_PUSH_RISK_PROGRESS = "reports.push_risk_progress"
TASK_PUSH_WORK_DAILY = "reports.push_work_daily"
TASK_PUSH_WORK_WEEKLY = "reports.push_work_weekly"
TASK_GENERATE_RISK_EVENTS = "risks.generate_events"
TASK_SYNC_LEGACY_PROJECTS = "legacy.sync_projects"
TASK_SYNC_LEGACY_TASKS = "legacy.sync_tasks"

View File

@@ -1,127 +1,138 @@
from collections.abc import Callable
from typing import Any
from app.core.config import get_settings
from app.modules.feishu.constants import FeishuReceiveIdType
from app.core.background.task_queue.constants import (
TASK_PUSH_ATTENDANCE_SUMMARY,
TASK_PUSH_DAILY_BRIEF,
TASK_PUSH_PROJECT_WEEKLY,
TASK_PUSH_RISK_PROGRESS,
TASK_PUSH_WORK_DAILY,
TASK_PUSH_WORK_WEEKLY,
)
from app.core.background.task_queue.dispatcher import dispatch_task
from app.core.config import get_settings
from app.core.constants import ActorValue
from app.modules.feishu.constants import FeishuReceiveIdType
from app.modules.reports.constants import ReportPushType, ReportTitle
ReportBuilder = Callable[[Any, str], dict[str, Any]]
def enqueue_daily_brief_push(
receive_id: str | None = None,
receive_id_type: str = FeishuReceiveIdType.CHAT_ID,
actor: str = "scheduler",
actor: str = ActorValue.SCHEDULER,
) -> dict[str, Any]:
settings = get_settings()
if settings.task_queue_enabled:
from app.core.database import SessionLocal
from app.modules.reports.constants import ReportPushStatus, ReportTitle, ReportType
from app.modules.reports.services import ReportService
from app.tasks import celery_app
db = SessionLocal()
try:
push_run = ReportService(db).create_push_run(
report_type=ReportType.DAILY,
title=ReportTitle.DAILY_BRIEF,
receive_id=receive_id,
receive_id_type=receive_id_type,
actor=actor,
status=ReportPushStatus.QUEUED,
)
async_result = celery_app.signature(
TASK_PUSH_DAILY_BRIEF,
kwargs={
"receive_id": receive_id,
"receive_id_type": receive_id_type,
"actor": actor,
"push_run_code": push_run.code,
},
).apply_async()
ReportService(db).update_push_run(
push_run.code,
ReportPushStatus.QUEUED,
task_id=async_result.id,
)
finally:
db.close()
return {
"queued": True,
"mode": "celery",
"task_name": TASK_PUSH_DAILY_BRIEF,
"task_id": async_result.id,
"push_run_code": push_run.code,
}
def inline() -> Any:
from app.core.database import SessionLocal
from app.modules.reports.services import ReportService
db = SessionLocal()
try:
report = ReportService(db).daily_brief()
return ReportService(db).push_report(report, receive_id, receive_id_type, actor)
finally:
db.close()
return dispatch_task(
TASK_PUSH_DAILY_BRIEF,
{
"receive_id": receive_id,
"receive_id_type": receive_id_type,
"actor": actor,
},
inline,
return _enqueue_report_push(
task_name=TASK_PUSH_DAILY_BRIEF,
report_type=ReportPushType.DAILY_BRIEF,
title=ReportTitle.DAILY_BRIEF,
build_report=_build_daily_brief,
receive_id=receive_id,
receive_id_type=receive_id_type,
actor=actor,
)
def enqueue_project_weekly_push(
receive_id: str | None = None,
receive_id_type: str = FeishuReceiveIdType.CHAT_ID,
actor: str = "scheduler",
actor: str = ActorValue.SCHEDULER,
) -> dict[str, Any]:
return _enqueue_report_push(
task_name=TASK_PUSH_PROJECT_WEEKLY,
report_type=ReportPushType.PROJECT_WEEKLY,
title=ReportTitle.PROJECT_WEEKLY,
build_report=_build_project_weekly,
receive_id=receive_id,
receive_id_type=receive_id_type,
actor=actor,
)
def enqueue_attendance_summary_push(
receive_id: str | None = None,
receive_id_type: str = FeishuReceiveIdType.CHAT_ID,
actor: str = ActorValue.SCHEDULER,
) -> dict[str, Any]:
return _enqueue_report_push(
task_name=TASK_PUSH_ATTENDANCE_SUMMARY,
report_type=ReportPushType.ATTENDANCE_SUMMARY,
title=ReportTitle.ATTENDANCE_SUMMARY,
build_report=_build_attendance_summary,
receive_id=receive_id,
receive_id_type=receive_id_type,
actor=actor,
)
def enqueue_risk_progress_push(
receive_id: str | None = None,
receive_id_type: str = FeishuReceiveIdType.CHAT_ID,
actor: str = ActorValue.SCHEDULER,
) -> dict[str, Any]:
return _enqueue_report_push(
task_name=TASK_PUSH_RISK_PROGRESS,
report_type=ReportPushType.RISK_PROGRESS,
title=ReportTitle.RISK_PROGRESS,
build_report=_build_risk_progress,
receive_id=receive_id,
receive_id_type=receive_id_type,
actor=actor,
)
def enqueue_work_daily_push(
receive_id: str | None = None,
receive_id_type: str = FeishuReceiveIdType.CHAT_ID,
actor: str = ActorValue.SCHEDULER,
) -> dict[str, Any]:
return _enqueue_report_push(
task_name=TASK_PUSH_WORK_DAILY,
report_type=ReportPushType.WORK_DAILY,
title=ReportTitle.WORK_DAILY,
build_report=_build_work_daily,
receive_id=receive_id,
receive_id_type=receive_id_type,
actor=actor,
)
def enqueue_work_weekly_push(
receive_id: str | None = None,
receive_id_type: str = FeishuReceiveIdType.CHAT_ID,
actor: str = ActorValue.SCHEDULER,
) -> dict[str, Any]:
return _enqueue_report_push(
task_name=TASK_PUSH_WORK_WEEKLY,
report_type=ReportPushType.WORK_WEEKLY,
title=ReportTitle.WORK_WEEKLY,
build_report=_build_work_weekly,
receive_id=receive_id,
receive_id_type=receive_id_type,
actor=actor,
)
def _enqueue_report_push(
task_name: str,
report_type: str,
title: str,
build_report: ReportBuilder,
receive_id: str | None,
receive_id_type: str,
actor: str,
) -> dict[str, Any]:
settings = get_settings()
if settings.task_queue_enabled:
from app.core.database import SessionLocal
from app.modules.reports.constants import ReportPushStatus, ReportTitle, ReportType
from app.modules.reports.services import ReportService
from app.tasks import celery_app
db = SessionLocal()
try:
push_run = ReportService(db).create_push_run(
report_type=ReportType.WEEKLY,
title=ReportTitle.PROJECT_WEEKLY,
receive_id=receive_id,
receive_id_type=receive_id_type,
actor=actor,
status=ReportPushStatus.QUEUED,
)
async_result = celery_app.signature(
TASK_PUSH_PROJECT_WEEKLY,
kwargs={
"receive_id": receive_id,
"receive_id_type": receive_id_type,
"actor": actor,
"push_run_code": push_run.code,
},
).apply_async()
ReportService(db).update_push_run(
push_run.code,
ReportPushStatus.QUEUED,
task_id=async_result.id,
)
finally:
db.close()
return {
"queued": True,
"mode": "celery",
"task_name": TASK_PUSH_PROJECT_WEEKLY,
"task_id": async_result.id,
"push_run_code": push_run.code,
}
return _queue_celery_report_push(
task_name=task_name,
report_type=report_type,
title=title,
receive_id=receive_id,
receive_id_type=receive_id_type,
actor=actor,
)
def inline() -> Any:
from app.core.database import SessionLocal
@@ -129,13 +140,14 @@ def enqueue_project_weekly_push(
db = SessionLocal()
try:
report = ReportService(db).project_weekly()
return ReportService(db).push_report(report, receive_id, receive_id_type, actor)
service = ReportService(db)
report = build_report(service, actor)
return service.push_report(report, receive_id, receive_id_type, actor)
finally:
db.close()
return dispatch_task(
TASK_PUSH_PROJECT_WEEKLY,
task_name,
{
"receive_id": receive_id,
"receive_id_type": receive_id_type,
@@ -143,3 +155,79 @@ def enqueue_project_weekly_push(
},
inline,
)
def _queue_celery_report_push(
task_name: str,
report_type: str,
title: str,
receive_id: str | None,
receive_id_type: str,
actor: str,
) -> dict[str, Any]:
from app.core.database import SessionLocal
from app.modules.reports.constants import ReportPushStatus
from app.modules.reports.services import ReportService
from app.tasks import celery_app
db = SessionLocal()
try:
push_run = ReportService(db).create_push_run(
report_type=report_type,
title=title,
receive_id=receive_id,
receive_id_type=receive_id_type,
actor=actor,
status=ReportPushStatus.QUEUED,
)
async_result = celery_app.signature(
task_name,
kwargs={
"receive_id": receive_id,
"receive_id_type": receive_id_type,
"actor": actor,
"push_run_code": push_run.code,
},
).apply_async()
ReportService(db).update_push_run(
push_run.code,
ReportPushStatus.QUEUED,
task_id=async_result.id,
)
finally:
db.close()
return {
"queued": True,
"mode": "celery",
"task_name": task_name,
"task_id": async_result.id,
"push_run_code": push_run.code,
}
def _build_daily_brief(service: Any, actor: str) -> dict[str, Any]:
_ = actor
return service.daily_brief()
def _build_project_weekly(service: Any, actor: str) -> dict[str, Any]:
_ = actor
return service.project_weekly()
def _build_attendance_summary(service: Any, actor: str) -> dict[str, Any]:
_ = actor
return service.attendance_summary()
def _build_risk_progress(service: Any, actor: str) -> dict[str, Any]:
_ = actor
return service.risk_progress()
def _build_work_daily(service: Any, actor: str) -> dict[str, Any]:
return service.work_daily_report(reporter=actor, actor=actor)
def _build_work_weekly(service: Any, actor: str) -> dict[str, Any]:
return service.work_weekly_report(reporter=actor, actor=actor)