|
@@ -124,8 +124,12 @@ async function statsHistory(o = {}) {
|
|
|
else if (Array.isArray(d.result)) rowsRaw = d.result;
|
|
else if (Array.isArray(d.result)) rowsRaw = d.result;
|
|
|
const daily = rowsRaw.filter((x) => x && x.ds && /^\d{8}$/.test(String(x.ds)));
|
|
const daily = rowsRaw.filter((x) => x && x.ds && /^\d{8}$/.test(String(x.ds)));
|
|
|
const all = rowsRaw.find((x) => x && x.ds === 'all') || null;
|
|
const all = rowsRaw.find((x) => x && x.ds === 'all') || null;
|
|
|
|
|
+ // 形态守卫:有原始行却一条 ds 形态都对不上 = 接口/页面结构变了。
|
|
|
|
|
+ // 不标记的话上层会把这批行当"合法空数据"静默落库(全零类问题同根因)。
|
|
|
|
|
+ const shapeWarning = rowsRaw.length > 0 && !daily.length && !all;
|
|
|
return {
|
|
return {
|
|
|
ok: !!res.ok, status: res.status, risk: !!res.risk, authFail: !!res.authFail,
|
|
ok: !!res.ok, status: res.status, risk: !!res.risk, authFail: !!res.authFail,
|
|
|
|
|
+ shapeWarning,
|
|
|
code: d.code != null ? d.code : (d.success === false ? 'success:false' : undefined),
|
|
code: d.code != null ? d.code : (d.success === false ? 'success:false' : undefined),
|
|
|
msg: d.msg || d.message || undefined,
|
|
msg: d.msg || d.message || undefined,
|
|
|
contextOrigin: res.contextOrigin || null,
|
|
contextOrigin: res.contextOrigin || null,
|
|
@@ -374,10 +378,13 @@ async function melody(o = {}) {
|
|
|
* 订单履约巡检 —— 一次抓「当前」的异常/待办订单计数(全公司或指定门店)。
|
|
* 订单履约巡检 —— 一次抓「当前」的异常/待办订单计数(全公司或指定门店)。
|
|
|
* ⚠️ 这是**实时快照**,不是历史;要看趋势必须定时落库累积。
|
|
* ⚠️ 这是**实时快照**,不是历史;要看趋势必须定时落库累积。
|
|
|
*
|
|
*
|
|
|
- * @returns {Promise<{newOrder:number, urge:number, preparing:number, callDelivery:number,
|
|
|
|
|
- * riderAccept:number, unprocessedBooking:number, bookingOrderIds:string[],
|
|
|
|
|
- * exceptional:number, cancel:number, refund:number, fastRefund:number,
|
|
|
|
|
- * noProcesNegotiate:*, timeOutNegotiate:*, needRemindException:boolean, raw:object}>}
|
|
|
|
|
|
|
+ * @returns {Promise<{ok:boolean, newOrder:?number, urge:?number, preparing:?number, callDelivery:?number,
|
|
|
|
|
+ * riderAccept:?number, unprocessedBooking:?number, bookingOrderIds:string[],
|
|
|
|
|
+ * exceptional:?number, cancel:?number, refund:?number, fastRefund:?number,
|
|
|
|
|
+ * noProcesNegotiate:*, timeOutNegotiate:*, needRemindException:boolean, raw:object,
|
|
|
|
|
+ * risk:boolean, authFail:boolean}>}
|
|
|
|
|
+ * 任一子调用失败(HTTP/风控/鉴权/业务错误)时 ok=false 且各计数为 **null** ——
|
|
|
|
|
+ * 绝不返回 0:那会把"取数失败"伪装成"健康零负载"快照。
|
|
|
*/
|
|
*/
|
|
|
async function orderAlerts(o = {}) {
|
|
async function orderAlerts(o = {}) {
|
|
|
const shopId = o.shopId == null ? -1 : o.shopId;
|
|
const shopId = o.shopId == null ? -1 : o.shopId;
|
|
@@ -387,12 +394,16 @@ async function orderAlerts(o = {}) {
|
|
|
melody({ ...P, service: 'PollingService', method: 'nonCoreOrders', path: '/fulfill/weborder/nonCoreOrders/', params: { shopId } }),
|
|
melody({ ...P, service: 'PollingService', method: 'nonCoreOrders', path: '/fulfill/weborder/nonCoreOrders/', params: { shopId } }),
|
|
|
melody({ ...P, service: 'PollingService', method: 'unprocessedOrders', path: '/fulfill/weborder/unprocessedOrders/', params: { shopId } }),
|
|
melody({ ...P, service: 'PollingService', method: 'unprocessedOrders', path: '/fulfill/weborder/unprocessedOrders/', params: { shopId } }),
|
|
|
]);
|
|
]);
|
|
|
- const A = (ab.result && typeof ab.result === 'object') ? ab.result : {};
|
|
|
|
|
- const N = (non.result && typeof non.result === 'object') ? non.result : {};
|
|
|
|
|
- const U = (un.result && typeof un.result === 'object') ? un.result : {};
|
|
|
|
|
- const n = (v) => Number(v) || 0;
|
|
|
|
|
- const bookingIds = (N.bookingNotice && Array.isArray(N.bookingNotice.orderIds)) ? N.bookingNotice.orderIds : [];
|
|
|
|
|
|
|
+ const subFail = (r) => !r || r.ok === false || !!r.risk || !!r.authFail || !!r.error;
|
|
|
|
|
+ const anyFail = subFail(ab) || subFail(non) || subFail(un);
|
|
|
|
|
+ const A = (ab && ab.result && typeof ab.result === 'object') ? ab.result : {};
|
|
|
|
|
+ const N = (non && non.result && typeof non.result === 'object') ? non.result : {};
|
|
|
|
|
+ const U = (un && un.result && typeof un.result === 'object') ? un.result : {};
|
|
|
|
|
+ // 失败时计数不可信 → 一律 null,让调用方显式处理"不可知",而不是当成 0
|
|
|
|
|
+ const n = (v) => (anyFail ? null : (Number(v) || 0));
|
|
|
|
|
+ const bookingIds = (!anyFail && N.bookingNotice && Array.isArray(N.bookingNotice.orderIds)) ? N.bookingNotice.orderIds : [];
|
|
|
return {
|
|
return {
|
|
|
|
|
+ ok: !anyFail,
|
|
|
shopId,
|
|
shopId,
|
|
|
newOrder: n(U.newOrderCount),
|
|
newOrder: n(U.newOrderCount),
|
|
|
urge: n(N.urgeOrderCount),
|
|
urge: n(N.urgeOrderCount),
|
|
@@ -405,11 +416,12 @@ async function orderAlerts(o = {}) {
|
|
|
cancel: n(A.cancelOrderCount),
|
|
cancel: n(A.cancelOrderCount),
|
|
|
refund: n(A.refundOrderCount),
|
|
refund: n(A.refundOrderCount),
|
|
|
fastRefund: n(A.fastRefundOrderCount),
|
|
fastRefund: n(A.fastRefundOrderCount),
|
|
|
- noProcesNegotiate: A.noProcesNegotiateOrder == null ? null : n(A.noProcesNegotiateOrder),
|
|
|
|
|
- timeOutNegotiate: A.timeOutNegotiateOrder == null ? null : n(A.timeOutNegotiateOrder),
|
|
|
|
|
- needRemindException: !!A.needRemindExceptionOrder,
|
|
|
|
|
|
|
+ noProcesNegotiate: anyFail ? null : (A.noProcesNegotiateOrder == null ? null : n(A.noProcesNegotiateOrder)),
|
|
|
|
|
+ timeOutNegotiate: anyFail ? null : (A.timeOutNegotiateOrder == null ? null : n(A.timeOutNegotiateOrder)),
|
|
|
|
|
+ needRemindException: !anyFail && !!A.needRemindExceptionOrder,
|
|
|
raw: { abnormal: A, nonCore: N, unprocessed: U },
|
|
raw: { abnormal: A, nonCore: N, unprocessed: U },
|
|
|
- risk: ab.risk || non.risk || un.risk,
|
|
|
|
|
|
|
+ risk: !!(ab && ab.risk) || !!(non && non.risk) || !!(un && un.risk),
|
|
|
|
|
+ authFail: !!(ab && ab.authFail) || !!(non && non.authFail) || !!(un && un.authFail),
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -578,10 +590,14 @@ async function settleList(o = {}) {
|
|
|
const maxPage = Number(o.maxPage || 60);
|
|
const maxPage = Number(o.maxPage || 60);
|
|
|
const rows = [];
|
|
const rows = [];
|
|
|
let pageNo = 1, totalPage = 1, meta = null;
|
|
let pageNo = 1, totalPage = 1, meta = null;
|
|
|
|
|
+ let last = null; // 末次翻页响应:决定整体 ok(失败不得伪装成"成功 0 行")
|
|
|
|
|
+ let pages = 0;
|
|
|
while (pageNo <= Math.min(totalPage, maxPage)) {
|
|
while (pageNo <= Math.min(totalPage, maxPage)) {
|
|
|
const r = await agw('bill', '/gw/h5api/ele-shop-bill-query.ShopSummaryQueryFacade.queryChainSettleListByPage',
|
|
const r = await agw('bill', '/gw/h5api/ele-shop-bill-query.ShopSummaryQueryFacade.queryChainSettleListByPage',
|
|
|
[{ shopId: unit, pageNo, pageSize: size, startBillDate: msOf(from, 'start'), endBillDate: msOf(to, 'end'), chainId: unit, settleBizType: biz }],
|
|
[{ shopId: unit, pageNo, pageSize: size, startBillDate: msOf(from, 'start'), endBillDate: msOf(to, 'end'), chainId: unit, settleBizType: biz }],
|
|
|
{ port });
|
|
{ port });
|
|
|
|
|
+ last = r;
|
|
|
|
|
+ pages++;
|
|
|
const d = r.payload || {};
|
|
const d = r.payload || {};
|
|
|
if (Array.isArray(d.list)) rows.push(...d.list);
|
|
if (Array.isArray(d.list)) rows.push(...d.list);
|
|
|
totalPage = Number(d.totalPage || 1) || 1;
|
|
totalPage = Number(d.totalPage || 1) || 1;
|
|
@@ -589,7 +605,17 @@ async function settleList(o = {}) {
|
|
|
if (!Array.isArray(d.list) || d.list.length < size) break;
|
|
if (!Array.isArray(d.list) || d.list.length < size) break;
|
|
|
pageNo++;
|
|
pageNo++;
|
|
|
}
|
|
}
|
|
|
- return { ok: true, unitId: unit, biz, from, to, rows, meta, ensured: ens };
|
|
|
|
|
|
|
+ // 口径统一:末页 HTTP 失败 / 风控 / 鉴权失效 / 业务层没回 list(空区间成功时 list 也是数组,只是为空)
|
|
|
|
|
+ const ld = (last && last.payload) || null;
|
|
|
|
|
+ const failed = !last || !last.ok || !!last.risk || !!last.authFail || !ld || !Array.isArray(ld.list);
|
|
|
|
|
+ return {
|
|
|
|
|
+ ok: !failed,
|
|
|
|
|
+ risk: !!(last && last.risk), authFail: !!(last && last.authFail),
|
|
|
|
|
+ error: failed
|
|
|
|
|
+ ? `结算账单翻页异常(第 ${pages} 页):${!last ? '无响应' : last.authFail ? '鉴权失效' : last.risk ? '触发风控' : !last.ok ? `HTTP ${last.status || '?'}` : '响应缺少 list 字段(接口形态可能已变化)'}`
|
|
|
|
|
+ : null,
|
|
|
|
|
+ unitId: unit, biz, from, to, rows, meta, ensured: ens,
|
|
|
|
|
+ };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** 结算账单区间汇总(按日聚合 → 收入/支出/结算额/天数/状态分布) */
|
|
/** 结算账单区间汇总(按日聚合 → 收入/支出/结算额/天数/状态分布) */
|
|
@@ -640,16 +666,28 @@ async function growthTaskRate(o = {}) {
|
|
|
const size = Number(o.size || 50), maxPage = Number(o.maxPage || 20);
|
|
const size = Number(o.size || 50), maxPage = Number(o.maxPage || 20);
|
|
|
const rows = [];
|
|
const rows = [];
|
|
|
let page = 1, total = 1;
|
|
let page = 1, total = 1;
|
|
|
|
|
+ let last = null;
|
|
|
while (page <= Math.min(total, maxPage)) {
|
|
while (page <= Math.min(total, maxPage)) {
|
|
|
const r = await agw('growth', '/gw/h5api/ele-shop-growth-product.ChainBusinessTaskPageWebService.getBusinessTaskCompleteRateByPage',
|
|
const r = await agw('growth', '/gw/h5api/ele-shop-growth-product.ChainBusinessTaskPageWebService.getBusinessTaskCompleteRateByPage',
|
|
|
[{ chainId, page, size, data: { detailType: o.detailType || 'DETAIL_TYPE_SHOP', brandId: o.brandId == null ? -1 : o.brandId, useNew: true } }], { port });
|
|
[{ chainId, page, size, data: { detailType: o.detailType || 'DETAIL_TYPE_SHOP', brandId: o.brandId == null ? -1 : o.brandId, useNew: true } }], { port });
|
|
|
|
|
+ last = r;
|
|
|
const d = r.payload || {};
|
|
const d = r.payload || {};
|
|
|
if (Array.isArray(d.data)) rows.push(...d.data);
|
|
if (Array.isArray(d.data)) rows.push(...d.data);
|
|
|
total = Number(d.total || 0) ? Math.ceil(Number(d.total) / size) : 1;
|
|
total = Number(d.total || 0) ? Math.ceil(Number(d.total) / size) : 1;
|
|
|
if (!Array.isArray(d.data) || d.data.length < size) break;
|
|
if (!Array.isArray(d.data) || d.data.length < size) break;
|
|
|
page++;
|
|
page++;
|
|
|
}
|
|
}
|
|
|
- return { ok: true, rows };
|
|
|
|
|
|
|
+ // 口径统一:末页失败/形态异常不再报 ok:true(否则翻页失败会被当"完成率全空"误读)
|
|
|
|
|
+ const ld = (last && last.payload) || null;
|
|
|
|
|
+ const failed = !last || !last.ok || !!last.risk || !!last.authFail || !ld || !Array.isArray(ld.data);
|
|
|
|
|
+ return {
|
|
|
|
|
+ ok: !failed,
|
|
|
|
|
+ risk: !!(last && last.risk), authFail: !!(last && last.authFail),
|
|
|
|
|
+ error: failed
|
|
|
|
|
+ ? `生意任务完成率翻页异常:${!last ? '无响应' : last.authFail ? '鉴权失效' : last.risk ? '触发风控' : !last.ok ? `HTTP ${last.status || '?'}` : '响应缺少 data 字段(接口形态可能已变化)'}`
|
|
|
|
|
+ : null,
|
|
|
|
|
+ rows,
|
|
|
|
|
+ };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** 成长中心品牌列表(brandId 用于周报 / 任务过滤) */
|
|
/** 成长中心品牌列表(brandId 用于周报 / 任务过滤) */
|