fix(timezone): 审批单据时间统一为 naive 北京时间,消除 8 小时偏差

成因:approved_at / executed_at 列是 timestamp without time zone,而赋值用了
带时区的 datetime.now(timezone(timedelta(hours=8)))。psycopg2 对 naive 列不会
剥掉 tzinfo,而是转成 naive UTC 再写入,结果比同为北京时间的 created_at 早 8 小时。
(代码里并无 datetime.utcnow(),真实成因是 aware 值被驱动的隐式 UTC 转换。)

改动:
  · outbound_service / borrow_service 的审批分支
    datetime.now(beijing_tz).replace(tzinfo=None) → beijing_time()
    (免审批分支已在上一轮改为 beijing_time,此处补齐审批分支);
  · purchase_service 审批/完结分支同源缺陷一并修复;
  · scrap_approval / scrap_approval_service 的 _beijing()、_beijing_now() 由
    tz-aware 改为 naive —— 配合上述迁移把列类型改为 naive,
    若仍返回 aware 值,timestamptz→timestamp 后会反向早 8 小时。

实测四表(scrap/outbound/borrow/purchase)created_at 与 approved_at 差值
均在同一秒内(-1ms ~ -12ms)。
This commit is contained in:
yueli
2026-09-10 10:14:31 +08:00
parent 2045a89330
commit 8755837fe8
5 changed files with 33 additions and 18 deletions

View File

@ -2,7 +2,7 @@ import uuid # .material -> .base refactor checked
from datetime import datetime, timezone, timedelta
from sqlalchemy import or_, func, desc, and_
from sqlalchemy.orm import joinedload
from app.extensions import db
from app.extensions import db, beijing_time
from app.models.outbound import TransOutbound, OutboundApproval
# 引入所有库存模型以进行查询
@ -816,7 +816,8 @@ class OutboundApprovalService:
)
else:
# 默认不审批:创建即已通过(status=1),直接进入“待库管执行”
from datetime import datetime, timezone, timedelta
# ★ approved_at 必须与 created_at(beijing_time) 同为 naive 本地时间:
# approved_at 列是 timestamp without time zone,传 aware 时间会被驱动转成 UTC 存库,导致早 8 小时
approval = OutboundApproval(
request_no=request_no,
applicant_id=applicant_id,
@ -824,7 +825,7 @@ class OutboundApprovalService:
outbound_type=outbound_type,
status=1,
actual_approver_id=applicant_id,
approved_at=datetime.now(timezone(timedelta(hours=8))),
approved_at=beijing_time(),
)
# 直接存储前端传来的物料信息快照,不查询/不关联具体库存记录
@ -1006,8 +1007,8 @@ class OutboundApprovalService:
"""
from app.models.outbound import OutboundApproval
beijing_tz = timezone(timedelta(hours=8))
current_time = datetime.now(beijing_tz).replace(tzinfo=None)
# ★ 统一取 naive 北京时间,与 created_at(beijing_time) 同口径
current_time = beijing_time()
approval = OutboundApproval.query.get(request_id)
if not approval: