fix(pricing): 统一将计价计算从向下取整改为四舍五入
统一修复所有计价相关模块的取整逻辑:将Lua计价函数中的直接返回改为四舍五入取整,同时将前后端代码中的Math.floor替换为Math.round,确保全链路的票价计算采用标准四舍五入规则,避免出现不合理的定价偏差。
This commit is contained in:
Binary file not shown.
+15
-3
@@ -8,6 +8,12 @@ const io = require('../io');
|
|||||||
// Helper to get IP
|
// Helper to get IP
|
||||||
const getIp = (req) => (req.headers['x-forwarded-for'] || '').toString().split(',')[0].trim() || req.ip || req.connection?.remoteAddress || '';
|
const getIp = (req) => (req.headers['x-forwarded-for'] || '').toString().split(',')[0].trim() || req.ip || req.connection?.remoteAddress || '';
|
||||||
|
|
||||||
|
const resolveStationName = (code) => {
|
||||||
|
if (!code) return '';
|
||||||
|
const s = (DataService.getStations?.() || []).find(x => String(x.code).trim().toLowerCase() === String(code).trim().toLowerCase());
|
||||||
|
return s ? (s.name || s.cn_name || code) : code;
|
||||||
|
};
|
||||||
|
|
||||||
const normalizeTicketId = (v) => {
|
const normalizeTicketId = (v) => {
|
||||||
const s0 = String(v || '').replace(/\s+/g, '');
|
const s0 = String(v || '').replace(/\s+/g, '');
|
||||||
if (!s0) return '';
|
if (!s0) return '';
|
||||||
@@ -829,6 +835,7 @@ router.post('/ic-cards/:id/sync', async (req, res) => {
|
|||||||
result: patch.last_result,
|
result: patch.last_result,
|
||||||
reason: patch.last_reason || undefined,
|
reason: patch.last_reason || undefined,
|
||||||
station_code: station_code || undefined,
|
station_code: station_code || undefined,
|
||||||
|
station_name: resolveStationName(station_code),
|
||||||
entry_station: card.entry_station || undefined,
|
entry_station: card.entry_station || undefined,
|
||||||
exit_station: card.exit_station || undefined,
|
exit_station: card.exit_station || undefined,
|
||||||
fare: patch.last_fare,
|
fare: patch.last_fare,
|
||||||
@@ -916,6 +923,7 @@ router.post('/cards/open', async (req, res) => {
|
|||||||
card_id: card.card_id,
|
card_id: card.card_id,
|
||||||
order_code: voucher_code,
|
order_code: voucher_code,
|
||||||
station_code,
|
station_code,
|
||||||
|
station_name: resolveStationName(station_code),
|
||||||
device,
|
device,
|
||||||
balance,
|
balance,
|
||||||
deposit,
|
deposit,
|
||||||
@@ -1067,6 +1075,7 @@ router.post('/cards/check', async (req, res) => {
|
|||||||
action,
|
action,
|
||||||
result: 'pass',
|
result: 'pass',
|
||||||
station_code,
|
station_code,
|
||||||
|
station_name: resolveStationName(station_code),
|
||||||
entry_station: station_code,
|
entry_station: station_code,
|
||||||
device,
|
device,
|
||||||
balance: toMoney(card.balance)
|
balance: toMoney(card.balance)
|
||||||
@@ -1130,6 +1139,7 @@ router.post('/cards/check', async (req, res) => {
|
|||||||
action,
|
action,
|
||||||
result: 'pass',
|
result: 'pass',
|
||||||
station_code,
|
station_code,
|
||||||
|
station_name: resolveStationName(station_code),
|
||||||
entry_station,
|
entry_station,
|
||||||
exit_station: station_code,
|
exit_station: station_code,
|
||||||
fare,
|
fare,
|
||||||
@@ -1186,6 +1196,7 @@ router.post('/tickets/sale', async (req, res) => {
|
|||||||
train_type,
|
train_type,
|
||||||
cost: cost || 0,
|
cost: cost || 0,
|
||||||
station_code: station_code || '',
|
station_code: station_code || '',
|
||||||
|
station_name: resolveStationName(station_code),
|
||||||
device: device || 'unknown',
|
device: device || 'unknown',
|
||||||
trips_total: tTotal,
|
trips_total: tTotal,
|
||||||
trips_remaining: tRemain
|
trips_remaining: tRemain
|
||||||
@@ -1277,7 +1288,7 @@ router.post('/tickets/check', async (req, res) => {
|
|||||||
const cur = idx[ticket_id];
|
const cur = idx[ticket_id];
|
||||||
|
|
||||||
const deny = async (reason) => {
|
const deny = async (reason) => {
|
||||||
const ev = { ts, type: 'check', ticket_id, action, result: 'deny', reason, station_code: station_code_raw || '', device };
|
const ev = { ts, type: 'check', ticket_id, action, result: 'deny', reason, station_code: station_code_raw || '', station_name: resolveStationName(station_code_raw), device };
|
||||||
await DataService.appendTicketEvent(ev);
|
await DataService.appendTicketEvent(ev);
|
||||||
await DataService.upsertTicketIndex({
|
await DataService.upsertTicketIndex({
|
||||||
ticket_id,
|
ticket_id,
|
||||||
@@ -1329,7 +1340,7 @@ router.post('/tickets/check', async (req, res) => {
|
|||||||
if (!matchesStation(start)) return deny('wrong_station');
|
if (!matchesStation(start)) return deny('wrong_station');
|
||||||
if (entered && !exited) return deny('already_entered');
|
if (entered && !exited) return deny('already_entered');
|
||||||
|
|
||||||
const ev = { ts, type: 'check', ticket_id, action, result: 'pass', station_code: start, device };
|
const ev = { ts, type: 'check', ticket_id, action, result: 'pass', station_code: start, station_name: resolveStationName(start), device };
|
||||||
await DataService.appendTicketEvent(ev);
|
await DataService.appendTicketEvent(ev);
|
||||||
await DataService.upsertTicketIndex({
|
await DataService.upsertTicketIndex({
|
||||||
ticket_id,
|
ticket_id,
|
||||||
@@ -1355,7 +1366,7 @@ router.post('/tickets/check', async (req, res) => {
|
|||||||
|
|
||||||
const newTripsRemaining = Math.max(0, (Number(tripsRemainEffective) || 0) - 1);
|
const newTripsRemaining = Math.max(0, (Number(tripsRemainEffective) || 0) - 1);
|
||||||
const shouldSetUsed = newTripsRemaining <= 0;
|
const shouldSetUsed = newTripsRemaining <= 0;
|
||||||
const ev = { ts, type: 'check', ticket_id, action, result: 'pass', station_code: terminal, device, trips_remaining: newTripsRemaining };
|
const ev = { ts, type: 'check', ticket_id, action, result: 'pass', station_code: terminal, station_name: resolveStationName(terminal), device, trips_remaining: newTripsRemaining };
|
||||||
await DataService.appendTicketEvent(ev);
|
await DataService.appendTicketEvent(ev);
|
||||||
await DataService.upsertTicketIndex({
|
await DataService.upsertTicketIndex({
|
||||||
ticket_id,
|
ticket_id,
|
||||||
@@ -1399,6 +1410,7 @@ router.post('/tickets/status', async (req, res) => {
|
|||||||
result,
|
result,
|
||||||
reason,
|
reason,
|
||||||
station_code: station_code || '',
|
station_code: station_code || '',
|
||||||
|
station_name: resolveStationName(station_code),
|
||||||
device: device || 'unknown',
|
device: device || 'unknown',
|
||||||
trips_remaining,
|
trips_remaining,
|
||||||
rides_remaining
|
rides_remaining
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ const svgGenerator = require('../services/svg-generator');
|
|||||||
// Helper to get IP
|
// Helper to get IP
|
||||||
const getIp = (req) => (req.headers['x-forwarded-for'] || '').toString().split(',')[0].trim() || req.ip || req.connection?.remoteAddress || '';
|
const getIp = (req) => (req.headers['x-forwarded-for'] || '').toString().split(',')[0].trim() || req.ip || req.connection?.remoteAddress || '';
|
||||||
|
|
||||||
|
const resolveStationName = (code) => {
|
||||||
|
if (!code) return '';
|
||||||
|
const s = (DataService.getStations?.() || []).find(x => String(x.code).trim().toLowerCase() === String(code).trim().toLowerCase());
|
||||||
|
return s ? (s.name || s.cn_name || code) : code;
|
||||||
|
};
|
||||||
|
|
||||||
const appendReqLog = (req, { category, type, detail, source, level } = {}) => {
|
const appendReqLog = (req, { category, type, detail, source, level } = {}) => {
|
||||||
const entry = {
|
const entry = {
|
||||||
ts: new Date().toISOString(),
|
ts: new Date().toISOString(),
|
||||||
@@ -196,8 +202,8 @@ router.get('/fares/query', (req, res) => {
|
|||||||
const discount = Number.isFinite(discountRaw) && discountRaw > 0 ? discountRaw : 1;
|
const discount = Number.isFinite(discountRaw) && discountRaw > 0 ? discountRaw : 1;
|
||||||
const regularBase = Number(result.regular);
|
const regularBase = Number(result.regular);
|
||||||
const expressBase = Number(result.express);
|
const expressBase = Number(result.express);
|
||||||
const regularDiscounted = Number.isFinite(regularBase) ? Math.floor(regularBase * discount) : null;
|
const regularDiscounted = Number.isFinite(regularBase) ? Math.round(regularBase * discount) : null;
|
||||||
const expressDiscounted = Number.isFinite(expressBase) ? Math.floor(expressBase * discount) : null;
|
const expressDiscounted = Number.isFinite(expressBase) ? Math.round(expressBase * discount) : null;
|
||||||
appendReqLog(req, { category: 'public', type: 'fare_query', detail: { from, to, from_code: fromCode, to_code: toCode, ok: true } });
|
appendReqLog(req, { category: 'public', type: 'fare_query', detail: { from, to, from_code: fromCode, to_code: toCode, ok: true } });
|
||||||
res.json({
|
res.json({
|
||||||
from_code: fromCode || null,
|
from_code: fromCode || null,
|
||||||
@@ -502,6 +508,7 @@ router.post('/tickets/record', async (req, res) => {
|
|||||||
train_type,
|
train_type,
|
||||||
cost,
|
cost,
|
||||||
station_code,
|
station_code,
|
||||||
|
station_name: resolveStationName(station_code),
|
||||||
device,
|
device,
|
||||||
trips_total,
|
trips_total,
|
||||||
trips_remaining
|
trips_remaining
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ const LogicService = {
|
|||||||
const baseExp = Number(baseBoth?.express ?? 0) || 0;
|
const baseExp = Number(baseBoth?.express ?? 0) || 0;
|
||||||
const discount = Number(cfg?.promotion?.discount ?? 1);
|
const discount = Number(cfg?.promotion?.discount ?? 1);
|
||||||
const base = (trainType === 'Express') ? baseExp : baseReg;
|
const base = (trainType === 'Express') ? baseExp : baseReg;
|
||||||
return Math.floor(base * (discount > 0 ? discount : 1));
|
return Math.round(base * (discount > 0 ? discount : 1));
|
||||||
} catch (_) { return 0; }
|
} catch (_) { return 0; }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1644,7 +1644,7 @@ local function computeCost(src, dst, trainType)
|
|||||||
local u, du = heapPop()
|
local u, du = heapPop()
|
||||||
if not u then break end
|
if not u then break end
|
||||||
if du == dist[u] then
|
if du == dist[u] then
|
||||||
if u == dst then return du end
|
if u == dst then return math.floor(du + 0.5) end
|
||||||
for v, w in pairs(adj[u] or {}) do
|
for v, w in pairs(adj[u] or {}) do
|
||||||
local nd = du + (tonumber(w) or 0)
|
local nd = du + (tonumber(w) or 0)
|
||||||
if nd < (dist[v] or math.huge) then
|
if nd < (dist[v] or math.huge) then
|
||||||
|
|||||||
+1
-1
@@ -567,7 +567,7 @@
|
|||||||
const discountedRaw = (type==='Express')
|
const discountedRaw = (type==='Express')
|
||||||
? (fare?.discounted_express_fare ?? fare?.['优惠后特快票价'])
|
? (fare?.discounted_express_fare ?? fare?.['优惠后特快票价'])
|
||||||
: (fare?.discounted_regular_fare ?? fare?.['优惠后常规票价']);
|
: (fare?.discounted_regular_fare ?? fare?.['优惠后常规票价']);
|
||||||
const discountedSingle = Number(discountedRaw ?? Math.floor(base * disc) ?? 0);
|
const discountedSingle = Number(discountedRaw ?? Math.round(base * disc) ?? 0);
|
||||||
const price = discountedSingle * trips;
|
const price = discountedSingle * trips;
|
||||||
|
|
||||||
const routeKey = (type === 'Express') ? 'express_path' : 'regular_path';
|
const routeKey = (type === 'Express') ? 'express_path' : 'regular_path';
|
||||||
|
|||||||
Reference in New Issue
Block a user