修订记录
+{{ revisionInfo.revision_time }} — {{ revisionInfo.revision_remark }}
+{{ detail.product_name }}
+计价方式:{{ getDetailPricingTypeLabel(detail.pricing_type) }}
+公式说明:{{ detail.formula_note }}
+公式:{{ detail.formula_expr }}
+面积:{{ detail.area_sqm }}㎡
++ 附加费: + {{ s.name }} ¥{{ s.amount }}{{ si < detail.surcharge_items.length - 1 ? ',' : '' }} +
+供应商型号:{{ detail.supplier_model }}
+价格档位:{{ detail.price_tier }}
+{{ revisionInfo.revision_time }} — {{ revisionInfo.revision_remark }}
+{{ actionHint }}
@@ -138,6 +185,7 @@ const loading = ref(true); const orderInfo = ref(null); const items = ref([]); const calcDetail = ref(null); +const revisionInfo = ref(null); const approveOpinion = ref(""); const actionLoading = ref(""); const isModal = computed(() => route.query.modal === "1"); @@ -173,6 +221,7 @@ async function loadDetail() { orderInfo.value = data.orderInfo; items.value = data.items || []; calcDetail.value = data.calculationDetail || null; + revisionInfo.value = data.revisionInfo || null; } function handleClose() { @@ -194,6 +243,34 @@ function buildCopyText() { return lines.join("\n"); } +const PRICING_TYPE_LABELS = { + area: "按面积", + kg: "按重量", + weight_g: "按克重", + unit: "按件", + 件: "按件", + linear_m: "按长度", +}; + +function getPricingTypeLabel(item, idx) { + // 优先从 calcDetail.item_details 中获取 + if (calcDetail.value && calcDetail.value.item_details && calcDetail.value.item_details[idx]) { + const pt = calcDetail.value.item_details[idx].pricing_type; + if (pt) return PRICING_TYPE_LABELS[pt] || pt; + } + return '-'; +} + +function getDetailPricingTypeLabel(pricingType) { + return PRICING_TYPE_LABELS[pricingType] || pricingType; +} + +function getItemCostTotal(item) { + const qty = Number(item.quantity) || 0; + const cost = Number(item.costPrice) || 0; + return (qty * cost).toFixed(2); +} + async function handleApprove(result) { if (!orderInfo.value) return; actionLoading.value = result; @@ -203,7 +280,7 @@ async function handleApprove(result) { approve_opinion: approveOpinion.value || (result === "pass" ? "审批通过" : "审批驳回"), }; - if (orderInfo.value.orderStatus === "cancel_pending") { + if (orderInfo.value.orderStatus === "cancel_pending" || orderInfo.value.orderStatus === "cancel_fulfillment_pending") { await cancelApproveOrder(orderInfo.value.orderId, payload); toast.success(result === "pass" ? "取消审批已通过" : "已驳回取消申请"); } else { @@ -300,6 +377,24 @@ td { color: #374151; } .calc-result { margin-top: 12px; padding-top: 12px; border-top: 1px solid #e5e7eb; } .tax-note { color: #9ca3af; font-size: 12px; margin-left: 16px; } +/* 修订记录 */ +.revision-card { background: #fef3c7; border-color: #f59e0b; } +.revision-time { margin: 0 0 12px; color: #92400e; font-size: 13px; } +.revision-diff { display: flex; flex-direction: column; gap: 8px; } +.diff-row { display: flex; align-items: center; gap: 8px; font-size: 13px; } +.diff-label { color: #6b7280; min-width: 60px; } +.diff-before { color: #dc2626; text-decoration: line-through; } +.diff-arrow { color: #9ca3af; } +.diff-after { color: #16a34a; font-weight: 500; } + +/* 单项计算明细 */ +.item-calc-details { margin-top: 16px; padding-top: 16px; border-top: 1px solid #e5e7eb; } +.item-calc-details h4 { margin: 0 0 12px; font-size: 14px; color: #374151; } +.item-calc-row { padding: 8px 12px; background: #f9fafb; border-radius: 8px; margin-bottom: 8px; } +.item-calc-row:last-child { margin-bottom: 0; } +.item-calc-name { margin: 0 0 4px; font-weight: 500; color: #374151; font-size: 13px; } +.item-calc-row p { margin: 2px 0; color: #6b7280; font-size: 12px; } + /* 审批操作 */ .action-card { background: #f9fafb; } .action-hint { margin: 0 0 16px; color: #6b7280; font-size: 13px; } diff --git a/frontend/web-admin/src/views/OrdersPage.vue b/frontend/web-admin/src/views/OrdersPage.vue index d6dfcf8..6cfc4ed 100644 --- a/frontend/web-admin/src/views/OrdersPage.vue +++ b/frontend/web-admin/src/views/OrdersPage.vue @@ -163,7 +163,7 @@ {{ detail.order.statusText }} 自发 -{{ log.approve_opinion }}
@@ -900,6 +904,7 @@ import { cancelOrderById, fetchOrderDetailAdmin, updateOrder, + reviseOrder, submitOrderToReview, changeOrderStatus, settleOrder, @@ -1070,6 +1075,7 @@ const editForm = reactive({ commission_amount: 0, payment_method: '', remark: '', + revision_reason: '', }); const editItems = ref([]); const productOptions = ref([]); @@ -1107,7 +1113,9 @@ function handleProductChange(item, event) { } function canEditOrder(status) { - return ['draft', 'rejected', 'pending_approve'].includes(status); + // 管理员可修订:草稿/已退回用 updateOrder,其他非终态用 reviseOrder + const terminalStatuses = ['canceled', 'settled']; + return !terminalStatuses.includes(status); } function enterEditMode() { @@ -1128,6 +1136,7 @@ function enterEditMode() { editForm.payment_method = detail.order.paymentMethod === '-' ? '' : detail.order.paymentMethod; editForm.remark = detail.order.remark === '-' ? '' : detail.order.remark; editForm.customer_demand = detail.order.customerDemand || ''; + editForm.revision_reason = ''; // 复制商品明细 editItems.value = (detail.items || []).map(item => ({...item})); } @@ -1179,33 +1188,55 @@ async function saveEdit() { editSaving.value = true; try { - const payload = { - customer_name: editForm.customer_name.trim(), - customer_mobile: editForm.customer_mobile.trim(), - customer_address: editForm.customer_address.trim() || null, - order_source: editForm.order_source.trim() || null, - delivery_type: editForm.delivery_type.trim() || null, - contract_amount: Number(editForm.contract_amount) || 0, - rebate_total: Number(editForm.rebate_total) || 0, - freight_total: Number(editForm.freight_total) || 0, - tax_total: Number(editForm.tax_total) || 0, - other_fee_total: Number(editForm.other_fee_total) || 0, - commission_amount: Number(editForm.commission_amount) || 0, - payment_method: editForm.payment_method.trim() || null, - remark: editForm.remark.trim() || null, - customer_demand: editForm.customer_demand.trim() || null, - items: editItems.value.map(item => ({ - product_id: item.product_id || null, - product_name: item.product_name.trim(), - specification: item.specification?.trim() || '', - unit: item.unit?.trim() || '', - quantity: Number(item.quantity) || 1, - sale_price: Number(item.sale_price) || 0, - cost_price: Number(item.cost_price) || 0, - })), - }; - await updateOrder(detail.orderId, payload); - toast.success('订单已更新'); + const itemsPayload = editItems.value.map(item => ({ + product_id: item.product_id || null, + product_name: item.product_name.trim(), + specification: item.specification?.trim() || '', + unit: item.unit?.trim() || '', + quantity: Number(item.quantity) || 1, + sale_price: Number(item.sale_price) || 0, + cost_price: Number(item.cost_price) || 0, + })); + + const rawStatus = detail.order.rawStatus; + const isDraftOrRejected = ['draft', 'rejected'].includes(rawStatus); + + if (isDraftOrRejected) { + // 草稿/已退回:使用普通更新接口 + const payload = { + customer_name: editForm.customer_name.trim(), + customer_mobile: editForm.customer_mobile.trim(), + customer_address: editForm.customer_address.trim() || null, + order_source: editForm.order_source.trim() || null, + delivery_type: editForm.delivery_type.trim() || null, + contract_amount: Number(editForm.contract_amount) || 0, + rebate_total: Number(editForm.rebate_total) || 0, + freight_total: Number(editForm.freight_total) || 0, + tax_total: Number(editForm.tax_total) || 0, + other_fee_total: Number(editForm.other_fee_total) || 0, + commission_amount: Number(editForm.commission_amount) || 0, + payment_method: editForm.payment_method.trim() || null, + remark: editForm.remark.trim() || null, + customer_demand: editForm.customer_demand.trim() || null, + items: itemsPayload, + }; + await updateOrder(detail.orderId, payload); + toast.success('订单已更新'); + } else { + // 已提交/已审批等状态:使用修订接口(自动重算成本) + const payload = { + items: itemsPayload, + revision_reason: editForm.revision_reason?.trim() || '管理员修改', + contract_amount: Number(editForm.contract_amount) || undefined, + rebate_total: Number(editForm.rebate_total) || 0, + freight_total: Number(editForm.freight_total) || 0, + tax_total: Number(editForm.tax_total) || 0, + other_fee_total: Number(editForm.other_fee_total) || 0, + }; + await reviseOrder(detail.orderId, payload); + toast.success('订单已修订,成本已自动重算'); + } + isEditing.value = false; await loadDetail(); await handleSearch(); @@ -1651,7 +1682,7 @@ async function handleApprovalPass() { detailActionBusy.value = false; } } else { - // 非自发订单:需要选择工厂和司机 + // 非自发订单:调用审批接口,后端统一处理工厂分配和物流任务创建 if (!selectedFactoryId.value) { toast.error('请选择下发工厂'); return; @@ -1662,20 +1693,11 @@ async function handleApprovalPass() { } detailActionBusy.value = true; try { - // 先选择工厂并进入待工厂状态 - await changeOrderStatus(detail.orderId, { - target_status: 'pending_factory', + await approveOrder(detail.orderId, { + approve_result: 'pass', + approve_opinion: '审批通过', factory_id: selectedFactoryId.value, - }); - // 再分配司机并创建物流任务 - await createLogisticsTask({ - order_id: detail.orderId, driver_id: Number(selectedDriverId.value), - pickup_address: getFactoryAddress(selectedFactoryId.value) || detail.order.factoryName || '', - delivery_address: detail.order.customerAddress || '', - pickup_content: detail.order.customerName ? `${detail.order.customerName} 订单货物` : '', - quantity: 1, - factory_id: selectedFactoryId.value, }); toast.success('审批通过,已下发工厂并分配司机'); await loadDetail(); @@ -1706,7 +1728,8 @@ async function handleApprovalReject() { // 驳回取消申请 - 使用 cancel-approve 接口 await cancelApproveOrder(detail.orderId, {approve_result: 'refuse', approve_opinion: opinion || '驳回取消'}); } else { - await changeOrderStatus(detail.orderId, {target_status: 'rejected', approve_opinion: opinion || '退回'}); + // 使用审批接口退回,确保审批日志正确记录 + await approveOrder(detail.orderId, {approve_result: 'reject', approve_opinion: opinion || '退回'}); } toast.success(isCancelFlow ? '已驳回取消' : '已退回'); await loadDetail(); diff --git a/frontend/web-admin/src/views/PricingRulesPage.vue b/frontend/web-admin/src/views/PricingRulesPage.vue index c5ad5c8..31bcd07 100644 --- a/frontend/web-admin/src/views/PricingRulesPage.vue +++ b/frontend/web-admin/src/views/PricingRulesPage.vue @@ -214,6 +214,8 @@ + + + + + + + +