每天 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)
74 lines
3.0 KiB
YAML
74 lines
3.0 KiB
YAML
|
||
services:
|
||
db:
|
||
image: pgvector/pgvector:pg15 # 换成这个
|
||
container_name: inventory_db
|
||
restart: always
|
||
environment:
|
||
POSTGRES_USER: test
|
||
POSTGRES_PASSWORD: 1234
|
||
POSTGRES_DB: inventory_system
|
||
volumes:
|
||
- ./pgdata_docker:/var/lib/postgresql/data # 这里保持不变,Docker会自动创建这个新文件夹
|
||
ports:
|
||
- "5435:5432"
|
||
|
||
backend:
|
||
build:
|
||
context: ./inventory-backend
|
||
container_name: inventory_api
|
||
restart: always
|
||
ports:
|
||
- "8000:8000"
|
||
volumes:
|
||
- ./inventory-backend:/app
|
||
- ./inventory-backend/uploads:/app/uploads
|
||
command: gunicorn -c gunicorn.conf.py run:app --reload
|
||
environment:
|
||
DATABASE_URL: postgresql://test:1234@db:5432/inventory_system
|
||
MAIL_ENABLED: "true"
|
||
MAIL_SERVER: smtp.mxhichina.com
|
||
MAIL_PORT: "465"
|
||
MAIL_USERNAME: wms@iris-rs.cn
|
||
MAIL_PASSWORD: Q7nYyyESWlaThKjx
|
||
MAIL_DEFAULT_SENDER: wms@iris-rs.cn
|
||
MAIL_USE_SSL: "true"
|
||
MAIL_USE_TLS: "false"
|
||
# MOM 系统日报收件人(逗号分隔可配多个)。每天 17:30 北京时间发送。
|
||
MAIL_DAILY_REPORT_RECIPIENTS: ${MAIL_DAILY_REPORT_RECIPIENTS:-duxingchen@iris-rs.cn}
|
||
# Track 系统 Webhook 通知(未配置则后端静默跳过,不通知)
|
||
TRACK_WEBHOOK_URL: ${TRACK_WEBHOOK_URL:-}
|
||
TRACK_WEBHOOK_KEY: ${TRACK_WEBHOOK_KEY:-}
|
||
# Track 系统查询接口(扫码入库拉取产品信息)
|
||
TRACK_API_URL: ${TRACK_API_URL:-}
|
||
# Track 出库 webhook(发货出库时通知 Track 标记"已出库")
|
||
TRACK_OUTBOUND_WEBHOOK_URL: ${TRACK_OUTBOUND_WEBHOOK_URL:-}
|
||
# ★ 多 Track 实例路由表:按 material_base.company_name 决定走哪个实例。
|
||
# 上面三个扁平变量保留为「未命中路由表」时的回落默认值(向后兼容)。
|
||
# ★ 用容器名互访(同属 projects_default 网络),不走宿主机端口。
|
||
TRACK_ROUTES: >-
|
||
{"IRIS":{"api":"http://track_backend:8000",
|
||
"inbound":"http://track_backend:8000/api/v1/external/webhooks/mom-inbound",
|
||
"outbound":"http://track_backend:8000/api/v1/external/webhooks/mom-outbound"},
|
||
"LICA":{"api":"http://lica_backend:8000",
|
||
"inbound":"http://lica_backend:8000/api/v1/external/webhooks/mom-inbound",
|
||
"outbound":"http://lica_backend:8000/api/v1/external/webhooks/mom-outbound"}}
|
||
# ★ 对内接口(Track → MOM)共享密钥,请求头 X-API-Key。
|
||
# 与 TRACK_WEBHOOK_KEY 刻意分离:方向相反、权限不同(能发起报废审批),
|
||
# 独立轮换不连坐。未配置则内部接口一律 503(Fail-Closed),不静默放行。
|
||
MOM_INTERNAL_API_KEY: ${MOM_INTERNAL_API_KEY:-}
|
||
depends_on:
|
||
- db
|
||
|
||
frontend:
|
||
build:
|
||
context: ./inventory-web
|
||
container_name: inventory_ui
|
||
restart: always
|
||
volumes:
|
||
- ./inventory-web:/app
|
||
- /app/node_modules
|
||
ports:
|
||
- "5175:5173"
|
||
depends_on:
|
||
- backend |