refactor(stage14): 单站日报 10 条查询移入 repository(能源域四分之三)
改动
- energy/hydrogen-station-board.ts 的 10 条查询全部移入 energy/repository.ts,
参数表达式逐条对应(含区间、站点、以及租户与手工充值来源的固定顺序)。
- 相关 SQL 片段助手搬进 constants.ts 并导出:
NORMALIZED_PLATE / NORMALIZED_PLATE_UNICODE / EXTERNAL_CUSTOMER_MAP_CTE,
以及 HYDROGEN_TENANT_ID / MANUAL_RECHARGE_SOURCE。
它们本质是 SQL,继续留在路由里会让"路由不含 SQL"形同虚设;放 constants.ts
是因为被多条查询共享,避免复制。
- 路由文件随之删掉 4 个只为 SQL 服务的 constants 导入(HYDROGEN_BASE_WHERE /
_B / HYDROGEN_LOCAL / HYDROGEN_TABLE),它们已不再被该文件使用。
- 架构守卫允许 constants.ts 存放共享 SQL 片段,并注明理由。
提取方式
- 用平衡括号扫描从原文件按顺序取出 10 条 SQL 与各自的参数数组(含查询 10 的多行参数),
按调用点跨度精确替换,避免手工转写 200 余行 SQL。
验证
- energy/routes.test.ts 的 13 个既有用例在提取前后均通过,其中包含单站看板的
SQL 与参数断言(迁移未改变查询文本、参数顺序与响应口径)。
- 全量 lint / test(191) / build 全绿,可达性 0 未引用文件。
未完成
- hydrogen-bi-v2.ts 仍有 7 条带 ${sqlWhere} / ${groupOrder} 等动态插值的查询,
需要把 where 组装函数一并搬进 repository;完成后再把 energy 加入守护清单。
This commit is contained in:
@@ -95,7 +95,7 @@ server/
|
|||||||
| `ele/` | `routes.ts` `repository.ts` `model.ts`(+ `model.test.ts` `routes.test.ts`) | ✅ 完整分层;改造前后 SQL/参数/响应体已做等价性验证 |
|
| `ele/` | `routes.ts` `repository.ts` `model.ts`(+ `model.test.ts` `routes.test.ts`) | ✅ 完整分层;改造前后 SQL/参数/响应体已做等价性验证 |
|
||||||
| `feedback/` | `routes.ts` `repository.ts` `oss.ts`(+ `routes.test.ts`) | ✅ 完整分层;同上 |
|
| `feedback/` | `routes.ts` `repository.ts` `oss.ts`(+ `routes.test.ts`) | ✅ 完整分层;同上 |
|
||||||
| `mileage/` | `index.ts`(聚合)+ `monitoring.ts` `targets.ts` `trend.ts` `daily-report.ts` `vehicle-recent.ts` + `*-model.ts` + `cache.ts` `oneos-api.ts` `daily-report-{service,store,scheduler}.ts` | 路由 / 模型 / 服务已分开,**SQL 仍在各路由文件内** |
|
| `mileage/` | `index.ts`(聚合)+ `monitoring.ts` `targets.ts` `trend.ts` `daily-report.ts` `vehicle-recent.ts` + `*-model.ts` + `cache.ts` `oneos-api.ts` `daily-report-{service,store,scheduler}.ts` | 路由 / 模型 / 服务已分开,**SQL 仍在各路由文件内** |
|
||||||
| `energy/` | `index.ts`(聚合)+ `electric.ts` `etc.ts` `hydrogen-bi-v2.ts` `hydrogen-station-board.ts` + `repository.ts`(电能/ETC 已迁入)+ `query-model.ts` `cache.ts` `constants.ts` | 🔶 **部分分层**:`electric` / `etc` 的 SQL 已在 repository,两个 hydrogen 文件仍在路由内,因此该域尚未进守护清单 |
|
| `energy/` | `index.ts`(聚合)+ `electric.ts` `etc.ts` `hydrogen-station-board.ts` + `repository.ts` + `hydrogen-bi-v2.ts`(仍有 SQL)+ `query-model.ts` `cache.ts` `constants.ts` | 🔶 **部分分层**:前三个路由文件的 SQL 已迁入 repository,SQL 片段进了 `constants.ts`;`hydrogen-bi-v2.ts` 仍有 7 条带动态插值的查询,因此该域尚未进守护清单 |
|
||||||
| `scheduling/` | `index.ts`(聚合)+ `suggestions.ts` `notify.ts` `repository.ts` + `algorithm.ts` `notification-model.ts` | ✅ 完整分层;跨域数据源(里程车辆信息 / OneOS)显式注入 |
|
| `scheduling/` | `index.ts`(聚合)+ `suggestions.ts` `notify.ts` `repository.ts` + `algorithm.ts` `notification-model.ts` | ✅ 完整分层;跨域数据源(里程车辆信息 / OneOS)显式注入 |
|
||||||
| `hydrogen-heatmap/` | `routes.ts` `repository.ts` `model.ts`(+ `routes.test.ts`) | ✅ 完整分层;`buildWhere` 片段与参数顺序已锁定 |
|
| `hydrogen-heatmap/` | `routes.ts` `repository.ts` `model.ts`(+ `routes.test.ts`) | ✅ 完整分层;`buildWhere` 片段与参数顺序已锁定 |
|
||||||
| `vehicle-heatmap/` | `routes.ts` `repository.ts` `model.ts`(+ `routes.test.ts`) | ✅ 完整分层;同时覆盖 MySQL(考核批次)与 PG(定位点)两个库 |
|
| `vehicle-heatmap/` | `routes.ts` `repository.ts` `model.ts`(+ `routes.test.ts`) | ✅ 完整分层;同时覆盖 MySQL(考核批次)与 PG(定位点)两个库 |
|
||||||
|
|||||||
@@ -192,7 +192,9 @@ test("已完整分层的域:repository.ts 存在,且 SQL 只允许出现在
|
|||||||
}
|
}
|
||||||
for (const file of readdirSync(abs)) {
|
for (const file of readdirSync(abs)) {
|
||||||
if (!file.endsWith(".ts")) continue;
|
if (!file.endsWith(".ts")) continue;
|
||||||
if (file === "repository.ts" || file.endsWith(".test.ts")) continue;
|
// repository.ts 是 SQL 的归属地;constants.ts 允许存放被多条查询共享的
|
||||||
|
// SQL 片段(如公共 WHERE / CTE),否则这些片段会被迫复制到每个查询里。
|
||||||
|
if (file === "repository.ts" || file === "constants.ts" || file.endsWith(".test.ts")) continue;
|
||||||
if (SQL.test(stripComments(readFileSync(path.join(abs, file), "utf8")))) {
|
if (SQL.test(stripComments(readFileSync(path.join(abs, file), "utf8")))) {
|
||||||
offenders.push(`${dir}/${file} 仍含 SQL`);
|
offenders.push(`${dir}/${file} 仍含 SQL`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,3 +10,30 @@ export const HYDROGEN_BASE_WHERE_B = `b.del_flag = '0'`;
|
|||||||
export const HYDROGEN_FUEL_ONLY_WHERE = `${HYDROGEN_BASE_WHERE} AND COALESCE(record_source, '') <> 'external_recharge_manual'`;
|
export const HYDROGEN_FUEL_ONLY_WHERE = `${HYDROGEN_BASE_WHERE} AND COALESCE(record_source, '') <> 'external_recharge_manual'`;
|
||||||
export const HYDROGEN_FUEL_ONLY_WHERE_B = `${HYDROGEN_BASE_WHERE_B} AND COALESCE(b.record_source, '') <> 'external_recharge_manual'`;
|
export const HYDROGEN_FUEL_ONLY_WHERE_B = `${HYDROGEN_BASE_WHERE_B} AND COALESCE(b.record_source, '') <> 'external_recharge_manual'`;
|
||||||
export const ELECTRIC_LOCAL = `charging_start_time`;
|
export const ELECTRIC_LOCAL = `charging_start_time`;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 单站看板的 SQL 片段与租户常量。
|
||||||
|
// 这些是 SQL 的一部分,放在 constants.ts 而不是路由里,避免 SQL 拼装散落两处。
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** 租户只由服务端配置决定,不接受浏览器传值。 */
|
||||||
|
export const HYDROGEN_TENANT_ID = process.env.HYDROGEN_TENANT_ID?.trim() || '000000';
|
||||||
|
export const MANUAL_RECHARGE_SOURCE = 'external_recharge_manual';
|
||||||
|
export const NORMALIZED_PLATE = (column: string) =>
|
||||||
|
`UPPER(REPLACE(REPLACE(REPLACE(TRIM(${column}), ' ', ''), '.', ''), '·', ''))`;
|
||||||
|
export const NORMALIZED_PLATE_UNICODE = (column: string) =>
|
||||||
|
`CONVERT(${NORMALIZED_PLATE(column)} USING utf8mb4) COLLATE utf8mb4_unicode_ci`;
|
||||||
|
export const EXTERNAL_CUSTOMER_MAP_CTE = `
|
||||||
|
WITH external_vehicle_candidates AS (
|
||||||
|
SELECT ${NORMALIZED_PLATE_UNICODE('plate_number')} AS normalizedPlate,
|
||||||
|
MAX(NULLIF(TRIM(actual_user), '')) AS customerName,
|
||||||
|
COUNT(DISTINCT NULLIF(TRIM(actual_user), '')) AS customerCount
|
||||||
|
FROM hydrogen_order_transfer_external_vehicle_user
|
||||||
|
WHERE NULLIF(TRIM(plate_number), '') IS NOT NULL
|
||||||
|
GROUP BY ${NORMALIZED_PLATE_UNICODE('plate_number')}
|
||||||
|
), external_vehicle_map AS (
|
||||||
|
SELECT normalizedPlate, customerName
|
||||||
|
FROM external_vehicle_candidates
|
||||||
|
WHERE customerCount = 1
|
||||||
|
AND customerName IS NOT NULL
|
||||||
|
)`;
|
||||||
|
|||||||
@@ -2,7 +2,18 @@ import type { Hono } from 'hono';
|
|||||||
import type { RowDataPacket } from 'mysql2';
|
import type { RowDataPacket } from 'mysql2';
|
||||||
import type hydrogenPool from '../../db/hydrogen.js';
|
import type hydrogenPool from '../../db/hydrogen.js';
|
||||||
import type { cached } from './cache.js';
|
import type { cached } from './cache.js';
|
||||||
import { HYDROGEN_BASE_WHERE, HYDROGEN_BASE_WHERE_B, HYDROGEN_LOCAL, HYDROGEN_TABLE } from './constants.js';
|
import {
|
||||||
|
loadCustomerMonth,
|
||||||
|
loadExternalCustomerMonth,
|
||||||
|
loadExternalReceipts,
|
||||||
|
loadRangePayments,
|
||||||
|
loadStationDaily,
|
||||||
|
loadStationDailyForStation,
|
||||||
|
loadStationList,
|
||||||
|
loadStationPaymentDaily,
|
||||||
|
loadSummaryDaily,
|
||||||
|
loadSummaryPaymentDaily,
|
||||||
|
} from './repository.js';
|
||||||
import { dateRangeClause, enumerateDateRange, resolveDateRange } from './query-model.js';
|
import { dateRangeClause, enumerateDateRange, resolveDateRange } from './query-model.js';
|
||||||
|
|
||||||
export interface HydrogenStationBoardDependencies {
|
export interface HydrogenStationBoardDependencies {
|
||||||
@@ -20,28 +31,8 @@ function numberValue(value: unknown): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 单站接口没有租户入参,租户只能由服务端配置决定,不能接受浏览器传值。
|
// 单站接口没有租户入参,租户只能由服务端配置决定,不能接受浏览器传值。
|
||||||
const HYDROGEN_TENANT_ID = process.env.HYDROGEN_TENANT_ID?.trim() || '000000';
|
|
||||||
const MANUAL_RECHARGE_SOURCE = 'external_recharge_manual';
|
|
||||||
|
|
||||||
const NORMALIZED_PLATE = (column: string) =>
|
|
||||||
`UPPER(REPLACE(REPLACE(REPLACE(TRIM(${column}), ' ', ''), '.', ''), '·', ''))`;
|
|
||||||
const NORMALIZED_PLATE_UNICODE = (column: string) =>
|
|
||||||
`CONVERT(${NORMALIZED_PLATE(column)} USING utf8mb4) COLLATE utf8mb4_unicode_ci`;
|
|
||||||
|
|
||||||
const EXTERNAL_CUSTOMER_MAP_CTE = `
|
|
||||||
WITH external_vehicle_candidates AS (
|
|
||||||
SELECT ${NORMALIZED_PLATE_UNICODE('plate_number')} AS normalizedPlate,
|
|
||||||
MAX(NULLIF(TRIM(actual_user), '')) AS customerName,
|
|
||||||
COUNT(DISTINCT NULLIF(TRIM(actual_user), '')) AS customerCount
|
|
||||||
FROM hydrogen_order_transfer_external_vehicle_user
|
|
||||||
WHERE NULLIF(TRIM(plate_number), '') IS NOT NULL
|
|
||||||
GROUP BY ${NORMALIZED_PLATE_UNICODE('plate_number')}
|
|
||||||
), external_vehicle_map AS (
|
|
||||||
SELECT normalizedPlate, customerName
|
|
||||||
FROM external_vehicle_candidates
|
|
||||||
WHERE customerCount = 1
|
|
||||||
AND customerName IS NOT NULL
|
|
||||||
)`;
|
|
||||||
|
|
||||||
// 单站经营看板只做只读聚合。列表仅返回所选区间内存在有效加氢记录的站点。
|
// 单站经营看板只做只读聚合。列表仅返回所选区间内存在有效加氢记录的站点。
|
||||||
export function registerHydrogenStationBoardRoute(
|
export function registerHydrogenStationBoardRoute(
|
||||||
@@ -59,87 +50,12 @@ export function registerHydrogenStationBoardRoute(
|
|||||||
const cacheKey = `hydrogen/station-board?start=${range.start}&end=${range.end}${stationId ? `&station=${stationId}` : ''}`;
|
const cacheKey = `hydrogen/station-board?start=${range.start}&end=${range.end}${stationId ? `&station=${stationId}` : ''}`;
|
||||||
|
|
||||||
const data = await cached(cacheKey, async () => {
|
const data = await cached(cacheKey, async () => {
|
||||||
const [stationRows] = await hydrogenPool.query<RowDataPacket[]>(
|
const stationRows = await loadStationList(hydrogenPool, { start: range.start, end: range.end });
|
||||||
`SELECT s.id,
|
|
||||||
COALESCE(NULLIF(s.station_short_name, ''), NULLIF(s.station_name, ''), CONCAT('未知站点 #', s.id)) AS name,
|
|
||||||
COALESCE(NULLIF(dp.NAME, ''), NULLIF(s.province, ''), '未归属') AS province,
|
|
||||||
COALESCE(NULLIF(dc.NAME, ''), NULLIF(s.city, ''), '未归属') AS city,
|
|
||||||
ROUND(COALESCE(SUM(b.amount_kg), 0), 2) AS kg,
|
|
||||||
ROUND(COALESCE(SUM(b.cost_total), 0), 2) AS fee,
|
|
||||||
COUNT(b.id) AS recordCount,
|
|
||||||
DATE_FORMAT(MAX(b.${HYDROGEN_LOCAL}), '%Y-%m-%d %H:%i:%s') AS latestLedgerTime
|
|
||||||
FROM hydrogen_station s
|
|
||||||
LEFT JOIN ${HYDROGEN_TABLE} b
|
|
||||||
ON b.station_id = s.id
|
|
||||||
AND ${HYDROGEN_BASE_WHERE_B}
|
|
||||||
AND b.tenant_id = ?
|
|
||||||
AND COALESCE(b.record_source, '') <> ?
|
|
||||||
AND ${dateRangeClause(`b.${HYDROGEN_LOCAL}`)}
|
|
||||||
LEFT JOIN common_district dp
|
|
||||||
ON CONVERT(dp.CODE USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
|
||||||
= CONVERT(s.province USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
|
||||||
AND dp.STATUS = 'VALID'
|
|
||||||
LEFT JOIN common_district dc
|
|
||||||
ON CONVERT(dc.CODE USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
|
||||||
= CONVERT(s.city USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
|
||||||
AND dc.STATUS = 'VALID'
|
|
||||||
WHERE s.del_flag = '0'
|
|
||||||
GROUP BY s.id, s.station_short_name, s.station_name, dp.NAME, s.province, dc.NAME, s.city
|
|
||||||
ORDER BY kg DESC, name ASC`,
|
|
||||||
[HYDROGEN_TENANT_ID, MANUAL_RECHARGE_SOURCE, range.start, range.end],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [paymentRows] = await hydrogenPool.query<RowDataPacket[]>(
|
const paymentRows = await loadRangePayments(hydrogenPool, { start: range.start, end: range.end });
|
||||||
`SELECT station_id AS stationId,
|
const summaryDailyRows = await loadSummaryDaily(hydrogenPool, { start: range.start, end: range.end });
|
||||||
ROUND(SUM(amount), 2) AS amount,
|
const stationDailyRows = await loadStationDaily(hydrogenPool, { start: range.start, end: range.end });
|
||||||
COUNT(*) AS paymentCount,
|
const summaryPaymentDailyRows = await loadSummaryPaymentDaily(hydrogenPool, { start: range.start, end: range.end });
|
||||||
DATE_FORMAT(MAX(payment_date), '%Y-%m-%d') AS latestPaymentDate
|
|
||||||
FROM hydrogen_station_payment
|
|
||||||
WHERE del_flag = '0'
|
|
||||||
AND payment_date >= ?
|
|
||||||
AND payment_date <= ?
|
|
||||||
GROUP BY station_id`,
|
|
||||||
[range.start, range.end],
|
|
||||||
);
|
|
||||||
const [summaryDailyRows] = await hydrogenPool.query<RowDataPacket[]>(
|
|
||||||
`SELECT DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m-%d') AS date,
|
|
||||||
ROUND(SUM(b.amount_kg), 2) AS kg,
|
|
||||||
ROUND(SUM(b.cost_total), 2) AS fee,
|
|
||||||
COUNT(*) AS recordCount
|
|
||||||
FROM ${HYDROGEN_TABLE} b
|
|
||||||
WHERE ${HYDROGEN_BASE_WHERE_B}
|
|
||||||
AND b.tenant_id = ?
|
|
||||||
AND COALESCE(b.record_source, '') <> ?
|
|
||||||
AND ${dateRangeClause(`b.${HYDROGEN_LOCAL}`)}
|
|
||||||
GROUP BY DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m-%d')
|
|
||||||
ORDER BY date ASC`,
|
|
||||||
[HYDROGEN_TENANT_ID, MANUAL_RECHARGE_SOURCE, range.start, range.end],
|
|
||||||
);
|
|
||||||
const [stationDailyRows] = await hydrogenPool.query<RowDataPacket[]>(
|
|
||||||
`SELECT b.station_id AS stationId,
|
|
||||||
DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m-%d') AS date,
|
|
||||||
ROUND(SUM(b.amount_kg), 2) AS kg
|
|
||||||
FROM ${HYDROGEN_TABLE} b
|
|
||||||
WHERE ${HYDROGEN_BASE_WHERE_B}
|
|
||||||
AND b.tenant_id = ?
|
|
||||||
AND COALESCE(b.record_source, '') <> ?
|
|
||||||
AND ${dateRangeClause(`b.${HYDROGEN_LOCAL}`)}
|
|
||||||
GROUP BY b.station_id, DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m-%d')
|
|
||||||
ORDER BY stationId ASC, date ASC`,
|
|
||||||
[HYDROGEN_TENANT_ID, MANUAL_RECHARGE_SOURCE, range.start, range.end],
|
|
||||||
);
|
|
||||||
const [summaryPaymentDailyRows] = await hydrogenPool.query<RowDataPacket[]>(
|
|
||||||
`SELECT DATE_FORMAT(payment_date, '%Y-%m-%d') AS date,
|
|
||||||
ROUND(SUM(amount), 2) AS amount,
|
|
||||||
COUNT(*) AS paymentCount
|
|
||||||
FROM hydrogen_station_payment
|
|
||||||
WHERE del_flag = '0'
|
|
||||||
AND payment_date >= ?
|
|
||||||
AND payment_date <= ?
|
|
||||||
GROUP BY DATE_FORMAT(payment_date, '%Y-%m-%d')
|
|
||||||
ORDER BY date ASC`,
|
|
||||||
[range.start, range.end],
|
|
||||||
);
|
|
||||||
const paymentByStation = new Map(paymentRows.map(row => [numberValue(row.stationId), row]));
|
const paymentByStation = new Map(paymentRows.map(row => [numberValue(row.stationId), row]));
|
||||||
const dailyKgByStation = new Map<number, Map<string, number>>();
|
const dailyKgByStation = new Map<number, Map<string, number>>();
|
||||||
for (const row of stationDailyRows) {
|
for (const row of stationDailyRows) {
|
||||||
@@ -176,35 +92,8 @@ export function registerHydrogenStationBoardRoute(
|
|||||||
|
|
||||||
let selected = null;
|
let selected = null;
|
||||||
if (stationId) {
|
if (stationId) {
|
||||||
const [dailyRows] = await hydrogenPool.query<RowDataPacket[]>(
|
const dailyRows = await loadStationDailyForStation(hydrogenPool, { stationId: stationId as number, start: range.start, end: range.end });
|
||||||
`SELECT DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m-%d') AS date,
|
const dailyPaymentRows = await loadStationPaymentDaily(hydrogenPool, { stationId: stationId as number, start: range.start, end: range.end });
|
||||||
ROUND(SUM(b.amount_kg), 2) AS kg,
|
|
||||||
ROUND(SUM(b.cost_total), 2) AS fee,
|
|
||||||
ROUND(AVG(NULLIF(b.cost_price, 0)), 2) AS avgPrice,
|
|
||||||
COUNT(*) AS recordCount
|
|
||||||
FROM ${HYDROGEN_TABLE} b
|
|
||||||
WHERE ${HYDROGEN_BASE_WHERE_B}
|
|
||||||
AND b.tenant_id = ?
|
|
||||||
AND COALESCE(b.record_source, '') <> ?
|
|
||||||
AND b.station_id = ?
|
|
||||||
AND ${dateRangeClause(`b.${HYDROGEN_LOCAL}`)}
|
|
||||||
GROUP BY DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m-%d')
|
|
||||||
ORDER BY date ASC`,
|
|
||||||
[HYDROGEN_TENANT_ID, MANUAL_RECHARGE_SOURCE, stationId, range.start, range.end],
|
|
||||||
);
|
|
||||||
const [dailyPaymentRows] = await hydrogenPool.query<RowDataPacket[]>(
|
|
||||||
`SELECT DATE_FORMAT(payment_date, '%Y-%m-%d') AS date,
|
|
||||||
ROUND(SUM(amount), 2) AS amount,
|
|
||||||
COUNT(*) AS paymentCount
|
|
||||||
FROM hydrogen_station_payment
|
|
||||||
WHERE del_flag = '0'
|
|
||||||
AND station_id = ?
|
|
||||||
AND payment_date >= ?
|
|
||||||
AND payment_date <= ?
|
|
||||||
GROUP BY DATE_FORMAT(payment_date, '%Y-%m-%d')
|
|
||||||
ORDER BY date ASC`,
|
|
||||||
[stationId, range.start, range.end],
|
|
||||||
);
|
|
||||||
const paymentByDate = new Map(dailyPaymentRows.map(row => [String(row.date), row]));
|
const paymentByDate = new Map(dailyPaymentRows.map(row => [String(row.date), row]));
|
||||||
const ledgerByDate = new Map(dailyRows.map(row => [String(row.date), row]));
|
const ledgerByDate = new Map(dailyRows.map(row => [String(row.date), row]));
|
||||||
let previousKg = 0;
|
let previousKg = 0;
|
||||||
@@ -226,92 +115,9 @@ export function registerHydrogenStationBoardRoute(
|
|||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
const [customerMonthRows] = await hydrogenPool.query<RowDataPacket[]>(
|
const customerMonthRows = await loadCustomerMonth(hydrogenPool, { stationId: stationId as number, end: range.end });
|
||||||
`${EXTERNAL_CUSTOMER_MAP_CTE}
|
const externalCustomerMonthRows = await loadExternalCustomerMonth(hydrogenPool, { stationId: stationId as number, end: range.end });
|
||||||
SELECT DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m') AS month,
|
const externalReceiptRows = await loadExternalReceipts(hydrogenPool, { start: range.start, end: range.end });
|
||||||
COALESCE(m.customerName, NULLIF(TRIM(b.customer_name), ''), NULLIF(TRIM(b.system_customer_name), ''), '未关联客户') AS customerName,
|
|
||||||
ROUND(SUM(b.amount_kg), 2) AS kg,
|
|
||||||
ROUND(SUM(b.cost_total), 2) AS fee,
|
|
||||||
COUNT(*) AS recordCount
|
|
||||||
FROM ${HYDROGEN_TABLE} b
|
|
||||||
LEFT JOIN external_vehicle_map m
|
|
||||||
ON CONVERT(${NORMALIZED_PLATE('b.license_plate')} USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
|
||||||
= CONVERT(m.normalizedPlate USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
|
||||||
AND (b.vehicle_id IS NULL OR b.vehicle_id = 0)
|
|
||||||
WHERE ${HYDROGEN_BASE_WHERE_B}
|
|
||||||
AND b.tenant_id = ?
|
|
||||||
AND COALESCE(b.record_source, '') <> ?
|
|
||||||
AND b.station_id = ?
|
|
||||||
AND b.${HYDROGEN_LOCAL} >= DATE_SUB(DATE_FORMAT(?, '%Y-%m-01'), INTERVAL 11 MONTH)
|
|
||||||
AND b.${HYDROGEN_LOCAL} < DATE_ADD(?, INTERVAL 1 DAY)
|
|
||||||
GROUP BY DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m'),
|
|
||||||
COALESCE(m.customerName, NULLIF(TRIM(b.customer_name), ''), NULLIF(TRIM(b.system_customer_name), ''), '未关联客户')
|
|
||||||
ORDER BY month ASC, kg DESC`,
|
|
||||||
[HYDROGEN_TENANT_ID, MANUAL_RECHARGE_SOURCE, stationId, range.end, range.end],
|
|
||||||
);
|
|
||||||
const [externalCustomerMonthRows] = await hydrogenPool.query<RowDataPacket[]>(
|
|
||||||
`${EXTERNAL_CUSTOMER_MAP_CTE}
|
|
||||||
SELECT DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m') AS month,
|
|
||||||
m.customerName,
|
|
||||||
ROUND(SUM(b.amount_kg), 2) AS kg,
|
|
||||||
ROUND(SUM(COALESCE(b.fee_total, b.cost_total)), 2) AS fee,
|
|
||||||
COUNT(*) AS recordCount
|
|
||||||
FROM ${HYDROGEN_TABLE} b
|
|
||||||
INNER JOIN external_vehicle_map m
|
|
||||||
ON ${NORMALIZED_PLATE_UNICODE('b.license_plate')}
|
|
||||||
= m.normalizedPlate
|
|
||||||
WHERE ${HYDROGEN_BASE_WHERE_B}
|
|
||||||
AND b.tenant_id = ?
|
|
||||||
AND COALESCE(b.record_source, '') <> ?
|
|
||||||
AND b.station_id = ?
|
|
||||||
AND (b.vehicle_id IS NULL OR b.vehicle_id = 0)
|
|
||||||
AND b.${HYDROGEN_LOCAL} >= DATE_SUB(DATE_FORMAT(?, '%Y-%m-01'), INTERVAL 11 MONTH)
|
|
||||||
AND b.${HYDROGEN_LOCAL} < DATE_ADD(?, INTERVAL 1 DAY)
|
|
||||||
GROUP BY DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m'), m.customerName
|
|
||||||
ORDER BY month ASC, kg DESC, m.customerName ASC`,
|
|
||||||
[HYDROGEN_TENANT_ID, MANUAL_RECHARGE_SOURCE, stationId, range.end, range.end],
|
|
||||||
);
|
|
||||||
const [externalReceiptRows] = await hydrogenPool.query<RowDataPacket[]>(
|
|
||||||
`SELECT * FROM (
|
|
||||||
SELECT CONCAT('auto:', CAST(id AS CHAR)) AS id,
|
|
||||||
DATE_FORMAT(summary_date, '%Y-%m-%d') AS date,
|
|
||||||
customer_name AS customerName,
|
|
||||||
amount,
|
|
||||||
pay_method AS payMethod,
|
|
||||||
source,
|
|
||||||
spot_record_count AS sourceRecordCount,
|
|
||||||
DATE_FORMAT(COALESCE(refresh_time, update_time), '%Y-%m-%d %H:%i:%s') AS updatedAt
|
|
||||||
FROM hydrogen_external_recharge_daily_summary
|
|
||||||
WHERE tenant_id = ?
|
|
||||||
AND summary_date >= ?
|
|
||||||
AND summary_date <= ?
|
|
||||||
UNION ALL
|
|
||||||
SELECT CONCAT('manual:', CAST(id AS CHAR)) AS id,
|
|
||||||
DATE_FORMAT(${HYDROGEN_LOCAL}, '%Y-%m-%d') AS date,
|
|
||||||
COALESCE(NULLIF(customer_name, ''), '未关联客户') AS customerName,
|
|
||||||
fee_total AS amount,
|
|
||||||
recharge_pay_method AS payMethod,
|
|
||||||
record_source AS source,
|
|
||||||
1 AS sourceRecordCount,
|
|
||||||
DATE_FORMAT(update_time, '%Y-%m-%d %H:%i:%s') AS updatedAt
|
|
||||||
FROM ${HYDROGEN_TABLE}
|
|
||||||
WHERE ${HYDROGEN_BASE_WHERE}
|
|
||||||
AND tenant_id = ?
|
|
||||||
AND record_source = ?
|
|
||||||
AND ${HYDROGEN_LOCAL} >= ?
|
|
||||||
AND ${HYDROGEN_LOCAL} < DATE_ADD(?, INTERVAL 1 DAY)
|
|
||||||
) externalReceipts
|
|
||||||
ORDER BY date DESC, customerName ASC, id ASC`,
|
|
||||||
[
|
|
||||||
HYDROGEN_TENANT_ID,
|
|
||||||
range.start,
|
|
||||||
range.end,
|
|
||||||
HYDROGEN_TENANT_ID,
|
|
||||||
MANUAL_RECHARGE_SOURCE,
|
|
||||||
range.start,
|
|
||||||
range.end,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
selected = {
|
selected = {
|
||||||
daily,
|
daily,
|
||||||
customerMonths: customerMonthRows.map(row => ({
|
customerMonths: customerMonthRows.map(row => ({
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
import type { RowDataPacket } from 'mysql2';
|
import type { RowDataPacket } from 'mysql2';
|
||||||
import { dateRangeClause, type CustomerKind } from './query-model.js';
|
import { dateRangeClause, type CustomerKind } from './query-model.js';
|
||||||
|
import {
|
||||||
|
EXTERNAL_CUSTOMER_MAP_CTE,
|
||||||
|
HYDROGEN_BASE_WHERE,
|
||||||
|
HYDROGEN_BASE_WHERE_B,
|
||||||
|
HYDROGEN_LOCAL,
|
||||||
|
HYDROGEN_TABLE,
|
||||||
|
HYDROGEN_TENANT_ID,
|
||||||
|
MANUAL_RECHARGE_SOURCE,
|
||||||
|
NORMALIZED_PLATE,
|
||||||
|
NORMALIZED_PLATE_UNICODE,
|
||||||
|
} from './constants.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 能源域的数据访问。
|
* 能源域的数据访问。
|
||||||
@@ -118,3 +129,234 @@ export async function loadElectricDailyRows(
|
|||||||
);
|
);
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 氢能单站日报(/hydrogen/station-board)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
/** 区间内有有效加氢记录的站点列表。 */
|
||||||
|
export async function loadStationList(db: Database, args: { start: string; end: string }): Promise<RowDataPacket[]> {
|
||||||
|
const { start, end } = args;
|
||||||
|
const [rows] = await db.query<RowDataPacket[]>(`SELECT s.id,
|
||||||
|
COALESCE(NULLIF(s.station_short_name, ''), NULLIF(s.station_name, ''), CONCAT('未知站点 #', s.id)) AS name,
|
||||||
|
COALESCE(NULLIF(dp.NAME, ''), NULLIF(s.province, ''), '未归属') AS province,
|
||||||
|
COALESCE(NULLIF(dc.NAME, ''), NULLIF(s.city, ''), '未归属') AS city,
|
||||||
|
ROUND(COALESCE(SUM(b.amount_kg), 0), 2) AS kg,
|
||||||
|
ROUND(COALESCE(SUM(b.cost_total), 0), 2) AS fee,
|
||||||
|
COUNT(b.id) AS recordCount,
|
||||||
|
DATE_FORMAT(MAX(b.${HYDROGEN_LOCAL}), '%Y-%m-%d %H:%i:%s') AS latestLedgerTime
|
||||||
|
FROM hydrogen_station s
|
||||||
|
LEFT JOIN ${HYDROGEN_TABLE} b
|
||||||
|
ON b.station_id = s.id
|
||||||
|
AND ${HYDROGEN_BASE_WHERE_B}
|
||||||
|
AND b.tenant_id = ?
|
||||||
|
AND COALESCE(b.record_source, '') <> ?
|
||||||
|
AND ${dateRangeClause(`b.${HYDROGEN_LOCAL}`)}
|
||||||
|
LEFT JOIN common_district dp
|
||||||
|
ON CONVERT(dp.CODE USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
||||||
|
= CONVERT(s.province USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
||||||
|
AND dp.STATUS = 'VALID'
|
||||||
|
LEFT JOIN common_district dc
|
||||||
|
ON CONVERT(dc.CODE USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
||||||
|
= CONVERT(s.city USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
||||||
|
AND dc.STATUS = 'VALID'
|
||||||
|
WHERE s.del_flag = '0'
|
||||||
|
GROUP BY s.id, s.station_short_name, s.station_name, dp.NAME, s.province, dc.NAME, s.city
|
||||||
|
ORDER BY kg DESC, name ASC`, [HYDROGEN_TENANT_ID, MANUAL_RECHARGE_SOURCE, start, end]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 区间内现结流水(按站点汇总)。 */
|
||||||
|
export async function loadRangePayments(db: Database, args: { start: string; end: string }): Promise<RowDataPacket[]> {
|
||||||
|
const { start, end } = args;
|
||||||
|
const [rows] = await db.query<RowDataPacket[]>(`SELECT station_id AS stationId,
|
||||||
|
ROUND(SUM(amount), 2) AS amount,
|
||||||
|
COUNT(*) AS paymentCount,
|
||||||
|
DATE_FORMAT(MAX(payment_date), '%Y-%m-%d') AS latestPaymentDate
|
||||||
|
FROM hydrogen_station_payment
|
||||||
|
WHERE del_flag = '0'
|
||||||
|
AND payment_date >= ?
|
||||||
|
AND payment_date <= ?
|
||||||
|
GROUP BY station_id`, [start, end]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 全部站点逐日汇总。 */
|
||||||
|
export async function loadSummaryDaily(db: Database, args: { start: string; end: string }): Promise<RowDataPacket[]> {
|
||||||
|
const { start, end } = args;
|
||||||
|
const [rows] = await db.query<RowDataPacket[]>(`SELECT DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m-%d') AS date,
|
||||||
|
ROUND(SUM(b.amount_kg), 2) AS kg,
|
||||||
|
ROUND(SUM(b.cost_total), 2) AS fee,
|
||||||
|
COUNT(*) AS recordCount
|
||||||
|
FROM ${HYDROGEN_TABLE} b
|
||||||
|
WHERE ${HYDROGEN_BASE_WHERE_B}
|
||||||
|
AND b.tenant_id = ?
|
||||||
|
AND COALESCE(b.record_source, '') <> ?
|
||||||
|
AND ${dateRangeClause(`b.${HYDROGEN_LOCAL}`)}
|
||||||
|
GROUP BY DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m-%d')
|
||||||
|
ORDER BY date ASC`, [HYDROGEN_TENANT_ID, MANUAL_RECHARGE_SOURCE, start, end]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 站点逐日汇总。 */
|
||||||
|
export async function loadStationDaily(db: Database, args: { start: string; end: string }): Promise<RowDataPacket[]> {
|
||||||
|
const { start, end } = args;
|
||||||
|
const [rows] = await db.query<RowDataPacket[]>(`SELECT b.station_id AS stationId,
|
||||||
|
DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m-%d') AS date,
|
||||||
|
ROUND(SUM(b.amount_kg), 2) AS kg
|
||||||
|
FROM ${HYDROGEN_TABLE} b
|
||||||
|
WHERE ${HYDROGEN_BASE_WHERE_B}
|
||||||
|
AND b.tenant_id = ?
|
||||||
|
AND COALESCE(b.record_source, '') <> ?
|
||||||
|
AND ${dateRangeClause(`b.${HYDROGEN_LOCAL}`)}
|
||||||
|
GROUP BY b.station_id, DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m-%d')
|
||||||
|
ORDER BY stationId ASC, date ASC`, [HYDROGEN_TENANT_ID, MANUAL_RECHARGE_SOURCE, start, end]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 全部站点逐日现结。 */
|
||||||
|
export async function loadSummaryPaymentDaily(db: Database, args: { start: string; end: string }): Promise<RowDataPacket[]> {
|
||||||
|
const { start, end } = args;
|
||||||
|
const [rows] = await db.query<RowDataPacket[]>(`SELECT DATE_FORMAT(payment_date, '%Y-%m-%d') AS date,
|
||||||
|
ROUND(SUM(amount), 2) AS amount,
|
||||||
|
COUNT(*) AS paymentCount
|
||||||
|
FROM hydrogen_station_payment
|
||||||
|
WHERE del_flag = '0'
|
||||||
|
AND payment_date >= ?
|
||||||
|
AND payment_date <= ?
|
||||||
|
GROUP BY DATE_FORMAT(payment_date, '%Y-%m-%d')
|
||||||
|
ORDER BY date ASC`, [start, end]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 指定站点逐日加氢。 */
|
||||||
|
export async function loadStationDailyForStation(db: Database, args: { stationId: number; start: string; end: string }): Promise<RowDataPacket[]> {
|
||||||
|
const { stationId, start, end } = args;
|
||||||
|
const [rows] = await db.query<RowDataPacket[]>(`SELECT DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m-%d') AS date,
|
||||||
|
ROUND(SUM(b.amount_kg), 2) AS kg,
|
||||||
|
ROUND(SUM(b.cost_total), 2) AS fee,
|
||||||
|
ROUND(AVG(NULLIF(b.cost_price, 0)), 2) AS avgPrice,
|
||||||
|
COUNT(*) AS recordCount
|
||||||
|
FROM ${HYDROGEN_TABLE} b
|
||||||
|
WHERE ${HYDROGEN_BASE_WHERE_B}
|
||||||
|
AND b.tenant_id = ?
|
||||||
|
AND COALESCE(b.record_source, '') <> ?
|
||||||
|
AND b.station_id = ?
|
||||||
|
AND ${dateRangeClause(`b.${HYDROGEN_LOCAL}`)}
|
||||||
|
GROUP BY DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m-%d')
|
||||||
|
ORDER BY date ASC`, [HYDROGEN_TENANT_ID, MANUAL_RECHARGE_SOURCE, stationId, start, end]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 指定站点逐日现结。 */
|
||||||
|
export async function loadStationPaymentDaily(db: Database, args: { stationId: number; start: string; end: string }): Promise<RowDataPacket[]> {
|
||||||
|
const { stationId, start, end } = args;
|
||||||
|
const [rows] = await db.query<RowDataPacket[]>(`SELECT DATE_FORMAT(payment_date, '%Y-%m-%d') AS date,
|
||||||
|
ROUND(SUM(amount), 2) AS amount,
|
||||||
|
COUNT(*) AS paymentCount
|
||||||
|
FROM hydrogen_station_payment
|
||||||
|
WHERE del_flag = '0'
|
||||||
|
AND station_id = ?
|
||||||
|
AND payment_date >= ?
|
||||||
|
AND payment_date <= ?
|
||||||
|
GROUP BY DATE_FORMAT(payment_date, '%Y-%m-%d')
|
||||||
|
ORDER BY date ASC`, [stationId, start, end]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 指定站点的客户月度汇总。 */
|
||||||
|
export async function loadCustomerMonth(db: Database, args: { stationId: number; end: string }): Promise<RowDataPacket[]> {
|
||||||
|
const { stationId, end } = args;
|
||||||
|
const [rows] = await db.query<RowDataPacket[]>(`${EXTERNAL_CUSTOMER_MAP_CTE}
|
||||||
|
SELECT DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m') AS month,
|
||||||
|
COALESCE(m.customerName, NULLIF(TRIM(b.customer_name), ''), NULLIF(TRIM(b.system_customer_name), ''), '未关联客户') AS customerName,
|
||||||
|
ROUND(SUM(b.amount_kg), 2) AS kg,
|
||||||
|
ROUND(SUM(b.cost_total), 2) AS fee,
|
||||||
|
COUNT(*) AS recordCount
|
||||||
|
FROM ${HYDROGEN_TABLE} b
|
||||||
|
LEFT JOIN external_vehicle_map m
|
||||||
|
ON CONVERT(${NORMALIZED_PLATE('b.license_plate')} USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
||||||
|
= CONVERT(m.normalizedPlate USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
||||||
|
AND (b.vehicle_id IS NULL OR b.vehicle_id = 0)
|
||||||
|
WHERE ${HYDROGEN_BASE_WHERE_B}
|
||||||
|
AND b.tenant_id = ?
|
||||||
|
AND COALESCE(b.record_source, '') <> ?
|
||||||
|
AND b.station_id = ?
|
||||||
|
AND b.${HYDROGEN_LOCAL} >= DATE_SUB(DATE_FORMAT(?, '%Y-%m-01'), INTERVAL 11 MONTH)
|
||||||
|
AND b.${HYDROGEN_LOCAL} < DATE_ADD(?, INTERVAL 1 DAY)
|
||||||
|
GROUP BY DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m'),
|
||||||
|
COALESCE(m.customerName, NULLIF(TRIM(b.customer_name), ''), NULLIF(TRIM(b.system_customer_name), ''), '未关联客户')
|
||||||
|
ORDER BY month ASC, kg DESC`, [HYDROGEN_TENANT_ID, MANUAL_RECHARGE_SOURCE, stationId, end, end]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 指定站点的外部客户月度汇总(含映射 CTE)。 */
|
||||||
|
export async function loadExternalCustomerMonth(db: Database, args: { stationId: number; end: string }): Promise<RowDataPacket[]> {
|
||||||
|
const { stationId, end } = args;
|
||||||
|
const [rows] = await db.query<RowDataPacket[]>(`${EXTERNAL_CUSTOMER_MAP_CTE}
|
||||||
|
SELECT DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m') AS month,
|
||||||
|
m.customerName,
|
||||||
|
ROUND(SUM(b.amount_kg), 2) AS kg,
|
||||||
|
ROUND(SUM(COALESCE(b.fee_total, b.cost_total)), 2) AS fee,
|
||||||
|
COUNT(*) AS recordCount
|
||||||
|
FROM ${HYDROGEN_TABLE} b
|
||||||
|
INNER JOIN external_vehicle_map m
|
||||||
|
ON ${NORMALIZED_PLATE_UNICODE('b.license_plate')}
|
||||||
|
= m.normalizedPlate
|
||||||
|
WHERE ${HYDROGEN_BASE_WHERE_B}
|
||||||
|
AND b.tenant_id = ?
|
||||||
|
AND COALESCE(b.record_source, '') <> ?
|
||||||
|
AND b.station_id = ?
|
||||||
|
AND (b.vehicle_id IS NULL OR b.vehicle_id = 0)
|
||||||
|
AND b.${HYDROGEN_LOCAL} >= DATE_SUB(DATE_FORMAT(?, '%Y-%m-01'), INTERVAL 11 MONTH)
|
||||||
|
AND b.${HYDROGEN_LOCAL} < DATE_ADD(?, INTERVAL 1 DAY)
|
||||||
|
GROUP BY DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m'), m.customerName
|
||||||
|
ORDER BY month ASC, kg DESC, m.customerName ASC`, [HYDROGEN_TENANT_ID, MANUAL_RECHARGE_SOURCE, stationId, end, end]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 外部客户进账(自动 + 手工充值合并)。 */
|
||||||
|
export async function loadExternalReceipts(db: Database, args: { start: string; end: string }): Promise<RowDataPacket[]> {
|
||||||
|
const { start, end } = args;
|
||||||
|
const [rows] = await db.query<RowDataPacket[]>(`SELECT * FROM (
|
||||||
|
SELECT CONCAT('auto:', CAST(id AS CHAR)) AS id,
|
||||||
|
DATE_FORMAT(summary_date, '%Y-%m-%d') AS date,
|
||||||
|
customer_name AS customerName,
|
||||||
|
amount,
|
||||||
|
pay_method AS payMethod,
|
||||||
|
source,
|
||||||
|
spot_record_count AS sourceRecordCount,
|
||||||
|
DATE_FORMAT(COALESCE(refresh_time, update_time), '%Y-%m-%d %H:%i:%s') AS updatedAt
|
||||||
|
FROM hydrogen_external_recharge_daily_summary
|
||||||
|
WHERE tenant_id = ?
|
||||||
|
AND summary_date >= ?
|
||||||
|
AND summary_date <= ?
|
||||||
|
UNION ALL
|
||||||
|
SELECT CONCAT('manual:', CAST(id AS CHAR)) AS id,
|
||||||
|
DATE_FORMAT(${HYDROGEN_LOCAL}, '%Y-%m-%d') AS date,
|
||||||
|
COALESCE(NULLIF(customer_name, ''), '未关联客户') AS customerName,
|
||||||
|
fee_total AS amount,
|
||||||
|
recharge_pay_method AS payMethod,
|
||||||
|
record_source AS source,
|
||||||
|
1 AS sourceRecordCount,
|
||||||
|
DATE_FORMAT(update_time, '%Y-%m-%d %H:%i:%s') AS updatedAt
|
||||||
|
FROM ${HYDROGEN_TABLE}
|
||||||
|
WHERE ${HYDROGEN_BASE_WHERE}
|
||||||
|
AND tenant_id = ?
|
||||||
|
AND record_source = ?
|
||||||
|
AND ${HYDROGEN_LOCAL} >= ?
|
||||||
|
AND ${HYDROGEN_LOCAL} < DATE_ADD(?, INTERVAL 1 DAY)
|
||||||
|
) externalReceipts
|
||||||
|
ORDER BY date DESC, customerName ASC, id ASC`, [HYDROGEN_TENANT_ID, start, end, HYDROGEN_TENANT_ID, MANUAL_RECHARGE_SOURCE, start, end]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user