Commit Graph

2 Commits

Author SHA1 Message Date
1d6beaa376 feat(daily-report): 正文保持摘要,新增 Excel 附件给全量明细
日报此前把明细压到极简(修改只列变更字段≥3 的记录、新增只给模块计数),
"199 条新增只看到 3 个模块名",查不到某条具体记录改了什么。

但也不能全塞进正文:实测某日 354 条修改展开近 3000 行,客户端渲染卡顿,
部分企业邮件网关会把超长自动邮件判为垃圾或截断 —— 发件方照样收到 250,
表面看一切正常,这比"信息少"更难排查。

故拆成两路:正文保持摘要(render),附件给全量明细(render_excel),
六个工作表不设 DETAIL_MIN_FIELDS 之类的阈值。

- email_service: send_email/send_email_async 支持 attachments。数据是内存
  字节、不落盘;中文文件名走 RFC 2231;.xlsx 用官方 MIME 类型,写错会让
  Outlook 当成未知二进制、附件无法直接打开。
- audit_export_service: 新增。审计日志 → 可读值/Excel 的共用层。
  「同一条「实际审批人ID: 7」一边翻了人名一边没翻」这类漂移,比不翻译更
  难发现,故取值/翻译/排版逻辑集中在此,由日报与审计页导出共用。
- daily_report_service: 私有副本改为消费上述共用层(纯重构)。
  附件大小与重构前逐字节一致(490.5K / 193.9K / 10.3K),行为未变。

附件生成失败时降级为纯文本正文继续发送,但记 error 级日志 —— 降级后邮件
看起来完全正常,不留显眼日志的话附件静默丢失可以持续几个月没人发现。
2026-09-28 09:46:25 +08:00
ded5dfd7b3 feat(report): MOM 系统日报(纯模板,不接 AI)
每天 17:30(北京时间)自动汇总当日 新增/修改/删除、出库、借库,
渲染为纯文本邮件发送。

为什么不用 AI
--------------
此前那份日报由 Dify(外部 AI 服务,见 api/v1/ai_proxy.py)生成,
好处是能写人话,代价是:
  · 外部依赖一断,整份日报就没了 —— 这正是"AI 坏了"之后发生的事;
  · 按次计费、走网络、要管密钥;
  · **会在报表里编数字**,而日报是给人做决策用的,数字必须可信。
日报是固定格式的结构化统计(计数 + 清单),本来也不需要创造力。
数据全在库里:audit_logs / trans_outbound / trans_borrow。

口径与审计页保持一致
--------------------
  · action 经 canon_action() 归一化(历史数据里 CREATE 与「新增」混用,
    不归一会漏掉早期数据);
  · 默认排除 system 占位账号(与审计页默认的"真实用户"视图一致)。
  · 凡有上限处一律显式写明「另有 N 条」,不静默截断。
  · 变更值全部翻译成人类可读:状态码按模块查表、审批人/物料 ID → 名称、
    布尔 → 是/否、人名串规整。翻不动就原样显示 —— 报表里的错译比裸数字
    更难被发现,也更危险。

★ 顺带修掉两个既有缺陷(日报每天都会走这条路径,不修会一直中招):

1) 定时任务被 8 个 worker 重复执行
   gunicorn.conf.py 配了 8 worker,而 run.py 在**模块级**启动 APScheduler
   —— gunicorn 未开 preload_app,每个 worker 独立 import 一次 run.py,
   于是每个 worker 各起一份调度器,同一 cron 任务被并发执行 8 次。
   现有库存预警邮件因此每天重复发 8 封。
   新增 app/utils/job_lock.py:用 PostgreSQL 会话级咨询锁做跨进程互斥,
   抢到锁的 worker 才执行,其余静默跳过。不引入新组件(compose 里没有
   redis,redis_client 恒为 None,相关装饰器全程 fail-open,指望不上)。
   实测 8 线程并发抢锁恰好 1 个成功。

2) 邮箱授权码被打印进日志
   email_service.py 的 print(f"[DEBUG send_email] cfg = {cfg}") 会把含
   password 的整个配置打到 stdout → docker logs。改为只打非敏感字段。

新增配置:MAIL_DAILY_REPORT_RECIPIENTS(逗号分隔,默认 duxingchen@iris-rs.cn)
2026-09-23 17:16:26 +08:00