feat(energy): integrate self-operated station customers and receipts
ci/woodpecker/push/woodpecker Pipeline was canceled
ci/woodpecker/push/woodpecker Pipeline was canceled
Co-authored-by: HiFox Agent <agents-noreply@hifox.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import type { Hono } from 'hono';
|
||||
import type { RowDataPacket } from 'mysql2';
|
||||
import type hydrogenPool from '../../hydrogen-db.js';
|
||||
import type { cached } from './cache.js';
|
||||
import { HYDROGEN_BASE_WHERE_B, HYDROGEN_LOCAL, HYDROGEN_TABLE } from './constants.js';
|
||||
import { HYDROGEN_BASE_WHERE, HYDROGEN_BASE_WHERE_B, HYDROGEN_LOCAL, HYDROGEN_TABLE } from './constants.js';
|
||||
import { dateRangeClause, enumerateDateRange, resolveDateRange } from './query-model.js';
|
||||
|
||||
export interface HydrogenStationBoardDependencies {
|
||||
@@ -19,6 +19,30 @@ function numberValue(value: unknown): number {
|
||||
return Number(value) || 0;
|
||||
}
|
||||
|
||||
// 单站接口没有租户入参,租户只能由服务端配置决定,不能接受浏览器传值。
|
||||
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(
|
||||
app: Hono,
|
||||
@@ -48,6 +72,8 @@ export function registerHydrogenStationBoardRoute(
|
||||
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
|
||||
@@ -60,7 +86,7 @@ export function registerHydrogenStationBoardRoute(
|
||||
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`,
|
||||
[range.start, range.end],
|
||||
[HYDROGEN_TENANT_ID, MANUAL_RECHARGE_SOURCE, range.start, range.end],
|
||||
);
|
||||
|
||||
const [paymentRows] = await hydrogenPool.query<RowDataPacket[]>(
|
||||
@@ -82,10 +108,12 @@ export function registerHydrogenStationBoardRoute(
|
||||
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`,
|
||||
[range.start, range.end],
|
||||
[HYDROGEN_TENANT_ID, MANUAL_RECHARGE_SOURCE, range.start, range.end],
|
||||
);
|
||||
const [stationDailyRows] = await hydrogenPool.query<RowDataPacket[]>(
|
||||
`SELECT b.station_id AS stationId,
|
||||
@@ -93,10 +121,12 @@ export function registerHydrogenStationBoardRoute(
|
||||
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`,
|
||||
[range.start, range.end],
|
||||
[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,
|
||||
@@ -154,11 +184,13 @@ export function registerHydrogenStationBoardRoute(
|
||||
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`,
|
||||
[stationId, range.start, range.end],
|
||||
[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,
|
||||
@@ -195,19 +227,90 @@ export function registerHydrogenStationBoardRoute(
|
||||
});
|
||||
|
||||
const [customerMonthRows] = await hydrogenPool.query<RowDataPacket[]>(
|
||||
`SELECT DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m') AS month,
|
||||
COALESCE(NULLIF(MAX(b.system_customer_name), ''), NULLIF(MAX(b.customer_name), ''), '未关联客户') AS customerName,
|
||||
`${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(b.system_customer_id, b.customer_id, 0)
|
||||
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`,
|
||||
[stationId, range.end, range.end],
|
||||
[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 = {
|
||||
daily,
|
||||
@@ -218,6 +321,28 @@ export function registerHydrogenStationBoardRoute(
|
||||
fee: numberValue(row.fee),
|
||||
recordCount: numberValue(row.recordCount),
|
||||
})),
|
||||
externalCustomerMonths: externalCustomerMonthRows.map(row => ({
|
||||
month: String(row.month),
|
||||
customerName: String(row.customerName),
|
||||
kg: numberValue(row.kg),
|
||||
fee: numberValue(row.fee),
|
||||
recordCount: numberValue(row.recordCount),
|
||||
})),
|
||||
// 快照和手工充值都没有可靠站点归属,不能把客户级进账误报为当前站点收益。
|
||||
externalReceipts: {
|
||||
scope: 'customer',
|
||||
reason: '当前租户全部外部客户进账,不按站点归属,不计入本单站经营收益或现结 KPI。',
|
||||
rows: externalReceiptRows.map(row => ({
|
||||
id: String(row.id),
|
||||
date: String(row.date),
|
||||
customerName: String(row.customerName),
|
||||
amount: numberValue(row.amount),
|
||||
payMethod: String(row.payMethod),
|
||||
source: String(row.source),
|
||||
sourceRecordCount: numberValue(row.sourceRecordCount),
|
||||
updatedAt: typeof row.updatedAt === 'string' ? row.updatedAt : null,
|
||||
})),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user