|
|
70ddb27519
|
fix(audit): 采集真实客户端 IP,修正经 nginx 反代后全记成容器地址
原实现取 request.client.host,但所有请求都经前端 nginx 反代
(frontend/nginx.conf 的 location /api/),后端看到的 TCP 对端永远是
nginx 容器在 docker 网络里的内网地址 —— 手机、电脑、不同的人,审计里
记下的都是同一个 IP,「谁从哪来」这一列失去意义。
改为 _client_ip(),按可信度取值:
1. X-Real-IP —— nginx 用 proxy_set_header 强制覆盖,客户端伪造不了
2. X-Forwarded-For 首段 —— 兼容将来在前面再加网关 / CDN 的部署
3. 直连对端 —— 开发环境走 Vite 代理(未开 xfwd),上头两个头都不会有
|
2026-10-08 14:39:01 +08:00 |
|
|
|
07ad2b6ca0
|
fix(audit): 还原动作曾被记成「新增」,显式登记 restore
POST /products/{id}/restore 没有在 _SEGMENT_ACTION 里登记动作,
兜底到 _METHOD_ACTION 的 POST → create,于是在审计页上显示成「新增」——
「把删掉的东西找回来」被记成了「新建了一个产品」,语义完全走样。
这正是代码里那条规矩的实例:新端点若语义与 HTTP 方法不一致,必须显式登记
动作并同步 ACTION_LABELS,否则要么显示英文 key、要么显示一个语义错误的现成标签。
新增业务端点后,务必去审计页确认它记成了什么动作。
|
2026-09-28 17:33:10 +08:00 |
|
|
|
6e9876993f
|
feat(api): 产品与任务引入逻辑删除,删除可还原、审计可追溯
物理删除有两个后果:审计日志反查不到业务名称(目标列退化成 UUID),
删除动作本身也丢失全部上下文。改为软删除后数据保留 90 天,可还原、可追查。
过滤做成全局的:core/soft_delete.py 用 do_orm_execute 事件注入
deleted_at IS NULL,覆盖全部 72 处 select(Product) 及关联预加载,一处都不用改。
逐个手写就是当初说的「手写必然漏」,而且漏的是业务正确性——已删除的产品
会重新冒到列表、看板和统计里。需要看已删除数据的两处(审计名称反查、
将来的还原接口)显式传 include_deleted 逃生口。
注册点放在 core/database.py 而非 main.py:过滤是 Session 的属性,
放 main 里会让一次性脚本和测试静默失去过滤。serial_number 的唯一索引
必须改成部分唯一索引(WHERE deleted_at IS NULL),否则删掉一个产品后
同序列号再也建不了——删掉重贴一张标是车间高频操作。
软删不再切断父子关系:旧实现把 parent_product_id / parent_task_id 置空,
不可逆,会让还原还回来一个被拆散的产品。现在整棵子树一起隐藏、关系原样保留。
删除动作经 ORM 赋值触发 audit_diff,审计里记录实体被删前的状态快照。
附 90 天物理清除脚本(默认试运行)。删除顺序不可改:实测指向
products/tasks 的 13 条外键里只有 2 条有自动规则,其余全是 NO ACTION,
子表不清干净父表就删不掉,自引用也得先断开。
|
2026-09-28 17:05:00 +08:00 |
|
|
|
1ec8c1d979
|
refactor(audit): 采集范围收敛,补齐可读名反查与字段级变更 Diff
采集范围只保留两类:写操作(含被拒绝的 4xx/5xx)与导出/下载 GET。
POST /auth/refresh 走精确黑名单屏蔽——前端自动续期是纯后台行为,
混进来既刷屏又虚高人员统计的操作次数;核心业务详情 GET 的采集规则整体移除,
只读不改的查询会把真正的修改淹没。导出/下载改按完整路径段匹配,
原先的子串匹配会把 GET /print/config 整条误采。
读取侧把 target_id 批量反查成业务可读名(产品→序列号、任务→任务名、
消息→标题、订单→订单号),产品被删后审计仍能显示编号而非 UUID;
details 列加 none_as_null,避免空请求体被存成 JSON 字面量 null 导致
WHERE details IS NULL 永远查不到。
新增字段级变更:core/audit_diff.py 用 ORM 的 before_flush 采集
{实体: {字段: {old, new}}},独立存 changes 列——details 记用户提交的意图,
changes 记库里真正改掉的字段,后者能捕捉到 DTO 里没有的隐式改动。
|
2026-09-28 17:04:44 +08:00 |
|
|
|
5666e65786
|
feat(calendar): 新增工作日历,支持调休补班
工时统计原先的判定是 `weekday() < 5 and day not in holidays`,而 holidays 表只有
day + name(语义是「放假日期」),所以这套判定只能减不能加。法定补班的周末
(如 2026-09-20 周日、10-10 周六)按定义就是周末,会被 weekday() 直接短路掉,
整天不计工时 —— 系统性少算。
加 kind 列区分两种语义:holiday 放假(不计工时)/ workday 补班(计工时)。存量
行靠 server_default 回填成 holiday(语义不变),不需要单独 UPDATE。
判定收进 WorkCalendar,优先级固定为 补班 → 放假 → 周末。补班必须排第一:若先判
weekday(),周日会立刻返回 False 而永远走不到补班分支,这正是原 bug 的形态。
另用值对象而非「再加一个同型集合参数」—— 两个相邻的 set[date] 参数写反了 Python
不报错,结果是工时静默错算,与本次要修的失效模式同源。
|
2026-09-28 15:01:40 +08:00 |
|
|
|
a6b78150fc
|
feat(audit): 记录并展示请求体(脱敏,8KB 上限)
details 字段此前恒为 NULL —— 中间件从不传它,「新增/修改了哪些字段、
值是什么」完全没有留痕,前端详情抽屉的「变更详情」区块因此永远不渲染。
- 中间件在 dispatch 中捕获 POST/PUT/PATCH 的请求体写入 details。
必须在 call_next 之前读:Starlette 的 _CachedRequest 会把读过的 body
缓存下来转交下游,所以不会饿死业务端点;反过来在响应后读,流已被
消费,只会拿到空
- 大小控制:先看 Content-Length(大文件上传不必读进内存),超 8KB 只记
体积不记内容 —— 截断的 JSON 无法解析,记下体积对排查反而更有用
- 脱敏统一交给 record_audit 的 sanitize_details,中间件不重复实现:
两处各写一份敏感字段规则,迟早漂移出一个漏网的
- 前端详情抽屉新增「提交内容」区块,顶层键值对平铺展示(审计里的请求体
多是三五个扁平字段,裸 JSON 的花括号引号反而碍事)
|
2026-09-23 16:01:11 +08:00 |
|
|
|
9de8eaee89
|
feat(audit): 按业务语义细化动作推断,修复 scan 路径目标提取
动作原先只按 HTTP 方法推断,POST 一律记成「新增」,于是「新建产品」
「给设备登记出库明细」「给设备登记报废」在审计页上完全看不出区别。
- 新增 _FEATURE_ACTION 映射(HTTP 方法 + 路径特征段),推断改为三层、
先命中者胜:业务语义 → 端点动作词 → HTTP 方法兜底
- 覆盖出库 / 报废 / 留言 / 子任务 / 任务记录 / 打印配置 / MOM 回调等动作。
匹配路径中任意一段而非末段,因为
DELETE /products/{id}/outbound-materials/by-order/{no} 的末段是单号
- 修复 GET /products/scan/{sn} 把 "scan" 当目标落库:新增 _ID_PREFIX_SKIP
(命中则往后再看一段,真正的 ID 在其后),与 _NON_ID_SEGMENTS
(命中即放弃提取)是两种不同处理,故分开成两个集合
- 顺带修复 /external/webhooks/... 把模块名 "webhooks" 当目标 ——
补上通用规则:模块名本身不参与 ID 提取
- ACTION_LABELS 补齐 11 个新动作的中文标签
|
2026-09-23 16:01:02 +08:00 |
|
|
|
1267cb03de
|
feat(audit): 审计「目标」改为显示产品序列号
中间件从 URL 推导 target 时拿到的是产品 UUID 主键,列表和导出直接把它
渲染出来 —— 同一个产品的多条记录长得一模一样,管理员根本认不出是哪台
设备(实测某产品连续 8 条记录肉眼无法区分)。
- 新增 resolve_product_names():读取端按 target_id 批量反查 serial_number,
一次 IN 查询覆盖整页,不给写入路径加负担,历史数据也能一并变可读
- 列表端点与 CSV 导出共用同一份映射,避免「页面上看到的」和「导出出去的」
口径漂移
- 中间件不再把 UUID 兜底写进 target_name:那是「可读名」字段,原注释误以为
路径里的 ID 就是身份证号,实际路径带的是 UUID 主键
- 前端补「目标」列(此前列表压根没有);动作 Tag 按破坏性分色
(新增绿 / 修改蓝 / 删除红),原先三种动作同为蓝色,扫列表分不出删还是增
|
2026-09-23 16:00:49 +08:00 |
|
|
|
551819e0e3
|
feat(scrap): Track 侧生产报废 —— 提交、回查、金额
料领到产线后在生产中报废,要在 MOM 里走报废流程并能统计金额。
- mom_scrap_client:Track **唯一**一处主动写 MOM 的通道。读仍走直连只读库
(MOM 查询接口有权限与行级隔离),写必须走接口(跨库直写会绕过 MOM 的
全部业务校验、权限与审批)。
- product_scrap_service:归属校验是关键 —— 可见范围是整台设备、不是「谁领的」,
不能靠隐藏来防,必须在写入前确认这条 mom_line_id 就挂在这台设备上。
申请人直接用当前登录人(Track 的 sub 就是 MOM sys_user.id),
MOM 里显示的就是本人,不需要服务账号也不会串人。
- 幂等:track_ref 由前端在打开弹层时生成一次、重试复用;网络超时后重试
不该在 MOM 里多报一张单。
- 状态与金额**实时回查 MOM**,不在本地存副本:报废没有回调,本地那份立刻
就过期;且金额取决于执行时的实际扫码量(MOM 允许少扫),受理量 ≠ 执行量。
⚠️ 未执行时 total_loss 是 null 不是 0 —— 0 会让人以为「这东西不值钱」。
- MOM_INTERNAL_API_KEY 走环境变量且不给默认值:未配置时报废提交 503,
而不是让一个写接口在生产上默默开着。
|
2026-09-23 15:18:06 +08:00 |
|
|
|
42f6e242b4
|
fix(qrcode): 二维码接口去鉴权,并把 qrcode 路径排除出审计
前端以 <img src="/api/v1/products/qrcode/{sn}"> 引用该端点,而 <img> 无法携带
Authorization 头 —— 加了鉴权会让所有二维码图片加载失败(页面显示成破图),
并在审计里刷出大量 401。
去鉴权是安全的:本函数不查数据库,只校验长度并把这个字符串渲染成二维码,
没有任何业务数据泄露面(序列号本身就是调用方提供的)。也刻意不支持 ?token=
兜底 —— 把 JWT 放进 URL 会渗进访问日志、浏览器历史与 Referer,比它想解决的
问题更糟。
随之而来的副作用必须一并处理:qrcode 路径命中 _TRACKED_READ_PREFIXES 里的
/api/v1/products 前缀、又不是 bare list,会被判成「查看详情」逐条留痕。列表页
一次渲染就并发拉几十张图,逐条留痕会把审计日志塞满,真正有价值的操作反而被
淹没。故把 /api/v1/products/qrcode 加进 _IGNORED_PREFIXES。
实测:二维码正常加载;审计中不再出现 qrcode 记录。
|
2026-09-22 13:43:36 +08:00 |
|
|
|
192c8ee9cc
|
feat(audit): 扩大 GET 采集范围 + 修正动作文案
【扩大采集:核心业务数据的「查看详情」】
新增 _TRACKED_READ_PREFIXES(notifications / tasks / orders /
products / records)—— products 是补的:GET /products/scan/{sn}(扫码查询)
是整个车间最高频的读操作,不采它等于没采"活跃度"。
_should_audit 的 GET 分支改为三级判断:
敏感读(export/download/print) → 受跟踪前缀且非裸列表 → 否则不采
用 _is_bare_list 跳过「拉整个列表」:
· 列表接口被前端高频轮询(消息、任务列表尤其明显),逐条留痕会让
audit_logs 迅速膨胀,真正有价值的操作反而被淹没;
· 只有「查看详情」(/tasks/{id}) 才代表用户真的点开了某条业务数据。
判定用「去掉末尾斜杠后是否恰好等于某前缀」,天然排除查询串。
【修正文案】
- "read": "查询" → "查看详情"(前者易被误解成"随便搜了一下")
- 新增 "mark_read": "标为已读",并在 _SEGMENT_ACTION 补 "read" 映射 ——
否则 PUT /notifications/{id}/read 会回退到 _METHOD_ACTION(PUT→update),
把"点开一条通知"记成"修改了某样东西"
- "refresh": "刷新令牌" → "上线"(token 2 小时一换,业务上视作一次上线)
⚠️ 两点须知:
1. notifications / orders 目录下【只有列表路由】,而列表按规则不采集,
故这两个模块不会产生查看记录 —— 后端没有"查看单条消息"的接口。
且消息列表被前端轮询,采集它反而会造成日志爆炸,跳过是正确的。
2. products 被纳入后,工人每扫一次码就多一条记录(50 人 × 每天数百次
≈ 上万条/天)。若嫌吵,去掉该前缀一行即可。
注:读接口鉴权已在上一提交补齐,故这些"查看详情"记录能正确挂上操作人。
|
2026-09-21 13:47:44 +08:00 |
|
|
|
c3667fe00d
|
fix(audit): 刷新令牌记录不再显示「未认证」
问题:审计列表里 POST /auth/refresh 的操作人恒为「未认证」。
根因:本接口刻意不挂 get_current_user —— 能用到这里,正是因为 access token
已过期、请求里没有 Authorization 头,JWT 依赖不执行,request.state 里
从未写入操作人。而"谁在何时尝试刷新"恰恰是该留痕的信息。
修复:
- core/security.py 新增 peek_token_identity(),与 decode_token 的唯一区别是
关闭过期校验(刷新场景令牌本就过期,若因过期解不出来还是会漏记)。
签名校验照常进行,伪造令牌解不出任何东西。
⚠️ docstring 中明确:该函数只许用于写审计字段,鉴权一律走 get_current_user
- refresh 接口解码 refresh token 取得 sub/username/display_name/role 写入 state。
refresh token 的载荷与 access token 完全一致,只有 type 字段不同。
附带效果:活动打点读的正是 request.state.audit_user,修复后刷新请求也会
被计为一次活动 —— 语义正确(会刷新说明用户正在使用)。
验证(7/7):正常刷新记到中文人名与角色;过期令牌虽被拒 401 但仍能记到人;
伪造令牌不认人、显示未认证。历史记录不追溯。
|
2026-09-21 13:05:41 +08:00 |
|
|
|
39697ca3ad
|
feat(audit): 每日活动打点表 —— 修正日活「上线/下线时间」口径
【问题】
上线/下线时间此前取自审计记录(写操作)时间,当天只翻看、没做写操作的人
会被整条漏掉;而取登录时间更错(Refresh Token 有效期 7 天,用户不必每天登录,
会出现「登录次数 0 却操作 35 次」的自相矛盾报表)。
【方案 C+:一天一人一行的小状态表】
- 新增 user_daily_seen(user_id, day, first_seen_at, last_seen_at),
迁移 k1l2m3n4o5p6(紧接 j1k2l3m4n5o6)
- 打点挂在 RequestContextMiddleware —— 它是最外层,能覆盖【所有】请求,
含不被审计的普通 GET。挂审计中间件没用:那里只记写操作,正是漏人的原因
- 节流:进程内缓存,同一用户 2 分钟内只落盘一次,把"每请求一次写库"
压到"每人每 2 分钟一次";代价是末次活动时间最多落后 2 分钟
- UPSERT on_conflict_do_update 只刷 last_seen_at,first_seen_at 保持当天首次值
- 打点失败全部吞掉并记日志,绝不影响业务请求
【为什么不复用 audit_logs】
「末次活动」是需要不断 UPDATE 的状态,而审计流水必须只增不改 ——
能改的审计记录等于没有审计价值。写进审计表还会让表随访问量线性膨胀。
【查询合并】
get_daily_usage 改为「活动表 ∪ 审计表」并集:只有活动记录的(只看不操作)
和只有审计记录的(本表上线前的历史数据)都会出现。
上线/下线时间取两者的【最早 / 最晚】而非"活动表优先"——打点有 2 分钟节流、
跨零点或写库失败时可能晚于当天第一次写操作,取 min/max 后结果永不劣于任一来源。
零前端改动、零移动端发版:打点在服务端,PC 与移动端同一套口径、同一张表。
|
2026-09-21 13:05:35 +08:00 |
|
|
|
7635802a42
|
feat(audit): 新增操作审计日志(表/中间件/查询接口)+ 角色常量收敛
背景:系统此前没有操作审计。task_logs 的 task_id 是 NOT NULL 外键,只能挂在
任务上,且全项目仅 4 处写入点 —— 登录、导出、产品增删改、收编完全不留痕。
需求方整理的问题清单里「无审计日志查看页」正源于此:不是没有页面,是没数据。
设计参考 MOM(KCGL) 的 audit_logs / audit_listener,但按 Track 栈做了取舍:
1) 写入时机:MOM 用 SQLAlchemy event listener + 同事务写入,优点是零侵入,
缺点是**业务回滚时审计一起消失**,而失败/被拒的操作(越权尝试、参数错误)
恰恰最需要留痕。Track 改为响应生成后用**独立 session** 写入:
- 业务回滚不影响审计(已验证 422/401 失败操作同样落库)
- 审计写入失败也不影响业务(全包裹 try/except)
- 代价:非原子提交,响应后进程立即被 kill 可能丢一条(已注释说明取舍)
2) 采集方式:中间件自动采集写操作 + 导出/下载/打印这类「读但敏感」的 GET。
路径段推导 module/action/target_id。不做手写埋点,因为手写必然漏 ——
task_logs 只有 4 处写入点就是前车之鉴。
3) 增量价值:新增 request_id 字段,与 core/logging.py 的结构化日志打通,
凭一个 ID 就能从审计记录直接跳到那一次接口日志。MOM 无此字段。
4) 敏感信息:details 经 sanitize_details 递归剔除 password/token/secret 等键;
中间件不读请求体,登录明文密码不会落库(已断言表内无密码痕迹)。
配套改动:
- core/roles.py:角色常量与 is_admin 收敛为单一事实来源。此前同一份
「管理员角色」规则散在 task_service、products.py 内联判断和前端
constants/task.ts 三处,已因此发生过「移动端漏判 SUPERVISOR 误挡主管」。
task_service 改为从 core.roles 导入同名常量,保持既有引用可用。
- core/deps.py:抽出 require_roles/require_admin 可复用依赖,替代内联判断。
- main.py:500 响应显式补 X-Request-ID 头 —— 该响应由 ServerErrorMiddleware
生成,位于 RequestContextMiddleware 外层,中间件没机会写头。
- auth.py:登录校验前把「尝试的账号」写入 request.state,使登录事件
(含失败登录)可归属到人,可用于追踪暴力破解。
验证:本地起 PostgreSQL 17 + 迁移后跑端到端测试,32/32 通过
(TestClient 每个请求新建事件循环,与模块级 asyncpg 连接池冲突会报
"got Future attached to a different loop",故改用 httpx.AsyncClient +
ASGITransport 单循环;生产 uvicorn 单循环无此问题)。
|
2026-09-21 02:23:19 +00:00 |
|
|
|
04eb87b091
|
fix: 访问日志的 user 字段恒为 null —— 改用 request.state 跨 task 传递
上一提交(4454047)的 RequestContextMiddleware 通过 contextvar 读取 user,
但 Starlette 的 BaseHTTPMiddleware 用 anyio start_soon 把下游放进新 task
执行,而 asyncio 每个 Task 创建时会复制 context —— 路由内 set 的
contextvar 不会回流到中间件,导致访问日志的 user 永远是 null。
修复:
- get_current_user 同时写 contextvar(供请求任务内业务日志用)与
request.state(由 ASGI scope 承载,跨 task 可见),并带上
display_name / role 备用。
- 中间件 _log_access 改为优先读 request.state.audit_user。
验证(TestClient + 解析最终 JSON 输出,而非读 record 属性):
- track.access 日志 user=zhangsan01(经 request.state)
- 请求任务内 track.service 日志 user=zhangsan01(经 contextvar)
- 两者 request_id 一致;X-Request-ID 透传正常
|
2026-09-21 02:11:41 +00:00 |
|
|
|
4454047ce3
|
fix: 修复任务分页总数错误 + 补齐可观测性基建
分页总数(#5):
- get_all_tasks 的 total 原为 len(flat_tasks)(当前页条数),移动端
「我的任务」用 tasks.length < total 判断 hasMore,首页满员时恒为
false,列表永远停在第一页 20 条。改为独立 COUNT 查询(与
notifications.py 已有写法保持一致)。
可观测性(#9):
- 新增 core/logging.py:单行 JSON 结构化日志 + request_id/user 上下文
注入;零第三方依赖;接管 uvicorn 自带 handler 避免格式绕过。
- 新增 core/middleware.py:RequestContextMiddleware 生成/透传
X-Request-ID 并回写响应头,输出含耗时/用户的结构化访问日志。
- 新增 core/health.py:拆分存活/就绪探针。/health/live 不触依赖;
/health/ready 探主库,不可用返回 503;MOM 挂掉仅降级不摘流量。
- main.py:全局异常处理器只把堆栈写日志,响应体仅回 request_id;
接入可选 Sentry(未装 SDK 时静默跳过)。
- auth_service:解析 Token 后写入 user 上下文,日志自动带操作人。
注:异常处理器由 ServerErrorMiddleware 调用,此时 contextvar 已被重置,
故 request_id 同时写入 request.state(由 ASGI scope 承载)再读取。
已用 TestClient 验证:探针状态码/检查项、X-Request-ID 透传与生成、
500 响应携带可对账的 request_id 且不泄露堆栈。
|
2026-09-21 01:57:20 +00:00 |
|
|
|
d666bbd4ed
|
feat: 直接完结通道与报表口径收口(后端)
【直接完结:保留动作,剥离状态】
- transfer_task 权限硬拦截:仅 SUPER_ADMIN / SUPERVISOR。判据取签名保护的
operator_role,刻意不用客户端可通过 ?operator_id= 伪造的 operator_id
- 直接完结【不再改写】overall_status —— 它只是任务闭环动作,不改变物理状态。
已出库设备完结后依然是「已出库」,回归 WIP 矩阵的已出库列
- 物理终态保护:create_task / receive_task / transfer_task 三处写入点统一加
_is_physical_terminal 守卫,禁止用任务名覆写 已入库/在库/已出库。
历史缺陷:「发货测试」会把设备的「已出库」标识静默抹掉,跨报表口径随之打架
【位置与工序口径】
- _recalc_product_location:已出库且闲置 → 位置清空(货发走就离场)。
只清「已出库」;已入库/在库的设备确实还在仓库里,位置必须保留
- MOM 出库回调同步清空 current_location_id
- 新增 ProductResponse.current_step:只认活跃主干任务,无活跃任务时返回
宏观终态或空。不再让已 COMPLETED 的历史工序(扫码出库/测试…)冒充"当前工序"
【售后烙印去死锁】
- 拆分 OUTBOUND_QC_STEPS(发货测试) / AFTER_SALES_REPAIR_STEPS(售后维修)
- resolve_phase_for_step 与 _mark_after_sales_if_reactivated 双双改为仅
「售后维修」触发 AFTER_SALES,解除「已出库 + 发货测试」被永久烙印的死锁
- PRODUCTION_OVERALL_STEPS 补入「发货测试」,使出厂质检在生产阶段合法
(否则 _enforce_step_isolation 会把已出库设备的发货测试直接 400)
【游魂收口】
- get_wip_matrix / get_wip_matrix_detail 的 is_terminal 增加"名下再无任何活跃
任务则视同终结态",让被直接完结的生产设备接受时间筛选,不再恒挂在看板上
冒充在制。两处必须一字不差同步,否则会出现"矩阵有数、下钻为空"
|
2026-09-17 17:05:42 +08:00 |
|
|
|
f22eae315f
|
fix: 售后回流口径统一 — 状态双字段同步、工序名归一、标签配色
产品存在 overall_status 与 status 两个状态字段,此前各写入点各写一份映射、
甚至只改 overall_status 不改 status,导致回流设备(product.status 停留在
OUTBOUND)污染看板统计口径。
- lifecycle.py: 把映射表收敛为单一来源 overall_to_product_status /
sync_product_status;新增 normalize_after_sales_step,将售后设备沿用生产
阶段写法的历史工序名(测试/维修)折算到售后区独立工序名
- product_service.py: 删除本地 _OVERALL_TO_STATUS 副本,改用共享函数
- dashboard_service.py: WIP 矩阵补出 lifecycle_phase 列,活跃任务判定
(is_active) 提前到所有终结态判定之前,避免残留 OUTBOUND 被误判为完结
- scripts/fix_product_status.py: 历史数据修复脚本(一次性)
- constants/task.ts: 售后工序标签由红色改紫色 —— 红色在本系统是「驳回/危险」
语义色,售后只是另一条流转支线,用红色会让操作员误以为设备报错
|
2026-09-15 10:57:29 +08:00 |
|
|
|
b9f9b897a1
|
feat: 新增产品生命周期阶段字段lifecycle_phase与售后工序词表
|
2026-09-14 14:49:34 +08:00 |
|
|
|
73faa1fd93
|
feat: Track 打通已完成/已入库/已出库状态闭环与外部联动
- 完工转交入库同步 status=COMPLETED;MOM 入库/出库回调同步 status
- 新增 mom-outbound webhook(发货出库标记已出库),lookup 返回 material_id
- VALID_OVERALL_STATUS 新增已出库;update_overall_status 同步 status 字段
- WIP 矩阵区分已入库/待仓库收货;虚拟节点正确处理已出库/转入在库人
|
2026-09-01 13:53:24 +08:00 |
|
|
|
ad4b55dece
|
feat(工作日): 新增工作小时计算工具 + 节假日表与管理接口
- time_utils 新增 to_beijing(统一naive按UTC转北京时间) + working_duration_hours(排除周末/节假日)
- Holiday 模型 + alembic 迁移建 holidays 表
- GET/POST/DELETE /holidays 节假日管理接口,可随时配置放假日期
|
2026-08-28 15:08:05 +08:00 |
|
|
|
c3f3a5e291
|
fix: 看板动态时间改为北京时间 + 操作人显示中文姓名
1. 时区修复:
- database.py: PG连接池会话级设置 TimeZone=Asia/Shanghai
- dashboard_service.py: 格式化时间显式 astimezone(BEIJING_TZ)
- 兜底: tzinfo为None时默认当作北京时间处理
2. 操作人中文姓名:
- 收集所有operator_id → 调用mom_cache批量翻译
- 优先中文姓名 → 英文用户名兜底
- operator_id为None时不再显示"系统",留空
|
2026-08-12 13:28:38 +08:00 |
|
|
|
d59f732a0e
|
fix: 数据库会话事务安全兜底
get_db() 依赖注入增加 try/except/rollback/finally:
- 请求处理中发生异常时自动 rollback
- 无论成功或异常,finally 中确保 close 释放连接
- 防止异常导致连接泄漏或脏事务残留
|
2026-08-12 12:04:45 +08:00 |
|
|
|
69f3e35d14
|
fix: Dashboard统计数据修复 + 生产环境SECRET_KEY强制校验
1. Dashboard统计Bug修复
- Task统计改用TASK_STATUS_PENDING/WIP/COMPLETED大写常量
- 旧代码使用小写"pending"/"in_progress"永远匹配不到数据
- 修复后tasks_pending/tasks_in_progress/tasks_completed返回真实值
2. 生产环境SECRET_KEY强制校验
- 新增model_validator:DEBUG=False且SECRET_KEY为默认值时抛出ValueError
- 阻止使用默认密钥部署到生产环境
|
2026-08-12 12:03:07 +08:00 |
|
|
|
b71c5a2d07
|
feat: 双Token认证(Access 2h/Refresh 7d) + 通知系统(转交/驳回自动推送)
|
2026-08-07 11:44:04 +08:00 |
|
|
|
82f474f71f
|
chore: 基础设施 — 阿里云源、北京时间、HEX计数器、打印机配置
|
2026-08-05 14:00:13 +08:00 |
|
|
|
2b967afbf6
|
chore: 更新后端依赖,初始化数据库迁移与 MOM 外部系统连接
|
2026-08-04 17:09:33 +08:00 |
|
|
|
895fed6ac3
|
chore: 添加 .gitignore 并移除敏感/缓存文件
- 添加 .gitignore (Python __pycache__, .env, .idea, node_modules 等)
- 从追踪中移除 backend/.env, frontend/.env (密钥文件)
- 从追踪中移除 __pycache__/ 目录 (编译缓存)
- 从追踪中移除 .idea/ (IDE 配置)
|
2026-08-04 17:03:17 +08:00 |
|
|
|
17105dc9c2
|
初始提交:项目基础结构
- backend: FastAPI 后端服务 (Python)
- frontend: React + Tauri 前端应用
- docker-compose.yml: 容器编排配置
|
2026-08-04 10:05:59 +08:00 |
|