|
|
@@ -6,7 +6,7 @@
|
|
|
// API接口导入
|
|
|
import { add, update as updateOrderHeader, getDetail } from '@/api/order/order'
|
|
|
import { getList as getOrderItemList } from '@/api/order/order-item'
|
|
|
-import { createSalesOrder, updateOrder, addOrderItem } from '@/api/order/sales-order'
|
|
|
+import { createSalesOrder, updateOrder, addOrderItem, updateOrderItem } from '@/api/order/sales-order'
|
|
|
import { getCustomerInfo } from '@/api/common/index'
|
|
|
import { getList as getAddressList } from '@/api/order/address'
|
|
|
import { submitOrderToU9 } from '@/api/order/sales-order'
|
|
|
@@ -158,6 +158,13 @@ export default {
|
|
|
materialDetails: [],
|
|
|
|
|
|
/**
|
|
|
+ * 远程明细原始快照映射
|
|
|
+ * @description 记录从服务器加载的远程物料明细的原始值,用于草稿状态下的变更对比
|
|
|
+ * @type {Record<string, {orderQuantity:number, confirmQuantity:number, unitPrice:number, taxRate:number}>}
|
|
|
+ */
|
|
|
+ originalRemoteDetailsById: {},
|
|
|
+
|
|
|
+ /**
|
|
|
* 订单类型选项列表
|
|
|
* @description 订单类型下拉选择器的选项数据
|
|
|
* @type {typeof ORDER_TYPE_OPTIONS}
|
|
|
@@ -332,10 +339,10 @@ export default {
|
|
|
callback()
|
|
|
}
|
|
|
},
|
|
|
- trigger: 'blur'
|
|
|
- }
|
|
|
- ],
|
|
|
- receiverRegion: [
|
|
|
+ trigger: 'blur'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ receiverRegion: [
|
|
|
{
|
|
|
required: true,
|
|
|
message: '请输入收货地区',
|
|
|
@@ -440,6 +447,16 @@ export default {
|
|
|
}
|
|
|
]
|
|
|
}
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否为草稿状态
|
|
|
+ * @description 根据订单状态判断是否为草稿
|
|
|
+ * @returns {boolean}
|
|
|
+ */
|
|
|
+ isDraft() {
|
|
|
+ const status = Number(this.formData && this.formData.status)
|
|
|
+ return status === ORDER_STATUS.DRAFT
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -659,8 +676,8 @@ export default {
|
|
|
try {
|
|
|
// 并行加载订单详情和物料明细数据以提高性能
|
|
|
const [orderResponse, materialResponse] = await Promise.all([
|
|
|
- getDetail(orderId),
|
|
|
- this.loadMaterialDetails(orderId)
|
|
|
+ getDetail(String(orderId)),
|
|
|
+ this.loadMaterialDetails(String(orderId))
|
|
|
])
|
|
|
|
|
|
// 验证订单详情响应数据
|
|
|
@@ -681,6 +698,9 @@ export default {
|
|
|
// 设置物料明细数据(确保是数组类型)
|
|
|
this.materialDetails = Array.isArray(materialResponse) ? materialResponse : []
|
|
|
|
|
|
+ // 新增:建立远程明细原始快照,用于后续对比并持久化更新
|
|
|
+ this.originalRemoteDetailsById = this.buildOriginalRemoteSnapshot(this.materialDetails)
|
|
|
+
|
|
|
console.log(`成功加载订单详情,订单编码: ${orderData.orderCode || orderId}`)
|
|
|
|
|
|
} catch (error) {
|
|
|
@@ -803,6 +823,52 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
|
|
|
+ // 构建远程明细原始快照
|
|
|
+ buildOriginalRemoteSnapshot(materials) {
|
|
|
+ const map = {}
|
|
|
+ try {
|
|
|
+ (materials || [])
|
|
|
+ .filter(m => m && m.dataSource === MaterialDetailDataSource.REMOTE)
|
|
|
+ .forEach(m => {
|
|
|
+ const key = String(m.id || m.itemId || m.itemCode || '')
|
|
|
+ if (!key) return
|
|
|
+ map[key] = {
|
|
|
+ orderQuantity: Math.round(Number(m.orderQuantity) || 0),
|
|
|
+ confirmQuantity: Math.round(Number(m.confirmQuantity) || 0),
|
|
|
+ unitPrice: preciseRound(Number(m.unitPrice) || 0, 2),
|
|
|
+ taxRate: preciseRound(Number(m.taxRate) || 0, 4)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } catch (e) {
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.warn('构建原始快照失败:', e)
|
|
|
+ }
|
|
|
+ return map
|
|
|
+ },
|
|
|
+
|
|
|
+ // 判断远程明细是否发生变化(与原始快照对比)
|
|
|
+ hasRemoteMaterialChanged(currentRow) {
|
|
|
+ if (!currentRow) return false
|
|
|
+ const key = String(currentRow.id || currentRow.itemId || currentRow.itemCode || '')
|
|
|
+ if (!key) return false
|
|
|
+ const original = this.originalRemoteDetailsById && this.originalRemoteDetailsById[key]
|
|
|
+ if (!original) return false
|
|
|
+
|
|
|
+ const curr = {
|
|
|
+ orderQuantity: Math.round(Number(currentRow.orderQuantity) || 0),
|
|
|
+ confirmQuantity: Math.round(Number(currentRow.confirmQuantity) || 0),
|
|
|
+ unitPrice: preciseRound(Number(currentRow.unitPrice) || 0, 2),
|
|
|
+ taxRate: preciseRound(Number(currentRow.taxRate) || 0, 4)
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ curr.orderQuantity !== original.orderQuantity ||
|
|
|
+ curr.confirmQuantity !== original.confirmQuantity ||
|
|
|
+ curr.unitPrice !== original.unitPrice ||
|
|
|
+ curr.taxRate !== original.taxRate
|
|
|
+ )
|
|
|
+ },
|
|
|
+
|
|
|
/**
|
|
|
* 映射订单数据到表单格式
|
|
|
* @description 将API返回的订单数据安全地映射为表单数据格式,并格式化数字字段
|
|
|
@@ -820,10 +886,10 @@ export default {
|
|
|
return {
|
|
|
id: orderData.id ? String(orderData.id) : undefined,
|
|
|
orderCode: String(orderData.orderCode || ''),
|
|
|
- orgId: orderData.orgId ? Number(orderData.orgId) : undefined,
|
|
|
+ orgId: orderData.orgId ? String(orderData.orgId) : undefined,
|
|
|
orgCode: String(orderData.orgCode || ''),
|
|
|
orgName: String(orderData.orgName || ''),
|
|
|
- customerId: Number(orderData.customerId) || null,
|
|
|
+ customerId: orderData.customerId ? String(orderData.customerId) : null,
|
|
|
customerCode: String(orderData.customerCode || ''),
|
|
|
customerName: String(orderData.customerName || ''),
|
|
|
orderType: Number(orderData.orderType) || ORDER_TYPES.NORMAL,
|
|
|
@@ -886,12 +952,23 @@ export default {
|
|
|
.slice(0, 5)
|
|
|
.map(it => `${it.itemName || it.itemCode || '物料'}:订单数量 ${Number(it.orderQuantity || 0)} > 可用数量 ${Number(it.availableQuantity || 0)}`)
|
|
|
.join('\n')
|
|
|
- await this.$alert(
|
|
|
- `库存不足,以下物料订单数量超过可用数量:\n${detailText}${exceededItems.length > 5 ? '\n...' : ''}`,
|
|
|
- '库存不足',
|
|
|
- { customClass: 'order-stock-alert' }
|
|
|
- )
|
|
|
- return
|
|
|
+ try {
|
|
|
+ await this.$confirm(
|
|
|
+ `库存不足,以下物料订单数量超过可用数量:\n${detailText}${exceededItems.length > 5 ? '\n...' : ''}\n\n是否继续提交?`,
|
|
|
+ '库存不足',
|
|
|
+ {
|
|
|
+ confirmButtonText: '继续提交',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ customClass: 'order-stock-alert',
|
|
|
+ distinguishCancelAndClose: true
|
|
|
+ }
|
|
|
+ )
|
|
|
+ // 用户确认,继续后续提交流程
|
|
|
+ } catch (e) {
|
|
|
+ // 用户取消或关闭,终止提交
|
|
|
+ return
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
this.saveLoading = true
|
|
|
@@ -914,8 +991,13 @@ export default {
|
|
|
*/
|
|
|
this.$emit(ORDER_FORM_EVENTS.SAVE_SUCCESS, response.data.data)
|
|
|
|
|
|
- // 返回列表
|
|
|
- this.handleBack()
|
|
|
+ // 保持在当前页:编辑模式不返回列表,仅在新增模式返回列表
|
|
|
+ if (!this.isEdit) {
|
|
|
+ // 新增模式:保存成功后返回列表
|
|
|
+ this.handleBack()
|
|
|
+ } else {
|
|
|
+ // 编辑模式:留在当前页,方便继续编辑
|
|
|
+ }
|
|
|
|
|
|
} catch (error) {
|
|
|
const errorMessage = this.isEdit ? '订单更新失败,请重试' : '订单创建失败,请重试'
|
|
|
@@ -996,7 +1078,7 @@ export default {
|
|
|
*/
|
|
|
async submitOrderData(submitData) {
|
|
|
if (this.isEdit) {
|
|
|
- // 编辑状态下:先仅更新订单基础信息(不包含物料明细),再单独添加“导入”的物料
|
|
|
+ // 编辑状态下:先仅更新订单基础信息(不包含物料明细),再单独处理导入新增与远程变更
|
|
|
// 第一步:更新订单头
|
|
|
const headerData = this.prepareSubmitData()
|
|
|
const headerResponse = await updateOrderHeader(headerData)
|
|
|
@@ -1013,7 +1095,7 @@ export default {
|
|
|
const orderCode = (this.formData && this.formData.orderCode) || ''
|
|
|
|
|
|
const payloads = importedMaterials.map(material => ({
|
|
|
- orderId: String(orderId), // 以字符串传输,避免大整数精度问题
|
|
|
+ orderId: String(orderId),
|
|
|
orderCode,
|
|
|
itemId: String(material.itemId || ''),
|
|
|
itemCode: material.itemCode || '',
|
|
|
@@ -1040,7 +1122,48 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 两步均成功,返回头部更新的响应
|
|
|
+ // 第三步:草稿状态下,找出远程明细的变更并调用 updateOrderItem 持久化
|
|
|
+ if (this.isDraft) {
|
|
|
+ const orderId = (this.formData && this.formData.id) || this.orderId
|
|
|
+ const remoteChanged = (this.materialDetails || [])
|
|
|
+ .filter(m => m.dataSource === MaterialDetailDataSource.REMOTE)
|
|
|
+ .filter(m => this.hasRemoteMaterialChanged(m))
|
|
|
+
|
|
|
+ if (remoteChanged.length > 0) {
|
|
|
+ const updatePayloads = remoteChanged.map(material => ({
|
|
|
+ id: String(material.id || ''),
|
|
|
+ orderId: String(orderId || ''),
|
|
|
+ orderCode: (this.formData && this.formData.orderCode) || '',
|
|
|
+ itemId: String(material.itemId || ''),
|
|
|
+ itemCode: material.itemCode || '',
|
|
|
+ itemName: material.itemName || '',
|
|
|
+ specs: material.specs || '',
|
|
|
+ mainItemCategoryId: String(material.mainItemCategoryId || ''),
|
|
|
+ mainItemCategoryName: material.mainItemCategoryName || '',
|
|
|
+ warehouseId: String(material.warehouseId || ''),
|
|
|
+ warehouseName: material.warehouseName || '',
|
|
|
+ availableQuantity: Number(material.availableQuantity) || 0,
|
|
|
+ orderQuantity: Number(material.orderQuantity) || 0,
|
|
|
+ confirmQuantity: Number(material.confirmQuantity) || Number(material.orderQuantity) || 0,
|
|
|
+ unitPrice: Number(material.unitPrice) || 0,
|
|
|
+ taxRate: Number(material.taxRate) || 0,
|
|
|
+ taxAmount: Number(material.taxAmount) || 0,
|
|
|
+ totalAmount: Number(material.totalAmount) || 0,
|
|
|
+ itemStatus: material.itemStatus || ORDER_ITEM_STATUS.UNCONFIRMED
|
|
|
+ }))
|
|
|
+
|
|
|
+ const updateResults = await Promise.all(updatePayloads.map(p => updateOrderItem(p)))
|
|
|
+ const updatesOk = updateResults.every(r => r && r.data && r.data.success)
|
|
|
+ if (!updatesOk) {
|
|
|
+ throw new Error('部分物料更新失败')
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新成功后,刷新原始快照,避免后续重复提交
|
|
|
+ this.originalRemoteDetailsById = this.buildOriginalRemoteSnapshot(this.materialDetails)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 返回头部更新的响应
|
|
|
return headerResponse
|
|
|
} else {
|
|
|
// 新建状态下使用createSalesOrder接口,包含物料明细数据
|
|
|
@@ -1305,6 +1428,65 @@ export default {
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
+ * 处理物料批量删除事件
|
|
|
+ * @description 批量删除选中的物料记录,仅允许删除可删除的物料
|
|
|
+ * @param {import('./types').MaterialBatchDeleteEventData} data - 批量删除事件数据
|
|
|
+ * @returns {void}
|
|
|
+ * @public
|
|
|
+ * @this {import('./types').OrderFormMixinComponent}
|
|
|
+ */
|
|
|
+ handleMaterialBatchDelete({ rows, count }) {
|
|
|
+ if (!Array.isArray(rows) || rows.length === 0) {
|
|
|
+ this.$message.warning('请选择要删除的物料')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否有不可删除的物料
|
|
|
+ const undeletableItems = rows.filter(row => !row.isDeletable)
|
|
|
+ if (undeletableItems.length > 0) {
|
|
|
+ const itemNames = undeletableItems.map(item => item.itemName).join('、')
|
|
|
+ this.$message.warning(`以下物料不允许删除:${itemNames}`)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确认删除操作
|
|
|
+ this.$confirm(`确定要删除选中的 ${count} 条物料记录吗?`, '批量删除确认', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+ try {
|
|
|
+ // 批量删除物料
|
|
|
+ const deletedItems = []
|
|
|
+ rows.forEach(row => {
|
|
|
+ const materialIndex = this.materialDetails.findIndex(item =>
|
|
|
+ item.itemCode === row.itemCode &&
|
|
|
+ item.dataSource === row.dataSource
|
|
|
+ )
|
|
|
+
|
|
|
+ if (materialIndex !== -1) {
|
|
|
+ const deletedItem = this.materialDetails.splice(materialIndex, 1)[0]
|
|
|
+ deletedItems.push(deletedItem.itemName)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ if (deletedItems.length > 0) {
|
|
|
+ this.$message.success(`成功删除 ${deletedItems.length} 条物料记录`)
|
|
|
+ // 触发选择重置,清空已删除的选中项
|
|
|
+ this.$refs.materialDetailTable?.resetSelection()
|
|
|
+ } else {
|
|
|
+ this.$message.warning('未找到要删除的物料记录')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error('批量删除物料失败,请重试')
|
|
|
+ console.error('批量删除物料失败:', error)
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ // 用户取消删除操作
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
* 处理物料导入事件
|
|
|
* @description 将导入的物料数据添加到物料明细列表中,格式化数字字段并标记为可删除
|
|
|
* @param {MaterialDetailRecord[]} importedMaterials - 导入的物料数据数组
|
|
|
@@ -1478,7 +1660,7 @@ export default {
|
|
|
handleCustomerSelected(/** @type {import('./types').CustomerSelectData} */ customerData) {
|
|
|
if (this.formData) {
|
|
|
// 更新客户相关字段
|
|
|
- this.$set(this.formData, 'customerId', customerData.customerId)
|
|
|
+ this.$set(this.formData, 'customerId', customerData.customerId != null ? String(customerData.customerId) : null)
|
|
|
this.$set(this.formData, 'customerCode', customerData.customerCode)
|
|
|
this.$set(this.formData, 'customerName', customerData.customerName)
|
|
|
|
|
|
@@ -1501,7 +1683,7 @@ export default {
|
|
|
handleAddressSelected(/** @type {import('./types').AddressSelectData} */ addressData) {
|
|
|
if (this.formData) {
|
|
|
// 更新地址相关字段
|
|
|
- this.$set(this.formData, 'addressId', addressData.addressId)
|
|
|
+ this.$set(this.formData, 'addressId', addressData.addressId != null ? String(addressData.addressId) : '')
|
|
|
this.$set(this.formData, 'receiverName', addressData.receiverName || '')
|
|
|
this.$set(this.formData, 'receiverPhone', addressData.receiverPhone || '')
|
|
|
this.$set(this.formData, 'receiverRegion', addressData.regionName || '')
|