diff --git a/db_migrations/phase9_fix_return_time_utc.sql b/db_migrations/phase9_fix_return_time_utc.sql new file mode 100644 index 0000000..6af12f5 --- /dev/null +++ b/db_migrations/phase9_fix_return_time_utc.sql @@ -0,0 +1,112 @@ +-- ============================================================================= +-- 修正存量「归还时间 / 报废时间」的 8 小时时区偏差 +-- +-- 问题 +-- process_return 与 scrap_sources.deduct 原先用 datetime.now() 写 return_time / +-- operation_time,取的是**容器本地时间**(Docker 默认 UTC);而同一行的 +-- borrow_time 由 beijing_time() 写入。同一行两个时钟,差 8 小时。 +-- 实测(开发库):53 行 trans_borrow 出现「归还时间早于借出时间」这种 +-- 物理上不可能的数据,例如 id=80 借出 09-09 11:04、归还 09-09 03:46。 +-- +-- 代码侧已修 +-- 提交 f7c49f4(2026-09-17 09:18 北京时间)起改用 beijing_time(), +-- **新写入的数据已经是北京时间**(已验证 TransReturn / TransBorrowReturn / +-- TransScrap 三张表的模型默认值均为北京)。本脚本只处理存量。 +-- +-- --------------------------------------------------------------------------- +-- ★★ 执行前必读:请先跑「第 0 步 自检」,据其结果决定是否执行 ★★ +-- +-- 1) 若自检的「异常行数」为 0,且 UTC 区间计数为 0 —— 说明**本服务器无此问题** +-- (例如 API 容器的 TZ 本就是 Asia/Shanghai,datetime.now() 一直是北京时间), +-- **不要执行第 2 步**,否则会把正确的时间又 +8 小时。 +-- +-- 2) 分界时间 @boundary 的含义:**修复代码在本服务器部署生效的时刻**。 +-- · 该时刻之前写入的行 → UTC,需 +8h; +-- · 该时刻之后写入的行 → 北京时间,不能动。 +-- 开发库用的是提交时间 2026-09-17 09:18(北京)= 01:18(UTC), +-- 两组数据之间通常有一段空档,请用自检查看空档是否存在。 +-- 若你的部署时间不同,请替换下方两处 '2026-09-17 01:18:00'。 +-- +-- 幂等性 +-- ⚠ 本脚本**不幂等** —— 重复执行会把已经修正过的时间再加 8 小时。 +-- 请在执行前确认已备份,且只执行一次。 +-- +-- 回滚 +-- 把两处 `+ interval '8 hours'` 改成 `- interval '8 hours'`,用同一条件再执行一次。 +-- ============================================================================= + + +-- ============================================================================= +-- 第 0 步:自检(只读,先跑这一段看结果) +-- ============================================================================= +SELECT '=== 0.1) 物理上不可能的「归还早于借出」行数 ===' AS "核对项"; +SELECT count(*) AS 异常行数 + FROM trans_borrow + WHERE return_time IS NOT NULL AND borrow_time IS NOT NULL + AND return_time < borrow_time; + +SELECT '=== 0.2) 按分界切分:UTC 段 / 空档 / 北京段 ===' AS "核对项"; +SELECT + count(*) FILTER (WHERE return_time < '2026-09-17 01:18:00') AS UTC段_需修正, + count(*) FILTER (WHERE return_time >= '2026-09-17 01:18:00' + AND return_time < '2026-09-17 09:18:00') AS 空档_应为0, + count(*) FILTER (WHERE return_time >= '2026-09-17 09:18:00') AS 北京段_不可动 + FROM trans_borrow + WHERE return_time IS NOT NULL; + +SELECT '=== 0.3) trans_scrap 同样切分 ===' AS "核对项"; +SELECT + count(*) FILTER (WHERE operation_time < '2026-09-17 01:18:00') AS UTC段_需修正, + count(*) FILTER (WHERE operation_time >= '2026-09-17 01:18:00' + AND operation_time < '2026-09-17 09:18:00') AS 空档_应为0, + count(*) FILTER (WHERE operation_time >= '2026-09-17 09:18:00') AS 北京段_不可动 + FROM trans_scrap; + +-- 判读: +-- · 0.1 为 0 且 0.2/0.3 的 UTC 段也为 0 → 无需修正,停止。 +-- · 0.1 大于 0 → 确有此问题,可继续第 2 步。 + + +-- ============================================================================= +-- 第 1 步:备份(强烈建议) +-- ============================================================================= +-- CREATE TABLE trans_borrow_bak_20260918 AS +-- SELECT id, return_time, return_operator FROM trans_borrow; +-- CREATE TABLE trans_scrap_bak_20260918 AS +-- SELECT id, operation_time FROM trans_scrap; + + +-- ============================================================================= +-- 第 2 步:修正(确认第 0 步结果后再执行) +-- ============================================================================= +BEGIN; + +UPDATE trans_borrow + SET return_time = return_time + interval '8 hours' + WHERE return_time IS NOT NULL + AND return_time < '2026-09-17 01:18:00'; -- ← 替换为你的部署时刻(UTC 口径) + +UPDATE trans_scrap + SET operation_time = operation_time + interval '8 hours' + WHERE operation_time < '2026-09-17 01:18:00'; -- ← 同上 + +COMMIT; + + +-- ============================================================================= +-- 第 3 步:执行后核对 +-- ============================================================================= +SELECT '=== 3.1) 异常行数应为 0 ===' AS "核对项"; +SELECT count(*) AS 异常行数 + FROM trans_borrow + WHERE return_time IS NOT NULL AND borrow_time IS NOT NULL + AND return_time < borrow_time; + +SELECT '=== 3.2) 抽样:归还时间应晚于借出时间,且落在工作时段 ===' AS "核对项"; +SELECT id, + to_char(borrow_time, 'MM-DD HH24:MI') AS 借出, + to_char(return_time, 'MM-DD HH24:MI') AS 归还 + FROM trans_borrow + WHERE return_time IS NOT NULL + ORDER BY return_time DESC + LIMIT 8;