dingdanquanliucheng/frontend/mini-app/pages/manager/approve-detail/approve-detail.js
wsb1224 c002bef5ab fix: 修复订单结算流程中提成金额判断逻辑
Web管理后台:模板中commission_amount(snake_case)与数据对象commissionAmount(camelCase)不匹配,
导致待结算按钮永远不显示,所有已完成订单直接跳到标记结算。
小程序:零提成订单缺少直接标记结算路径,调用/settle API会因状态不匹配报错。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 19:07:20 +08:00

683 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 订单详情页面(审批 + 完整信息展示)
* 职责:展示订单完整信息(基础信息、费用明细、产品列表、收货信息、审批记录),
* 支持审批通过/退回,待审批状态下可选择下发工厂。
*/
var subscribe = require("../../../utils/subscribe");
function mapOrderStatus(status) {
var app = getApp();
return app.mapOrderStatus(status);
}
function getStatusBadge(status) {
var map = {
pending_approve: "badge-orange", cancel_pending: "badge-orange",
cancel_fulfillment_pending: "badge-orange",
approved: "badge-green", rejected: "badge-red",
pending_driver: "badge-purple",
accepted: "badge-blue", picked_up: "badge-teal",
pending_logistics: "badge-orange", in_transit: "badge-blue",
completed: "badge-green",
pending_settle: "badge-orange", settled: "badge-purple",
canceled: "badge-red",
};
return map[status] || "badge-gray";
}
function fmt(v) { return Number(v || 0).toFixed(2); }
/** 计算总需求面积0.35m × 0.55m × 500 = 96.25 m² */
function computeTotalDemand(item) {
var spec = (item.demand_specification || "").trim();
var qty = Number(item.quantity) || 0;
if (!spec || !qty) return "";
var m = spec.match(/([\d.]+)\s*(cm|厘米|m|米|mm|毫米)?\s*[×xX*]\s*([\d.]+)\s*(cm|厘米|m|米|mm|毫米)?/i);
if (!m) return "";
var unitMap = {"m": 1, "米": 1, "cm": 0.01, "厘米": 0.01, "mm": 0.001, "毫米": 0.001};
var v1 = parseFloat(m[1]) * (unitMap[m[2]] || 0.01);
var v2 = parseFloat(m[3]) * (unitMap[m[4]] || 0.01);
var total = v1 * v2 * qty;
return m[1] + (m[2] || "cm") + " × " + m[3] + (m[4] || "cm") + " × " + qty + " = " + total.toFixed(2) + " m²";
}
/** 计算成本公式10.50 × 96.25 = 1010.63 */
function computeCostFormula(item) {
var qty = Number(item.quantity) || 0;
var price = Number(item.cost_price) || 0;
if (!qty || !price) return "";
return fmt(price) + " × " + qty + " = " + fmt(qty * price);
}
Page({
data: {
statusBarHeight: 20,
taskId: null,
loading: true,
actionLoading: false,
message: "",
orderInfo: null,
products: [],
approveLogs: [],
logistics: null,
traceList: [],
factoryList: [],
selectedFactoryIndex: -1,
selectedFactoryId: null,
driverList: [],
selectedDriverIndex: -1,
selectedDriverId: null,
taskForm: {
pickup_address: "",
delivery_address: "",
pickup_content: "",
quantity: "1",
},
serviceBound: true,
showPendingTrackingEdit: false,
pendingTrackingNumber: "",
},
async onLoad(options) {
var app = getApp();
this.setData({
statusBarHeight: app.globalData.statusBarHeight,
taskId: Number(options.id || 0),
});
await this.loadDetail();
this.loadFactoryList();
this.loadDriverList();
},
onShow() {
this.checkServiceBind();
},
async checkServiceBind() {
var app = getApp();
if (!app.globalData.authToken) return true;
try {
var res = await app.request({ url: "/api/auth/service-bind-status" });
this.setData({ serviceBound: res.bound });
return res.bound;
} catch (e) { /* ignore */ }
return this.data.serviceBound !== false;
},
onOfficialAccountError(e) {
// deprecated: kept for compatibility
},
goBindService() {
wx.navigateTo({ url: "/pages/bind-service/bind-service" });
},
promptBindService: function () {
var that = this;
wx.showModal({
title: "请先关注服务号",
content: "审批前请先关注并绑定微信服务号,以便接收审批提醒。",
confirmText: "去绑定",
confirmColor: "#2563eb",
success: function (res) {
if (res.confirm) {
that.goBindService();
}
},
});
},
async loadDetail() {
var app = getApp();
this.setData({ loading: true, message: "" });
try {
var data = await app.request({ url: "/api/orders/" + this.data.taskId, method: "GET" });
var sale = Number(data.sale_price_total || 0);
var cost = Number(data.cost_price_total || 0);
var rebate = Number(data.rebate_total || 0);
var freight = Number(data.freight_total || 0);
var tax = Number(data.tax_total || 0);
var commission = Number(data.commission_amount || 0);
var profit = Number(data.profit_total || 0);
var incomeAmount = Number(data.contract_amount || 0) || sale;
var profitRate = incomeAmount > 0 ? (profit / incomeAmount * 100).toFixed(1) + "%" : "-";
// 费用明细:有值才显示
var feeItems = [
{ label: "合同金额", value: fmt(data.contract_amount), isIncome: true },
{ label: "成本", value: fmt(cost) },
];
if (rebate > 0) feeItems.push({ label: "回扣", value: fmt(rebate) });
if (freight > 0) feeItems.push({ label: "运费", value: fmt(freight) });
if (tax > 0) feeItems.push({ label: "税费", value: fmt(tax) });
if (commission > 0) feeItems.push({ label: "佣金", value: fmt(commission) });
var otherFee = Number(data.other_fee_total || 0);
if (otherFee > 0) feeItems.push({ label: "其他费用", value: fmt(otherFee) });
feeItems.push({ label: "利润", value: fmt(profit), isProfit: true });
// 产品明细(从 data.items 构建,包含规格/需求规格/单位等完整字段)
var rawItems = data.items || [];
var products = [];
for (var i = 0; i < rawItems.length; i++) {
var ci = rawItems[i];
if (ci.product_name) {
// 计价方式中文映射
var pricingTypeMap = { area: "按面积", kg: "按重量", unit: "按件", : "按件", linear_m: "按米", weight_g: "按克重", volume: "按体积" };
products.push({
name: ci.product_name,
spec: ci.specification || "",
demandSpec: ci.demand_specification || "",
computedTotalDemand: computeTotalDemand(ci),
qty: ci.quantity || 0,
unit: ci.unit || "",
baseUnitPrice: fmt(ci.base_unit_price || ci.cost_price),
pricingUnit: ci.pricing_unit || "",
costPrice: fmt(ci.cost_price),
costFormula: computeCostFormula(ci),
costAmount: fmt(Number(ci.quantity || 0) * Number(ci.cost_price || 0)),
pricingType: pricingTypeMap[ci.pricing_type] || "",
areaSqm: ci.area_sqm ? fmt(ci.area_sqm) + "㎡" : "",
supplierModel: ci.supplier_model || "",
priceTier: ci.price_tier || "",
formulaDetail: ci.formula_detail || "",
});
}
}
// 审批记录
var approveLogs = data.approve_logs || [];
// 物流信息:后端无任务时返回 {},需过滤掉空对象
var rawTask = data.logistics_info && data.logistics_info.task;
var logistics = (rawTask && rawTask.task_id) ? rawTask : null;
this.setData({
orderInfo: Object.assign({}, data, {
statusText: mapOrderStatus(data.order_status),
badgeClass: getStatusBadge(data.order_status),
profitTotalText: fmt(profit),
profitRate: profitRate,
profitClass: profit >= 0 ? "positive" : "negative",
profitArrow: profit >= 0 ? "↑" : "↓",
feeItems: feeItems,
}),
products: products,
approveLogs: approveLogs,
logistics: logistics,
traceList: [],
});
// 加载物流轨迹:有物流任务单号或订单单号都加载
if ((logistics && logistics.tracking_number) || (data && data.tracking_number)) {
this.loadTrace();
}
} catch (error) {
this.setData({ message: error.message || "加载失败" });
} finally {
this.setData({ loading: false });
}
},
async loadTrace() {
var app = getApp();
try {
var result = await app.request({
url: "/api/logistics/" + this.data.taskId + "/trace",
method: "GET",
});
var traceList = (result && result.trace_list) || [];
this.setData({ traceList: traceList });
} catch (e) {
console.error("加载物流轨迹失败:", e);
}
},
async loadFactoryList() {
var app = getApp();
try {
var data = await app.request({
url: "/api/suppliers?supplier_type=factory&status=1&page_size=100",
method: "GET",
});
var list = data.list || data || [];
this.setData({ factoryList: list });
} catch (e) {
console.error("加载工厂列表失败:", e);
}
},
onFactoryChange: function (e) {
var idx = Number(e.detail.value);
var factory = this.data.factoryList[idx];
if (factory) {
this.setData({
selectedFactoryIndex: idx,
selectedFactoryId: factory.supplier_id || factory.supplierId || null,
});
}
},
async loadDriverList() {
var app = getApp();
try {
var data = await app.request({ url: "/api/system/users/drivers", method: "GET" });
var list = data.list || data || [];
this.setData({ driverList: list });
} catch (e) {
console.error("加载司机列表失败:", e);
}
},
onDriverChange: function (e) {
var idx = Number(e.detail.value);
var driver = this.data.driverList[idx];
if (driver) {
this.setData({
selectedDriverIndex: idx,
selectedDriverId: driver.user_id || driver.id || null,
});
}
},
handleApprove: async function (event) {
var that = this;
var result = event.currentTarget.dataset.result;
var orderInfo = this.data.orderInfo;
if (!orderInfo) return;
var serviceBound = await this.checkServiceBind();
if (serviceBound === false) {
this.promptBindService();
return;
}
var isCancelFlow = ["cancel_pending", "cancel_fulfillment_pending"].indexOf(orderInfo.order_status) >= 0;
var targetUrl = isCancelFlow
? "/api/orders/" + orderInfo.order_id + "/cancel-approve"
: "/api/orders/" + orderInfo.order_id + "/approve";
if (result === "pass") {
// 非自发订单审批通过时,工厂为必选,司机为可选
if (!isCancelFlow && orderInfo.order_status === "pending_approve" && !orderInfo.self_delivery) {
if (!that.data.selectedFactoryId) {
wx.showToast({ title: "请选择工厂", icon: "none" });
return;
}
}
// 通过:直接确认
var confirmMsg = isCancelFlow ? "确认同意取消此订单?" : "确认审批通过?";
wx.showModal({
title: "确认通过",
content: confirmMsg,
confirmColor: "#2563eb",
success: function (res) {
if (!res.confirm) return;
var payload = { approve_result: result, approve_opinion: "同意" };
// 如果是待审批状态且未自发附加工厂ID司机可选
if (!isCancelFlow && orderInfo.order_status === "pending_approve" && !orderInfo.self_delivery) {
payload.factory_id = that.data.selectedFactoryId;
if (that.data.selectedDriverId) {
payload.driver_id = that.data.selectedDriverId;
}
}
that._submitApproval(targetUrl, payload);
},
});
} else {
// 退回:弹出输入框填写原因
wx.showModal({
title: "退回原因",
editable: true,
placeholderText: "请输入退回原因(可选)",
confirmColor: "#be123c",
success: function (res) {
if (!res.confirm) return;
var opinion = res.content || "退回";
that._submitApproval(targetUrl, { approve_result: result, approve_opinion: opinion });
},
});
}
},
_submitApproval: async function (targetUrl, payload) {
var that = this;
var app = getApp();
that.setData({ actionLoading: true, message: "" });
// 请求订阅消息授权
await subscribe.ensureSubscribePermission("approve");
app.request({
url: targetUrl, method: "POST",
data: payload,
}).then(function () {
if (payload.approve_result === "pass") {
// 区分是否分配了司机factory_id 存在说明是非自发订单审批)
if (payload.factory_id && payload.driver_id) {
wx.showToast({ title: "审批通过,已下发工厂并分配司机", icon: "success" });
} else if (payload.factory_id) {
wx.showToast({ title: "审批通过,已下发工厂,待后续分配司机", icon: "success" });
} else {
// 自发订单或取消审批:复制采购信息
var orderData = that.data.orderInfo;
var text = "";
if (orderData) {
var lines = [];
var isPickup = orderData.delivery_type === "自提";
// 非自提:复制收货信息
if (!isPickup) {
var contactParts = [orderData.customer_name, orderData.customer_mobile, orderData.customer_address].filter(function(v) { return v; });
if (contactParts.length) lines.push("收货信息:" + contactParts.join(" "));
}
// 客户需求
if (orderData.customer_demand) lines.push("客户需求: " + orderData.customer_demand);
text = lines.join("\n");
}
if (text) {
wx.setClipboardData({ data: text });
} else {
wx.showToast({ title: "审批通过", icon: "success" });
}
}
} else {
wx.showToast({ title: "已退回", icon: "success" });
}
return that.loadDetail();
}).catch(function (error) {
that.setData({ message: error.message || "操作失败" });
}).finally(function () {
that.setData({ actionLoading: false });
});
},
// 任务表单输入
onTaskFormInput: function (e) {
var field = e.currentTarget.dataset.field;
var obj = {};
obj["taskForm." + field] = e.detail.value;
this.setData(obj);
},
// 提交审核(草稿/已驳回 → 待审批)
handleSubmitReview: function () {
var that = this;
var app = getApp();
wx.showModal({
title: "确认提交",
content: "确认提交此订单进入审核?",
confirmColor: "#2563eb",
success: function (res) {
if (!res.confirm) return;
that.setData({ actionLoading: true, message: "" });
app.request({
url: "/api/orders/" + that.data.orderInfo.order_id + "/submit",
method: "POST",
}).then(function () {
wx.showToast({ title: "已提交审核", icon: "success" });
return that.loadDetail();
}).catch(function (error) {
that.setData({ message: error.message || "提交失败" });
}).finally(function () {
that.setData({ actionLoading: false });
});
},
});
},
// 分配/重新分配司机(待司机接单)
handleAssignDriver: function () {
var that = this;
var app = getApp();
var orderInfo = this.data.orderInfo;
if (!orderInfo) return;
if (!that.data.selectedDriverId) {
wx.showToast({ title: "请选择司机", icon: "none" });
return;
}
var doAssign = function () {
that.setData({ actionLoading: true, message: "" });
app.request({
url: "/api/logistics/tasks",
method: "POST",
data: {
order_id: orderInfo.order_id,
driver_id: Number(that.data.selectedDriverId),
pickup_address: orderInfo.factory_name || "",
delivery_address: orderInfo.customer_address || "",
pickup_content: orderInfo.customer_name ? orderInfo.customer_name + " 订单货物" : "",
quantity: 1,
factory_id: orderInfo.factory_id || undefined,
},
}).then(function () {
wx.showToast({ title: "司机分配成功", icon: "success" });
return that.loadDetail();
}).catch(function (error) {
that.setData({ message: error.message || "分配失败" });
}).finally(function () {
that.setData({ actionLoading: false });
});
};
// 如果已有物流任务,直接复用原任务内容重新分配司机
if (that.data.logistics && that.data.logistics.task_id) {
wx.showModal({
title: "重新分配",
content: "确定要重新分配司机吗?将复用原物流任务内容,只更换司机。",
confirmColor: "#2563eb",
success: function (res) {
if (!res.confirm) return;
that.setData({ actionLoading: true, message: "" });
app.request({
url: "/api/logistics/tasks/" + that.data.logistics.task_id + "/reassign",
method: "POST",
data: { driver_id: Number(that.data.selectedDriverId) },
}).then(function () {
wx.showToast({ title: "司机分配成功", icon: "success" });
return that.loadDetail();
}).catch(function (error) {
that.setData({ message: error.message || "重新分配失败" });
}).finally(function () {
that.setData({ actionLoading: false });
});
},
});
} else {
doAssign();
}
},
// 简单状态变更
handleSimpleTransition: function (e) {
var that = this;
var target = e.currentTarget.dataset.target;
var labelMap = {
picked_up: "确认揽货",
in_transit: "绑定物流单号",
shipped: "确认收货",
completed: "标记完成",
pending_settle: "待结算",
settled: "标记结算",
};
wx.showModal({
title: "确认操作",
content: "确认" + (labelMap[target] || "变更状态") + "",
confirmColor: "#2563eb",
success: function (res) {
if (!res.confirm) return;
that._changeStatus(target, labelMap[target] || "操作成功");
},
});
},
_changeStatus: function (targetStatus, successMsg) {
var that = this;
var app = getApp();
that.setData({ actionLoading: true, message: "" });
app.request({
url: "/api/orders/" + that.data.orderInfo.order_id + "/status",
method: "POST",
data: { target_status: targetStatus },
}).then(function () {
wx.showToast({ title: successMsg || "操作成功", icon: "success" });
return that.loadDetail();
}).catch(function (error) {
that.setData({ message: error.message || "操作失败" });
}).finally(function () {
that.setData({ actionLoading: false });
});
},
// 申请取消
handleApplyCancel: function () {
var that = this;
wx.showModal({
title: "申请取消",
editable: true,
placeholderText: "请输入取消原因(必填)",
confirmColor: "#be123c",
success: function (res) {
if (!res.confirm) return;
var reason = (res.content || "").trim();
if (!reason) {
wx.showToast({ title: "请输入取消原因", icon: "none" });
return;
}
var app = getApp();
that.setData({ actionLoading: true, message: "" });
app.request({
url: "/api/orders/" + that.data.orderInfo.order_id + "/cancel",
method: "POST",
data: { cancel_reason: reason },
}).then(function () {
wx.showToast({ title: "已发起取消", icon: "success" });
return that.loadDetail();
}).catch(function (error) {
that.setData({ message: error.message || "取消失败" });
}).finally(function () {
that.setData({ actionLoading: false });
});
},
});
},
// 结算提成
handleSettle: function () {
var that = this;
var app = getApp();
wx.showModal({
title: "确认结算",
content: "确认结算此订单提成?",
confirmColor: "#2563eb",
success: function (res) {
if (!res.confirm) return;
that.setData({ actionLoading: true, message: "" });
app.request({
url: "/api/orders/" + that.data.orderInfo.order_id + "/settle",
method: "POST",
}).then(function () {
wx.showToast({ title: "提成已结算", icon: "success" });
return that.loadDetail();
}).catch(function (error) {
that.setData({ message: error.message || "结算失败" });
}).finally(function () {
that.setData({ actionLoading: false });
});
},
});
},
// 填写快递单号pending_logistics 状态,无物流任务)
openPendingTrackingEdit: function () {
var that = this;
var app = getApp();
wx.showModal({
title: "填写快递单号",
editable: true,
placeholderText: "请输入快递单号",
confirmColor: "#2563eb",
success: function (res) {
if (!res.confirm) return;
var trackingNumber = (res.content || "").trim();
if (!trackingNumber) {
wx.showToast({ title: "请输入快递单号", icon: "none" });
return;
}
that.setData({ actionLoading: true });
app.request({
url: "/api/orders/" + that.data.orderInfo.order_id + "/tracking",
method: "PUT",
data: { tracking_number: trackingNumber, remark: "填写快递单号" },
}).then(function () {
wx.showToast({ title: "快递单号已填写,订单已进入运输状态", icon: "success" });
return that.loadDetail();
}).catch(function (error) {
that.setData({ message: error.message || "填写快递单号失败" });
}).finally(function () {
that.setData({ actionLoading: false });
});
},
});
},
// 修改物流单号(有物流任务时)
openLogisticsTrackingEdit: function () {
var that = this;
var app = getApp();
var logistics = this.data.logistics;
if (!logistics || !logistics.task_id) {
wx.showToast({ title: "暂无物流任务,无法修改", icon: "none" });
return;
}
wx.showModal({
title: "修改物流单号",
editable: true,
placeholderText: "请输入新快递单号",
confirmColor: "#2563eb",
success: function (res) {
if (!res.confirm) return;
var trackingNumber = (res.content || "").trim();
if (!trackingNumber) {
wx.showToast({ title: "请输入快递单号", icon: "none" });
return;
}
that.setData({ actionLoading: true });
app.request({
url: "/api/logistics/tasks/" + logistics.task_id + "/tracking",
method: "PUT",
data: { tracking_number: trackingNumber, remark: "修改物流单号" },
}).then(function () {
wx.showToast({ title: "物流单号已更新", icon: "success" });
return that.loadDetail();
}).catch(function (error) {
that.setData({ message: error.message || "修改物流单号失败" });
}).finally(function () {
that.setData({ actionLoading: false });
});
},
});
},
handleBack: function () { wx.navigateBack({ delta: 1 }); },
// 分享给好友
onShareAppMessage: function () {
var orderInfo = this.data.orderInfo;
var title = orderInfo ? "订单详情 " + (orderInfo.order_no || "") : "订单详情";
return {
title: title,
path: "/pages/manager/approve-detail/approve-detail?id=" + this.data.taskId,
};
},
// 分享到朋友圈
onShareTimeline: function () {
var orderInfo = this.data.orderInfo;
var title = orderInfo ? "订单详情 " + (orderInfo.order_no || "") : "订单详情";
return {
title: title,
query: "id=" + this.data.taskId,
};
},
});