Files
maxf 28fcfc6aec ## 1️⃣ 文档与代码一致性 ✅(修复 4 处滞后)
**发现并已修复的文档滞后**(后期代码先于文档,审计时补齐):

| 滞后点 | 修复 |
|---|---|
| `24.附录` users 表缺 `wechat_openid` 列(二期微信登录加的) | ✅ 已补 |
| `24.附录` 缺 `refuel_records` 表(三期记账) | ✅ 已补 |
| `24.附录` 错误码缺 `1012/1013`(三期) | ✅ 已补 |
| `34.附录` 小程序页面目录缺 `calc/refuels`(三期) | ✅ 已补 |

**核对一致项**:
- API 路由 **A1-A23 + `/api/auth/wechat`** 全部注册,与 22/33/43 文档逐项对应
- `schema.sql` 11 表 = 24 附录(10 + refuel)+ 45 附录
- 小程序 8 页文件齐全(js/wxml/wxss/json × 8),`refuels.wxml` 数据绑定与 js 匹配
- H5 8 页面全部可访问

## 2️⃣ 后期变更对前期的影响 ✅ 无破坏

综合回归 **20 项全 PASS**:
- **一期**:账号密码登录 / Cookie 会话 / dashboard / provinces(31省) / history ✅
- **二期**:Bearer token 认证(与 Cookie **双通道共存**,互不冲突)✅
- **三期**:refuel 首条保存 / 区间油耗 8.25 / 统计 / 预填 ✅
- **前端**:9 个资源(8 页 + 静态 JS)全部 200 ✅

关键:二期的 `users` 加列是**幂等增量迁移**(ALTER),未动一期任何表结构;一期 API 与前端全部正常。

## 3️⃣ 整体可行性 ✅ 在可控范围

| 维度 | 状态 |
|---|---|
| 构建/静态检查 | `go build / vet` 全绿 |
| 单元测试 | 6 包 17 单测全过(auth/crawler/cron/db/push/service) |
| 规模 | 11 表 · 23+ 接口 · 8 H5 页面 · 8 小程序页面 · 17 份文档 · ~90 代码文件 |
| 三阶段功能 | 监控预测提醒 → 微信小程序 → 加油记账/真实油耗,全部落地 |

**非阻塞小项**(可后续优化,不影响运行):
1. `refuels.wxml` 第 11 行 `wx:for` 与 `wx:if` 同节点——功能正常,写法可规范
2. api 包 handler 层无独立单测——由端到端回归覆盖(service 层已有单测)

---

**结论:项目整体健康、文档与代码对齐、三阶段功能完整可用。** 4 处文档滞后已修复,无功能性问题。可以进入部署/上线阶段。
2026-09-25 16:56:07 +08:00

5.9 KiB
Raw Permalink Blame History

⛽ 三期 · 记账功能 — 附录

上游:docs/40.记账概要设计.md、docs/24.附录.md(全系统通用规则) 数据表 DDL / 公式 / 漏记规则 / 级联重算 / 错误码


一、数据表设计

1.1 加油记录表 refuel_records

CREATE TABLE IF NOT EXISTS refuel_records (
    id               INTEGER PRIMARY KEY AUTOINCREMENT,            -- 记录 id
    user_id          INTEGER NOT NULL REFERENCES users(id),        -- 归属用户(私有数据)
    vehicle_id       INTEGER NOT NULL REFERENCES vehicles(id),     -- 归属车辆(油耗按车独立计算)
    refuel_at        TEXT    NOT NULL,                             -- 加油时间(默认当前,可改)
    odometer         REAL    NOT NULL,                             -- 当前码表里程(km)
    price_per_liter  REAL    NOT NULL,                             -- 油价(元/升)
    amount           REAL    NOT NULL,                             -- 支付金额(元)
    discount         REAL    NOT NULL DEFAULT 0,                   -- 优惠金额(元)
    net_amount       REAL    NOT NULL,                             -- 实付金额 = amount - discount
    liters           REAL    NOT NULL,                             -- 加油量 = net_amount / price
    fuel_type        TEXT,                                         -- 油号(92/95/98/0,可空,缺省取该车油号)
    full_tank        INTEGER NOT NULL DEFAULT 1,                   -- 是否加满:1=是(仅标签展示,不参与计算)
    note             TEXT,                                         -- 备注
    calc_distance_km REAL,                                         -- 区间行驶里程(距上次,可空)
    calc_l_per_100km REAL,                                         -- 区间油耗 L/100km(可空)
    calc_yuan_per_km REAL,                                         -- 区间成本 元/km(可空)
    anomaly          TEXT,                                         -- 异常标记:NULL/odometer(里程回退)
    created_at       TEXT    DEFAULT (datetime('now','localtime')),-- 创建时间
    updated_at       TEXT    DEFAULT (datetime('now','localtime')) -- 更新时间
);
CREATE INDEX IF NOT EXISTS idx_refuel_veh ON refuel_records(vehicle_id, refuel_at);
CREATE INDEX IF NOT EXISTS idx_refuel_user ON refuel_records(user_id);

1.2 数据库迁移

  • schema.sql 增加上述表(新库直接建)。
  • db.Open 的 migrate 函数增加幂等 CREATE TABLE IF NOT EXISTS refuel_records(旧库自动建)。

二、公式与计算规则

2.1 基本公式

量 公式 精度
实付金额 net net = amount - discount 2 位
加油量 liters liters = net / price 2 位
区间里程 distance distance = odometer_now - last_odometer 整数 km
区间油耗 L/100km l_per_100km = liters / distance × 100 1 位
区间成本 元/km yuan_per_km = net / distance 2 位

2.2 计算生效条件

条件 行为
该车无上次记录(首条) calc_* 全 NULL("无基准")
distance <= 0(里程未前进/回退) calc_* NULL,anomaly=odometer
price <= 0 或 net <= 0 拒绝保存(400+1012)

full_tank 仅作标签(历史记录展示"加满"徽标),不参与油耗计算。

2.3 异常检测规则(漏记提示)

对本次记录(相比该车历史平均;无历史平均则跳过):

异常判定(满足任一):
  a) 本次油价 price > 历史平均 price × 2
  b) 本次油耗 calc_l_per_100km > 历史平均 L/100km × 2
→ possible_missing = true("耗油量异常,是否有漏记?")

三、漏记处理与级联重算

3.1 漏记/异常处理

场景 处理
漏记一次加油 下次记录自动以最近有效记录为基准(平均油耗);possible_missing 提示补录
用户补录 历史列表"补一笔",填漏记那次里程/金额/油价 → 按 refuel_at 排序落位 → 级联重算
里程回退 odometer_now <= last_odometer → 该条油耗 NULL + anomaly=odometer,标黄提示
加满标签 full_tank 复选框仅作标签展示,不参与油耗计算;历史记录显示"加满"徽标
删除记录 其后一条的基准变为被删记录的上一条,级联重算

3.2 级联重算规则

操作       影响范围                                 重算动作
─────────────────────────────────────────────────────────────────
新增 R3    R3(与上次 R2)                          重算 R3
编辑 R2    R2(与上次 R1)、R3(与 R2)             重算 R2、R3
删除 R2    R3(上次基准改为 R1)                   重算 R3
补录 R2    R2(与 R1)、R3(与 R2)                 重算 R2、R3

3.3 重算实现

func recalc(conn, vehicleID, fromRefuelID):
    // 取该车按时间排序的记录,从 fromRefuelID 开始逐条重算
    // 每条的上次基准 = 前一条有效记录

重算在同一事务内完成,保证一致性。


四、错误码扩展

code HTTP 含义
1012 400 加油记录参数非法(金额/油价/里程/车辆归属)
1013 409 加油记录冲突(同一车同一时间重复记录)

其余复用 docs/24.附录.md 错误码表(0/1001-1011/5000)。


五、与通用规则衔接

  • 时间:refuel_at 用 Asia/Shanghai 本地时间(YYYY-MM-DD HH:MM:SS)。
  • 权限:/api/refuels* 挂 authGuard;按 user_id 归属,vehicle_id 归属校验。
  • 金额精度:元保留 2 位;油耗 L/100km 保留 1 位。
  • 数据分层:refuel_records 为用户私有数据(含 user_id),按用户隔离。