feat: 数字员工财务报告体系与定时提醒及看板快照调度

- 新增数字员工财务报告生成、邮件投递与渲染调度器
- 引入员工画像扫描调度与定时提醒任务
- 完善财务看板快照、排行口径与部门人员占比计算
- 优化数字员工工作看板仪表盘与技能目录
- 增强前端总览页图表、工作台摘要与顶部导航栏交互
- 新增差旅申请规划推动提醒与报销创建会话状态管理
- 补充财务报告、看板调度、数字员工工作记录测试覆盖
This commit is contained in:
caoxiaozhu
2026-06-03 09:25:23 +08:00
parent 0c74b4ab4a
commit 15006a05a7
114 changed files with 7356 additions and 650 deletions

View File

@@ -29,7 +29,6 @@ from app.services.demo_company_simulation_catalog import ( # noqa: E402
BUDGETED_STATUSES,
PENDING_STATUSES,
SIM_BUDGET_PREFIX,
SIM_CLAIM_PREFIX,
SIM_EMPLOYEE_PREFIX,
SIM_PROJECT_CODE,
SIM_RESERVATION_PREFIX,
@@ -60,6 +59,8 @@ RECENT_DATES = (
datetime(2026, 6, 1, 15, 0, tzinfo=UTC),
datetime(2026, 6, 2, 6, 0, tzinfo=UTC),
)
PERIOD_START = date(2026, 1, 1)
PERIOD_END = date(2026, 6, 2)
@dataclass(frozen=True, slots=True)
@@ -139,6 +140,7 @@ def repair_distribution(db, *, apply: bool) -> RepairSummary:
if apply:
_normalize_sim_claim_workflow(sim_claims)
_clamp_sim_claim_dates(sim_claims)
_redistribute_employees(sim_employees, departments, employee_plan)
db.flush()
employees_by_dept = _employees_by_department(db)
@@ -235,8 +237,8 @@ def _sim_claims(db) -> list[ExpenseClaim]:
db.scalars(
select(ExpenseClaim)
.options(selectinload(ExpenseClaim.items))
.where(ExpenseClaim.claim_no.like(f"{SIM_CLAIM_PREFIX}%"))
.order_by(ExpenseClaim.claim_no.asc())
.where(ExpenseClaim.project_code == SIM_PROJECT_CODE)
.order_by(ExpenseClaim.created_at.asc(), ExpenseClaim.claim_no.asc())
).all()
)
@@ -254,6 +256,23 @@ def _normalize_sim_claim_workflow(claims: list[ExpenseClaim]) -> None:
claim.approval_stage = normalized.approval_stage
def _clamp_sim_claim_dates(claims: list[ExpenseClaim]) -> None:
for index, claim in enumerate(claims):
occurred_at = claim.occurred_at or claim.submitted_at
if occurred_at is None:
continue
if PERIOD_START <= occurred_at.date() <= PERIOD_END:
continue
anchor = RECENT_DATES[index % len(RECENT_DATES)]
claim.occurred_at = anchor - _hours(2)
if claim.submitted_at is not None or claim.status != "draft":
claim.submitted_at = anchor
claim.created_at = claim.occurred_at
claim.updated_at = anchor + _hours(1)
for item in claim.items or []:
item.item_date = claim.occurred_at.date()
def _counts_by_weight(total: int) -> dict[str, int]:
raw = [(code, total * weight) for code, weight in DEPARTMENT_PLAN]
counts = {code: int(value) for code, value in raw}