kkfluous
26f7d7ab3f
feat(auth): 能源管理模块需要 BI-LEADER-ENERGY 角色
...
ci/woodpecker/push/woodpecker Pipeline was successful
- 新增 ENERGY_ACCESS_ROLES 与 canAccessEnergy(roles) 守卫(全量权限角色亦可访问)
- 后端 /api/energy/* 加模块级守卫:无角色返回 403
- 前端 App.tsx 按角色动态注入 EnergyModule,无权限时主导航不显示
- dev mock 用户(前端 + 后端)追加 BI-LEADER-ENERGY 便于本地调试
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-30 17:55:29 +08:00
kkfluous
f06b0d21eb
perf(energy): SWR 缓存 + 自调度刷新,氢能总览 6s → 13ms
...
ci/woodpecker/push/woodpecker Pipeline was successful
接口侧:
- cache.ts 改为 stale-while-revalidate:每个 key 自调度,TTL 到期前 5s 后台刷新,用户永远命中热缓存
- 闲置 10 分钟后停止调度,避免空跑
- loader 失败保留旧值 + 10s 后退避重试
- 所有 4 个端点支持 ?force=1 强制绕过缓存
前端 HydrogenOverview:
- 顶部加 RefreshCw 按钮(强刷绕过缓存),带旋转动画
- 显示"更新于 X 秒前"相对时间
- 刷新中:顶部 0.5px 流光进度条,不替换内容、不闪烁
- 60s 静默自动刷新(命中后端热缓存)
实测:cold 6.1s → 命中 13ms(470× 提速)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-30 17:43:24 +08:00
kkfluous
6ad4b5e2a4
feat(energy): 氢能总览补全维度(5KPI+收支+客户/加氢站全量+年份切换)
...
ci/woodpecker/push/woodpecker Pipeline was successful
按 BI 页面 (https://bi.lnh2e.com/lingniu/decision/link/0iqP ) 完整还原:
- 5 张 KPI:累计加氢量 / 累计加氢费 / 时享加氢获利 / 本月加氢 / 本日加氢
- 月度收支对比柱图:成本支出 vs 客户收入双柱
- 加氢站加氢汇总(全量 55 站):加氢量+占比+氢费收入+收入占比,进度条
- 客户账单 Top 30:承担方 / 加氢量 / 成本支出 / 应收
- 年份切换(2025/2026),全量数据按选定年份重算
- 关键修正:用 cost_type 区分客户单/我司单(cost_type=2 客户单,cost_type=3 我司单),获利口径与 BI 对齐
后端 /hydrogen/overview 重写:
- 增加 customers/stations/availableYears/year 字段
- KPI 含 yearProfit/monthProfit/todayProfit
- monthly 含 fee/revenue/profit
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-30 16:43:05 +08:00
kkfluous
ad8ec50038
refactor(energy): 氢能总览参照 BI 重构 + 月度趋势 + 高密度 KPI
...
ci/woodpecker/push/woodpecker Pipeline was successful
参考 https://bi.lnh2e.com/lingniu/decision/link/0iqP 重新设计:
- 4 张高密度 KPI 卡:累计加氢量 / 累计加氢费 / 本月加氢 / 本日加氢
每张含主指标 + 2 行明细(我司/客户、加氢费/占比)
- 新增年内月度加氢量柱图(缺失月份补 0)
- 数字格式化:万元/亿元/T 单位自动切换,tabular-nums 对齐
- 后端 /hydrogen/overview 增加 monthly 字段
- 骨架屏同步更新
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-30 15:54:35 +08:00
kkfluous
e0183986ee
feat(energy): 氢/电统一时间速选为「本周/本月/近15天」+ 缺失日补 0
...
ci/woodpecker/push/woodpecker Pipeline was successful
- 后端 Range 类型精简到 thisWeek / thisMonth / last15
rangeClause 同步精简;删除 today / thisQuarter / last7 / last30 分支
- 新增 enumerateDates(range):列出 range 内全部日期,用于补零
- /hydrogen/daily:用 enumerateDates 补齐缺失日期 totalKg=0、stations=[]
补零后基于完整日期序列重算环比(0→上一日有值时显示 -100%)
- /electric/monthly:增加 range 参数,扁平日聚合 + 月份分组
缺失日期同样补零;环比基于补零序列重算
- 默认 range 改 last15
前端
- HydrogenDaily QUICK_PICK_OPTIONS 收紧到 3 项,默认 last15
- ElectricDaily 之前没有日期速选,现按氢能样式加上同样 3 项
类型 DateQuickPick 改 'thisWeek' | 'thisMonth' | 'last15'
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-30 14:56:13 +08:00
kkfluous
57207debfb
fix(energy): hydrogen_time 已是 CST 字面值,去掉多余的 +8 HOUR 转换
...
ci/woodpecker/push/woodpecker Pipeline was successful
之前认为 hydrogen_time / charging_start_time 字段存的是 UTC,
所有氢能查询都加了 DATE_ADD(..., INTERVAL 8 HOUR) 转 CST。
但实际上字段存的是 CST 字面值,转换反而把日期边界提前了 8 小时,
导致诸如「04-28 嘉兴嘉燃经开站」的统计少算了部分晚间订单。
实测:
- 之前:04-28 嘉燃经开 = 144.36 Kg(CST 转换错位)
- 现在:04-28 嘉燃经开 = 153.81 Kg(与业务方直接 DATE(hydrogen_time) 口径一致)
电能 charging_start_time 同样去掉转换。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-29 20:54:22 +08:00
kkfluous
d3fa2fd4d6
revert(energy): 取消 GF_HECRI_BILL 过滤,全部数据展示
...
ci/woodpecker/push/woodpecker Pipeline was successful
按用户要求恢复全量统计:移除 4 处 GF 过滤子句和相关常量。
现在 GF_HECRI_BILL 历史订单会与 JQ 新订单一同计入。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-29 20:28:52 +08:00
kkfluous
8b4fb6563f
refactor(energy): 简化为始终过滤 GF_HECRI_BILL,移除条件判断
...
ci/woodpecker/push/woodpecker Pipeline was successful
去掉 shouldFilterGfBills 探测、5 分钟缓存、JQ 存在性判定。
4 处氢能查询无条件追加 GF_HECRI_BILL 过滤,逻辑更直接。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-29 20:26:52 +08:00
kkfluous
e187c0d02e
feat(energy): 嘉燃经开站存在 JQ 单时全局过滤 GF_HECRI_BILL 历史订单
...
ci/woodpecker/push/woodpecker Pipeline was successful
业务背景:旧系统加氢账单使用 GF_HECRI_BILL 前缀,新系统统一改用 JQ 前缀。
切换期间两套数据共存,会重复计入加氢量。约定:当嘉兴嘉燃经开站出现 JQ
订单(视为切到新单号体系),全局过滤掉 GF_HECRI_BILL 前缀的历史订单。
实现:
- shouldFilterGfBills() 探测嘉兴嘉燃经开站是否有 JQ 单,结果缓存 5 分钟
- GF_EXCLUDE_CLAUSE = b.bill_code NOT LIKE 'GF\_HECRI\_BILL%' ESCAPE '\\'
- 应用到 4 处氢能查询:KPI、Top5、区域占比、daily 站点聚合
实测:当前嘉燃经开 4508 JQ + 665 GF,嘉锦 11783 JQ + 1 GF,全部 GF 已隐藏。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-29 20:23:03 +08:00
kkfluous
c788dd4577
fix(energy): 单价直接取 MAX(cost_price),不重算不返 null
...
ci/woodpecker/push/woodpecker Pipeline was successful
之前用 MIN=MAX...ELSE NULL 判定,再 NULLIF 排零,遇到「1 笔 0 元免费单 + 多笔 35 元正价单」
仍可能误判混合,最终页面显示「—」(如佛山豪汇石油加氢站)。
按业务约定:单价就是订单上记录的成本价,不做"统一性"判定,也不返 null。
改用 MAX(b.cost_price):
- 自然忽略 0 元免费/赠送单(被正价 max 掉)
- 同价组等于原价
- 极少数真正混合价组也展示该日付出过的最高单价(仍是订单上的真实数字)
回退类型:HydrogenStationRow.pricePerKg 重新固定为 number。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-29 19:56:26 +08:00
kkfluous
3851335843
fix(energy): 氢能单价不再加权,混合价组显示「—」并修复 hydrogen_time 歧义
...
ci/woodpecker/push/woodpecker Pipeline was successful
- pricePerKg 改为 CASE WHEN MIN=MAX THEN MIN ELSE NULL,
同价组返回原价(无小数误读),混合价组返回 null
- 类型 HydrogenStationRow.pricePerKg: number | null
- 前端 mobile/desktop 两处展示在 null 时显示「—」
- 修复 ER_NON_UNIQ_ERROR:tab_import_hydrogen_order 也有 hydrogen_time 字段,
把 SELECT/ORDER BY 中 ${HYDROGEN_LOCAL} 替换为显式 b.hydrogen_time 限定
- 实测:712 个站点-日组中 682 个同价直接显示原价,30 个混合显示「—」
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-29 19:51:41 +08:00
kkfluous
d0a644cf18
fix(energy): 氢能站点名补全 tab_import_hydrogen_order,单价改为按量加权
...
ci/woodpecker/push/woodpecker Pipeline was successful
站点名 fallback 链新增第三档:
内部站表 → 外部站表 → tab_import_hydrogen_order(by bill_code) → 「未关联/未知 #ID」
经此关联,原来 140 条「未知站点」补全为 9 个真实站名(洛阳新红山、佛山新城等)
单价之前用 AVG(cost_price) 简单平均,混合价组会算出 34.0334... 这种
意外小数(看起来像被「重新计算」)。改为按量加权效率价:
ROUND(SUM(cost_expense) / NULLIF(SUM(hydrogen_quantity), 0), 2)
单一价格组自动等于原价,混合价组得到真实付款单价。
Top5 与每日聚合两处 SQL 同步修改。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-29 19:46:56 +08:00
kkfluous
0d30ee2df5
fix(energy): 氢能站点 fallback 区分「未关联」与「未知 #ID」
...
ci/woodpecker/push/woodpecker Pipeline was successful
之前两张 site 表都查不到的账单,UI 一律显示「未知站点」。
其中 137 条 station_id 为 NULL(账单未填站点),3 条 station_id
有值但站点表没收录(站点删除/字典漂移)。账单上的 cost_price 是真实的,
所以会出现「未知站点 25 元/Kg」这种可追溯但难定位的情况。
现在 SQL fallback 改为:
- station_id IS NULL → 「未关联站点」
- station_id 不为空但 join 不上 → 「未知站点 #ID」
方便定位具体是哪条字典记录缺失。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-29 19:41:57 +08:00
kkfluous
d1d79f1c7c
feat(energy): 电能统计切到 bi_ele_charge_record,外部数据接通
...
ci/woodpecker/push/woodpecker Pipeline was successful
- /api/energy/electric/overview & /electric/monthly 不再读 tab_energy_electricity_bill
- 改读 bi_ele_charge_record:kwh/fee/start_time
- 外部/我司用 vehicle_kind 区分(external/internal)
- 电能默认 customer 由 'external' 改 'lingniu',与导入页约定一致
- ElectricDaily 移除「数据对接中…」友好空状态(外部已有数据)
ele 导入页同步收紧:
- 命中系统车辆=internal,未命中(含车牌为空)一律 external
- 移除 unknown 分类、KPI 卡、批次列、过滤按钮、UploadResult 字段
- 历史 unknown 行已 UPDATE 为 external
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-29 19:11:52 +08:00
kkfluous
5493e27e49
feat(energy): 用 truck_id 区分外部/我司,外部数据空时给友好提示
...
ci/woodpecker/push/woodpecker Pipeline was successful
- 后端 customerClause 改为基于 truck_id:外部=IS NULL,我司=IS NOT NULL
- KPI 内联条件(ourYearKg/Fee、customerYearKg、lingniuBornKg/Fee)同步切换为 truck_id
- 调用方 /hydrogen/daily 与 /electric/monthly 改传 b.truck_id / truck_id
- 当前外部账单 truck_id 尚未对接,HydrogenDaily/ElectricDaily 在 customer=external 且无数据时
改展示「数据对接中…」友好状态(插头图标 + 蓝色脉冲),替代「暂无数据」
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-29 17:52:03 +08:00
kkfluous
c02c1aa62c
perf(energy): add 60s TTL cache for all 4 endpoints
...
Hydrogen overview was 1.8-2.0s (3 full-table aggregations on 66K rows).
With cache: cold 1 user/min eats the full query, all subsequent within
60s window return in ~10ms.
Implementation:
- New cache.ts with cached(key, loader) helper
- Per-key in-flight de-duplication: concurrent requests share one loader
- Each handler wrapped, cache key includes query params
(e.g. "hydrogen/daily?range=last30&customer=external")
- TTL 60s as requested
Measured speedups:
- hydrogen/overview: 1.96s → 12ms (165x)
- hydrogen/daily: 270ms → 11ms (24x)
- electric/overview: 93ms → 9ms (10x)
- electric/monthly: 36ms → 9ms (4x)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-28 17:50:48 +08:00
kkfluous
9a4f1945d9
feat(energy): connect to real DB (lingniu_prod)
...
Replace front-end mock data with live API backed by:
- tab_energy_hydrogen_bill (66.5K rows) joined with
tab_hydrogen_site (internal stations) and tab_outside_hydrogen_site
(external stations, joined via inner_site_id)
- tab_energy_electricity_bill (4.4K rows, all 龙王路充电站)
New server routes (src/server/routes/energy/):
- GET /api/energy/hydrogen/overview → KPI + Top5 站点 + 区域占比
- GET /api/energy/hydrogen/daily?range=&customer= → 日级 + 站点级下钻
- GET /api/energy/electric/overview → KPI + 本月柱图 (fallback to last
available month if current month has no data)
- GET /api/energy/electric/monthly?customer= → 6 个月分组日级表
Business rules encoded server-side:
- 客户类型: customer_id IS NULL = 羚牛承担, NOT NULL = 外部
- 时区: DATETIME 列字面值是 UTC,分组前 +8h 转成 CST
- 数据清理: hydrogen_time >= 2024-01-01 (排除 1900 年脏数据)
- 站点名 fallback: short_name → name → fixed_station_name → station_name → '未知站点'
- 区域归一化: SUBSTRING_INDEX(city, '-', -1) 取最后一段,去掉 '省'/'市'
让 '四川省-成都市' 和 '成都市' 合并为 '成都'
Component changes:
- All 4 components (HydrogenOverview, HydrogenDaily, ElectricOverview,
ElectricDaily) now use useEffect + fetch with loading/error states
- HydrogenDaily filtering moved to server (range + customer params)
→ drops client-side TODAY constant + isInPick switch
- ElectricOverview chart title is dynamic: shows 'YYYY-MM 每日充电'
when fallback kicks in (current month has no data)
- mock.ts deleted
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-28 16:42:37 +08:00