将采购信息构建逻辑从独立方法移入 _submitApproval 回调内, 直接从 orderInfo.items 原始数据构建,绕过 this.data.products 可能未就绪的时序问题;移除未使用的 _buildProcurementText 方法。 Co-Authored-By: Claude <noreply@anthropic.com>
635 lines
21 KiB
JavaScript
635 lines
21 KiB
JavaScript
/**
|
||
* 订单详情页面(审批 + 完整信息展示)
|
||
* 职责:展示订单完整信息(基础信息、费用明细、产品列表、收货信息、审批记录),
|
||
* 支持审批通过/退回,待审批状态下可选择下发工厂。
|
||
*/
|
||
|
||
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_factory: "badge-blue", pending_driver: "badge-purple",
|
||
accepted: "badge-blue", picked_up: "badge-teal",
|
||
delivered: "badge-green", 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); }
|
||
|
||
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",
|
||
},
|
||
},
|
||
|
||
async onLoad(options) {
|
||
var app = getApp();
|
||
this.setData({
|
||
statusBarHeight: app.globalData.statusBarHeight,
|
||
taskId: Number(options.id || 0),
|
||
});
|
||
await this.loadDetail();
|
||
this.loadFactoryList();
|
||
this.loadDriverList();
|
||
},
|
||
|
||
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 profitRate = sale > 0 ? (profit / sale * 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) });
|
||
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) {
|
||
products.push({
|
||
name: ci.product_name,
|
||
spec: ci.specification || "",
|
||
demandSpec: ci.demand_specification || "",
|
||
qty: ci.quantity || 0,
|
||
unit: ci.unit || "",
|
||
salePrice: fmt(ci.sale_price),
|
||
costAmount: fmt(Number(ci.quantity || 0) * Number(ci.cost_price || 0)),
|
||
});
|
||
}
|
||
}
|
||
|
||
// 审批记录
|
||
var approveLogs = data.approve_logs || [];
|
||
|
||
// 物流信息
|
||
var logistics = (data.logistics_info && data.logistics_info.task) || 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 ? "profit-positive" : "profit-negative",
|
||
profitArrow: profit >= 0 ? "↑" : "↓",
|
||
feeItems: feeItems,
|
||
}),
|
||
products: products,
|
||
approveLogs: approveLogs,
|
||
logistics: logistics,
|
||
traceList: [],
|
||
});
|
||
|
||
// 加载物流轨迹
|
||
if (logistics && logistics.tracking_number) {
|
||
this.loadTrace();
|
||
}
|
||
|
||
// 待工厂状态:自动填充任务表单
|
||
if (data.order_status === "pending_factory") {
|
||
this.setData({
|
||
taskForm: {
|
||
pickup_address: data.factory_name || "",
|
||
delivery_address: data.customer_address || "",
|
||
pickup_content: data.customer_name ? data.customer_name + " 订单货物" : "",
|
||
quantity: "1",
|
||
},
|
||
});
|
||
}
|
||
} 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: function (event) {
|
||
var that = this;
|
||
var result = event.currentTarget.dataset.result;
|
||
var orderInfo = this.data.orderInfo;
|
||
if (!orderInfo) 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") {
|
||
// 通过:直接确认
|
||
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和司机ID
|
||
if (!isCancelFlow && orderInfo.order_status === "pending_approve" && !orderInfo.tracking_number) {
|
||
if (that.data.selectedFactoryId) {
|
||
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 (res) {
|
||
if (payload.approve_result === "pass") {
|
||
// 从原始数据直接构建采购信息,避免 this.data 时序问题
|
||
var orderData = that.data.orderInfo;
|
||
var text = "";
|
||
if (orderData) {
|
||
var items = orderData.items || [];
|
||
var lines = [];
|
||
for (var i = 0; i < items.length; i++) {
|
||
var ci = items[i];
|
||
if (!ci.product_name) continue;
|
||
lines.push((lines.length + 1) + ". " + ci.product_name);
|
||
if (ci.specification) lines.push(" 规格: " + ci.specification);
|
||
if (ci.demand_specification) lines.push(" 需求规格: " + ci.demand_specification);
|
||
if (ci.quantity) lines.push(" 数量: " + ci.quantity + (ci.unit || ""));
|
||
}
|
||
if (orderData.remark) lines.push("备注: " + orderData.remark);
|
||
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 });
|
||
});
|
||
},
|
||
});
|
||
},
|
||
|
||
// 推进待工厂(已审批 → 待工厂处理)
|
||
handlePushToFactory: function () {
|
||
var that = this;
|
||
var app = getApp();
|
||
var orderInfo = this.data.orderInfo;
|
||
if (!orderInfo) return;
|
||
|
||
// 自发订单:直接完成
|
||
if (orderInfo.tracking_number) {
|
||
wx.showModal({
|
||
title: "确认",
|
||
content: "马甸工厂自发,审批通过后将直接完成订单",
|
||
confirmColor: "#2563eb",
|
||
success: function (res) {
|
||
if (!res.confirm) return;
|
||
that._changeStatus("completed", "订单已直接完成");
|
||
},
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!that.data.selectedFactoryId) {
|
||
wx.showToast({ title: "请选择工厂", icon: "none" });
|
||
return;
|
||
}
|
||
if (!that.data.selectedDriverId) {
|
||
wx.showToast({ title: "请选择司机", icon: "none" });
|
||
return;
|
||
}
|
||
|
||
that.setData({ actionLoading: true, message: "" });
|
||
app.request({
|
||
url: "/api/orders/" + orderInfo.order_id + "/status",
|
||
method: "POST",
|
||
data: { target_status: "pending_factory", factory_id: that.data.selectedFactoryId },
|
||
}).then(function () {
|
||
return 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: that.data.selectedFactoryId,
|
||
},
|
||
});
|
||
}).then(function () {
|
||
wx.showToast({ title: "已推进待工厂并分配司机", icon: "success" });
|
||
return that.loadDetail();
|
||
}).catch(function (error) {
|
||
that.setData({ message: error.message || "操作失败" });
|
||
}).finally(function () {
|
||
that.setData({ actionLoading: false });
|
||
});
|
||
},
|
||
|
||
// 分配任务(待工厂处理 → 待司机接单)
|
||
handleAssignTask: 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 form = that.data.taskForm;
|
||
if (!form.pickup_address.trim()) {
|
||
wx.showToast({ title: "请填写取货地址", icon: "none" });
|
||
return;
|
||
}
|
||
if (!form.delivery_address.trim()) {
|
||
wx.showToast({ title: "请填写送货地址", icon: "none" });
|
||
return;
|
||
}
|
||
if (!form.pickup_content.trim()) {
|
||
wx.showToast({ title: "请填写取货内容", icon: "none" });
|
||
return;
|
||
}
|
||
|
||
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: form.pickup_address.trim(),
|
||
delivery_address: form.delivery_address.trim(),
|
||
pickup_content: form.pickup_content.trim(),
|
||
quantity: Number(form.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 });
|
||
});
|
||
},
|
||
|
||
// 分配/重新分配司机(待司机接单)
|
||
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 });
|
||
app.request({
|
||
url: "/api/logistics/tasks/" + that.data.logistics.task_id + "/cancel",
|
||
method: "POST",
|
||
data: { cancel_reason: "管理员重新分配司机" },
|
||
}).then(function () {
|
||
return doAssign();
|
||
}).catch(function (error) {
|
||
that.setData({ message: error.message || "取消物流任务失败", actionLoading: false });
|
||
});
|
||
},
|
||
});
|
||
} else {
|
||
doAssign();
|
||
}
|
||
},
|
||
|
||
// 简单状态变更
|
||
handleSimpleTransition: function (e) {
|
||
var that = this;
|
||
var target = e.currentTarget.dataset.target;
|
||
var labelMap = {
|
||
picked_up: "确认揽货",
|
||
delivered: "确认送达",
|
||
production: "标记生产",
|
||
shipped: "标记发货",
|
||
completed: "标记完成",
|
||
pending_settle: "待结算",
|
||
};
|
||
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 });
|
||
});
|
||
},
|
||
});
|
||
},
|
||
|
||
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,
|
||
};
|
||
},
|
||
});
|