Commit 15102e6e by yubin

出库

parent 389f53a1
......@@ -68,3 +68,12 @@ export function listInventoryByMaterialId(materialId) {
params: { materialId: materialId }
})
}
export function Ship(data) {
return request({
url: '/inventory/inventory/ship',
method: 'post',
data: data
})
}
......@@ -51,3 +51,11 @@ export function listWarehouseInventory(warehouseId) {
params: { warehouseId: warehouseId }
})
}
export function ship(data) {
return request({
url: '/inventory/orders/ship',
method: 'post',
data: data
})
}
\ No newline at end of file
......@@ -66,7 +66,7 @@
</el-col>
</el-row>
<!-- 库存信息列表 -->
<!-- 库存信息列表 - 添加库存ID列 -->
<el-row v-if="form.materialId && form.materialId.trim()" style="margin: 10px 0;">
<el-col :span="24">
<div style="margin-bottom: 8px; font-weight: 600; color: #1989fa;">
......@@ -81,11 +81,10 @@
highlight-current-row
stripe
empty-text="暂无库存数据"
@selection-change="handleSelectionChange"
@row-click="handleRowClick"
:row-key="item => item.id"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="库存ID" width="100" />
<el-table-column prop="id" label="库存ID" width="100" />
<el-table-column prop="materialId" label="货物ID" width="100" />
<el-table-column prop="batchId" label="批次ID" width="100" />
<el-table-column prop="orderId" label="订单ID" width="120" />
......@@ -107,31 +106,20 @@
{{ (scope.row.quantity || 0) - (scope.row.lockedQuantity || 0) }}
</template>
</el-table-column>
<el-table-column label="选择数量" width="120">
<!-- 实际数量:填写即视为选中(原选择数量) -->
<el-table-column label="实际数量" width="120">
<template slot-scope="scope">
<el-input
v-model.number="scope.row.selectedQty"
v-model.number="scope.row.actualQuantity"
type="number"
size="mini"
min="1"
:max="(scope.row.quantity || 0) - (scope.row.lockedQuantity || 0)"
@input="handleRowQtyInput(scope.row)"
@input="handleRowActualQtyInput(scope.row); syncDetails()"
placeholder="输入数量"
/>
</template>
</el-table-column>
<el-table-column label="计划件数" width="120">
<template slot-scope="scope">
<el-input
v-model.number="scope.row.plannedPackages"
type="number"
size="mini"
min="0"
@input="handleRowPackagesInput(scope.row)"
placeholder="输入件数"
/>
</template>
</el-table-column>
</el-table>
<!-- 选中行的扩展字段填写区域 -->
......@@ -140,8 +128,9 @@
style="margin-top: 10px; padding: 10px; border: 1px solid #e6e6e6; border-radius: 4px;"
>
<div style="margin-bottom: 8px; font-weight: 600; color: #1989fa;">
库存【ID: {{ currentSelectedRow.id }}】明细信息
库存明细信息
</div>
<!-- 扩展字段区域保持不变 -->
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="约数" prop="divisor">
......@@ -150,12 +139,13 @@
placeholder="请输入约数"
type="number"
min="0"
@input="syncDetails()"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="标签颜色" prop="labelColor">
<el-select v-model="currentSelectedRow.labelColor" placeholder="请选择标签颜色">
<el-select v-model="currentSelectedRow.labelColor" placeholder="请选择标签颜色" @change="syncDetails()">
<el-option label="红色" value="1"></el-option>
<el-option label="蓝色" value="2"></el-option>
<el-option label="绿色" value="3"></el-option>
......@@ -163,35 +153,26 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="单价" prop="unitPrice">
<el-col :span="8">
<el-form-item label="计划数量" prop="plannedQuantity">
<el-input
v-model.number="currentSelectedRow.unitPrice"
placeholder="请输入单价"
v-model.number="currentSelectedRow.plannedQuantity"
type="number"
min="0"
step="0.01"
min="1"
:max="(currentSelectedRow.quantity || 0) - (currentSelectedRow.lockedQuantity || 0)"
@input="handleRowPlannedQtyInput(currentSelectedRow); syncDetails()"
placeholder="输入计划数量"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 10px;">
<el-col :span="12">
<el-form-item label="收货时间" prop="receivedAt">
<el-date-picker
v-model="currentSelectedRow.receivedAt"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择收货时间"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="收货人" prop="receivedBy">
<el-form-item label="发货方" prop="shippedBy">
<el-input
v-model="currentSelectedRow.receivedBy"
placeholder="请输入收货人"
v-model="currentSelectedRow.shippedBy"
placeholder="请输入发货方"
@input="syncDetails()"
/>
</el-form-item>
</el-col>
......@@ -202,6 +183,7 @@
<el-input
v-model="currentSelectedRow.voucherNumber"
placeholder="请输入凭证号"
@input="syncDetails()"
/>
</el-form-item>
</el-col>
......@@ -214,6 +196,7 @@
placeholder="请输入备注"
type="textarea"
rows="2"
@input="syncDetails()"
/>
</el-form-item>
</el-col>
......@@ -222,7 +205,7 @@
</el-col>
</el-row>
<!-- 已生成的明细预览 -->
<!-- 已生成的明细预览 - 添加库存ID列 -->
<el-row v-if="details.length > 0" style="margin: 10px 0;">
<el-col :span="24">
<div style="margin-bottom: 8px; font-weight: 600; color: #1989fa;">
......@@ -233,24 +216,22 @@
border
size="small"
max-height="200"
:row-key="item => item.inventoryId"
>
<el-table-column prop="batchId" label="批次ID" />
<el-table-column prop="inventoryId" label="库存ID" width="100" />
<el-table-column prop="batchCode" label="批次ID" />
<el-table-column prop="warehouseId" label="仓库ID" />
<el-table-column prop="locationId" label="库位ID" />
<el-table-column prop="plannedQuantity" label="计划数量" />
<el-table-column prop="actualQuantity" label="实际数量" />
<el-table-column prop="plannedPackages" label="计划件数" />
<el-table-column prop="actualPackages" label="实际件数" />
<el-table-column prop="divisor" label="约数" />
<el-table-column prop="unitPrice" label="单价" />
<el-table-column prop="labelColor" label="标签颜色">
<template slot-scope="scope">
{{ getLabelColorText(scope.row.labelColor) }}
</template>
</el-table-column>
<el-table-column prop="receivedAt" label="收货时间" />
<el-table-column prop="receivedBy" label="收货人" />
<el-table-column prop="voucherNumber" label="凭证号" />
<el-table-column prop="shippedBy" label="发货方" />
<el-table-column prop="remark" label="备注" />
<el-table-column label="操作" width="80">
<template slot-scope="scope">
......@@ -267,7 +248,7 @@
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click.native="handleClose">取消</el-button>
<el-button type="primary" @click.native="handleSubmit">确定</el-button>
<el-button type="primary" @click.native="handleSubmit">生成明细</el-button>
</div>
</el-dialog>
</template>
......@@ -276,7 +257,6 @@
import { listInventoryByMaterialId } from "@/api/inventory/inventory"
import MaterialSelector from '../../../components/materialsSeletor.vue'
// 防抖函数(适配Vue2 this上下文)
function debounce(fn, delay = 500) {
let timer = null
return function(...args) {
......@@ -308,14 +288,13 @@ export default {
},
data() {
return {
// 仅保留全局字段:货物ID、计划总数量
form: {
materialUuids: '',
materialId: '',
totalPlannedQuantity: null,
itemStatus: '1' // 对应数据库状态:1-待收货
itemStatus: 1
},
details: [],
details: [],
inventoryList: [],
loadingInstance: null,
rules: {
......@@ -324,26 +303,11 @@ export default {
message: '请选择货物ID',
trigger: ['change', 'blur']
}]
// totalPlannedQuantity: [{
// required: true,
// message: '请输入计划总数量',
// trigger: 'blur',
// type: 'number'
// }, {
// validator: (rule, value, callback) => {
// if (value && value < 1) {
// callback(new Error('计划总数量不能小于1'))
// } else {
// callback()
// }
// },
// trigger: 'blur'
// }]
},
openMaterialSelector: false,
selectedMaterialId: '',
selectedMaterialInfo: null,
currentSelectedRow: null // 存储当前点击的库存行
currentSelectedRow: null
}
},
created() {
......@@ -356,7 +320,7 @@ export default {
this.form = { ...this.$options.data().form, ...this.initForm }
this.selectedMaterialId = this.form.materialId || ''
this.selectedMaterialInfo = null
this.currentSelectedRow = null // 重置选中行
this.currentSelectedRow = null
if (this.form.materialId && this.form.materialId.trim()) {
this.handleMaterialIdChange()
}
......@@ -377,7 +341,7 @@ export default {
this.form = { ...this.$options.data().form, ...val }
this.selectedMaterialId = this.form.materialId || ''
this.selectedMaterialInfo = null
this.currentSelectedRow = null // 重置选中行
this.currentSelectedRow = null
if (this.form.materialId && this.form.materialId.trim()) {
this.handleMaterialIdChange()
}
......@@ -388,7 +352,6 @@ export default {
}
},
methods: {
// 标签颜色值转文本
getLabelColorText(value) {
const colorMap = {
'1': '红色',
......@@ -405,165 +368,152 @@ export default {
}
},
async handleMaterialIdChange() {
const materialId = this.form.materialId?.trim() || ''
if (!materialId) {
this.inventoryList = []
this.closeLoading()
return
}
try {
this.loadingInstance = this.$loading({
text: '查询中...',
target: this.$el,
lock: true,
background: 'rgba(0, 0, 0, 0.7)'
})
const res = await listInventoryByMaterialId(materialId)
if (res.code === 200) {
// 为每个库存行初始化扩展字段
this.inventoryList = (res.rows || []).map(item => ({
...item,
selectedQty: null,
plannedPackages: null,
divisor: null, // 约数
labelColor: '', // 标签颜色
unitPrice: null, // 单价
receivedAt: '', // 收货时间
receivedBy: '', // 收货人
voucherNumber: '', // 凭证号
remark: '', // 备注
quantity: item.quantity || 0,
lockedQuantity: item.lockedQuantity || 0
}))
if (this.inventoryList.length === 0) {
this.$message.warning('未查询到该物料的库存信息')
}
} else {
this.inventoryList = []
this.$message.error(res.msg || '查询库存失败')
const materialId = this.form.materialId?.trim() || ''; // 增加分号,避免解析错误
if (!materialId) {
this.inventoryList = [];
this.closeLoading();
return;
}
try {
this.loadingInstance = this.$loading({
text: '查询中...',
target: this.$el,
lock: true,
background: 'rgba(0, 0, 0, 0.7)'
});
const res = await listInventoryByMaterialId(materialId); // 增加分号
if (res.code === 200) {
// 按库存ID去重(修复语法:确保Map操作正确)
const uniqueInventoryMap = new Map();
(res.rows || []).forEach(item => {
if (!uniqueInventoryMap.has(item.id)) {
uniqueInventoryMap.set(item.id, item);
}
} catch (error) {
this.$message.error('查询库存失败:' + (error.message || '网络异常'))
this.inventoryList = []
} finally {
this.closeLoading()
});
const uniqueRows = Array.from(uniqueInventoryMap.values()); // 增加分号
this.inventoryList = uniqueRows.map(item => ({
...item,
actualQuantity: null,
plannedQuantity: null,
divisor: null,
labelColor: '',
unitPrice: null,
shippedAt: '',
shippedBy: '',
voucherNumber: '',
remark: '',
quantity: item.quantity || 0,
lockedQuantity: item.lockedQuantity || 0,
batchCode: item.batchId || ''
})); // 增加分号
if (this.inventoryList.length === 0) {
this.$message.warning('未查询到该物料的库存信息');
}
},
// 点击库存行触发 - 显示扩展字段填写区域
} else {
this.inventoryList = [];
this.$message.error(res.msg || '查询库存失败');
}
} catch (error) {
this.$message.error('查询库存失败:' + (error.message || '网络异常'));
this.inventoryList = [];
} finally {
this.closeLoading();
}
},
handleRowClick(row) {
if (!row) return
this.currentSelectedRow = row
// 自动选中当前行
this.$refs.inventoryTable.toggleRowSelection(row, true)
},
handleRowQtyInput(row) {
if (isNaN(row.selectedQty) || row.selectedQty === '') {
row.selectedQty = null
handleRowActualQtyInput(row) {
if (isNaN(row.actualQuantity) || row.actualQuantity === '') {
row.actualQuantity = null
return
}
const availableQty = (row.quantity || 0) - (row.lockedQuantity || 0)
if (row.selectedQty > availableQty) {
this.$message.warning(`选择数量不能超过可用数量${availableQty}`)
row.selectedQty = availableQty
} else if (row.selectedQty < 1) {
this.$message.warning('选择数量不能小于1')
row.selectedQty = 1
if (row.actualQuantity > availableQty) {
this.$message.warning(`实际数量不能超过可用数量${availableQty}`)
row.actualQuantity = availableQty
} else if (row.actualQuantity < 1) {
this.$message.warning('实际数量不能小于1')
row.actualQuantity = 1
}
// 核心改造1:清空其他行的实际数量,确保仅当前行有值
this.inventoryList.forEach(item => {
if (item.id !== row.id) {
item.actualQuantity = null
item.plannedQuantity = null
item.divisor = null
item.labelColor = ''
item.shippedBy = ''
item.voucherNumber = ''
item.remark = ''
}
})
},
handleRowPackagesInput(row) {
if (isNaN(row.plannedPackages) || row.plannedPackages === '') {
row.plannedPackages = null
handleRowPlannedQtyInput(row) {
if (isNaN(row.plannedQuantity) || row.plannedQuantity === '') {
row.plannedQuantity = null
return
}
if (row.plannedPackages < 0) {
this.$message.warning('计划件数不能小于0')
row.plannedPackages = 0
const availableQty = (row.quantity || 0) - (row.lockedQuantity || 0)
if (row.plannedQuantity > availableQty) {
this.$message.warning(`计划数量不能超过可用数量${availableQty}`)
row.plannedQuantity = availableQty
} else if (row.plannedQuantity < 1) {
this.$message.warning('计划数量不能小于1')
row.plannedQuantity = 1
}
},
handleSelectionChange(rows) {
// 过滤已取消选择的行
this.details = this.details.filter(detail =>
rows.some(row => row.id === detail.inventoryId)
)
// 处理选中行,携带行级扩展字段
rows.forEach(row => {
// 选择数量为空则跳过
if (row.selectedQty === null || row.selectedQty === '') return
syncDetails() {
// 核心修复2:先清空明细,避免重复累加
this.details = []
// 过滤出仅填写了实际数量的行(此时最多只有一行)
const validRow = this.inventoryList.find(row => {
if (row.actualQuantity === null || row.actualQuantity === '' || isNaN(row.actualQuantity)) return false
const availableQty = (row.quantity || 0) - (row.lockedQuantity || 0)
if (row.selectedQty > availableQty || row.selectedQty < 1) {
this.$message.warning(`行${row.id}的选择数量不合法,请重新输入`)
row.selectedQty = Math.min(Math.max(row.selectedQty, 1), availableQty)
}
// 校验行级必填字段(可根据实际需求调整)
if (!row.divisor && row.divisor !== 0) {
this.$message.warning(`库存ID:${row.id} 请填写约数`)
return
}
if (!row.labelColor) {
this.$message.warning(`库存ID:${row.id} 请选择标签颜色`)
return
}
if (!row.unitPrice && row.unitPrice !== 0) {
this.$message.warning(`库存ID:${row.id} 请填写单价`)
return
}
if (!row.receivedAt) {
this.$message.warning(`库存ID:${row.id} 请选择收货时间`)
return
}
if (!row.receivedBy) {
this.$message.warning(`库存ID:${row.id} 请填写收货人`)
return
}
return row.actualQuantity <= availableQty && row.actualQuantity >= 1
})
const existingIndex = this.details.findIndex(d => d.inventoryId === row.id)
if (validRow) {
const newDetail = {
inventoryId: row.id,
inventoryId: validRow.id, // 确保库存ID唯一
materialId: this.form.materialId,
batchId: row.batchId || '',
warehouseId: row.warehouseId || '',
locationId: row.locationId || '',
plannedQuantity: row.selectedQty,
actualQuantity: row.selectedQty,
plannedPackages: row.plannedPackages || 0,
actualPackages: row.plannedPackages || 0, // 实际件数默认等于计划件数
divisor: row.divisor, // 行级约数
labelColor: row.labelColor, // 行级标签颜色
unitPrice: row.unitPrice, // 行级单价
itemStatus: this.form.itemStatus || '1',
receivedAt: row.receivedAt, // 行级收货时间
receivedBy: row.receivedBy, // 行级收货人
voucherNumber: row.voucherNumber || '', // 行级凭证号
remark: row.remark || '' // 行级备注
batchCode: validRow.batchCode || '',
warehouseId: validRow.warehouseId || '',
locationId: validRow.locationId || '',
plannedQuantity: Number(validRow.plannedQuantity),
actualQuantity: Number(validRow.actualQuantity),
divisor: Number(validRow.divisor) || 0,
labelColor: Number(validRow.labelColor) || 0,
unitPrice: Number(validRow.unitPrice) || 0,
voucherNumber: validRow.voucherNumber || '',
itemStatus: Number(this.form.itemStatus) || 1,
shippedAt: validRow.shippedAt,
shippedBy: validRow.shippedBy,
remark: validRow.remark || '',
isUsed: 1,
sortNo: 0
}
if (existingIndex > -1) {
this.details.splice(existingIndex, 1, newDetail)
} else {
this.details.push(newDetail)
}
})
this.details.push(newDetail)
}
},
removeDetail(row) {
this.details = this.details.filter(d => d.inventoryId !== row.inventoryId)
if (this.$refs.inventoryTable && row.id) {
const inventoryRow = this.inventoryList.find(item => item.id === row.inventoryId)
if (inventoryRow) {
this.$refs.inventoryTable.toggleRowSelection(inventoryRow, false)
// 清空该行的填写内容
inventoryRow.selectedQty = null
inventoryRow.plannedPackages = null
inventoryRow.divisor = null
inventoryRow.labelColor = ''
inventoryRow.unitPrice = null
inventoryRow.receivedAt = ''
inventoryRow.receivedBy = ''
inventoryRow.voucherNumber = ''
inventoryRow.remark = ''
// 如果删除的是当前选中行,重置currentSelectedRow
if (this.currentSelectedRow && this.currentSelectedRow.id === row.inventoryId) {
this.currentSelectedRow = null
}
const inventoryRow = this.inventoryList.find(item => item.id === row.inventoryId)
if (inventoryRow) {
inventoryRow.actualQuantity = null
inventoryRow.plannedQuantity = null
inventoryRow.divisor = null
inventoryRow.labelColor = ''
inventoryRow.unitPrice = null
inventoryRow.shippedAt = ''
inventoryRow.shippedBy = ''
inventoryRow.voucherNumber = ''
inventoryRow.remark = ''
if (this.currentSelectedRow && this.currentSelectedRow.id === row.inventoryId) {
this.currentSelectedRow = null
}
}
},
......@@ -578,12 +528,53 @@ export default {
this.$message.error('表单校验失败,请检查必填项')
return
}
this.syncDetails()
if (this.details.length === 0) {
this.$message.error('请选择库存并填写数量生成明细')
this.$message.error('请填写实际数量并完善明细信息')
return
}
// 核心改造3:兜底校验 - 检查是否存在重复的inventoryId(防御性编程)
const inventoryIds = this.details.map(d => d.inventoryId)
const uniqueInventoryIds = [...new Set(inventoryIds)]
if (uniqueInventoryIds.length !== inventoryIds.length) {
this.$message.error('发现重复的库存ID明细,请重新选择!')
return
}
// 计算实际总数量
let hasError = false
this.details.forEach(detail => {
const row = this.inventoryList.find(r => r.id === detail.inventoryId)
if (!row) return
if (row.divisor === null && row.divisor !== 0) {
this.$message.warning(`库存ID:${row.id} 请填写约数`)
hasError = true
}
if (!row.labelColor) {
this.$message.warning(`库存ID:${row.id} 请选择标签颜色`)
hasError = true
}
if (!row.voucherNumber) {
this.$message.warning(`库存ID:${row.id} 请填写凭证号`)
hasError = true
}
if (!row.shippedBy) {
this.$message.warning(`库存ID:${row.id} 请填写发货方`)
hasError = true
}
if (!row.plannedQuantity) {
this.$message.warning(`库存ID:${row.id} 请填写计划数量`)
hasError = true
}
if (!row.actualQuantity) {
this.$message.warning(`库存ID:${row.id} 请填写实际数量`)
hasError = true
}
})
if (hasError) return
const totalActual = this.details.reduce((sum, d) => sum + (d.actualQuantity || 0), 0)
if (this.form.totalPlannedQuantity && this.form.totalPlannedQuantity !== totalActual) {
try {
......@@ -613,7 +604,7 @@ export default {
this.selectedMaterialId = ''
this.selectedMaterialInfo = null
this.openMaterialSelector = false
this.currentSelectedRow = null // 重置选中行
this.currentSelectedRow = null
if (this.$refs.detailForm) {
this.$refs.detailForm.resetFields()
}
......@@ -683,7 +674,6 @@ export default {
min-height: 500px;
}
/* 修复模态框遮罩层点击穿透问题 */
/deep/ .el-dialog__wrapper {
.el-modal__mask {
pointer-events: auto !important;
......
......@@ -122,13 +122,20 @@
</el-row>
<!-- 主表格 -->
<el-table v-loading="loading" :data="ordersList" @selection-change="handleSelectionChange">
<el-table
ref="mainTable"
v-loading="loading"
:data="ordersList"
@selection-change="handleSelectionChange"
:key="tableKey"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="出库单号" align="center" prop="orderId" width="150" />
<el-table-column label="系统编号" align="center" prop="systemNo" width="150" />
<el-table-column label="入库类型" align="center" prop="orderTypeId" width="120">
<template slot-scope="scope">
<dict-tag :options="dict.type.inbound_order_type" :value="scope.row.orderTypeId"/>
<dict-tag v-if="dict.type.inbound_order_type" :options="dict.type.inbound_order_type" :value="scope.row.orderTypeId"/>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="批次ID" align="center" prop="batchCode" width="120" />
......@@ -136,7 +143,8 @@
<el-table-column label="货主ID" align="center" prop="ownerId" width="100" />
<el-table-column label="订单状态" align="center" prop="orderStatus" width="150">
<template slot-scope="scope">
<dict-tag :options="dict.type.inbound_order_status" :value="scope.row.orderStatus"/>
<dict-tag v-if="dict.type.inbound_order_status" :options="dict.type.inbound_order_status" :value="scope.row.orderStatus"/>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="出库日期" align="center" prop="inboundDate" width="150">
......@@ -149,7 +157,7 @@
<el-table-column label="实际量" align="center" prop="totalActualQuantity" width="100" />
<el-table-column label="总件数" align="center" prop="totalPackages" width="100" />
<el-table-column label="备注" align="center" prop="remark" width="150" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120">
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
<template slot-scope="scope">
<el-button
size="mini"
......@@ -161,6 +169,13 @@
<el-button
size="mini"
type="text"
icon="el-icon-send"
@click="handleShip(scope.row)"
v-hasPermi="['inventory:orders:edit']"
>出货</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['inventory:orders:remove']"
......@@ -266,7 +281,6 @@
<el-divider content-position="center">入库单明细信息</el-divider>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<!-- 点击该按钮打开子组件弹窗 -->
<el-button type="primary" icon="el-icon-plus" size="mini" @click="openDetailDialog">添加</el-button>
</el-col>
<el-col :span="1.5">
......@@ -274,37 +288,45 @@
</el-col>
</el-row>
<el-table
:data="inboundOrderItemsList"
:data="outboundOrderItemsList"
:row-class-name="rowInboundOrderItemsIndex"
@selection-change="handleInboundOrderItemsSelectionChange"
ref="inboundOrderItems"
border
style="width: 100%;"
v-if="outboundOrderItemsList.length > 0"
:row-key="item => item.inventoryId"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="序号" align="center" prop="index" width="50"/>
<el-table-column prop="inventoryId" label="库存ID" width="100" />
<el-table-column label="货物ID" prop="materialId" width="150" />
<el-table-column label="批次ID" prop="batchId" width="150" />
<el-table-column label="批次ID" prop="batchCode" width="150" />
<el-table-column label="仓库ID" prop="warehouseId" width="150" />
<el-table-column label="库位ID" prop="locationId" width="150" />
<el-table-column label="计划数量" prop="plannedQuantity" width="150" />
<el-table-column label="实际数量" prop="actualQuantity" width="150" />
<el-table-column label="计划件数" prop="plannedPackages" width="150" />
<el-table-column label="实际件数" prop="actualPackages" width="150" />
<el-table-column label="约数" prop="divisor" width="150" />
<el-table-column label="标签颜色" prop="labelColor" width="150" />
<el-table-column label="标签颜色" prop="labelColor" width="150">
<template slot-scope="scope">
{{ getLabelColorText(scope.row.labelColor) }}
</template>
</el-table-column>
<el-table-column label="单价" prop="unitPrice" width="150" />
<el-table-column label="凭证号" prop="voucherNumber" width="150" />
<el-table-column label="状态" prop="itemStatus" width="150">
<template slot-scope="scope">
<dict-tag :options="dict.type.inbound_order_item_status" :value="scope.row.itemStatus"/>
<dict-tag v-if="dict.type.inbound_order_item_status" :options="dict.type.inbound_order_item_status" :value="scope.row.itemStatus"/>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="收货时间" prop="receivedAt" width="150">
<el-table-column label="发货时间" prop="shippedAt" width="150">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.receivedAt, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
<span>{{ parseTime(scope.row.shippedAt, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="收货人" prop="receivedBy" width="150" />
<el-table-column label="发货方" prop="shippedBy" width="150" />
<el-table-column label="备注" prop="remark" width="150" />
<el-table-column label="操作" align="center" width="80">
<template slot-scope="scope">
<el-button
......@@ -316,6 +338,9 @@
</template>
</el-table-column>
</el-table>
<div v-else class="empty-tip" style="text-align: center; padding: 20px; color: #999;">
暂无明细数据,请点击添加按钮添加
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
......@@ -323,7 +348,7 @@
</div>
</el-dialog>
<!-- 明细项添加/编辑子组件弹窗OutboundOrderFormWithItems -->
<!-- 明细项添加/编辑子组件弹窗 -->
<OutboundOrderFormWithItems
:title="detailDialogTitle"
:open="detailDialogOpen"
......@@ -336,18 +361,19 @@
</template>
<script>
import { listOrders, getOrders, delOrders, addOrders, updateOrders } from "@/api/inventory/orders"
// 导入明细子组件
import { listOrders, getOrders, delOrders, addOrders, updateOrders, ship } from "@/api/inventory/orders"
import OutboundOrderFormWithItems from './OutboundOrderFormWithItems.vue'
export default {
name: "Orders",
dicts: ['inbound_order_status', 'inbound_order_type', 'inbound_order_item_status'],
components: {
OutboundOrderFormWithItems // 注册子组件
OutboundOrderFormWithItems
},
data() {
return {
// 添加tableKey解决tableId渲染问题
tableKey: 1,
// 遮罩层
loading: true,
// 选中数组
......@@ -366,31 +392,34 @@ export default {
title: "",
// 主弹窗是否显示
open: false,
// 明细表格数据
inboundOrderItemsList: [],
// 明细列表
outboundOrderItemsList: [],
// 选中的明细行
selectedInboundOrderItems: [],
// 明细子弹窗相关
detailDialogOpen: false, // 子弹窗是否显示
detailDialogTitle: "新增明细项", // 子弹窗标题
detailDialogOpen: false,
detailDialogTitle: "新增明细项",
currentDetailItem: {
materialUuids: '', // 存储物料选择器返回的materialCodes
inventoryId: '',
materialUuids: '',
materialId: '',
batchId: '',
batchCode: '',
warehouseId: '',
locationId: '',
plannedQuantity: '',
actualQuantity: '',
plannedPackages: '',
actualPackages: '',
divisor: '',
labelColor: '',
unitPrice: '',
itemStatus: 'pending',
receivedAt: '',
receivedBy: ''
}, // 当前编辑的明细项
isEditDetail: false, // 标记是否为编辑明细
voucherNumber: '',
itemStatus: 1,
shippedAt: '',
shippedBy: '',
isUsed: 1,
sortNo: 0,
remark: ''
},
isEditDetail: false,
// 查询参数
queryParams: {
pageNum: 1,
......@@ -423,30 +452,59 @@ export default {
}
},
created() {
this.getList()
// 延迟加载避免初始化渲染问题
this.$nextTick(() => {
this.getList()
})
},
methods: {
// ========== 明细子弹窗相关方法 ==========
// 核心出货方法
async handleShip(row) {
try {
// 调用ship接口提交数据到后端
await ship(row)
// 提示成功并刷新列表
this.$modal.msgSuccess("出货操作成功,数据已提交到后端")
this.getList()
} catch (error) {
// 错误处理
this.$modal.msgError(error.msg || "出货操作失败")
}
},
// 标签颜色值转文本
getLabelColorText(value) {
const colorMap = {
'1': '红色',
'2': '蓝色',
'3': '绿色',
'4': '黄色'
}
return colorMap[value] || '-'
},
// 打开明细子弹窗(新增)
openDetailDialog() {
this.isEditDetail = false
this.detailDialogTitle = "新增明细项"
this.currentDetailItem = {
materialUuids: '', // 初始化materialUuids(接收materialCodes)
inventoryId: '',
materialUuids: '',
materialId: '',
batchId: '',
warehouseId: '',
batchCode: '',
warehouseId: this.form.warehouseId || '',
locationId: '',
plannedQuantity: '',
actualQuantity: '',
plannedPackages: '',
actualPackages: '',
divisor: '',
labelColor: '',
unitPrice: '',
itemStatus: 'pending', // 默认状态
receivedAt: '',
receivedBy: ''
voucherNumber: '',
itemStatus: 1,
shippedAt: '',
shippedBy: '',
isUsed: 1,
sortNo: 0,
remark: ''
}
this.detailDialogOpen = true
},
......@@ -454,73 +512,108 @@ export default {
editDetailItem(row) {
this.isEditDetail = true
this.detailDialogTitle = "编辑明细项"
// 兼容materialUuids:优先取row.materialUuids,无则用materialId(适配返回的materialCodes)
this.currentDetailItem = {
...row,
materialUuids: row.materialUuids || row.materialId || ''
}
inventoryId: row.inventoryId || '',
materialUuids: row.materialId || '',
divisor: row.divisor || 0,
itemStatus: row.itemStatus || 1,
labelColor: row.labelColor || 0,
plannedQuantity: row.plannedQuantity || 0,
actualQuantity: row.actualQuantity || 0,
remark: row.remark || ''
}
this.detailDialogOpen = true
},
// 接收子组件提交的明细数据(核心修改:适配materialCodes返回格式
// 接收子组件提交的明细数据(核心修复:新增去重逻辑
handleDetailSubmit(details) {
// 兼容子组件返回格式:如果是materialCodes结构,提取第一个值赋值给materialId
const formatDetail = (item) => {
// 处理物料选择器返回的materialCodes数组
if (item.materialUuids && Array.isArray(item.materialUuids)) {
item.materialId = item.materialUuids[0] || '' // 单选取第一个值
item.materialUuids = item.materialUuids[0] || '' // 同步更新materialUuids
}
// 兼容子组件返回的原始格式
return item
}
// 统一转为数组处理
const detailList = Array.isArray(details) ? details : [details]
// 格式化每条明细的物料字段
const formattedList = detailList.map(item => formatDetail(item))
if (this.isEditDetail) {
// 编辑模式:替换原有单条数据
const editIndex = this.inboundOrderItemsList.findIndex(item => item.index === this.currentDetailItem.index)
// 编辑模式
const editIndex = this.outboundOrderItemsList.findIndex(item => item.index === this.currentDetailItem.index)
if (editIndex > -1) {
this.inboundOrderItemsList.splice(editIndex, 1, {
...formattedList[0],
index: this.currentDetailItem.index
this.outboundOrderItemsList.splice(editIndex, 1, {
...detailList[0],
index: this.currentDetailItem.index,
orderId: this.form.id || this.form.orderId,
inventoryId: detailList[0].inventoryId || '',
itemStatus: Number(detailList[0].itemStatus) || 1,
labelColor: Number(detailList[0].labelColor) || 0,
divisor: Number(detailList[0].divisor) || 0,
plannedQuantity: Number(detailList[0].plannedQuantity) || 0,
actualQuantity: Number(detailList[0].actualQuantity) || 0,
isUsed: 1,
sortNo: 0,
remark: detailList[0].remark || ''
})
}
this.$message.success("编辑明细成功")
} else {
// 新增模式:批量添加多条明细
const newDetails = formattedList.map((item, idx) => {
const newIndex = this.inboundOrderItemsList.length + idx + 1
return { ...item, index: newIndex }
})
this.inboundOrderItemsList = [...this.inboundOrderItemsList, ...newDetails]
// 新增模式:按库存ID去重,避免重复添加
const existingInventoryIds = new Set(this.outboundOrderItemsList.map(item => item.inventoryId))
// 过滤已存在的库存ID
const newDetails = detailList
.filter(item => item.inventoryId && !existingInventoryIds.has(item.inventoryId))
.map((item, idx) => {
const newIndex = this.outboundOrderItemsList.length + idx + 1
return {
...item,
index: newIndex,
orderId: this.form.id || this.form.orderId,
inventoryId: item.inventoryId || '',
itemStatus: Number(item.itemStatus) || 1,
labelColor: Number(item.labelColor) || 0,
divisor: Number(item.divisor) || 0,
plannedQuantity: Number(item.plannedQuantity) || 0,
actualQuantity: Number(item.actualQuantity) || 0,
isUsed: 1,
sortNo: 0,
remark: item.remark || ''
}
})
// 自动计算主表总数量
// 无新数据则提示
if (newDetails.length === 0) {
this.$message.warning("该库存明细已存在,无法重复添加")
this.detailDialogOpen = false
return
}
this.outboundOrderItemsList = [...this.outboundOrderItemsList, ...newDetails]
this.calcTotalQuantity()
this.$message.success(`成功新增${newDetails.length}条明细`)
}
this.detailDialogOpen = false
// 更新tableKey触发重新渲染
this.tableKey += 1
},
// 自动计算主表的计划总量/实际总量/总件数
// 计算主表总数量
calcTotalQuantity() {
const totalPlanned = this.inboundOrderItemsList.reduce((sum, item) => sum + (Number(item.plannedQuantity) || 0), 0)
const totalActual = this.inboundOrderItemsList.reduce((sum, item) => sum + (Number(item.actualQuantity) || 0), 0)
const totalPackages = this.inboundOrderItemsList.reduce((sum, item) => sum + (Number(item.actualPackages) || 0), 0)
const totalPlanned = this.outboundOrderItemsList.reduce((sum, item) => {
const qty = item.plannedQuantity !== null ? Number(item.plannedQuantity) : 0
return sum + qty
}, 0)
const totalActual = this.outboundOrderItemsList.reduce((sum, item) => {
const qty = item.actualQuantity !== null ? Number(item.actualQuantity) : 0
return sum + qty
}, 0)
const totalPackages = this.outboundOrderItemsList.reduce((sum, item) => {
const divisor = item.divisor !== null ? Number(item.divisor) : 1
const actualQty = item.actualQuantity !== null ? Number(item.actualQuantity) : 0
return sum + Math.ceil(actualQty / divisor)
}, 0)
this.form.totalPlannedQuantity = totalPlanned
this.form.totalActualQuantity = totalActual
this.form.totalPackages = totalPackages
},
// ========== 明细表格相关方法 ==========
// 生成明细表格行序号
rowInboundOrderItemsIndex({ row, rowIndex }) {
if (!row.index) row.index = rowIndex + 1
if (row.index === undefined || row.index === null) {
row.index = rowIndex + 1
}
},
// 明细表格选择事件
handleInboundOrderItemsSelectionChange(val) {
......@@ -532,30 +625,32 @@ export default {
this.$message.warning('请选择要删除的明细行')
return
}
// 获取选中行索引并倒序删除
const selectedIndexes = this.selectedInboundOrderItems.map(item =>
this.inboundOrderItemsList.findIndex(row => row.index === item.index)
this.outboundOrderItemsList.findIndex(row => row.index === item.index)
)
selectedIndexes.sort((a, b) => b - a).forEach(index => {
this.inboundOrderItemsList.splice(index, 1)
this.outboundOrderItemsList.splice(index, 1)
})
// 重新更新序号
this.inboundOrderItemsList.forEach((row, index) => {
// 重新排序序号
this.outboundOrderItemsList.forEach((row, index) => {
row.index = index + 1
})
// 重新计算总数量
this.calcTotalQuantity()
this.$message.success('明细行删除成功')
},
// ========== 主表格/表单相关方法 ==========
/** 查询出库单主列表 */
getList() {
this.loading = true
listOrders(this.queryParams).then(response => {
this.ordersList = response.rows
this.total = response.total
this.ordersList = response.rows || []
this.total = response.total || 0
this.loading = false
// 更新tableKey解决渲染问题
this.tableKey += 1
}).catch(() => {
this.loading = false
this.ordersList = []
this.total = 0
})
},
// 取消按钮
......@@ -587,10 +682,12 @@ export default {
updateTime: null,
updateUserCode: null
}
// 重置明细表格
this.inboundOrderItemsList = []
this.outboundOrderItemsList = []
this.selectedInboundOrderItems = []
this.resetForm("form")
// 检查ref存在性
if (this.$refs.form) {
this.resetForm("form")
}
},
/** 搜索按钮操作 */
handleQuery() {
......@@ -599,7 +696,10 @@ export default {
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm")
// 检查ref存在性
if (this.$refs.queryForm) {
this.resetForm("queryForm")
}
this.handleQuery()
},
// 多选框选中数据
......@@ -614,39 +714,95 @@ export default {
this.open = true
this.title = "添加出库单"
},
/** 修改按钮操作 */
/** 修改按钮操作(修复:加载明细时去重) */
handleUpdate(row) {
this.reset()
const id = row.id || this.ids
getOrders(id).then(response => {
this.form = response.data
// 如果有明细数据,初始化明细表格
if (response.data.items) {
this.inboundOrderItemsList = response.data.items.map((item, index) => ({
this.form = response.data || {}
if (response.data && response.data.outboundOrderItemsList && Array.isArray(response.data.outboundOrderItemsList)) {
// 按库存ID去重
const uniqueItemsMap = new Map()
response.data.outboundOrderItemsList.forEach(item => {
if (item.inventoryId && !uniqueItemsMap.has(item.inventoryId)) {
uniqueItemsMap.set(item.inventoryId, item)
}
})
const uniqueItems = Array.from(uniqueItemsMap.values())
this.outboundOrderItemsList = uniqueItems.map((item, index) => ({
...item,
// 兼容materialUuids:适配物料选择器返回的materialCodes格式
materialUuids: item.materialUuids || item.materialId || '',
index: index + 1
index: index + 1,
inventoryId: item.inventoryId || '',
divisor: item.divisor !== null ? Number(item.divisor) : 0,
itemStatus: item.itemStatus !== null ? Number(item.itemStatus) : 1,
labelColor: item.labelColor !== null ? Number(item.labelColor) : 0,
plannedQuantity: item.plannedQuantity !== null ? Number(item.plannedQuantity) : 0,
actualQuantity: item.actualQuantity !== null ? Number(item.actualQuantity) : 0,
isUsed: item.isUsed !== null ? Number(item.isUsed) : 1,
sortNo: item.sortNo !== null ? Number(item.sortNo) : 0,
materialUuids: item.materialId || '',
warehouseId: item.warehouseId || '',
locationId: item.locationId || '',
voucherNumber: item.voucherNumber || '',
shippedAt: item.shippedAt || '',
shippedBy: item.shippedBy || '',
remark: item.remark || '',
orderId: response.data.id
}))
// 计算总数量
this.calcTotalQuantity()
}
this.open = true
this.title = "修改出库单"
})
},
/** 提交按钮 */
/** 提交按钮(修复:提交前最后去重) */
submitForm() {
// 检查表单ref存在性
if (!this.$refs.form) {
this.$message.error('表单初始化失败,请重试')
return
}
this.$refs["form"].validate(async (valid) => {
if (valid) {
// 组装完整数据(主表+明细)
if (this.outboundOrderItemsList.length === 0) {
this.$message.warning('请至少添加一条明细数据')
return
}
// 提交前按库存ID去重
const uniqueDetailsMap = new Map()
this.outboundOrderItemsList.forEach(item => {
if (item.inventoryId && !uniqueDetailsMap.has(item.inventoryId)) {
uniqueDetailsMap.set(item.inventoryId, item)
}
})
const uniqueDetails = Array.from(uniqueDetailsMap.values())
const submitData = {
...this.form,
items: this.inboundOrderItemsList.map(item => {
const { index, ...rest } = item // 剔除序号
// 提交时确保materialId是单选的物料编码(从materialUuids/原materialId取值)
rest.materialId = rest.materialUuids || rest.materialId || ''
return rest
outboundOrderItemsList: uniqueDetails.map(item => {
const { index, materialUuids, ...rest } = item
return {
...rest,
orderId: this.form.id || this.form.orderId,
inventoryId: rest.inventoryId || '',
materialId: rest.materialId || '',
batchCode: rest.batchCode || '',
warehouseId: rest.warehouseId || '',
locationId: rest.locationId || '',
plannedQuantity: Number(rest.plannedQuantity) || 0,
actualQuantity: Number(rest.actualQuantity) || 0,
divisor: Number(rest.divisor) || 0,
labelColor: Number(rest.labelColor) || 0,
voucherNumber: rest.voucherNumber || '',
itemStatus: Number(rest.itemStatus) || 1,
shippedAt: rest.shippedAt || '',
shippedBy: rest.shippedBy || '',
remark: rest.remark || '',
isUsed: 1,
sortNo: Number(rest.sortNo) || 0
}
})
}
try {
......@@ -660,8 +816,13 @@ export default {
this.open = false
this.getList()
} catch (error) {
this.$modal.msgError(error.msg || "操作失败")
}
// 【关键】捕获异常时弹窗(覆盖接口报错场景)
if (error !== 'cancel') { // 排除用户点击取消的情况
// 优先用后端返回的msg,没有则显示默认文案
const errorMsg = error?.response?.data?.msg || '库存被修改请重新确认';
this.$modal.msgError(errorMsg); // 核心弹窗代码(若依风格错误弹窗)
}
}
}
})
},
......@@ -703,4 +864,9 @@ export default {
.el-table {
--el-table-row-hover-bg-color: #f8f9fa;
}
/* 空数据提示样式 */
.empty-tip {
color: #999;
font-size: 14px;
}
</style>
\ No newline at end of file
......@@ -4,7 +4,7 @@ import java.util.List;
import java.util.UUID;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.core.domain.entity.Materials;
import com.ruoyi.inventory.domain.InboundOrderItems;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
......@@ -19,7 +19,6 @@ import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.inventory.domain.InboundOrderItems;
import com.ruoyi.inventory.service.IInboundOrderItemsService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
......
......@@ -118,6 +118,10 @@
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies>
......
package com.ruoyi.common.annotation;
import java.lang.annotation.*;
/**
* 若依框架适配 - 方法串行执行注解
* 标记该注解的方法,同一分组内串行执行,不同分组并行
*
* @author RuoYi
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SerialExecution {
/**
* 串行分组(默认空,全局串行)
*/
String group() default "";
/**
* 是否公平锁(按线程等待顺序执行)
*/
boolean fair() default false;
}
\ No newline at end of file
package com.ruoyi.common.aspectj;
import com.ruoyi.common.annotation.SerialExecution;
import com.ruoyi.common.utils.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 若依框架适配 - 串行执行注解切面
* 参考若依 SysLogAspect 实现风格
*
* @author RuoYi
*/
@Aspect
@Component
public class SerialExecutionAspect {
/**
* 分组锁缓存:key=分组名,value=锁对象
*/
private final Map<String, Lock> groupLockCache = new ConcurrentHashMap<>();
/**
* 切入点:拦截所有标记@SerialExecution的方法
*/
@Pointcut("@annotation(com.ruoyi.common.annotation.SerialExecution)")
public void serialExecutionPointCut() {
}
/**
* 环绕通知:加锁执行,确保串行
*/
@Around("serialExecutionPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 获取方法注解信息(参考若依日志切面的参数解析方式)
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SerialExecution serialAnnotation = method.getAnnotation(SerialExecution.class);
if (serialAnnotation == null) {
return joinPoint.proceed();
}
// 2. 解析分组(默认空=全局分组)
String group = StringUtils.trimToEmpty(serialAnnotation.group());
boolean fair = serialAnnotation.fair();
// 3. 获取/创建分组锁(复用若依 StringUtils 工具类)
Lock lock = groupLockCache.computeIfAbsent(group, k -> new ReentrantLock(fair));
// 4. 加锁执行(try-finally 确保锁释放,避免死锁)
try {
lock.lock();
// 执行原方法(若依业务方法的执行逻辑)
return joinPoint.proceed();
} finally {
lock.unlock();
// 可选:若依日志记录(可接入若依的日志框架)
// LogUtils.info("串行方法执行完成,分组:{},方法:{}", group, method.getName());
}
}
}
\ No newline at end of file
......@@ -78,7 +78,16 @@ public class OutboundOrderItemsController extends BaseController
@PostMapping
public AjaxResult add(@RequestBody OutboundOrderItems outboundOrderItems)
{
return toAjax(outboundOrderItemsService.insertOutboundOrderItems(outboundOrderItems));
try {
// 调用业务层校验+插入逻辑
outboundOrderItemsService.insertOutboundOrderItems(outboundOrderItems);
// 校验通过:返回成功(若依标准成功响应)
return success("新增出库单明细成功");
} catch (Exception e) {
// 校验失败:捕获异常,返回"库存被修改请重新确认"(前端弹窗用)
logger.error("新增出库单明细失败", e);
return error("库存被修改请重新确认");
}
}
/**
......@@ -89,9 +98,19 @@ public class OutboundOrderItemsController extends BaseController
@PutMapping
public AjaxResult edit(@RequestBody OutboundOrderItems outboundOrderItems)
{
return toAjax(outboundOrderItemsService.updateOutboundOrderItems(outboundOrderItems));
try {
// 调用业务层校验+插入逻辑
outboundOrderItemsService.updateOutboundOrderItems(outboundOrderItems);
// 校验通过:返回成功(若依标准成功响应)
return success("新增出库单明细成功");
} catch (Exception e) {
// 校验失败:捕获异常,返回"库存被修改请重新确认"(前端弹窗用)
logger.error("新增出库单明细失败", e);
return error("库存被修改请重新确认");
}
}
/**
* 删除出库单明细
*/
......
......@@ -2,6 +2,8 @@ package com.ruoyi.inventory.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.inventory.domain.Inventory;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
......@@ -70,6 +72,17 @@ public class OutboundOrdersController extends BaseController
}
/**
* 出货
*/
@PreAuthorize("@ss.hasPermi('inventory:inventory:edit')")
@Log(title = "出货", businessType = BusinessType.UPDATE)
@PostMapping("/ship")
public AjaxResult Ship(@RequestBody OutboundOrders outboundOrders )
{
return toAjax(outboundOrdersService.ship(outboundOrders));
}
/**
* 新增出库单主
*/
@PreAuthorize("@ss.hasPermi('inventory:orders:add')")
......
......@@ -40,6 +40,10 @@ public class OutboundOrderItems extends BaseEntity
@Excel(name = "库位ID 检索条件")
private String locationId;
/** 库存ID */
private String inventoryId;
/** 计划数量 */
@Excel(name = "计划数量")
private Long plannedQuantity;
......@@ -89,7 +93,15 @@ public class OutboundOrderItems extends BaseEntity
@Excel(name = "排序号")
private String updateUserCode;
public void setId(String id)
public String getInventoryId() {
return inventoryId;
}
public void setInventoryId(String inventoryId) {
this.inventoryId = inventoryId;
}
public void setId(String id)
{
this.id = id;
}
......@@ -292,6 +304,7 @@ public class OutboundOrderItems extends BaseEntity
.append("createTime", getCreateTime())
.append("createUserCode", getCreateUserCode())
.append("updateTime", getUpdateTime())
.append("inventoryId", getInventoryId())
.append("updateUserCode", getUpdateUserCode())
.toString();
}
......
......@@ -22,6 +22,9 @@ public class OutboundOrderLog extends BaseEntity
@Excel(name = "出货单号ID")
private String orderId;
/** 库存ID */
private String inventoryId;
/** 货物ID */
@Excel(name = "货物ID")
private String materialId;
......@@ -81,7 +84,15 @@ public class OutboundOrderLog extends BaseEntity
this.warehouseId = warehouseId;
}
public String getWarehouseId()
public String getInventoryId() {
return inventoryId;
}
public void setInventoryId(String inventoryId) {
this.inventoryId = inventoryId;
}
public String getWarehouseId()
{
return warehouseId;
}
......@@ -137,6 +148,7 @@ public class OutboundOrderLog extends BaseEntity
.append("materialId", getMaterialId())
.append("warehouseId", getWarehouseId())
.append("batchCode", getBatchCode())
.append("inventoryId", getInventoryId())
.append("actualQuantity", getActualQuantity())
.append("itemStatus", getItemStatus())
.append("isUsed", getIsUsed())
......
......@@ -53,6 +53,8 @@ public interface InventoryMapper
*/
public int updateInventory(Inventory inventory);
/**
* 删除库存
*
......@@ -69,7 +71,7 @@ public interface InventoryMapper
*/
public int deleteInventoryByIds(String[] ids);
public List<Inventory> listByMatreialId(String materialId);
public List<Inventory> listByMaterialId(String materialId);
/**
* @description: 获取库存盘点详细信息
......
package com.ruoyi.inventory.mapper;
import java.util.List;
import com.ruoyi.inventory.domain.OutboundOrderItems;
import com.ruoyi.inventory.domain.OutboundOrderLog;
/**
......@@ -19,6 +21,7 @@ public interface OutboundOrderLogMapper
*/
public OutboundOrderLog selectOutboundOrderLogById(String id);
/**
* 查询出库明细子(仅用于锁定数量统计)列表
*
......@@ -34,7 +37,8 @@ public interface OutboundOrderLogMapper
* @param outboundOrderLog 出库明细子(仅用于锁定数量统计)
* @return 出库明细子(仅用于锁定数量统计)集合
*/
public Long selectLockedQuantity(OutboundOrderLog outboundOrderLog);
public Long selectLockedQuantityByInventory(String id);
/**
* 新增出库明细子(仅用于锁定数量统计)
......@@ -66,6 +70,24 @@ public interface OutboundOrderLogMapper
* @param ids 需要删除的数据主键集合
* @return 结果
*/
public int deleteOutboundOrderLogByOrdersId(String id);
/**
* 批量删除出库明细子(仅用于锁定数量统计)
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
public int deleteOutboundOrderLogByIds(String[] ids);
/**
* 批量新增出库单明细
*
* @param outboundOrderItemsList 出库单明细列表
* @return 结果
*/
public int batchOutboundOrderLog(List<OutboundOrderLog> outboundOrderLogs);
}
......@@ -2,21 +2,23 @@ package com.ruoyi.inventory.service;
import java.util.List;
import com.ruoyi.common.annotation.SerialExecution;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.inventory.domain.Inventory;
import com.ruoyi.inventory.domain.OutboundOrderItems;
import com.ruoyi.inventory.domain.StocktakeItems;
/**
* 库存Service接口
*
*
* @author ruoyi
* @date 2025-12-03
*/
public interface IInventoryService
public interface IInventoryService
{
/**
* 查询库存
*
*
* @param id 库存主键
* @return 库存
*/
......@@ -24,7 +26,7 @@ public interface IInventoryService
/**
* 查询库存列表
*
*
* @param inventory 库存
* @return 库存集合
*/
......@@ -34,7 +36,7 @@ public interface IInventoryService
/**
* 新增库存
*
*
* @param inventory 库存
* @return 结果
*/
......@@ -49,15 +51,23 @@ public interface IInventoryService
public int insertInventoryList(List<Inventory> inventoryList);
/**
* 修改库存
*
*
* @param inventory 库存
* @return 结果
*/
public int updateInventory(Inventory inventory);
int RefreshInventory(List<String> inventoryIds);
@SerialExecution(group = "inventoryRefresh", fair = true)
int ship(List<OutboundOrderItems> outboundOrderItems);
@SerialExecution(group = "inventoryRefresh", fair = true)
boolean inventoryLockValidation(List<OutboundOrderItems> outboundOrderItems);
/**
* 批量删除库存
*
*
* @param ids 需要删除的库存主键集合
* @return 结果
*/
......@@ -65,14 +75,13 @@ public interface IInventoryService
/**
* 删除库存信息
*
*
* @param id 库存主键
* @return 结果
*/
public int deleteInventoryById(String id);
public List<Inventory> listByMatreialId(String materialId);
/**
* @description: 获取库存盘点详细信息
* @author cs
......@@ -80,4 +89,6 @@ public interface IInventoryService
* @version 1.0
*/
public List<StocktakeItems> selectstocktakeItemsList();
}
......@@ -35,7 +35,6 @@ public interface IOutboundOrderLogService
*/
public int insertOutboundOrderLog(OutboundOrderLog outboundOrderLog);
Long selectLockedQuantity(OutboundOrderLog outboundOrderLog);
/**
* 修改出库明细子(仅用于锁定数量统计)
......
......@@ -58,4 +58,13 @@ public interface IOutboundOrdersService
* @return 结果
*/
public int deleteOutboundOrdersById(String id);
/**
* 出货
*
* @param id 出库单主主键
* @return 结果
*/
public int ship(OutboundOrders outboundOrders);
}
package com.ruoyi.inventory.service.impl;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import com.ruoyi.common.annotation.SerialExecution;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.inventory.domain.OutboundOrderItems;
import com.ruoyi.inventory.domain.OutboundOrderLog;
import com.ruoyi.inventory.domain.StocktakeItems;
import com.ruoyi.inventory.mapper.OutboundOrderLogMapper;
import org.springframework.beans.BeanUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.SystemUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.inventory.mapper.InventoryMapper;
......@@ -16,7 +21,7 @@ import com.ruoyi.inventory.service.IInventoryService;
/**
* 库存Service业务层处理
*
*
* @author ruoyi
* @date 2025-12-03
*/
......@@ -30,7 +35,7 @@ public class InventoryServiceImpl implements IInventoryService
/**
* 查询库存
*
*
* @param id 库存主键
* @return 库存
*/
......@@ -42,7 +47,7 @@ public class InventoryServiceImpl implements IInventoryService
/**
* 查询库存列表
*
*
* @param inventory 库存
* @return 库存
*/
......@@ -52,19 +57,16 @@ public class InventoryServiceImpl implements IInventoryService
return inventoryMapper.selectInventoryList(inventory);
}
@Override
public Inventory selectInventory(Inventory inventory)
{
Inventory inventory1 = inventoryMapper.selectInventory(inventory);
OutboundOrderLog outboundOrderLog = new OutboundOrderLog();
BeanUtils.copyProperties(inventory1,outboundOrderLog);
inventory1.setLockedQuantity(outboundOrderLogMapper.selectLockedQuantity(outboundOrderLog));
return inventory1;
return inventoryMapper.selectInventory(inventory);
}
/**
* 新增库存
*
*
* @param inventory 库存
* @return 结果
*/
......@@ -72,6 +74,7 @@ public class InventoryServiceImpl implements IInventoryService
public int insertInventory(Inventory inventory)
{
inventory.setCreateTime(DateUtils.getNowDate());
inventory.setCreateBy(SystemUtils.getUserName());
return inventoryMapper.insertInventory(inventory);
}
......@@ -85,25 +88,80 @@ public class InventoryServiceImpl implements IInventoryService
}
return count;
}
/**
* 修改库存
*
*
* @param inventory 库存
* @return 结果
*/
@Override
public int updateInventory(Inventory inventory)
{
List<Inventory> inventoryList = Collections.singletonList(inventory);
inventory.setUpdateTime(DateUtils.getNowDate());
inventory.setUpdateBy(SystemUtils.getUserName());
return inventoryMapper.updateInventory(inventory);
}
@SerialExecution( group = "inventoryRefresh", fair = true)
@Override
public int RefreshInventory(List<String> inventoryIds)
{
for (String inventoryId : inventoryIds) {
// 1. 空值前置校验:跳过空的循环项
if (inventoryId == null) {
continue;
}
// 6. 查询锁定数量(优化:只查一次)
Long lockedQuantity = outboundOrderLogMapper.selectLockedQuantityByInventory(inventoryId);
Inventory inventory = new Inventory();
inventory.setLockedQuantity(lockedQuantity);
inventory.setId(inventoryId);
inventoryMapper.updateInventory(inventory);
}
return 1;
}
@SerialExecution(group = "inventoryRefresh", fair = true)
@Override
public int ship(List<OutboundOrderItems> outboundOrderItems)
{
if (!outboundOrderItems.isEmpty()) {
for (OutboundOrderItems outboundOrderItem : outboundOrderItems) {
OutboundOrderLog outboundOrderLog = outboundOrderLogMapper.selectOutboundOrderLogById(outboundOrderItem.getId());
Inventory inventory =inventoryMapper.selectInventoryById(outboundOrderLog.getInventoryId());
inventory.setQuantity(inventory.getQuantity()-outboundOrderItem.getActualQuantity());
inventory.setLockedQuantity(inventory.getQuantity()-outboundOrderItem.getActualQuantity());
if (inventory.getQuantity()==0){
inventory.setInventoryStatus(0l);
}
updateInventory(inventory);
}
}
return 1;
}
@SerialExecution(group = "inventoryRefresh", fair = true)
@Override
public boolean inventoryLockValidation(List<OutboundOrderItems> outboundOrderItems)
{
if (!outboundOrderItems.isEmpty()) {
for (OutboundOrderItems outboundOrderItem : outboundOrderItems) {
Inventory inventory = inventoryMapper.selectInventoryById(outboundOrderItem.getInventoryId());
if (inventory.getLockedQuantity()+outboundOrderItem.getActualQuantity()>inventory.getQuantity()){
return false;
}
}
return true;
}
return true;
}
/**
* 批量删除库存
*
*
* @param ids 需要删除的库存主键
* @return 结果
*/
......@@ -115,7 +173,7 @@ public class InventoryServiceImpl implements IInventoryService
/**
* 删除库存信息
*
*
* @param id 库存主键
* @return 结果
*/
......@@ -125,11 +183,19 @@ public class InventoryServiceImpl implements IInventoryService
return inventoryMapper.deleteInventoryById(id);
}
@SerialExecution(group = "inventoryRefresh", fair = true)
@Override
public List<Inventory> listByMatreialId(String materialId) {
return inventoryMapper.listByMatreialId(materialId);
Inventory inventory = new Inventory();
inventory.setMaterialId(materialId);
List<String> inventoryIds = CollectionUtils.isEmpty(inventoryMapper.listByMaterialId(materialId))
? Collections.emptyList() // 空时返回空列表,避免后续NPE
: inventoryMapper.listByMaterialId(materialId).stream()
.map(inventory2 -> inventory2.getId()) // 提取ID(核心修正)
.collect(Collectors.toList());
RefreshInventory(inventoryIds);
return inventoryMapper.listByMaterialId(materialId);
}
/**
* @description: 获取库存盘点详细信息
* @author cs
......
package com.ruoyi.inventory.service.impl;
import java.util.ArrayList;
import java.util.List;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.inventory.domain.Inventory;
import com.ruoyi.inventory.domain.OutboundOrderLog;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.inventory.mapper.OutboundOrderItemsMapper;
......@@ -53,12 +49,7 @@ public class OutboundOrderItemsServiceImpl implements IOutboundOrderItemsService
return outboundOrderItemsMapper.selectOutboundOrderItemsList(outboundOrderItems);
}
/**
* 新增出库单明细
*
* @param outboundOrderItemsInventory 出库单明细库存DTO
* @return 结果
*/
@Override
public int insertOutboundOrderItems(OutboundOrderItems outboundOrderItems)
{
......@@ -66,12 +57,7 @@ public class OutboundOrderItemsServiceImpl implements IOutboundOrderItemsService
return outboundOrderItemsMapper.insertOutboundOrderItems(outboundOrderItems);
}
/**
* 修改出库单明细
*
* @param outboundOrderItemsInventory 出库单明细库存DTO
* @return 结果
*/
@Override
public int updateOutboundOrderItems(OutboundOrderItems outboundOrderItems)
{
......
......@@ -54,10 +54,6 @@ public class OutboundOrderLogServiceImpl implements IOutboundOrderLogService
{
return outboundOrderLogMapper.insertOutboundOrderLog(outboundOrderLog);
}
@Override
public Long selectLockedQuantity(OutboundOrderLog outboundOrderLog){
return outboundOrderLogMapper.selectLockedQuantity(outboundOrderLog);
}
/**
* 修改出库明细子(仅用于锁定数量统计)
*
......
package com.ruoyi.inventory.service.impl;
import java.util.List;
import com.ruoyi.common.annotation.SerialExecution;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.inventory.domain.Inventory;
import com.ruoyi.inventory.domain.OutboundOrderLog;
import com.ruoyi.inventory.mapper.InventoryMapper;
import com.ruoyi.inventory.mapper.OutboundOrderItemsMapper;
import com.ruoyi.inventory.mapper.OutboundOrderLogMapper;
import org.apache.commons.lang3.SystemUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.UUID;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.inventory.domain.OutboundOrderItems;
......@@ -14,19 +25,26 @@ import com.ruoyi.inventory.service.IOutboundOrdersService;
/**
* 出库单主Service业务层处理
*
*
* @author ruoyi
* @date 2025-12-03
*/
@Service
public class OutboundOrdersServiceImpl implements IOutboundOrdersService
public class OutboundOrdersServiceImpl implements IOutboundOrdersService
{
@Autowired
private OutboundOrdersMapper outboundOrdersMapper;
@Autowired
private OutboundOrderItemsMapper outboundOrderItemsMapper;
@Autowired
private OutboundOrderLogMapper outboundOrderLogMapper;
@Autowired
private InventoryServiceImpl inventoryService;
/**
* 查询出库单主
*
*
* @param id 出库单主主键
* @return 出库单主
*/
......@@ -39,7 +57,7 @@ public class OutboundOrdersServiceImpl implements IOutboundOrdersService
/**
* 查询出库单主列表
*
*
* @param outboundOrders 出库单主
* @return 出库单主
*/
......@@ -51,7 +69,7 @@ public class OutboundOrdersServiceImpl implements IOutboundOrdersService
/**
* 新增出库单主
*
*
* @param outboundOrders 出库单主
* @return 结果
*/
......@@ -60,6 +78,8 @@ public class OutboundOrdersServiceImpl implements IOutboundOrdersService
public int insertOutboundOrders(OutboundOrders outboundOrders)
{
outboundOrders.setCreateTime(DateUtils.getNowDate());
outboundOrders.setCreateBy(SystemUtils.getUserName());
outboundOrders.setId(UUID.randomUUID().toString());
int rows = outboundOrdersMapper.insertOutboundOrders(outboundOrders);
insertOutboundOrderItems(outboundOrders);
return rows;
......@@ -67,7 +87,7 @@ public class OutboundOrdersServiceImpl implements IOutboundOrdersService
/**
* 修改出库单主
*
*
* @param outboundOrders 出库单主
* @return 结果
*/
......@@ -77,13 +97,14 @@ public class OutboundOrdersServiceImpl implements IOutboundOrdersService
{
outboundOrders.setUpdateTime(DateUtils.getNowDate());
outboundOrdersMapper.deleteOutboundOrderItemsByOrderId(outboundOrders.getId());
outboundOrderLogMapper.deleteOutboundOrderLogByOrdersId(outboundOrders.getId());
insertOutboundOrderItems(outboundOrders);
return outboundOrdersMapper.updateOutboundOrders(outboundOrders);
}
/**
* 批量删除出库单主
*
*
* @param ids 需要删除的出库单主主键
* @return 结果
*/
......@@ -97,7 +118,7 @@ public class OutboundOrdersServiceImpl implements IOutboundOrdersService
/**
* 删除出库单主信息
*
*
* @param id 出库单主主键
* @return 结果
*/
......@@ -108,26 +129,88 @@ public class OutboundOrdersServiceImpl implements IOutboundOrdersService
outboundOrdersMapper.deleteOutboundOrderItemsByOrderId(id);
return outboundOrdersMapper.deleteOutboundOrdersById(id);
}
@SerialExecution(group = "inventoryRefresh", fair = true)
@Override
public int ship(OutboundOrders outboundOrders) {
OutboundOrderItems outboundOrderItems1 = new OutboundOrderItems();
outboundOrderItems1.setOrderId(outboundOrders.getId());
List<OutboundOrderItems> outboundOrderItems = outboundOrderItemsMapper.selectOutboundOrderItemsList(outboundOrderItems1);
List<OutboundOrderItems> outboundOrderItems2 = outboundOrderItems;
OutboundOrderLog outboundOrderLog = new OutboundOrderLog();
for (OutboundOrderItems outboundOrderItem : outboundOrderItems) {
outboundOrderItem.setItemStatus(3l);
outboundOrderItemsMapper.updateOutboundOrderItems(outboundOrderItem);
outboundOrderLog.setId(outboundOrderItem.getId());
outboundOrderLog.setItemStatus(outboundOrderItem.getItemStatus());
outboundOrderLogMapper.updateOutboundOrderLog(outboundOrderLog);
}
outboundOrders.setId(outboundOrders.getId());
outboundOrders.setOrderStatus(3l);
outboundOrders.setUpdateTime(DateUtils.getNowDate());
outboundOrders.setUpdateBy(SystemUtils.getUserName());
outboundOrdersMapper.updateOutboundOrders(outboundOrders);
inventoryService.ship(outboundOrderItems2);
return 1;
}
/**
* 新增出库单明细信息
*
*
* @param outboundOrders 出库单主对象
*/
public void insertOutboundOrderItems(OutboundOrders outboundOrders)
{
public void insertOutboundOrderItems(OutboundOrders outboundOrders) {
List<OutboundOrderItems> outboundOrderItemsList = outboundOrders.getOutboundOrderItemsList();
String id = outboundOrders.getId();
if (StringUtils.isNotNull(outboundOrderItemsList))
{
for (OutboundOrderItems outboundOrderItems : outboundOrderItemsList)
{
outboundOrderItems.setOrderId(id);
}
if (outboundOrderItemsList.size() > 0)
{
outboundOrdersMapper.batchOutboundOrderItems(outboundOrderItemsList);
}
// 1. 先做空列表校验(提前返回,避免无效逻辑)
if (outboundOrderItemsList == null || outboundOrderItemsList.isEmpty()) {
return;
}
// 2. 库存校验:失败时抛异常(核心修正:! 取反 + 异常抛出后代码立即终止)
boolean isValid = inventoryService.inventoryLockValidation(outboundOrderItemsList);
if (!isValid) { // 校验失败(返回false)时抛异常
throw new RuntimeException("库存被修改请重新确认"); // 抛异常后,方法立即停止运行
}
// 2. 为明细设置订单ID和主键ID
for (OutboundOrderItems items : outboundOrderItemsList) {
items.setOrderId(id);
// 生成无横线的UUID作为主键
items.setId(UUID.randomUUID().toString().replace("-", ""));
}
// 3. 批量插入出库单明细
outboundOrdersMapper.batchOutboundOrderItems(outboundOrderItemsList);
// 4. 正确拷贝明细列表到日志列表(修复核心错误:遍历逐个拷贝)
List<String> inventoryIds = new ArrayList<>();
List<OutboundOrderLog> outboundOrderLogs = new ArrayList<>();
for (OutboundOrderItems items : outboundOrderItemsList) {
OutboundOrderLog log = new OutboundOrderLog();
BeanUtils.copyProperties(items, log); // 单个对象属性拷贝
outboundOrderLogs.add(log);
inventoryIds.add(log.getInventoryId());
deleteOutboundOrdersById(items.getId());
}
// 5. 非空校验后插入日志(避免空列表触发SQL语法错误)
if (!outboundOrderLogs.isEmpty()) {
outboundOrderLogMapper.batchOutboundOrderLog(outboundOrderLogs);
}
// 7. 非空校验后刷新库存
if (!inventoryIds.isEmpty()) {
inventoryService.RefreshInventory(inventoryIds);
}
}
}
......@@ -123,9 +123,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<include refid="selectInventoryVo"/>
where id = #{id}
</select>
<select id="listByMatreialId" parameterType="String" resultMap="InventoryResult">
<select id="listByMaterialId" parameterType="String" resultMap="InventoryResult">
<include refid="selectInventoryVo"/>
where material_id = #{materialId}
where 1=1
<if test="materialId != null and materialId.trim() != ''">
and material_id = #{materialId}
</if>
<![CDATA[
and inventory_status = '1'
and quantity > locked_quantity
]]>
</select>
<insert id="insertInventory" parameterType="Inventory">
insert into inventory
......
......@@ -11,6 +11,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="batchCode" column="batch_code" />
<result property="warehouseId" column="warehouse_id" />
<result property="locationId" column="location_id" />
<result property="inventoryId" column="inventory_id" />
<result property="plannedQuantity" column="planned_quantity" />
<result property="actualQuantity" column="actual_quantity" />
<result property="divisor" column="divisor" />
......@@ -29,7 +30,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectOutboundOrderItemsVo">
select id, order_id, material_id, batch_code, warehouse_id, location_id, planned_quantity, actual_quantity, divisor, label_color, voucher_number, item_status, shipped_at, shipped_by, remark, is_used, sort_no, create_time, create_user_code, update_time, update_user_code from outbound_order_items
select id, order_id, material_id, batch_code, warehouse_id, location_id, inventory_id, planned_quantity, actual_quantity, divisor, label_color, voucher_number, item_status, shipped_at, shipped_by, remark, is_used, sort_no, create_time, create_user_code, update_time, update_user_code from outbound_order_items
</sql>
<select id="selectOutboundOrderItemsList" parameterType="OutboundOrderItems" resultMap="OutboundOrderItemsResult">
......@@ -40,6 +41,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="batchCode != null and batchCode != ''"> and batch_code = #{batchCode}</if>
<if test="warehouseId != null and warehouseId != ''"> and warehouse_id = #{warehouseId}</if>
<if test="locationId != null and locationId != ''"> and location_id = #{locationId}</if>
<if test="inventoryId != null and inventoryId != ''"> and inventory_id = #{inventoryId}</if>
<if test="plannedQuantity != null "> and planned_quantity = #{plannedQuantity}</if>
<if test="actualQuantity != null "> and actual_quantity = #{actualQuantity}</if>
<if test="divisor != null "> and divisor = #{divisor}</if>
......@@ -69,6 +71,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="batchCode != null">batch_code,</if>
<if test="warehouseId != null">warehouse_id,</if>
<if test="locationId != null">location_id,</if>
<if test="inventoryId != null">inventory_id,</if>
<if test="plannedQuantity != null">planned_quantity,</if>
<if test="actualQuantity != null">actual_quantity,</if>
<if test="divisor != null">divisor,</if>
......@@ -92,6 +95,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="batchCode != null">#{batchCode},</if>
<if test="warehouseId != null">#{warehouseId},</if>
<if test="locationId != null">#{locationId},</if>
<if test="inventoryId != null">#{inventoryId},</if>
<if test="plannedQuantity != null">#{plannedQuantity},</if>
<if test="actualQuantity != null">#{actualQuantity},</if>
<if test="divisor != null">#{divisor},</if>
......@@ -118,6 +122,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="batchCode != null">batch_code = #{batchCode},</if>
<if test="warehouseId != null">warehouse_id = #{warehouseId},</if>
<if test="locationId != null">location_id = #{locationId},</if>
<if test="inventoryId != null">inventory_id = #{inventoryId},</if>
<if test="plannedQuantity != null">planned_quantity = #{plannedQuantity},</if>
<if test="actualQuantity != null">actual_quantity = #{actualQuantity},</if>
<if test="divisor != null">divisor = #{divisor},</if>
......
......@@ -7,6 +7,7 @@
<resultMap type="OutboundOrderLog" id="OutboundOrderLogResult">
<result property="id" column="id" />
<result property="orderId" column="order_id" />
<result property="inventoryId" column="inventory_id" />
<result property="materialId" column="material_id" />
<result property="warehouseId" column="warehouse_id" />
<result property="batchCode" column="batch_code" />
......@@ -15,8 +16,9 @@
<result property="isUsed" column="is_used" />
</resultMap>
<!-- 补充inventory_id到查询字段中 -->
<sql id="selectOutboundOrderLogVo">
select id, order_id, material_id, warehouse_id, batch_code, actual_quantity, item_status, is_used
select id, order_id, inventory_id, material_id, warehouse_id, batch_code, actual_quantity, item_status, is_used
from outbound_order_log
</sql>
......@@ -24,6 +26,7 @@
<include refid="selectOutboundOrderLogVo"/>
<where>
<if test="orderId != null and orderId != ''"> and order_id = #{orderId}</if>
<if test="inventoryId != null and inventoryId != ''"> and inventory_id = #{inventoryId}</if>
<if test="materialId != null and materialId != ''"> and material_id = #{materialId}</if>
<if test="warehouseId != null and warehouseId != ''"> and warehouse_id = #{warehouseId}</if>
<if test="batchCode != null and batchCode != ''"> and batch_code = #{batchCode}</if>
......@@ -33,26 +36,39 @@
</where>
</select>
<select id="selectLockedQuantity" parameterType="OutboundOrderLog" resultType="java.lang.Long">
select ifnull(sum(actual_quantity), 0)
from outbound_order_log
<select id="deleteLog" parameterType="OutboundOrderLog" resultMap="OutboundOrderLogResult">
delete from outbound_order_log
<where>
<if test="materialId != null and materialId != ''"> and material_id = #{materialId}</if>
<if test="warehouseId != null and warehouseId != ''"> and warehouse_id = #{warehouseId}</if>
<if test="batchCode != null and batchCode != ''"> and batch_code = #{batchCode}</if>
<if test="itemStatus != null "> and item_status = #{itemStatus}</if>
<!-- 补充inventory_id条件 -->
<if test="inventoryId != null and inventoryId != ''"> and inventory_id = #{inventoryId}</if>
</where>
</select>
<!-- 修正参数错误:原#{id}改为#{inventoryId} -->
<select id="selectLockedQuantityByInventory" parameterType="OutboundOrderLog" resultType="java.lang.Long">
select ifnull(sum(actual_quantity), 0)
from outbound_order_log
where item_status=1
<if test="inventoryId != null and inventoryId != ''"> and inventory_id = #{inventoryId}</if>
</select>
<select id="selectOutboundOrderLogById" parameterType="String" resultMap="OutboundOrderLogResult">
<include refid="selectOutboundOrderLogVo"/>
where id = #{id}
</select>
<!-- 插入语句补充inventory_id -->
<insert id="insertOutboundOrderLog" parameterType="OutboundOrderLog">
insert into outbound_order_log
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="orderId != null">order_id,</if>
<if test="inventoryId != null">inventory_id,</if>
<if test="materialId != null">material_id,</if>
<if test="warehouseId != null">warehouse_id,</if>
<if test="batchCode != null">batch_code,</if>
......@@ -63,6 +79,7 @@
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="orderId != null">#{orderId},</if>
<if test="inventoryId != null">#{inventoryId},</if>
<if test="materialId != null">#{materialId},</if>
<if test="warehouseId != null">#{warehouseId},</if>
<if test="batchCode != null">#{batchCode},</if>
......@@ -72,10 +89,12 @@
</trim>
</insert>
<!-- 更新语句补充inventory_id -->
<update id="updateOutboundOrderLog" parameterType="OutboundOrderLog">
update outbound_order_log
<trim prefix="SET" suffixOverrides=",">
<if test="orderId != null">order_id = #{orderId},</if>
<if test="inventoryId != null">inventory_id = #{inventoryId},</if>
<if test="materialId != null">material_id = #{materialId},</if>
<if test="warehouseId != null">warehouse_id = #{warehouseId},</if>
<if test="batchCode != null">batch_code = #{batchCode},</if>
......@@ -90,10 +109,39 @@
delete from outbound_order_log where id = #{id}
</delete>
<delete id="deleteOutboundOrderLogByIds" parameterType="String">
delete from outbound_order_log where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
<delete id="deleteOutboundOrderLogByOrdersId" parameterType="String">
delete from outbound_order_log where order_id = #{id}
</delete>
<!-- 批量插入补充inventory_id -->
<insert id="batchOutboundOrderLog">
<!-- 增加非空判断,避免空列表导致SQL语法错误 -->
<if test="list != null and list.size() > 0">
insert into outbound_order_log(
id,
order_id,
inventory_id, <!-- 新增 -->
material_id,
warehouse_id,
batch_code, <!-- 对应实体类batchCode -->
actual_quantity,
item_status,
is_used
) values
<foreach item="item" index="index" collection="list" separator=",">
(
#{item.id},
#{item.orderId},
#{item.inventoryId},
#{item.materialId},
#{item.warehouseId},
#{item.batchCode},
#{item.actualQuantity},
#{item.itemStatus},
#{item.isUsed}
)
</foreach>
</if>
</insert>
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.inventory.mapper.OutboundOrdersMapper">
<resultMap type="OutboundOrders" id="OutboundOrdersResult">
<result property="id" column="id" />
<result property="orderId" column="order_id" />
......@@ -31,40 +31,40 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<collection property="outboundOrderItemsList" ofType="OutboundOrderItems" column="id" select="selectOutboundOrderItemsList" />
</resultMap>
<!-- 修复:仅保留子表outbound_order_items的字段映射,删除错误的主表字段 -->
<resultMap type="OutboundOrderItems" id="OutboundOrderItemsResult">
<result property="id" column="id" />
<result property="orderId" column="order_id" />
<result property="materialId" column="material_id" />
<result property="batchId" column="batch_id" />
<result property="warehouseId" column="warehouse_id" />
<result property="locationId" column="location_id" />
<result property="plannedQuantity" column="planned_quantity" />
<result property="actualQuantity" column="actual_quantity" />
<result property="plannedPackages" column="planned_packages" />
<result property="actualPackages" column="actual_packages" />
<result property="divisor" column="divisor" />
<result property="labelColor" column="label_color" />
<result property="voucherNumber" column="voucher_number" />
<result property="unitPrice" column="unit_price" />
<result property="itemStatus" column="item_status" />
<result property="receivedAt" column="received_at" />
<result property="receivedBy" column="received_by" />
<result property="remark" column="remark" />
<result property="isUsed" column="is_used" />
<result property="sortNo" column="sort_no" />
<result property="createTime" column="create_time" />
<result property="createUserCode" column="create_user_code" />
<result property="updateTime" column="update_time" />
<result property="updateUserCode" column="update_user_code" />
<result property="id" column="id" />
<result property="orderId" column="order_id" />
<result property="materialId" column="material_id" />
<result property="batchCode" column="batch_code" />
<result property="warehouseId" column="warehouse_id" />
<result property="locationId" column="location_id" />
<result property="plannedQuantity" column="planned_quantity" />
<result property="inventoryId" column="inventory_id" />
<result property="actualQuantity" column="actual_quantity" />
<result property="divisor" column="divisor" />
<result property="labelColor" column="label_color" />
<result property="voucherNumber" column="voucher_number" />
<result property="itemStatus" column="item_status" />
<result property="shippedAt" column="shipped_at" />
<result property="shippedBy" column="shipped_by" />
<result property="remark" column="remark" />
<result property="isUsed" column="is_used" />
<result property="sortNo" column="sort_no" />
<result property="createTime" column="create_time" />
<result property="createUserCode" column="create_user_code" />
<result property="updateTime" column="update_time" />
<result property="updateUserCode" column="update_user_code" />
</resultMap>
<sql id="selectOutboundOrdersVo">
select id, order_id, system_no, order_type_id, batch_code, warehouse_id, owner_id, order_status, inbound_date, destination, total_planned_quantity, total_actual_quantity, total_packages, remark, is_used, sort_no, create_time, create_user_code, update_time, update_user_code from outbound_orders
</sql>
<select id="selectOutboundOrdersList" parameterType="OutboundOrders" resultMap="OutboundOrdersResult">
<include refid="selectOutboundOrdersVo"/>
<where>
<where>
<if test="orderId != null and orderId != ''"> and order_id = #{orderId}</if>
<if test="systemNo != null and systemNo != ''"> and system_no = #{systemNo}</if>
<if test="orderTypeId != null and orderTypeId != ''"> and order_type_id = #{orderTypeId}</if>
......@@ -83,16 +83,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="updateUserCode != null and updateUserCode != ''"> and update_user_code = #{updateUserCode}</if>
</where>
</select>
<select id="selectOutboundOrdersById" parameterType="String" resultMap="OutboundOrdersOutboundOrderItemsResult">
select id, order_id, system_no, order_type_id, batch_code, warehouse_id, owner_id, order_status, inbound_date, destination, total_planned_quantity, total_actual_quantity, total_packages, remark, is_used, sort_no, create_time, create_user_code, update_time, update_user_code
from outbound_orders
where id = #{id}
</select>
<!-- 仅保留子表查询逻辑,字段完整且映射正确 -->
<select id="selectOutboundOrderItemsList" parameterType="String" resultMap="OutboundOrderItemsResult">
select id, order_id, material_id, batch_code, warehouse_id, location_id, planned_quantity, actual_quantity, divisor, label_color, voucher_number, item_status, shipped_at, shipped_by, remark, is_used, sort_no, create_time, create_user_code, update_time, update_user_code
from outbound_order_items
select id, order_id, material_id, batch_code, warehouse_id, location_id, planned_quantity, actual_quantity, divisor, label_color, voucher_number, item_status, shipped_at, shipped_by, remark, is_used, sort_no, create_time, create_user_code, update_time, update_user_code , inventory_id
from outbound_order_items
where order_id = #{id}
</select>
......@@ -119,7 +120,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="createUserCode != null">create_user_code,</if>
<if test="updateTime != null">update_time,</if>
<if test="updateUserCode != null">update_user_code,</if>
</trim>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="orderId != null">#{orderId},</if>
......@@ -141,7 +142,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="createUserCode != null">#{createUserCode},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="updateUserCode != null">#{updateUserCode},</if>
</trim>
</trim>
</insert>
<update id="updateOutboundOrders" parameterType="OutboundOrders">
......@@ -175,14 +176,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</delete>
<delete id="deleteOutboundOrdersByIds" parameterType="String">
delete from outbound_orders where id in
delete from outbound_orders where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<delete id="deleteOutboundOrderItemsByOrderIds" parameterType="String">
delete from outbound_order_items where order_id in
delete from outbound_order_items where order_id in
<foreach item="orderId" collection="array" open="(" separator="," close=")">
#{orderId}
</foreach>
......@@ -193,9 +194,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</delete>
<insert id="batchOutboundOrderItems">
insert into outbound_order_items( id, order_id, material_id, batch_id, warehouse_id, location_id, planned_quantity, actual_quantity, planned_packages, actual_packages, divisor, label_color, voucher_number, unit_price, item_status, received_at, received_by, remark, is_used, sort_no, create_time, create_user_code, update_time, update_user_code) values
insert into outbound_order_items(
id, order_id, material_id, batch_code, warehouse_id, location_id,
planned_quantity, actual_quantity, divisor, label_color, voucher_number,
item_status, shipped_at, shipped_by, remark, is_used, sort_no,
create_time, create_user_code, update_time, update_user_code,inventory_id
) values
<foreach item="item" index="index" collection="list" separator=",">
( #{item.id}, #{item.orderId}, #{item.materialId}, #{item.batchId}, #{item.warehouseId}, #{item.locationId}, #{item.plannedQuantity}, #{item.actualQuantity}, #{item.plannedPackages}, #{item.actualPackages}, #{item.divisor}, #{item.labelColor}, #{item.voucherNumber}, #{item.unitPrice}, #{item.itemStatus}, #{item.receivedAt}, #{item.receivedBy}, #{item.remark}, #{item.isUsed}, #{item.sortNo}, #{item.createTime}, #{item.createUserCode}, #{item.updateTime}, #{item.updateUserCode})
(
#{item.id}, #{item.orderId}, #{item.materialId}, #{item.batchCode}, #{item.warehouseId},
#{item.locationId}, #{item.plannedQuantity}, #{item.actualQuantity}, #{item.divisor},
#{item.labelColor}, #{item.voucherNumber}, #{item.itemStatus}, #{item.shippedAt},
#{item.shippedBy}, #{item.remark}, #{item.isUsed}, #{item.sortNo}, #{item.createTime},
#{item.createUserCode}, #{item.updateTime}, #{item.updateUserCode},#{item.inventoryId}
)
</foreach>
</insert>
</mapper>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论