547 lines
18 KiB
JavaScript
547 lines
18 KiB
JavaScript
const dayjs = require('dayjs');
|
|
const cloudDb = require('../config/cloudDatabase');
|
|
|
|
const DELIVERY_DURATION_EXCLUDED_WAREHOUSES = [
|
|
'常州简尚家居有限公司',
|
|
'三方-京东京造',
|
|
];
|
|
|
|
const DELIVERY_DURATION_EXCLUDED_LOGISTICS_KEYWORDS = [
|
|
'中铁',
|
|
'自提',
|
|
'物流',
|
|
'专线',
|
|
'线下快递',
|
|
'武进安能',
|
|
'京喜',
|
|
'京东自营',
|
|
'京造',
|
|
];
|
|
|
|
const UNKNOWN_WAREHOUSE = '未知仓库';
|
|
const UNKNOWN_COURIER = '未知物流公司';
|
|
const UNKNOWN_PROVINCE = '未知省份';
|
|
const REQUEST_TIMEOUT = 180000;
|
|
|
|
const DELIVERY_DETAIL_COLUMNS = [
|
|
{ key: 'innerOrderId', title: '内部订单号' },
|
|
{ key: 'onlineOrderNo', title: '线上订单号' },
|
|
{ key: 'waybillNo', title: '物流单号' },
|
|
{ key: 'courierName', title: '物流公司' },
|
|
{ key: 'platformName', title: '平台' },
|
|
{ key: 'shopName', title: '店铺' },
|
|
{ key: 'warehouseName', title: '发货仓' },
|
|
{ key: 'receiverAddress', title: '收货省市区' },
|
|
{ key: 'payTime', title: '支付时间' },
|
|
{ key: 'sendTime', title: '发货时间' },
|
|
{ key: 'fetchTime', title: '揽收时间' },
|
|
{ key: 'signTime', title: '签收时间' },
|
|
{ key: 'payToSignHours', title: '付款至签收时长(h)' },
|
|
{ key: 'sendToSignHours', title: '发货至签收时长(h)' },
|
|
];
|
|
|
|
function q(value) {
|
|
return `N'${String(value).replace(/'/g, "''")}'`;
|
|
}
|
|
|
|
function parseListParam(value) {
|
|
if (Array.isArray(value)) {
|
|
return [...new Set(value.flatMap((item) => parseListParam(item)))];
|
|
}
|
|
|
|
return [...new Set(String(value || '')
|
|
.split(',')
|
|
.map((item) => item.trim())
|
|
.filter(Boolean))];
|
|
}
|
|
|
|
function toNumber(value, fallback = 0) {
|
|
const parsed = Number(value);
|
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
}
|
|
|
|
function round2(value) {
|
|
const parsed = toNumber(value);
|
|
return Number(parsed.toFixed(2));
|
|
}
|
|
|
|
function normalizeDateRange({ startDate, endDate } = {}) {
|
|
const startCandidate = startDate && dayjs(startDate).isValid()
|
|
? dayjs(startDate)
|
|
: dayjs().startOf('month');
|
|
const endCandidate = endDate && dayjs(endDate).isValid()
|
|
? dayjs(endDate)
|
|
: dayjs();
|
|
|
|
const start = startCandidate.isAfter(endCandidate, 'day') ? endCandidate : startCandidate;
|
|
const end = endCandidate.isBefore(startCandidate, 'day') ? startCandidate : endCandidate;
|
|
|
|
return {
|
|
startDate: start.format('YYYY-MM-DD'),
|
|
endDate: end.format('YYYY-MM-DD'),
|
|
startDateTime: `${start.format('YYYY-MM-DD')} 00:00:00`,
|
|
endDateExclusive: `${end.add(1, 'day').format('YYYY-MM-DD')} 00:00:00`,
|
|
};
|
|
}
|
|
|
|
function sortTextValues(values) {
|
|
return values
|
|
.filter(Boolean)
|
|
.sort((a, b) => String(a).localeCompare(String(b), 'zh-Hans-CN'));
|
|
}
|
|
|
|
const excludedWarehousesSql = DELIVERY_DURATION_EXCLUDED_WAREHOUSES.map(q).join(', ');
|
|
const excludedLogisticsSql = DELIVERY_DURATION_EXCLUDED_LOGISTICS_KEYWORDS
|
|
.map((keyword) => `ISNULL(logistic_company_name, N'') NOT LIKE ${q(`%${keyword}%`)}`)
|
|
.join('\n AND ');
|
|
|
|
const provinceExpr = `
|
|
CASE
|
|
WHEN receiver_address IS NULL OR LTRIM(RTRIM(receiver_address)) = N'' THEN N'${UNKNOWN_PROVINCE}'
|
|
WHEN CHARINDEX(N'-', receiver_address) > 0 THEN LEFT(receiver_address, CHARINDEX(N'-', receiver_address) - 1)
|
|
WHEN CHARINDEX(N'自治区', receiver_address) > 0 THEN LEFT(receiver_address, CHARINDEX(N'自治区', receiver_address) + 2)
|
|
WHEN CHARINDEX(N'省', receiver_address) > 0 THEN LEFT(receiver_address, CHARINDEX(N'省', receiver_address))
|
|
WHEN CHARINDEX(N'市', receiver_address) > 0 THEN LEFT(receiver_address, CHARINDEX(N'市', receiver_address))
|
|
ELSE receiver_address
|
|
END`;
|
|
|
|
const deliveryBaseWhereSql = `
|
|
sign_time >= @startDateTime
|
|
AND sign_time < @endDateExclusive
|
|
AND pay_time IS NOT NULL
|
|
AND send_time IS NOT NULL
|
|
AND sign_time IS NOT NULL
|
|
AND sign_time >= pay_time
|
|
AND sign_time >= send_time
|
|
AND ISNULL(warehouse_name, N'') NOT IN (${excludedWarehousesSql})
|
|
AND (
|
|
logistic_company_name IS NULL
|
|
OR (
|
|
${excludedLogisticsSql}
|
|
)
|
|
)`;
|
|
|
|
const DELIVERY_AGGREGATE_SQL = `
|
|
SET NOCOUNT ON;
|
|
|
|
WITH delivery_base AS (
|
|
SELECT
|
|
CONVERT(varchar(10), sign_time, 120) AS stat_date,
|
|
COALESCE(NULLIF(LTRIM(RTRIM(warehouse_name)), N''), N'${UNKNOWN_WAREHOUSE}') AS warehouse_name,
|
|
COALESCE(NULLIF(LTRIM(RTRIM(logistic_company_name)), N''), N'${UNKNOWN_COURIER}') AS courier_name,
|
|
COALESCE(NULLIF(LTRIM(RTRIM(${provinceExpr})), N''), N'${UNKNOWN_PROVINCE}') AS province,
|
|
CAST(DATEDIFF(SECOND, pay_time, sign_time) AS DECIMAL(20, 2)) / 3600.0 AS pay_to_sign_hours,
|
|
CAST(DATEDIFF(SECOND, send_time, sign_time) AS DECIMAL(20, 2)) / 3600.0 AS send_to_sign_hours,
|
|
CASE WHEN DATEDIFF(SECOND, pay_time, sign_time) <= 48 * 3600 THEN 1 ELSE 0 END AS timely_48
|
|
FROM dbo.jst_order_express_details
|
|
WHERE ${deliveryBaseWhereSql}
|
|
)
|
|
SELECT
|
|
stat_date AS statDate,
|
|
warehouse_name AS warehouseName,
|
|
courier_name AS courierName,
|
|
province,
|
|
COUNT_BIG(*) AS orderCount,
|
|
CAST(ROUND(SUM(pay_to_sign_hours), 2) AS DECIMAL(20, 2)) AS payTotalHours,
|
|
CAST(ROUND(SUM(send_to_sign_hours), 2) AS DECIMAL(20, 2)) AS sendTotalHours,
|
|
SUM(timely_48) AS timely48Count
|
|
FROM delivery_base
|
|
GROUP BY stat_date, warehouse_name, courier_name, province
|
|
ORDER BY stat_date, warehouse_name, province, courier_name;
|
|
`;
|
|
|
|
function normalizeAggregateRow(row) {
|
|
const orderCount = toNumber(row.orderCount);
|
|
const payTotalHours = toNumber(row.payTotalHours);
|
|
const sendTotalHours = toNumber(row.sendTotalHours);
|
|
const timely48Count = toNumber(row.timely48Count);
|
|
|
|
return {
|
|
statDate: row.statDate || '',
|
|
warehouseName: row.warehouseName || UNKNOWN_WAREHOUSE,
|
|
courierName: row.courierName || UNKNOWN_COURIER,
|
|
province: row.province || UNKNOWN_PROVINCE,
|
|
orderCount,
|
|
payTotalHours,
|
|
sendTotalHours,
|
|
timely48Count,
|
|
};
|
|
}
|
|
|
|
function createBucket() {
|
|
return {
|
|
orderCount: 0,
|
|
payTotalHours: 0,
|
|
sendTotalHours: 0,
|
|
timely48Count: 0,
|
|
};
|
|
}
|
|
|
|
function addToBucket(bucket, row) {
|
|
bucket.orderCount += toNumber(row.orderCount);
|
|
bucket.payTotalHours += toNumber(row.payTotalHours);
|
|
bucket.sendTotalHours += toNumber(row.sendTotalHours);
|
|
bucket.timely48Count += toNumber(row.timely48Count);
|
|
}
|
|
|
|
function finalizeBucket(bucket) {
|
|
const orderCount = toNumber(bucket.orderCount);
|
|
const timely48Count = toNumber(bucket.timely48Count);
|
|
|
|
return {
|
|
orderCount,
|
|
payTotalHours: round2(bucket.payTotalHours),
|
|
sendTotalHours: round2(bucket.sendTotalHours),
|
|
avgPayToSignHours: orderCount > 0 ? round2(bucket.payTotalHours / orderCount) : null,
|
|
avgSendToSignHours: orderCount > 0 ? round2(bucket.sendTotalHours / orderCount) : null,
|
|
timely48Count,
|
|
timely48Rate: orderCount > 0 ? round2((timely48Count / orderCount) * 100) : null,
|
|
};
|
|
}
|
|
|
|
function buildFilterOptions(rows) {
|
|
return {
|
|
warehouses: sortTextValues([...new Set(rows.map((row) => row.warehouseName))]),
|
|
couriers: sortTextValues([...new Set(rows.map((row) => row.courierName))]),
|
|
provinces: sortTextValues([...new Set(rows.map((row) => row.province))]),
|
|
};
|
|
}
|
|
|
|
function filterRows(rows, filters) {
|
|
const warehouseSet = new Set(parseListParam(filters.warehouses));
|
|
const courierSet = new Set(parseListParam(filters.couriers));
|
|
const provinceSet = new Set(parseListParam(filters.provinces));
|
|
|
|
return rows.filter((row) => (
|
|
(warehouseSet.size === 0 || warehouseSet.has(row.warehouseName)) &&
|
|
(courierSet.size === 0 || courierSet.has(row.courierName)) &&
|
|
(provinceSet.size === 0 || provinceSet.has(row.province))
|
|
));
|
|
}
|
|
|
|
function buildSummary(rows) {
|
|
const bucket = createBucket();
|
|
rows.forEach((row) => addToBucket(bucket, row));
|
|
const summary = finalizeBucket(bucket);
|
|
|
|
return {
|
|
orderCount: summary.orderCount,
|
|
avgPayToSignHours: summary.avgPayToSignHours,
|
|
avgSendToSignHours: summary.avgSendToSignHours,
|
|
timely48Count: summary.timely48Count,
|
|
timely48Rate: summary.timely48Rate,
|
|
timeout48Count: Math.max(0, summary.orderCount - summary.timely48Count),
|
|
};
|
|
}
|
|
|
|
function buildCourierSummaries(rows) {
|
|
const grouped = new Map();
|
|
|
|
rows.forEach((row) => {
|
|
if (!grouped.has(row.courierName)) grouped.set(row.courierName, createBucket());
|
|
addToBucket(grouped.get(row.courierName), row);
|
|
});
|
|
|
|
return Array.from(grouped.entries())
|
|
.map(([name, bucket]) => ({
|
|
name,
|
|
...finalizeBucket(bucket),
|
|
}))
|
|
.sort((a, b) => (
|
|
toNumber(a.avgPayToSignHours, Number.MAX_SAFE_INTEGER) - toNumber(b.avgPayToSignHours, Number.MAX_SAFE_INTEGER) ||
|
|
String(a.name).localeCompare(String(b.name), 'zh-Hans-CN')
|
|
));
|
|
}
|
|
|
|
function buildTotalRow(rows, dimensions, key) {
|
|
const totalBucket = createBucket();
|
|
const courierMap = {};
|
|
|
|
rows.forEach((row) => {
|
|
addToBucket(totalBucket, row);
|
|
if (!courierMap[row.courierName]) courierMap[row.courierName] = createBucket();
|
|
addToBucket(courierMap[row.courierName], row);
|
|
});
|
|
|
|
const totalRow = {
|
|
key,
|
|
statDate: '总计',
|
|
warehouseName: dimensions.includes('warehouseName') ? '总计' : undefined,
|
|
province: dimensions.includes('province') ? '总计' : undefined,
|
|
total: finalizeBucket(totalBucket),
|
|
courierMap: {},
|
|
};
|
|
|
|
Object.entries(courierMap).forEach(([courierName, bucket]) => {
|
|
totalRow.courierMap[courierName] = finalizeBucket(bucket);
|
|
});
|
|
|
|
return totalRow;
|
|
}
|
|
|
|
function buildPivotRows(rows, dimensions, totalKey) {
|
|
const grouped = new Map();
|
|
|
|
rows.forEach((row) => {
|
|
const keyParts = dimensions.map((dimension) => row[dimension] || '');
|
|
const key = keyParts.join('\u0001');
|
|
if (!grouped.has(key)) {
|
|
const next = {
|
|
key: `${totalKey}:${keyParts.join('|')}`,
|
|
total: createBucket(),
|
|
courierMap: {},
|
|
};
|
|
dimensions.forEach((dimension) => {
|
|
next[dimension] = row[dimension] || '';
|
|
});
|
|
grouped.set(key, next);
|
|
}
|
|
|
|
const target = grouped.get(key);
|
|
addToBucket(target.total, row);
|
|
|
|
if (!target.courierMap[row.courierName]) {
|
|
target.courierMap[row.courierName] = createBucket();
|
|
}
|
|
addToBucket(target.courierMap[row.courierName], row);
|
|
});
|
|
|
|
const dataRows = Array.from(grouped.values())
|
|
.map((row) => {
|
|
const courierMap = {};
|
|
Object.entries(row.courierMap).forEach(([courierName, bucket]) => {
|
|
courierMap[courierName] = finalizeBucket(bucket);
|
|
});
|
|
|
|
return {
|
|
...row,
|
|
total: finalizeBucket(row.total),
|
|
courierMap,
|
|
};
|
|
})
|
|
.sort((a, b) => (
|
|
String(a.statDate || '').localeCompare(String(b.statDate || '')) ||
|
|
String(a.warehouseName || '').localeCompare(String(b.warehouseName || ''), 'zh-Hans-CN') ||
|
|
String(a.province || '').localeCompare(String(b.province || ''), 'zh-Hans-CN')
|
|
));
|
|
|
|
if (rows.length === 0) return [];
|
|
|
|
return [
|
|
buildTotalRow(rows, dimensions, `${totalKey}:total`),
|
|
...dataRows,
|
|
];
|
|
}
|
|
|
|
function buildDeliveryBoard(rows) {
|
|
return {
|
|
summary: buildSummary(rows),
|
|
couriers: buildCourierSummaries(rows),
|
|
courierDateRows: buildPivotRows(rows, ['statDate'], 'courier-date'),
|
|
courierProvinceRows: buildPivotRows(rows, ['statDate', 'province'], 'courier-province'),
|
|
warehouseCourierProvinceRows: buildPivotRows(rows, ['statDate', 'warehouseName', 'province'], 'warehouse-courier-province'),
|
|
};
|
|
}
|
|
|
|
async function queryLogisticsDeliveryDurationBoard(params = {}) {
|
|
const range = normalizeDateRange(params);
|
|
const aggregateRows = await cloudDb.query(
|
|
DELIVERY_AGGREGATE_SQL,
|
|
{
|
|
startDateTime: range.startDateTime,
|
|
endDateExclusive: range.endDateExclusive,
|
|
},
|
|
{ requestTimeout: REQUEST_TIMEOUT }
|
|
);
|
|
const normalizedRows = aggregateRows.map(normalizeAggregateRow);
|
|
const filteredRows = filterRows(normalizedRows, params);
|
|
const board = buildDeliveryBoard(filteredRows);
|
|
|
|
return {
|
|
...board,
|
|
filters: buildFilterOptions(normalizedRows),
|
|
detailColumns: DELIVERY_DETAIL_COLUMNS,
|
|
meta: {
|
|
startDate: range.startDate,
|
|
endDate: range.endDate,
|
|
statisticDateField: 'sign_time',
|
|
sourceTable: 'dbo.jst_order_express_details',
|
|
excludedWarehouses: DELIVERY_DURATION_EXCLUDED_WAREHOUSES,
|
|
excludedLogisticsKeywords: DELIVERY_DURATION_EXCLUDED_LOGISTICS_KEYWORDS,
|
|
calculatedAt: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
},
|
|
};
|
|
}
|
|
|
|
function appendInFilter(whereParts, params, column, values, prefix) {
|
|
const list = parseListParam(values);
|
|
if (list.length === 0) return;
|
|
|
|
const placeholders = list.map((value, index) => {
|
|
const key = `${prefix}${index}`;
|
|
params[key] = value;
|
|
return `@${key}`;
|
|
});
|
|
|
|
whereParts.push(`${column} IN (${placeholders.join(', ')})`);
|
|
}
|
|
|
|
function buildDetailSql(whereClause) {
|
|
return `
|
|
SET NOCOUNT ON;
|
|
|
|
WITH delivery_base AS (
|
|
SELECT
|
|
CAST(e.id AS nvarchar(64)) AS express_detail_id,
|
|
NULLIF(LTRIM(RTRIM(e.so_id)), N'') AS online_order_no,
|
|
NULLIF(LTRIM(RTRIM(e.logistic_id)), N'') AS waybill_no,
|
|
COALESCE(NULLIF(LTRIM(RTRIM(e.logistic_company_name)), N''), N'${UNKNOWN_COURIER}') AS courier_name,
|
|
NULLIF(LTRIM(RTRIM(e.platform_name)), N'') AS platform_name,
|
|
NULLIF(LTRIM(RTRIM(e.shop_name)), N'') AS shop_name,
|
|
COALESCE(NULLIF(LTRIM(RTRIM(e.warehouse_name)), N''), N'${UNKNOWN_WAREHOUSE}') AS warehouse_name,
|
|
NULLIF(LTRIM(RTRIM(e.receiver_address)), N'') AS receiver_address,
|
|
COALESCE(NULLIF(LTRIM(RTRIM(${provinceExpr.replace(/receiver_address/g, 'e.receiver_address')})), N''), N'${UNKNOWN_PROVINCE}') AS province,
|
|
e.pay_time,
|
|
e.send_time,
|
|
e.fetch_time,
|
|
e.sign_time,
|
|
CAST(DATEDIFF(SECOND, e.pay_time, e.sign_time) AS DECIMAL(20, 2)) / 3600.0 AS pay_to_sign_hours,
|
|
CAST(DATEDIFF(SECOND, e.send_time, e.sign_time) AS DECIMAL(20, 2)) / 3600.0 AS send_to_sign_hours,
|
|
CASE WHEN DATEDIFF(SECOND, e.pay_time, e.sign_time) <= 48 * 3600 THEN 1 ELSE 0 END AS timely_48
|
|
FROM dbo.jst_order_express_details e
|
|
WHERE
|
|
e.sign_time >= @startDateTime
|
|
AND e.sign_time < @endDateExclusive
|
|
AND e.pay_time IS NOT NULL
|
|
AND e.send_time IS NOT NULL
|
|
AND e.sign_time IS NOT NULL
|
|
AND e.sign_time >= e.pay_time
|
|
AND e.sign_time >= e.send_time
|
|
AND ISNULL(e.warehouse_name, N'') NOT IN (${excludedWarehousesSql})
|
|
AND (
|
|
e.logistic_company_name IS NULL
|
|
OR (
|
|
${DELIVERY_DURATION_EXCLUDED_LOGISTICS_KEYWORDS
|
|
.map((keyword) => `ISNULL(e.logistic_company_name, N'') NOT LIKE ${q(`%${keyword}%`)}`)
|
|
.join('\n AND ')}
|
|
)
|
|
)
|
|
),
|
|
numbered AS (
|
|
SELECT
|
|
ROW_NUMBER() OVER (ORDER BY sign_time ASC, express_detail_id ASC) AS __rowNumber,
|
|
COUNT_BIG(*) OVER() AS __total,
|
|
express_detail_id,
|
|
online_order_no AS onlineOrderNo,
|
|
waybill_no AS waybillNo,
|
|
courier_name AS courierName,
|
|
platform_name AS platformName,
|
|
shop_name AS shopName,
|
|
warehouse_name AS warehouseName,
|
|
province,
|
|
receiver_address AS receiverAddress,
|
|
CONVERT(varchar(19), pay_time, 120) AS payTime,
|
|
CONVERT(varchar(19), send_time, 120) AS sendTime,
|
|
CONVERT(varchar(19), fetch_time, 120) AS fetchTime,
|
|
CONVERT(varchar(19), sign_time, 120) AS signTime,
|
|
CAST(ROUND(pay_to_sign_hours, 2) AS DECIMAL(10, 2)) AS payToSignHours,
|
|
CAST(ROUND(send_to_sign_hours, 2) AS DECIMAL(10, 2)) AS sendToSignHours,
|
|
timely_48 AS timely48
|
|
FROM delivery_base
|
|
WHERE ${whereClause}
|
|
)
|
|
SELECT
|
|
page_data.__rowNumber,
|
|
page_data.__total,
|
|
COALESCE(NULLIF(LTRIM(RTRIM(afs.inner_order_id)), N''), page_data.express_detail_id) AS innerOrderId,
|
|
page_data.onlineOrderNo,
|
|
page_data.waybillNo,
|
|
page_data.courierName,
|
|
page_data.platformName,
|
|
page_data.shopName,
|
|
page_data.warehouseName,
|
|
page_data.province,
|
|
page_data.receiverAddress,
|
|
page_data.payTime,
|
|
page_data.sendTime,
|
|
page_data.fetchTime,
|
|
page_data.signTime,
|
|
page_data.payToSignHours,
|
|
page_data.sendToSignHours,
|
|
page_data.timely48
|
|
FROM numbered page_data
|
|
OUTER APPLY (
|
|
SELECT TOP 1
|
|
NULLIF(LTRIM(RTRIM(a.undefined1)), N'') AS inner_order_id
|
|
FROM dbo.jst_goods_profit_kpi_items_afs a
|
|
WHERE NULLIF(LTRIM(RTRIM(a.undefined2)), N'') = page_data.onlineOrderNo
|
|
ORDER BY TRY_CONVERT(datetime, REPLACE(a.undefined5, '/', '-')) DESC
|
|
) afs
|
|
WHERE page_data.__rowNumber BETWEEN @startRow AND @endRow
|
|
ORDER BY __rowNumber;
|
|
`;
|
|
}
|
|
|
|
async function queryLogisticsDeliveryDurationDetails(params = {}) {
|
|
const range = normalizeDateRange(params);
|
|
const page = Math.max(1, parseInt(params.page, 10) || 1);
|
|
const pageSize = Math.min(5000, Math.max(1, parseInt(params.pageSize, 10) || 20));
|
|
const startRow = ((page - 1) * pageSize) + 1;
|
|
const endRow = page * pageSize;
|
|
const queryParams = {
|
|
startDateTime: range.startDateTime,
|
|
endDateExclusive: range.endDateExclusive,
|
|
startRow,
|
|
endRow,
|
|
};
|
|
const whereParts = ['1 = 1'];
|
|
|
|
appendInFilter(whereParts, queryParams, 'warehouse_name', params.warehouses, 'warehouse');
|
|
appendInFilter(whereParts, queryParams, 'courier_name', params.couriers, 'courier');
|
|
appendInFilter(whereParts, queryParams, 'province', params.provinces, 'province');
|
|
|
|
const rows = await cloudDb.query(
|
|
buildDetailSql(whereParts.join(' AND ')),
|
|
queryParams,
|
|
{ requestTimeout: REQUEST_TIMEOUT }
|
|
);
|
|
const total = rows.length > 0 ? toNumber(rows[0].__total) : 0;
|
|
|
|
return {
|
|
columns: DELIVERY_DETAIL_COLUMNS,
|
|
rows: rows.map((row) => ({
|
|
innerOrderId: row.innerOrderId || '',
|
|
onlineOrderNo: row.onlineOrderNo || '',
|
|
waybillNo: row.waybillNo || '',
|
|
courierName: row.courierName || UNKNOWN_COURIER,
|
|
platformName: row.platformName || '',
|
|
shopName: row.shopName || '',
|
|
warehouseName: row.warehouseName || UNKNOWN_WAREHOUSE,
|
|
province: row.province || UNKNOWN_PROVINCE,
|
|
receiverAddress: row.receiverAddress || '',
|
|
payTime: row.payTime || '',
|
|
sendTime: row.sendTime || '',
|
|
fetchTime: row.fetchTime || '',
|
|
signTime: row.signTime || '',
|
|
payToSignHours: row.payToSignHours === null || row.payToSignHours === undefined ? '' : round2(row.payToSignHours),
|
|
sendToSignHours: row.sendToSignHours === null || row.sendToSignHours === undefined ? '' : round2(row.sendToSignHours),
|
|
timely48: toNumber(row.timely48) === 1 ? '是' : '否',
|
|
})),
|
|
total,
|
|
page,
|
|
pageSize,
|
|
meta: {
|
|
startDate: range.startDate,
|
|
endDate: range.endDate,
|
|
statisticDateField: 'sign_time',
|
|
sourceTable: 'dbo.jst_order_express_details',
|
|
},
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
queryLogisticsDeliveryDurationBoard,
|
|
queryLogisticsDeliveryDurationDetails,
|
|
DELIVERY_DETAIL_COLUMNS,
|
|
};
|