1.新增多选盘点和EXCEL导入盘点

2.EWM接口优化
3.出库由自动改为手动下发
4.出库校验逻辑修改
5.库存合并功能
6.第二种入库模式
7.WMS新增图标统计
This commit is contained in:
杨学谦 2026-01-26 14:20:07 +08:00
parent a52149750f
commit 32a853eecb
51 changed files with 4423 additions and 149 deletions

View File

@ -11,6 +11,7 @@
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"axios": "^1.3.3", "axios": "^1.3.3",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"echarts": "^5.4.3",
"element-plus": "^2.9.8", "element-plus": "^2.9.8",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"moment": "^2.29.4", "moment": "^2.29.4",
@ -5558,6 +5559,20 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/echarts": {
"version": "5.4.3",
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.4.3.tgz",
"integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==",
"dependencies": {
"tslib": "2.3.0",
"zrender": "5.4.4"
}
},
"node_modules/echarts/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
},
"node_modules/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz",
@ -12223,6 +12238,19 @@
"engines": { "engines": {
"node": ">=10" "node": ">=10"
} }
},
"node_modules/zrender": {
"version": "5.4.4",
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.4.4.tgz",
"integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==",
"dependencies": {
"tslib": "2.3.0"
}
},
"node_modules/zrender/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
} }
}, },
"dependencies": { "dependencies": {
@ -16416,6 +16444,22 @@
"integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==", "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==",
"dev": true "dev": true
}, },
"echarts": {
"version": "5.4.3",
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.4.3.tgz",
"integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==",
"requires": {
"tslib": "2.3.0",
"zrender": "5.4.4"
},
"dependencies": {
"tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
}
}
},
"ee-first": { "ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz",
@ -21683,6 +21727,21 @@
"resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.9.tgz", "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"dev": true "dev": true
},
"zrender": {
"version": "5.4.4",
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.4.4.tgz",
"integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==",
"requires": {
"tslib": "2.3.0"
},
"dependencies": {
"tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
}
}
} }
} }
} }

View File

@ -11,6 +11,7 @@
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"axios": "^1.3.3", "axios": "^1.3.3",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"echarts": "^5.4.3",
"element-plus": "^2.9.8", "element-plus": "^2.9.8",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"moment": "^2.29.4", "moment": "^2.29.4",

View File

@ -172,4 +172,13 @@ export const exportLocationDetailExcel = (data) => {
data: data, data: data,
timeout: 600000 timeout: 600000
}) })
}
// 导入盘点任务
export const importInventoryExcel = (data) => {
return request({
url: '/excel/importGoods', // 根据你提供的地址修改
method: 'post',
data: data,
timeout: 600000
})
} }

View File

@ -1,5 +1,13 @@
import request from "@/http/request"; import request from "@/http/request";
export function batchIssueTasks(params) {
return request({
url: '/taskQuery/updateOutsTaskStatus',
method: 'post',
data: params
})
}
/** /**
* 分页查找物料 * 分页查找物料
*/ */

View File

@ -8,6 +8,14 @@ export const queryStocksByPage = (params) => {
}) })
} }
export const querySpecialEmptyStocksForMix = (params) => {
return request({
url: '/stock/querySpecialEmptyStocksForMix',
method: 'post',
data: params
})
}
export const queryStocks = (params) => { export const queryStocks = (params) => {
return request({ return request({
url: '/stock/queryStocks', url: '/stock/queryStocks',
@ -16,10 +24,27 @@ export const queryStocks = (params) => {
}) })
} }
export const queryStocksBySpecial = (params) => {
return request({
url: '/stock/queryStocksBySpecial',
method: 'post',
data: params
})
}
export const querySpecialEmptyStocksForPDA = (params) => {
return request({
url: '/stock/querySpecialEmptyStocksForPDA',
method: 'post',
data: params
})
}
export const queryStockUpdateByPage = (params) => { export const queryStockUpdateByPage = (params) => {
return request({ return request({
url: '/stock/queryStockUpdateByPage', url: '/stock/queryStockUpdateByPage',
method: 'post', method: 'post',
data: params data: params
}) })
} }

View File

@ -73,6 +73,14 @@ export const confirmCurrentTask = (params, config = {}) => {
}) })
} }
export const mergeStocks = (params) => {
return request({
url: '/task/createInventoryFromMixStock',
method: 'post',
data: params
})
}
export const confirmCurrentTaskByPDA = (params) => { export const confirmCurrentTaskByPDA = (params) => {
return request({ return request({
@ -115,6 +123,39 @@ export const confirmInventory = (params) => {
data: params data: params
}) })
} }
export const confirmMixStock = (params) => {
return request({
url: '/task/confirmMixStock',
method: 'post',
data: params,
timeout: 30000
})
}
export const pdaOutTask = (params) => {
return request({
url: '/task/pdaOutTask',
method: 'post',
data: params
})
}
export const queryInOutBoundStatistics = (params) => {
return request({
url: '/board/queryInOutBoundStatistics',
method: 'post',
data: params
})
}
export const queryStockAgeDistribution = (params) => {
return request({
url: '/board/queryStockAgeDistribution',
method: 'get',
data: params
})
}
// 获取缺料数量 // 获取缺料数量
export const getGoodsLackQty = (params) => { export const getGoodsLackQty = (params) => {
return request({ return request({

View File

@ -2,6 +2,7 @@ import axios from 'axios'
const request = axios.create({ const request = axios.create({
baseURL: 'http://172.18.222.253:12315/wms', baseURL: 'http://172.18.222.253:12315/wms',
//baseURL: 'http://172.18.222.253:12306/wms',
//baseURL: 'http://localhost:12315/wms', //baseURL: 'http://localhost:12315/wms',
timeout: 5000 timeout: 5000
}) })

View File

@ -274,6 +274,7 @@ const handleSelectionChange = (row) => {
} }
} }
//
// //
const handleSelectAllChange = (val) => { const handleSelectAllChange = (val) => {
displayStocks.value.forEach(row => { displayStocks.value.forEach(row => {
@ -281,12 +282,15 @@ const handleSelectAllChange = (val) => {
}) })
if (val) { if (val) {
// // -
selectedRows.value = [...displayStocks.value] selectedRows.value = [...displayStocks.value]
} else { } else {
// // -
selectedRows.value = [] selectedRows.value = []
} }
//
displayStocks.value = [...displayStocks.value]
} }
// //
const handleBatchInventory = () => { const handleBatchInventory = () => {
@ -320,14 +324,13 @@ const confirmBatchInventory = () => {
if (res.data.code === 0) { if (res.data.code === 0) {
ElMessage.success('批量盘点成功'); ElMessage.success('批量盘点成功');
showBatchInventoryDialog.value = false; showBatchInventoryDialog.value = false;
search(); //
// //
selectedRows.value.forEach(row => { selectedRows.value = []
row.checked = false; isSelectAll.value = false
});
selectedRows.value = []; //
isSelectAll.value = false; search(); //
} else { } else {
ElMessage.error(res.data.message); ElMessage.error(res.data.message);
} }
@ -336,6 +339,18 @@ const confirmBatchInventory = () => {
}); });
}; };
//
const resetSelection = () => {
selectedRows.value = []
isSelectAll.value = false
if (displayStocks.value && displayStocks.value.length > 0) {
displayStocks.value.forEach(row => {
row.checked = false
})
//
displayStocks.value = [...displayStocks.value]
}
}
// //
const search = () => { const search = () => {
@ -356,11 +371,21 @@ const search = () => {
const data = response.data const data = response.data
console.log(data) console.log(data)
if (data != null) { if (data != null) {
displayStocks.value = data.lists // checked
displayStocks.value = data.lists.map(item => ({
...item,
checked: false
}))
baseTableQuery.total = data.total baseTableQuery.total = data.total
//
selectedRows.value = []
isSelectAll.value = false
} else { } else {
displayStocks.value = [] displayStocks.value = []
baseTableQuery.total = 0 baseTableQuery.total = 0
selectedRows.value = []
isSelectAll.value = false
} }
} else { } else {
ElMessage.error(response.message) ElMessage.error(response.message)
@ -381,6 +406,7 @@ const clearQuery = () => {
stockQuery.fromDate = null, stockQuery.fromDate = null,
stockQuery.toDate = null, stockQuery.toDate = null,
stockQuery.noUseDays = null stockQuery.noUseDays = null
resetSelection()
} }
const loadAllGoodsInfo = () => { const loadAllGoodsInfo = () => {
const request = { const request = {

View File

@ -0,0 +1,426 @@
<template>
<el-config-provider :locale="zhCn">
<el-container class="content">
<div class="work-area">
<fieldset class="search-area">
<el-form ref="searchQueryFormRef" :model="searchQueryFormEntity" :label-position="labelPosition"
label-width="158px" style="max-width: 100%" status-icon>
<div style="display: flex;justify-content: space-between;">
<el-row>
<el-form-item label="箱号">
<el-input v-model="searchQueryFormEntity.vehicleId" @keyup.enter="search()" clearable/>
</el-form-item>
<el-form-item label="料号">
<el-input v-model="searchQueryFormEntity.goodsId" @keyup.enter="search()" clearable/>
</el-form-item>
</el-row>
<div style="align-content: center;">
<el-row>
<el-button type="primary" class="btn-search" @click="search()">查询</el-button>
<el-button type="warning" class="btn-search" @click="clearQuery()">清除输入</el-button>
<el-button type="success" class="btn-search" @click="importExcel">导入盘点任务</el-button>
</el-row>
</div>
</div>
</el-form>
</fieldset>
<div class="table-area">
<el-table :data="tableData" stripe border v-loading="tableLoading" class="table-class"
:max-height="maxHeight" highlight-current-row @row-click="getCurrentRow"
:header-cell-style="{ 'text-align': 'center' }" :cell-style="{ 'text-align': 'center' }"
@sort-change="handleSortChange">
<el-table-column width="65px" fixed="left">
<template v-slot="scope">
<el-radio :label="scope.row.inventoryId" v-model="inventoryId">&nbsp;</el-radio>
</template>
</el-table-column>
<el-table-column prop="inventoryId" label="任务号" fixed="left" min-width="120px" sortable="custom"
show-overflow-tooltip/>
<el-table-column prop="goodsId" label="料号" min-width="120px"
sortable="custom"
show-overflow-tooltip/>
<el-table-column prop="vehicleId" label="箱号" min-width="120px" sortable="custom"
show-overflow-tooltip/>
<el-table-column prop="stockNum" label="库存数量" min-width="120px" sortable="custom"
show-overflow-tooltip/>
<el-table-column prop="confirmNum" label="确认数量"
min-width="120px" sortable="custom" show-overflow-tooltip/>
<el-table-column prop="invStand" label="盘点站台" min-width="120px" sortable="custom"
show-overflow-tooltip/>
<el-table-column prop="invUser" label="盘点人" min-width="120px"
sortable="custom" show-overflow-tooltip/>
<el-table-column prop="invStatus" label="任务状态" :formatter="invStatusFormat" min-width="120px"
sortable="custom" show-overflow-tooltip/>
<el-table-column prop="invResult" label="盘点结果" :formatter="invResultFormat" min-width="120px" sortable="custom"
show-overflow-tooltip/>
<el-table-column prop="invCreateTime" label="创建时间" :formatter="timeFormat" min-width="120px"
sortable="custom"
show-overflow-tooltip/>
<el-table-column prop="invConfirmTime" label="确认时间" :formatter="timeFormat" min-width="120px"
sortable="custom"
show-overflow-tooltip/>
<el-table-column prop="invOrderId" label="任务组" min-width="120px"
sortable="custom"
show-overflow-tooltip/>
</el-table>
<br/>
<el-pagination v-model:current-page="baseTableQuery.currentPage"
v-model:page-size="baseTableQuery.pageSize" :page-sizes="[10, 25, 50]" :small="false"
:disabled="false" :background="false" :default-page-size="10" @size-change="search"
@current-change="search" layout="total, sizes, prev, pager, next, jumper"
:total="baseTableQuery.total"/>
</div>
</div>
</el-container>
<!-- Excel导入对话框 -->
<el-dialog v-model="excelDialogVisible" title="导入盘点任务" width="500px" :before-close="closeExcelDialog">
<el-upload
ref="uploadRef"
:auto-upload="false"
:limit="1"
accept=".xlsx, .xls"
:on-change="handleFileChange"
:file-list="fileList"
:show-file-list="true"
>
<template #trigger>
<el-button type="primary">选择Excel文件</el-button>
</template>
<el-button class="ml-3" type="success" @click="submitUpload" :disabled="!fileList.length">
上传文件
</el-button>
<template #tip>
<div class="el-upload__tip">
请上传Excel文件(.xlsx或.xls)文件格式请参考模板
</div>
</template>
</el-upload>
<template #footer>
<el-button @click="closeExcelDialog">取消</el-button>
</template>
</el-dialog>
</el-config-provider>
</template>
<script setup>
import store from '@/store'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import {queryInventoryRecordByPage} from '@/api/taskQuery.js'
import {timeFormatter} from '@/utils/formatter.js'
import {ref, reactive, onMounted, nextTick, onBeforeUnmount} from 'vue'
import {ElMessage, ElMessageBox} from 'element-plus'
import {genTableRequest} from '@/utils/generator.js'
import {labelPosition} from '@/constant/form.js'
import {invResultOptions} from '@/constant/options.js'
import {addAllOptionOfOptions} from '@/utils/generator.js'
import {importInventoryExcel} from '@/api/excel.js' //
/**
* 常量定义
*/
const STAND_ID = store.getters.getStandId
const USER_NAME = store.getters.getUserName
const DEFAULT_TOTAL_OPTIONS = 99
/**
* 变量定义
*/
let maxHeight = ref(window.innerHeight * 0.55)
let tableLoading = ref(false)
let tableData = ref([])
let baseTableQuery = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
sortBy: new Map([['invCreateTime', true]]),//
standId: STAND_ID,
userName: USER_NAME,
queryType: 1 //
})
let searchQueryFormEntity = reactive({
vehicleId: '',
goodsId: '',
invResult: DEFAULT_TOTAL_OPTIONS
})
let searchQueryFormRef = ref()
let inventoryId = ''
const queryTypeOptions = [
{
label: '未关闭',
value: 1
}
]
// Excel
let excelDialogVisible = ref(false)
let fileList = ref([])
let uploadRef = ref()
/**
* 系统方法
*/
onMounted(() => {
nextTick(() => {
window.addEventListener('resize', resizeHeight)
search()
})
})
onBeforeUnmount(() => {
nextTick(() => {
window.removeEventListener('resize', resizeHeight)
})
})
const resizeHeight = () => {
maxHeight.value = window.innerHeight * 0.55
}
/**
* 自定义方法
*/
//
const search = () => {
tableLoading.value = true
let request = genTableRequest(baseTableQuery)
//
request.vehicleId = searchQueryFormEntity.vehicleId.trim()
request.goodsId = searchQueryFormEntity.goodsId.trim()
request.invResult = searchQueryFormEntity.invResult === DEFAULT_TOTAL_OPTIONS ? null : searchQueryFormEntity.invResult
request.queryType = 1 //
queryInventoryRecordByPage(request).then((res) => {
const response = res.data
if (response.code === 0) {
const data = response.data
if (data != null) {
tableData.value = data.lists
baseTableQuery.total = data.total
} else {
tableData.value = []
baseTableQuery.total = 0
}
} else {
ElMessage.error(response.message)
}
}).catch(err => {
console.log(err)
ElMessage.error('查询数据异常。')
}).finally(() => {
tableLoading.value = false
})
}
const clearQuery = () => {
searchQueryFormEntity.vehicleId = ''
searchQueryFormEntity.goodsId = ''
searchQueryFormEntity.invResult = DEFAULT_TOTAL_OPTIONS
search()
}
const handleSortChange = (data) => {
if (baseTableQuery.sortBy.has(data.prop)) {
baseTableQuery.sortBy.delete(data.prop)
}
baseTableQuery.sortBy.set(data.prop, data.order.toLowerCase() === 'ascending')
search()
}
const getCurrentRow = (row) => {
inventoryId = row.inventoryId
}
const timeFormat = (row, column, cellValue, index) => {
return timeFormatter(cellValue)
}
// format
const invStatusFormat = (row, column, cellValue, index) => {
switch (cellValue) {
case 0:
return '初始化'
case 1:
return '已解析'
case 2:
return '已确认'
default:
return '未知状态'
}
}
// format
const invTypeFormat = (row, column, cellValue, index) => {
if (cellValue === 1) {
return '明盘'
} else if (cellValue === 2) {
return '盲盘'
} else {
return '未知类型'
}
}
// format
const invResultFormat = (row, column, cellValue, index) => {
switch (cellValue) {
case -99:
return '未盘'
case -1:
return '盘亏'
case 0:
return '正常'
case 1:
return '盘盈'
default:
return '未知结果'
}
}
// Excel
const importExcel = () => {
excelDialogVisible.value = true
fileList.value = []
}
const handleFileChange = (file, files) => {
fileList.value = [file]
}
const submitUpload = () => {
if (!fileList.value.length) {
ElMessage.warning('请选择要上传的文件')
return
}
const formData = new FormData()
formData.append('file', fileList.value[0].raw) // file
// FileVo
const fileVo = {
userName: USER_NAME, // 使 store
}
// FileVo JSON FormData
formData.append('fileVo', new Blob([JSON.stringify(fileVo)], { type: 'application/json' }))
importInventoryExcel(formData).then(res => {
const response = res.data
if (response.code === 0) {
ElMessage.success('导入成功')
search() //
closeExcelDialog()
} else {
// HTML<br>
let errorMessage = response.message || '导入失败'
// HTML
errorMessage = errorMessage.replace(/\n/g, '<br/>')
// 使 ElMessageBox
ElMessageBox.alert(errorMessage, '错误', {
confirmButtonText: '确定',
type: 'error',
dangerouslyUseHTMLString: true, // HTML
customClass: 'error-message-box' //
}).catch(() => {
//
})
}
}).catch(err => {
console.log(err)
ElMessage.error('导入数据异常')
})
}
const closeExcelDialog = () => {
excelDialogVisible.value = false
fileList.value = []
}
const downloadTemplate = () => {
// API
//
window.open('/api/inventory/template', '_blank')
}
</script>
<style scoped>
.content {
display: flex;
width: 100%;
}
.work-area {
width: 100%;
/* padding: 5px; */
}
.search-area {
margin: auto;
min-height: fit-content;
max-height: 40%;
margin-bottom: 10px;
min-width: inherit;
border: solid 1px;
border-radius: 10px;
box-shadow: 0px 15px 10px -15px #000;
overflow: auto;
padding: 10px;
}
.table-area {
margin: auto;
min-height: fit-content;
max-height: 60%;
margin-bottom: 10px;
min-width: inherit;
border: solid 1px;
border-radius: 10px;
box-shadow: 0px 15px 10px -15px #000;
overflow: auto;
padding: 10px;
}
.el-form-item {
margin: 5px 5px 5px 5px;
}
.el-form-item .el-input {
width: 196px;
}
.el-form-item .el-input-number {
width: 196px;
}
.table-class {
margin: 5px 5px 5px 5px;
width: inherit;
}
.el-pagination {
padding-left: 5px;
}
.my-autocomplete li {
width: 196px;
line-height: normal;
padding: 7px;
}
.my-autocomplete li .name {
text-overflow: ellipsis;
overflow: hidden;
}
.my-autocomplete li .addr {
font-size: 12px;
color: #b4b4b4;
}
.my-autocomplete li .highlighted .addr {
color: #ddd;
}
.my-autocomplete li .goods_id {
color: brown;
}
.my-autocomplete li .goods_name {
color: cornflowerblue;
}
.btn-search {
height: 30px;
width: 80px;
margin: auto 5px 5px auto;
color: black;
}
</style>

View File

@ -15,24 +15,30 @@
<el-input v-model="searchQueryFormEntity.workOrder" @keyup.enter="search()" <el-input v-model="searchQueryFormEntity.workOrder" @keyup.enter="search()"
clearable/> clearable/>
</el-form-item> </el-form-item>
<!-- <el-form-item label="工单号">--> <!-- <el-form-item label="工单号">-->
<!-- <el-input v-model="searchQueryFormEntity.goodsDesc" @keyup.enter="search()"--> <!-- <el-input v-model="searchQueryFormEntity.goodsDesc" @keyup.enter="search()"-->
<!-- clearable/>--> <!-- clearable/>-->
<!-- </el-form-item>--> <!-- </el-form-item>-->
</el-row> </el-row>
<div style="align-content: center;"> <div style="align-content: center;">
<el-row> <el-row>
<el-button type="primary" class="btn-search" @click="search()">查询</el-button> <el-button type="primary" class="btn-search" @click="search()">查询</el-button>
<el-button type="warning" class="btn-search" @click="clearQuery()">清除输入</el-button> <el-button type="warning" class="btn-search" @click="clearQuery()">清除输入</el-button>
<!-- 添加下发按钮 -->
<el-button type="success" class="btn-search"
@click="handleBatchIssue"
:disabled="selectedRows.length === 0">
批量下发({{ selectedRows.length }})
</el-button>
</el-row> </el-row>
<!-- <el-row>--> <!-- <el-row>-->
<!-- <el-button style="background-color: #00CED1;" class="btn-search"--> <!-- <el-button style="background-color: #00CED1;" class="btn-search"-->
<!-- @click="openUploadDialog()">导入数据--> <!-- @click="openUploadDialog()">导入数据-->
<!-- </el-button>--> <!-- </el-button>-->
<!-- <el-button type="success" class="btn-search"--> <!-- <el-button type="success" class="btn-search"-->
<!-- @click="exportExcel()">导出excel--> <!-- @click="exportExcel()">导出excel-->
<!-- </el-button>--> <!-- </el-button>-->
<!-- </el-row>--> <!-- </el-row>-->
</div> </div>
</div> </div>
</el-form> </el-form>
@ -46,6 +52,7 @@
<template #header> <template #header>
<el-checkbox <el-checkbox
v-model="isSelectAll" v-model="isSelectAll"
:indeterminate="isSelectIndeterminate"
@change="handleSelectAllChange"> @change="handleSelectAllChange">
&nbsp; &nbsp;
</el-checkbox> </el-checkbox>
@ -64,6 +71,7 @@
show-overflow-tooltip/> show-overflow-tooltip/>
<el-table-column prop="goodsId" label="料号" fixed="left" min-width="120px" sortable="custom" <el-table-column prop="goodsId" label="料号" fixed="left" min-width="120px" sortable="custom"
show-overflow-tooltip/> show-overflow-tooltip/>
<el-table-column prop="needNum" label="需求数量" min-width="120px" sortable="custom" <el-table-column prop="needNum" label="需求数量" min-width="120px" sortable="custom"
show-overflow-tooltip/> show-overflow-tooltip/>
<el-table-column prop="distributeNum" label="已分配数量" min-width="120px" sortable="custom" <el-table-column prop="distributeNum" label="已分配数量" min-width="120px" sortable="custom"
@ -92,7 +100,18 @@
sortable="custom" sortable="custom"
:formatter="(row, column, cellValue) => formatDateToDay(cellValue)" :formatter="(row, column, cellValue) => formatDateToDay(cellValue)"
show-overflow-tooltip/> show-overflow-tooltip/>
<el-table-column prop="taskStatus"
label="任务状态"
fixed="right"
min-width="120px"
sortable="custom"
show-overflow-tooltip>
<template #default="scope">
<span :class="getStatusClass(scope.row.taskStatus)">
{{ getStatusText(scope.row.taskStatus) }}
</span>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" min-width="180px"> <el-table-column label="操作" fixed="right" min-width="180px">
<template v-slot="scope"> <template v-slot="scope">
<div style="display: flex; gap: 5px; justify-content: center;"> <div style="display: flex; gap: 5px; justify-content: center;">
@ -107,8 +126,7 @@
size="small" size="small"
type="primary" type="primary"
@click="handleBatchEdit" @click="handleBatchEdit"
:disabled="selectedRows.length === 0" :disabled="selectedRows.length === 0" style="margin-top: 5px;">
style="margin-top: 5px;">
批量修改时间({{ selectedRows.length }}) 批量修改时间({{ selectedRows.length }})
</el-button> </el-button>
</div> </div>
@ -118,9 +136,15 @@
</el-table> </el-table>
<br/> <br/>
<el-pagination v-model:current-page="baseTableQuery.currentPage" <el-pagination v-model:current-page="baseTableQuery.currentPage"
v-model:page-size="baseTableQuery.pageSize" :page-sizes="[10, 25, 50]" :small="false" v-model:page-size="baseTableQuery.pageSize"
:disabled="false" :background="false" :default-page-size="10" @size-change="search" :page-sizes="[20, 50, 100]"
@current-change="search" layout="total, sizes, prev, pager, next, jumper" :small="false"
:disabled="false"
:background="false"
:default-page-size="20"
@size-change="search"
@current-change="search"
layout="total, sizes, prev, pager, next, jumper"
:total="baseTableQuery.total"/> :total="baseTableQuery.total"/>
</div> </div>
<el-dialog v-model="showEditDialog" title="编辑执行日期" width="30%" draggable> <el-dialog v-model="showEditDialog" title="编辑执行日期" width="30%" draggable>
@ -170,9 +194,9 @@
<script setup> <script setup>
import store from '@/store' import store from '@/store'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs' import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import {batchEditDate, editDate, getGoodsInfoByPage, upOutsType} from '@/api/goods.js' import {batchEditDate, editDate, getGoodsInfoByPage, upOutsType, batchIssueTasks} from '@/api/goods.js'
import {ref, reactive, onMounted, nextTick, onBeforeUnmount} from 'vue' import {ref, reactive, onMounted, nextTick, onBeforeUnmount} from 'vue'
import {ElMessage} from 'element-plus' import {ElMessage, ElMessageBox} from 'element-plus'
import {genTableRequest} from '@/utils/generator.js' import {genTableRequest} from '@/utils/generator.js'
import {labelPosition} from '@/constant/form.js' import {labelPosition} from '@/constant/form.js'
import UploadExcelBaseGoods from '@/excel/UploadExcelBaseGoods.vue' import UploadExcelBaseGoods from '@/excel/UploadExcelBaseGoods.vue'
@ -180,12 +204,36 @@ import UploadExcelKanban from '@/excel/UploadExcelKanban.vue'
import {exportGoodsExcel} from "@/api/excel"; import {exportGoodsExcel} from "@/api/excel";
import {dateFormatter} from "@/utils/formatter"; import {dateFormatter} from "@/utils/formatter";
import { getUserPermission } from '@/api/user.js' import { getUserPermission } from '@/api/user.js'
import {computed} from 'vue' // computed
/** /**
* 常量定义 * 常量定义
*/ */
const STAND_ID = store.getters.getStandId const STAND_ID = store.getters.getStandId
const USER_NAME = store.getters.getUserName const USER_NAME = store.getters.getUserName
//
const getStatusText = (status) => {
if (status === -1) {
return '待下发'
} else if (status === 0) {
return '已下发'
} else {
//
return status //
}
}
//
const getStatusClass = (status) => {
if (status === -1) {
return 'status-pending'
} else if (status === 0) {
return 'status-completed'
} else {
return 'status-default'
}
}
/** /**
* 变量定义 * 变量定义
*/ */
@ -194,9 +242,9 @@ let tableLoading = ref(false)
let tableData = ref([]) let tableData = ref([])
let baseTableQuery = reactive({ let baseTableQuery = reactive({
currentPage: 1, currentPage: 1,
pageSize: 10, pageSize: 20, // 1020
total: 0, total: 0,
sortBy: new Map([['goodsId', true]]),// sortBy: new Map([['goodsId', true]]),
standId: STAND_ID, standId: STAND_ID,
userName: USER_NAME userName: USER_NAME
}) })
@ -234,18 +282,45 @@ const resizeHeight = () => {
maxHeight.value = window.innerHeight * 0.55 maxHeight.value = window.innerHeight * 0.55
} }
//
const handleSelectionChange = (row) => { const handleSelectionChange = (row) => {
if (row.checked) { if (row.checked) {
// //
if (!selectedRows.value.some(item => item.goodsId === row.goodsId)) { if (!selectedRows.value.some(item => item.taskId === row.taskId)) {
selectedRows.value.push(row) selectedRows.value.push(row)
} }
} else { } else {
// //
selectedRows.value = selectedRows.value.filter(item => item.goodsId !== row.goodsId) selectedRows.value = selectedRows.value.filter(item => item.taskId !== row.taskId)
}
//
nextTick(() => {
//
})
}
//
const resetSelection = () => {
selectedRows.value = []
if (tableData.value) {
tableData.value.forEach(row => {
row.checked = false
})
tableData.value = [...tableData.value]
} }
} }
//
const isSelectAll = computed(() => {
if (tableData.value.length === 0) return false
return tableData.value.length === selectedRows.value.length && tableData.value.length > 0
})
//
const isSelectIndeterminate = computed(() => {
return selectedRows.value.length > 0 && selectedRows.value.length < tableData.value.length
})
// //
const handleBatchEdit = () => { const handleBatchEdit = () => {
const permissionParams = { const permissionParams = {
@ -280,21 +355,109 @@ const handleBatchEdit = () => {
}) })
} }
const handleSelectAllChange = (val) => {
tableData.value.forEach(row => {
row.checked = val
})
if (val) { //
// const handleBatchIssue = () => {
selectedRows.value = [...tableData.value] const permissionParams = {
} else { loginAccountUpdate: store.getters.getUser.loginAccount,
// roleIdOp: store.getters.getUser.roleId,
selectedRows.value = [] userName: USER_NAME
} }
getUserPermission(permissionParams).then(res => {
if (res.data.code === 0) {
//
const permissionStr = res.data.message || ''
if (!permissionStr.includes('B')) {
ElMessage.error('您没有批量下发的权限')
return
}
//
if (selectedRows.value.length === 0) {
ElMessage.warning('请至少选择一条记录')
return
}
//
ElMessageBox.confirm(
`确定要批量下发这 ${selectedRows.value.length} 条任务吗?`,
'确认下发',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
// taskIds
const taskIds = selectedRows.value.map(row => row.taskId)
const request = {
taskIds: taskIds,
}
//
batchIssueTasks(request).then((res) => {
if (res.data.code === 0) {
ElMessage.success(res.data.message)
//
resetSelection()
search()
} else {
ElMessage.error(res.data.message || '批量下发失败')
}
}).catch((err) => {
console.error('批量下发请求失败:', err)
ElMessage.error('批量下发请求失败')
})
}).catch(() => {
//
console.log('用户取消批量下发操作')
})
} else {
ElMessage.error(res.data.message || '权限检查失败')
}
}).catch(err => {
console.error('权限检查失败:', err)
ElMessage.error('权限检查失败')
})
} }
//
const clearSelection = () => {
//
selectedRows.value = []
// checkedfalse
tableData.value.forEach(row => {
row.checked = false
})
//
tableData.value = [...tableData.value] // 使 nextTick
}
const handleSelectAllChange = (val) => {
tableData.value.forEach(row => {
row.checked = val
// selectedRows
if (val && !selectedRows.value.some(item => item.taskId === row.taskId)) {
selectedRows.value.push(row)
} else if (!val) {
selectedRows.value = selectedRows.value.filter(item => item.taskId !== row.taskId)
}
})
//
tableData.value = [...tableData.value]
}
//
// //
const saveBatchEdit = (form) => { const saveBatchEdit = (form) => {
if (!form.pickingDate) { if (!form.pickingDate) {
@ -308,16 +471,13 @@ const saveBatchEdit = (form) => {
pickingDate: form.pickingDate pickingDate: form.pickingDate
} }
// API
batchEditDate(request).then((res) => { batchEditDate(request).then((res) => {
if (res.data.code === 0) { if (res.data.code === 0) {
ElMessage.success('批量修改成功') ElMessage.success('批量修改成功')
showBatchEditDialog.value = false showBatchEditDialog.value = false
resetSelection()
search() search()
// //
selectedRows.value.forEach(row => {
row.checked = false
})
selectedRows.value = [] selectedRows.value = []
} else { } else {
ElMessage.error(res.data.message) ElMessage.error(res.data.message)
@ -327,6 +487,7 @@ const saveBatchEdit = (form) => {
}) })
} }
// //
const saveEdit = (editForm) => { const saveEdit = (editForm) => {
let request = { let request = {
@ -340,6 +501,7 @@ const saveEdit = (editForm) => {
type: 'success', type: 'success',
}); });
search() search()
clearSelection()
} else { } else {
ElMessage({ ElMessage({
message: res.data.message, message: res.data.message,
@ -422,7 +584,7 @@ const handleEdit = (row) => {
return return
} }
editForm.taskId = row.taskId editForm.taskId = row.taskId
// 使 formatDateToDay // 使 formatDateToDay
editForm.pickingDate = row.pickingDate ? formatDateToDay(row.pickingDate) : '' editForm.pickingDate = row.pickingDate ? formatDateToDay(row.pickingDate) : ''
showEditDialog.value = true showEditDialog.value = true
} }
@ -457,8 +619,16 @@ const search = () => {
if (response.code === 0) { if (response.code === 0) {
const data = response.data const data = response.data
if (data != null) { if (data != null) {
tableData.value = data.lists // checked
const lists = data.lists.map(item => ({
...item,
checked: false
}))
tableData.value = lists
baseTableQuery.total = data.total baseTableQuery.total = data.total
//
selectedRows.value = []
} else { } else {
tableData.value = [] tableData.value = []
baseTableQuery.total = 0 baseTableQuery.total = 0
@ -615,4 +785,37 @@ const exportExcel = () => {
margin: auto 5px 5px auto; margin: auto 5px 5px auto;
color: black; color: black;
} }
</style> /* 任务状态样式 */
.status-pending {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
background-color: #fff7e6;
color: #fa8c16;
border: 1px solid #ffd591;
font-size: 12px;
font-weight: 500;
}
.status-completed {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
background-color: #f6ffed;
color: #52c41a;
border: 1px solid #b7eb8f;
font-size: 12px;
font-weight: 500;
}
.status-default {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
background-color: #f0f0f0;
color: #8c8c8c;
border: 1px solid #d9d9d9;
font-size: 12px;
font-weight: 500;
}
</style>

View File

@ -82,9 +82,9 @@
<!-- 添加全局加载指示器 --> <!-- 添加全局加载指示器 -->
<div v-if="showLoading" class="loading-overlay"> <div v-if="showLoading" class="loading-overlay">
<div class="loading-spinner"> <div class="loading-spinner-modern">
<i class="el-icon-loading"></i> <div class="spinner"></div>
<p>处理中...</p> <div class="text">数据请求中...</div>
</div> </div>
</div> </div>
</el-container> </el-container>
@ -228,7 +228,7 @@ export default {
// //
this.showLoading = true; this.showLoading = true;
confirmCurrentTask(request, { timeout: 15000 }).then(res => { confirmCurrentTask(request, { timeout: 60000 }).then(res => {
const responseData = res.data; const responseData = res.data;
if (responseData.code === 0) { if (responseData.code === 0) {
if (responseData.message === "继续拣选"){ if (responseData.message === "继续拣选"){
@ -489,6 +489,34 @@ export default {
font-size: 18px; font-size: 18px;
} }
/* 新增:更现代的加载动画 */
.loading-spinner-modern {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
}
.loading-spinner-modern .spinner {
width: 50px;
height: 50px;
border: 5px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
}
.loading-spinner-modern .text {
margin-top: 15px;
font-size: 16px;
color: white;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.highlight-pick-qty { .highlight-pick-qty {
background-color: #dbeeff !important; /* 淡蓝色背景 */ background-color: #dbeeff !important; /* 淡蓝色背景 */
font-weight: bold !important; /* 加粗 */ font-weight: bold !important; /* 加粗 */

View File

@ -0,0 +1,566 @@
<template>
<div class="pda-second-in">
<div class="header">
<h2>PDA扫码出库</h2>
</div>
<div class="scan-section">
<div class="input-group">
<label for="material-input">扫描物料条码:</label>
<input
id="material-input"
v-model="materialCode"
type="text"
placeholder="请扫描物料条码"
@keyup.enter="searchMaterial"
class="material-input"
/>
<button @click="searchMaterial" class="search-btn">搜索</button>
</div>
<div v-if="loading" class="loading">查询中...</div>
<div v-if="errorMessage" class="error-message">{{ errorMessage }}</div>
</div>
<div v-if="materialInfo" class="material-info">
<h3>物料信息</h3>
<div class="info-row">
<span class="label">物料编号:</span>
<span class="value">{{ materialInfo.materialCode }}</span>
</div>
<div class="info-row">
<span class="label">物料名称:</span>
<span class="value">{{ materialInfo.materialName }}</span>
</div>
</div>
<div v-if="storageBoxes.length > 0" class="boxes-list">
<h3>可用库存箱</h3>
<div
v-for="(box, index) in storageBoxes"
:key="box.id"
class="box-item"
@click="selectBox(box)"
>
<div class="box-header">
<span class="box-id">箱号: {{ box.boxId }}</span>
<span class="box-location">库位: {{ box.location }}</span>
</div>
<div class="box-details">
<div class="detail-row">
<span class="label">数量:</span>
<span class="value">{{ box.quantity }}</span>
</div>
<div class="detail-row">
<span class="label">库存状态:</span>
<span class="value">{{ getStockStatusText(box.stockStatus) }}</span>
</div>
<div class="detail-row">
<span class="label">入库时间:</span>
<span class="value">{{ formatDate(box.inboundTime) }}</span>
</div>
</div>
<button
:class="['outbound-btn',
selectedBox && selectedBox.id === box.id ? 'selected' : '',
box.stockStatus !== 0 ? 'disabled' : '']"
@click.stop="confirmOutbound(box)"
:disabled="box.stockStatus !== 0"
>
{{ box.stockStatus !== 0 ? '不可出库' : (selectedBox && selectedBox.id === box.id ? '已选择' : '出库') }}
</button>
</div>
</div>
<div v-else-if="materialInfo" class="no-data">
该物料暂无可用库存
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
// API
import {queryStocksBySpecial} from '@/api/stock';
// API
import {pdaOutTask} from '@/api/task';
//
const materialCode = ref('');
const materialInfo = ref(null);
const storageBoxes = ref([]);
const loading = ref(false);
const errorMessage = ref('');
const selectedBox = ref(null);
// API
const searchMaterial = async () => {
if (!materialCode.value.trim()) {
ElMessage.warning('请输入物料条码');
return;
}
loading.value = true;
errorMessage.value = '';
try {
// API - 使
const response = await queryStocksBySpecial({
goodsId: materialCode.value
});
if (response.data.code === 0) {
//
const result = response.data.data;
//
if (result && result.lists && Array.isArray(result.lists)) {
// lists
const stockItems = result.lists;
//
if (stockItems.length > 0) {
const firstItem = stockItems[0];
materialInfo.value = {
materialCode: materialCode.value,
materialName: firstItem.goodsDesc || '未知物料',
specification: firstItem.goodsId || '无规格',
unit: '个' //
};
//
storageBoxes.value = stockItems.map((item, index) => ({
id: item.stockId || `${materialCode.value}_${index}`,
boxId: item.vehicleId || `${index+1}`, // vehicleIdID
location: item.locationId || '未知库位',
quantity: item.remainNum || 0, // 使 remainNum
stockStatus: item.stockStatus, //
inboundTime: item.firstInTime || '' //
}));
} else {
//
materialInfo.value = {
materialCode: materialCode.value,
materialName: '未找到物料',
specification: '',
unit: ''
};
storageBoxes.value = [];
}
} else {
//
if (result && Array.isArray(result)) {
materialInfo.value = {
materialCode: materialCode.value,
materialName: result[0]?.goodsDesc || '未知物料',
specification: result[0]?.goodsId || '无规格',
unit: '个'
};
storageBoxes.value = result.map((item, index) => ({
id: item.stockId || `${materialCode.value}_${index}`,
boxId: item.vehicleId || `${index+1}`,
location: item.locationId || '未知库位',
quantity: item.remainNum || 0,
stockStatus: item.stockStatus, //
inboundTime: item.firstInTime || ''
}));
} else {
//
materialInfo.value = {
materialCode: materialCode.value,
materialName: '未找到物料',
specification: '',
unit: ''
};
storageBoxes.value = [];
}
}
} else {
// API
errorMessage.value = response.data.message || '查询失败';
}
} catch (error) {
loading.value = false;
errorMessage.value = '查询失败: ' + error.message;
console.error('Error searching material:', error);
} finally {
loading.value = false;
}
};
//
const selectBox = (box) => {
selectedBox.value = box;
};
//
const confirmOutbound = async (box) => {
// 0
if (box.stockStatus !== 0) {
ElMessage.warning('该箱子有任务,无法执行操作');
return;
}
try {
const result = await ElMessageBox.confirm(
`确认出库以下箱子?\n箱号: ${box.boxId}\n数量: ${box.quantity}`,
'确认出库',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
}
);
if (result === 'confirm') {
// API - vehicleIdorigin
const response = await pdaOutTask({
vehicleId: box.boxId, //
origin: box.location //
});
if (response.data.code === 0) {
ElMessage.success('出库成功');
//
storageBoxes.value = storageBoxes.value.filter(item => item.id !== box.id);
if (storageBoxes.value.length === 0) {
materialInfo.value = null;
}
selectedBox.value = null;
} else {
ElMessage.error(response.data.message || '出库失败');
}
}
} catch (error) {
if (error !== 'cancel') {
console.error('Outbound error:', error);
ElMessage.error('出库失败: ' + error.message);
}
}
};
//
const getStockStatusText = (status) => {
return status === 0 ? '在库中' : status === 1 ? '出库中' : '未知状态';
};
//
const formatDate = (dateString) => {
if (!dateString) return '';
const date = new Date(dateString);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
};
</script>
<style scoped>
.pda-second-in {
max-width: 100%;
padding: 15px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f7fa;
min-height: 100vh;
}
.header {
text-align: center;
margin-bottom: 25px;
padding: 15px 0;
background: linear-gradient(to right, #409eff, #3a8ee6);
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.header h2 {
margin: 0;
color: white;
font-size: 1.6em;
font-weight: 600;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.scan-section {
margin-bottom: 25px;
padding: 20px;
border: 1px solid #e0e6ed;
border-radius: 8px;
background-color: white;
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
.input-group {
display: flex;
flex-direction: column;
align-items: center; /* 让子元素拉伸以填满容器 */
gap: 12px;
}
.input-group label {
font-weight: 600;
color: #303133;
font-size: 15px;
}
.material-input {
padding: 14px;
font-size: 16px;
border: 1px solid #dcdfe6;
border-radius: 6px;
width: 300px; /* 修改:设置固定宽度 */
max-width: 100%; /* 防止在小屏幕上溢出 */
transition: border-color 0.3s;
background-color: #fafafa;
}
.material-input:focus {
outline: none;
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
background-color: white;
}
.search-btn {
padding: 16px 0; /* 水平方向无内边距,垂直方向增加 */
font-size: 16px;
background: linear-gradient(to bottom, #409eff, #3a8ee6);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
letter-spacing: 0.5px;
display: block; /* 确保按钮为块级元素 */
width: 100%; /* 横向充满容器 */
text-align: center; /* 文字居中 */
box-sizing: border-box; /* 包含边框和内边距在内的总宽度 */
}
.search-btn:hover {
background: linear-gradient(to bottom, #3a8ee6, #337ecc);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(64, 158, 255, 0.3);
}
.loading {
text-align: center;
padding: 15px;
color: #606266;
font-style: italic;
background-color: #ecf5ff;
border-radius: 4px;
margin-top: 10px;
}
.error-message {
color: #f56c6c;
padding: 12px;
background-color: #fef0f0;
border-radius: 6px;
margin-top: 12px;
border-left: 4px solid #f56c6c;
font-weight: 500;
}
.material-info {
margin-bottom: 25px;
padding: 20px;
border: 1px solid #ebeef5;
border-radius: 8px;
background-color: white;
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
.material-info h3 {
margin-top: 0;
margin-bottom: 18px;
color: #303133;
text-align: center;
font-size: 1.3em;
font-weight: 600;
color: #409eff;
border-bottom: 2px solid #ebeef5;
padding-bottom: 10px;
}
.info-row {
display: flex;
margin-bottom: 10px;
padding: 8px 0;
border-bottom: 1px dashed #f0f0f0;
}
.label {
font-weight: 600;
color: #606266;
min-width: 100px;
flex-shrink: 0;
}
.value {
color: #303133;
flex-grow: 1;
word-break: break-word;
font-weight: 500;
}
.boxes-list h3 {
margin-top: 0;
margin-bottom: 20px;
color: #303133;
text-align: center;
font-size: 1.3em;
font-weight: 600;
color: #409eff;
border-bottom: 2px solid #ebeef5;
padding-bottom: 10px;
}
.box-item {
border: 1px solid #ebeef5;
border-radius: 8px;
margin-bottom: 15px;
padding: 18px;
background-color: white;
position: relative;
transition: box-shadow 0.3s;
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
.box-item:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
border-color: #d2d9e6;
}
.box-header {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
padding-bottom: 10px;
border-bottom: 1px solid #ebeef5;
}
.box-id {
font-weight: 600;
color: #303133;
font-size: 1.1em;
}
.box-location {
color: #606266;
font-weight: 500;
}
.box-details {
margin-bottom: 18px;
}
.detail-row {
display: flex;
margin-bottom: 8px;
}
.detail-row .label {
min-width: 80px;
color: #909399;
font-size: 14px;
}
.detail-row .value {
flex-grow: 1;
color: #303133;
font-weight: 500;
}
.outbound-btn {
position: absolute;
right: 18px;
top: 50%;
transform: translateY(-50%);
padding: 10px 20px;
font-size: 14px;
background: linear-gradient(to bottom, #67c23a, #5daf34);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
box-shadow: 0 2px 4px rgba(103, 194, 58, 0.3);
}
.outbound-btn:hover:not(:disabled) {
background: linear-gradient(to bottom, #5daf34, #4a9e2a);
transform: translateY(calc(-50% - 1px));
box-shadow: 0 4px 8px rgba(103, 194, 58, 0.4);
}
.outbound-btn.selected {
background: linear-gradient(to bottom, #409eff, #3a8ee6);
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
}
.outbound-btn.selected:hover {
background: linear-gradient(to bottom, #3a8ee6, #337ecc);
}
.outbound-btn.disabled {
background: linear-gradient(to bottom, #c0c4cc, #a8abb2);
cursor: not-allowed;
opacity: 0.7;
box-shadow: none;
}
.no-data {
text-align: center;
padding: 40px 20px;
color: #909399;
font-style: italic;
font-size: 1.1em;
background-color: white;
border-radius: 8px;
border: 1px dashed #ebeef5;
margin-top: 10px;
}
/* 适配移动端触摸操作 */
@media (max-width: 768px) {
.pda-second-in {
padding: 10px;
}
.material-input, .search-btn {
font-size: 17px;
padding: 16px;
}
.box-item {
padding: 15px;
}
.outbound-btn {
padding: 12px 22px;
font-size: 15px;
right: 15px;
}
.header h2 {
font-size: 1.5em;
}
.material-info, .scan-section {
padding: 15px;
}
.box-details {
margin-bottom: 15px;
}
}
</style>

View File

@ -0,0 +1,616 @@
<template>
<el-config-provider :locale="zhCn">
<el-container class="content">
<div class="work-area">
<fieldset class="search-area">
<el-form :model="summaryQuery" :label-position="labelPosition"
label-width="auto" style="max-width: 100%" status-icon>
<div style="display: flex;justify-content: space-between;">
<el-row>
<el-form-item label="料号">
<el-input v-model="summaryQuery.goodsId"
@keyup.enter="searchSummary()"
clearable />
</el-form-item>
</el-row>
<div style="align-content: center;">
<el-row>
<el-button type="primary"
style="height: 30px; width: 80px; margin: auto 5px 5px auto; color: black;"
@click="searchSummary()">
查询汇总
</el-button>
<el-button type="warning"
style="height: 30px; width: 80px; margin: auto 5px 5px auto; color: black;"
@click="clearSummaryQuery()">
清除输入
</el-button>
</el-row>
</div>
</div>
</el-form>
</fieldset>
<div class="table-area">
<el-table :data="summaryList"
stripe
border
v-loading="tableLoading"
class="table-class"
:max-height="maxHeight"
:header-cell-style="{ 'text-align': 'center' }"
:cell-style="{ 'text-align': 'center' }">
<el-table-column prop="goodsId"
label="物料号"
min-width="150px"
show-overflow-tooltip />
<el-table-column prop="goodsDesc"
label="物料描述"
min-width="200px"
show-overflow-tooltip />
<el-table-column prop="remainNum"
label="总数量"
min-width="120px" />
<el-table-column prop="realNum"
label="条数"
min-width="100px" />
<el-table-column fixed="right"
label="操作"
width="120px">
<template v-slot="scope">
<el-button plain type="primary"
@click="showMergeDetails(scope.row)">
合并
</el-button>
</template>
</el-table-column>
</el-table>
<br />
<el-pagination v-model:current-page="summaryTableQuery.currentPage"
v-model:page-size="summaryTableQuery.pageSize"
:page-sizes="[10, 25, 50]"
:small="false"
:disabled="false"
:background="false"
:default-page-size="10"
@size-change="searchSummary"
@current-change="searchSummary"
layout="total, sizes, prev, pager, next, jumper"
:total="summaryTableQuery.total" />
</div>
<!-- 合并明细弹窗 -->
<el-dialog v-model="showMergeDialog"
title="合并明细"
width="80%"
:before-close="closeMergeDialog">
<el-descriptions :column="2" border>
<el-descriptions-item label="物料号">
{{ currentGoods.goodsId }}
</el-descriptions-item>
<el-descriptions-item label="物料描述">
{{ currentGoods.goodsDesc }}
</el-descriptions-item>
<el-descriptions-item label="总数量">
{{ currentGoods.remainNum }}
</el-descriptions-item>
<el-descriptions-item label="条数">
{{ currentGoods.realNum }}
</el-descriptions-item>
</el-descriptions>
<el-table :data="detailList"
stripe
border
v-loading="detailLoading"
:max-height="maxHeight * 0.6"
:header-cell-style="{ 'text-align': 'center' }"
:cell-style="{ 'text-align': 'center' }"
style="margin-top: 20px;"
@selection-change="handleSelectionChange">
<el-table-column type="selection"
width="55" />
<el-table-column prop="vehicleId"
label="箱号"
min-width="120px"
show-overflow-tooltip />
<el-table-column prop="goodsId"
label="物料号"
min-width="120px"
show-overflow-tooltip />
<el-table-column prop="goodsDesc"
label="物料名称"
min-width="120px"
show-overflow-tooltip />
<el-table-column prop="locationId"
label="库位"
:formatter="locationFormat"
min-width="120px"
show-overflow-tooltip />
<el-table-column prop="totalNum"
label="入库数量"
min-width="120px"
show-overflow-tooltip />
<el-table-column prop="remainNum"
label="剩余可用数量"
min-width="120px"
show-overflow-tooltip />
<el-table-column prop="realNum"
label="实际剩余数量"
min-width="120px"
show-overflow-tooltip />
<el-table-column prop="goodsStatus"
label="物料状态"
:formatter="goodsStatusFormat"
min-width="120px"
show-overflow-tooltip />
<el-table-column prop="stockStatus"
label="库存状态"
:formatter="stockStatusFormat"
min-width="120px"
show-overflow-tooltip>
<template #default="scope">
<el-tag :type="getStockStatusType(scope.row.stockStatus)">
{{ stockStatusFormat(scope.row) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="firstInTime"
label="上架时间"
:formatter="timeFormat"
min-width="120px"
show-overflow-tooltip />
</el-table>
<template #footer>
<span class="dialog-footer">
<el-button type="success"
style="height: 40px; width: 100px; color: black;"
@click="confirmMerge">
确认合并
</el-button>
<el-button type="warning"
style="height: 40px; width: 100px; color: black;"
@click="closeMergeDialog">
关闭
</el-button>
</span>
</template>
</el-dialog>
<!-- 目标载具选择弹窗 -->
<el-dialog v-model="showTargetSelectDialog"
title="选择合并目标载具"
width="60%"
:before-close="closeTargetSelectDialog">
<p>请选择合并的目标载具将其他选中载具的数据合并到此载具</p>
<el-radio-group v-model="targetVehicle" style="width: 100%; margin-top: 20px;">
<el-row :gutter="10">
<el-col
v-for="item in multipleSelection"
:key="item.vehicleId"
:span="8"
style="margin-bottom: 10px;"
>
<el-card
:class="['vehicle-card', { 'selected-card': targetVehicle === item.vehicleId }]"
@click="selectTargetVehicle(item.vehicleId)"
shadow="hover"
>
<div class="vehicle-info">
<p><strong>载具:</strong> {{ item.vehicleId }}</p>
<p><strong>库位:</strong> {{ locationFormat(item, null, item.locationId) }}</p>
<p><strong>数量:</strong> {{ item.remainNum }}</p>
</div>
</el-card>
</el-col>
</el-row>
</el-radio-group>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="executeMerge" :disabled="!targetVehicle">
确认合并
</el-button>
<el-button @click="closeTargetSelectDialog">
取消
</el-button>
</span>
</template>
</el-dialog>
</div>
</el-container>
</el-config-provider>
</template>
<script setup>
import store from '@/store'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import {querySpecialEmptyStocksForMix, queryStocksBySpecial} from '@/api/stock.js'
import {mergeStocks} from '@/api/task'
import { dateFormatter, locationFormatter, timeFormatter, yesOrNoFormatter } from '@/utils/formatter.js'
import { ref, reactive, onMounted, nextTick, onBeforeUnmount } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { genTableRequest, addAllOptionOfOptions } from '@/utils/generator.js'
import { labelPosition, shortcuts } from '@/constant/form'
import { stockStatusOptions, goodsStatusOptions } from '@/constant/options'
/**
* 变量定义
*/
let maxHeight = ref(window.innerHeight * 0.55)
let tableLoading = ref(false)
let detailLoading = ref(false)
let showMergeDialog = ref(false)
let showTargetSelectDialog = ref(false)
let targetVehicle = ref('')
//
let summaryList = ref([])
let summaryTableQuery = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
sortBy: new Map([['goodsId', true]]),
standId: store.getters.getStandId,
userName: store.getters.getUserName
})
let summaryQuery = reactive({
goodsId: ''
})
//
let currentGoods = ref({}) //
let detailList = ref([]) //
let multipleSelection = ref([]) //
/**
* 系统方法
*/
onMounted(() => {
nextTick(() => {
window.addEventListener('resize', resizeHeight)
searchSummary()
})
})
onBeforeUnmount(() => {
nextTick(() => {
window.removeEventListener('resize', resizeHeight)
})
})
const resizeHeight = () => {
maxHeight.value = window.innerHeight * 0.55
}
/**
* 自定义方法
*/
//
const handleSelectionChange = (val) => {
multipleSelection.value = val
}
//
const searchSummary = () => {
tableLoading.value = true
let request = genTableRequest(summaryTableQuery)
request.goodsId = summaryQuery.goodsId.trim()
querySpecialEmptyStocksForMix(request).then((res) => {
const response = res.data
if (response.code === 0) {
const data = response.data
if (data != null) {
summaryList.value = data.lists
summaryTableQuery.total = data.total
} else {
summaryList.value = []
summaryTableQuery.total = 0
}
} else {
ElMessage.error(response.message)
}
}).catch(err => {
console.log(err)
ElMessage.error('查询物料汇总信息异常。')
}).finally(() => {
tableLoading.value = false
})
}
//
const showMergeDetails = (row) => {
currentGoods.value = { ...row }
loadDetails(row.goodsId)
showMergeDialog.value = true
}
//
const loadDetails = (goodsId) => {
detailLoading.value = true
const request = {
goodsId: goodsId,
standId: store.getters.getStandId,
pageSize: 9999
}
queryStocksBySpecial(request).then((res) => {
const response = res.data
if (response.code === 0) {
detailList.value = response.data?.lists || []
} else {
ElMessage.error(response.message)
detailList.value = []
}
}).catch(err => {
console.log(err)
ElMessage.error('查询明细信息异常。')
detailList.value = []
}).finally(() => {
detailLoading.value = false
})
}
//
const confirmMerge = () => {
if (multipleSelection.value.length === 0) {
ElMessage.warning('请至少选择一条记录进行合并')
return
}
if (multipleSelection.value.length === 1) {
ElMessage.warning('请选择至少两条记录进行合并')
return
}
//
showTargetSelectDialog.value = true
}
//
const selectTargetVehicle = (vehicleId) => {
targetVehicle.value = vehicleId
}
//
const executeMerge = () => {
if (!targetVehicle.value) {
ElMessage.warning('请选择目标载具')
return
}
ElMessageBox.confirm(
`确认将选中的 ${multipleSelection.value.length} 条记录合并到载具 ${targetVehicle.value} 吗?`,
'确认合并',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
// MixStockRequest
const stockItems = multipleSelection.value.map(item => ({
vehicleNo: item.vehicleId, // vehicleNo
materialNo: item.goodsId // materialNo
}));
const mergeParams = {
stockItems: stockItems, // stockItems
bindBoxNo: targetVehicle.value, // bindBoxNo
standId: store.getters.getStandId //
};
mergeStocks(mergeParams).then((res) => {
const response = res.data
if (response.code === 0) {
ElMessage.success('合并操作成功')
closeTargetSelectDialog()
closeMergeDialog()
//
searchSummary()
} else {
ElMessage.error(response.message)
}
}).catch(err => {
console.log(err)
ElMessage.error('合并操作失败')
})
}).catch(() => {
//
})
}
//
const closeMergeDialog = () => {
showMergeDialog.value = false
detailList.value = []
currentGoods.value = {}
multipleSelection.value = []
}
//
const closeTargetSelectDialog = () => {
showTargetSelectDialog.value = false
targetVehicle.value = ''
//
multipleSelection.value = []
}
//
const clearSummaryQuery = () => {
summaryQuery.goodsId = ''
}
//
const locationFormat = (row, column, cellValue, index) => {
return locationFormatter(cellValue)
}
const timeFormat = (row, column, cellValue, index) => {
return timeFormatter(cellValue)
}
const goodsStatusFormat = (row, column, cellValue, index) => {
if (cellValue === 0) {
return '正常'
} else if (cellValue === 1) {
return '不合格'
} else if (cellValue === 2) {
return '延期'
} else if (cellValue === 3) {
return '过期'
} else if (cellValue === 5) {
return '长时间未使用'
}
}
//
const getStockStatusType = (status) => {
switch (status) {
case 0: //
return 'success'
case 1: //
return 'warning'
case 2: //
return 'info'
case 3: //
return 'primary'
case 4: //
return 'warning'
case 9: //
return 'danger'
default:
return 'info'
}
}
const stockStatusFormat = (row, column, cellValue, index) => {
// el-table formatter 使cellValue
// row
let statusValue;
// formatter使 cellValue
if (arguments.length >= 3) {
statusValue = cellValue;
} else {
// row stockStatus
statusValue = row.stockStatus;
}
switch (statusValue) {
case 0:
return '在库中';
case 1:
return '出库中';
case 2:
return '已出库';
case 3:
return '回库中';
case 4:
return '盘点中';
case 9:
return '库存锁定';
default:
return '异常';
}
}
</script>
<style scoped>
.content {
display: flex;
width: 100%;
}
.work-area {
width: 100%;
/* padding: 5px; */
}
.search-area {
margin: auto;
min-height: fit-content;
max-height: 40%;
margin-bottom: 10px;
min-width: inherit;
border: solid 1px;
border-radius: 10px;
box-shadow: 0px 15px 10px -15px #000;
overflow: auto;
padding: 10px;
}
.table-area {
margin: auto;
min-height: fit-content;
max-height: 60%;
margin-bottom: 10px;
min-width: inherit;
border: solid 1px;
border-radius: 10px;
box-shadow: 0px 15px 10px -15px #000;
overflow: auto;
padding: 10px;
}
.el-form-item {
margin: 5px 5px 5px 5px;
}
.el-form-item .el-input {
width: 196px;
}
.table-class {
margin: 5px 5px 5px 5px;
width: inherit;
}
.el-pagination {
padding-left: 5px;
}
.dialog-footer {
text-align: right;
padding: 20px 0 0;
}
.dialog-footer .el-button {
margin-left: 10px;
}
/* 新增样式 */
.vehicle-card {
cursor: pointer;
border: 2px solid transparent;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.vehicle-card:hover {
border-color: #409eff;
}
.selected-card {
border-color: #409eff;
background-color: #f0f9ff;
}
.vehicle-info p {
margin: 5px 0;
font-size: 14px;
word-break: break-all;
}
</style>

View File

@ -0,0 +1,402 @@
<template>
<el-config-provider :locale="zhCn">
<el-container class="content">
<div class="work-area">
<div style="display: flex; margin-top: 10px;">
<div v-for="(entity, index) in confirmEntities" :key="index" style="width: 32%; margin: 0 10px;">
<fieldset class="confirm-area">
<div style="text-align: center; font-size: 18px; font-weight: bold; margin-bottom: 0; color: #333;">
{{ getChildStandIdByIndex(index) }}
</div>
<el-form ref="confirmRef" :model="entity" :label-position="labelPosition" label-width="158px"
style="max-width: 100%" :rules="confirmRules" status-icon>
<div style="display: flex; flex-wrap: wrap;">
<div style="width: 50%; display: flex; flex-direction: column;">
<el-form-item label="合并任务号" prop="mixTaskId">
<el-input v-model="entity.mixTaskId" disabled/>
</el-form-item>
<el-form-item label="原载具号" prop="sourceVehicleId">
<el-input v-model="entity.sourceVehicleId" disabled/>
</el-form-item>
<el-form-item label="系统数量" prop="stockNum">
<el-input v-model="entity.stockNum" disabled/>
</el-form-item>
</div>
<div style="width: 50%; display: flex; flex-direction: column;">
<el-form-item label="物料号" prop="goodsId">
<el-input v-model="entity.goodsId" disabled/>
</el-form-item>
<el-form-item label="合并目标箱号" prop="targetVehicleId">
<el-input v-model="entity.targetVehicleId"
@input="onTargetVehicleIdChange(entity)"
disabled/>
</el-form-item>
<el-form-item label="确认数量" prop="confirmNum">
<el-input-number
style="width: 196px"
v-model.number="entity.confirmNum"
controls-position="right"
:min="0"
:max="entity.stockNum"
:precision="0"/>
</el-form-item>
</div>
</div>
<div style="display: flex; justify-content: center; gap: 80px; margin-top: 20px;">
<el-button
:type="isSameVehicle(entity) ? 'success' : 'warning'"
style="height: 50px; width: 100px; margin: auto 5px auto 5px; font-size: large; color: black;"
@click="confirmOrRelease(index)">
确认合并
</el-button>
</div>
</el-form>
</fieldset>
</div>
</div>
</div>
</el-container>
</el-config-provider>
</template>
<script setup>
import store from '@/store'
import {nextTick, onBeforeUnmount, onMounted, reactive, ref, watch, h} from 'vue'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import {useRoute} from "vue-router";
import {errorBox, warningBox} from "@/utils/myMessageBox";
import {ElMessage, ElMessageBox} from "element-plus";
import {labelPosition} from "@/constant/form";
import {loading} from "@/utils/loading";
import {getInventoryConfirm, confirmMixStock} from '@/api/task'; // 使API
const STAND_ID = store.getters.getStandId
const USER_NAME = store.getters.getUserName
let timer = ref()
const route = useRoute()//
let confirmRef = ref()
//
let confirmEntities = reactive([
{
mixTaskId: '',
sourceVehicleId: '',
targetVehicleId: '',
goodsId: '',
stockNum: null,
confirmNum: 0, // 0
locationId: '',
status: ''
},
{
mixTaskId: '',
sourceVehicleId: '',
targetVehicleId: '',
goodsId: '',
stockNum: null,
confirmNum: 0, // 0
locationId: '',
status: ''
},
{
mixTaskId: '',
sourceVehicleId: '',
targetVehicleId: '',
goodsId: '',
stockNum: null,
confirmNum: 0, // 0
locationId: '',
status: ''
}
])
const confirmRules = reactive({})
let pauseGetPickFlag = ref(false)
//
onMounted(() => {
nextTick(() => {
timer.value = setInterval(() => {
timerTask_1()
}, 1000)
})
})
onBeforeUnmount(() => {
clearInterval(timer.value)
})
//
watch(() => route.path, (newVal, oldVal) => {
if (newVal === '/stockMixConfirm') {
timer.value = setInterval(() => {
timerTask_1()
}, 1000)
} else {
clearInterval(timer.value)
}
})
// 1
const timerTask_1 = () => {
//
getMixTasks()
}
// - 使
const getMixTasks = () => {
if (pauseGetPickFlag.value) {
return
}
// ID
let childStandOrder = []
if (STAND_ID === 'P1') {
childStandOrder = ['P13', 'P12', 'P11']
} else if (STAND_ID === 'P2') {
childStandOrder = ['P16', 'P15', 'P14']
} else if (STAND_ID === 'P3') {
childStandOrder = ['P19', 'P18', 'P17']
} else {
childStandOrder = ['P23', 'P22', 'P21']
}
//
const promises = childStandOrder.map(standId => {
const request = {
standId: standId,
userName: USER_NAME
}
// 使
return getInventoryConfirm(request)
})
Promise.all(promises).then(responses => {
responses.forEach((res, index) => {
const response = res.data
if (response.code === 0) {
const confirmVo = response.data
const entity = confirmEntities[index]
//
const userInputConfirmNum = entity.confirmNum
entity.mixTaskId = confirmVo.inventoryId || ''
entity.sourceVehicleId = confirmVo.vehicleId || ''
entity.targetVehicleId = confirmVo.mixVehicle || ''
entity.goodsId = confirmVo.goodsId || ''
entity.stockNum = confirmVo.stockNum || null
entity.locationId = confirmVo.locationId || ''
entity.status = confirmVo.status || '待合并'
//
if (isDifferentVehicle(entity)) {
// 0
if (userInputConfirmNum !== null && userInputConfirmNum !== undefined) {
entity.confirmNum = userInputConfirmNum
} else {
entity.confirmNum = 0
}
} else {
// 使
entity.confirmNum = confirmVo.confirmNum || userInputConfirmNum || 0
}
} else if (response.code === 400) {
//
clearConfirmEntity(index)
warningBox(response.message)
}
})
pauseGetPickFlag.value = true
}).catch(err => {
console.log(err)
pauseGetPickFlag.value = true
errorBox('请求错误,请检查完原因后刷新界面。')
})
}
// ID
const getChildStandIdByIndex = (index) => {
// ID
let childStandOrder = []
if (STAND_ID === 'P1') {
childStandOrder = ['P13', 'P12', 'P11']
} else if (STAND_ID === 'P2') {
childStandOrder = ['P16', 'P15', 'P14']
} else if (STAND_ID === 'P3') {
childStandOrder = ['P19', 'P18', 'P17']
} else {
childStandOrder = ['P23', 'P22', 'P21']
}
return childStandOrder[index] || ''
}
//
const clearConfirmEntity = (index) => {
if (index >= 0 && index < confirmEntities.length) {
const entity = confirmEntities[index]
entity.mixTaskId = ''
entity.sourceVehicleId = ''
entity.targetVehicleId = ''
entity.goodsId = ''
entity.stockNum = null
entity.confirmNum = 0 //
entity.locationId = ''
entity.status = ''
}
}
//
const isSameVehicle = (entity) => {
return entity.sourceVehicleId && entity.targetVehicleId &&
entity.sourceVehicleId === entity.targetVehicleId
}
//
const isDifferentVehicle = (entity) => {
return entity.sourceVehicleId && entity.targetVehicleId &&
entity.sourceVehicleId !== entity.targetVehicleId
}
//
const onTargetVehicleIdChange = (entity) => {
//
//
}
//
const confirmOrRelease = async (index) => {
const entity = confirmEntities[index]
//
if (isDifferentVehicle(entity)) {
//
try {
await ElMessageBox({
title: '操作提示',
message: h('div', null, [
h('p', { style: 'font-size: 16px; color: #333; margin-bottom: 10px;' }, '请将物料从原载具'),
h('p', { style: 'font-size: 18px; color: #f56c6c; font-weight: bold; margin: 10px 0;' }, entity.sourceVehicleId),
h('p', { style: 'font-size: 16px; color: #333; margin-top: 10px;' }, '转移到目标容器'),
h('p', { style: 'font-size: 18px; color: #409eff; font-weight: bold; margin: 10px 0;' }, entity.targetVehicleId),
h('p', { style: 'font-size: 14px; color: #909399; margin-top: 15px;' }, `确认已放置后点击确定继续操作,当前确认数量:${entity.confirmNum}`)
]),
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
// 0
} catch {
//
return
}
} else if (isSameVehicle(entity)) {
//
try {
await ElMessageBox({
title: '确认合并',
message: h('div', null, [
h('p', { style: 'font-size: 16px; color: #333; margin-bottom: 10px;' }, '请核对好数量!'),
h('p', { style: 'font-size: 18px; color: #e6a23c; font-weight: bold; margin: 10px 0;' }, `当前确认数量:${entity.confirmNum}`),
]),
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
} catch {
//
return
}
//
if (entity.confirmNum === null || entity.confirmNum < 0 || entity.confirmNum > entity.stockNum) {
ElMessage.error(`请填写有效的确认数量0-${entity.stockNum}`)
return
}
} else {
//
ElMessage.error('请填写合并目标箱号')
return
}
//
const request = {
inventoryId: entity.mixTaskId,
vehicleId: entity.sourceVehicleId,
targetVehicleId: entity.targetVehicleId,
goodsId: entity.goodsId,
confirmNum: entity.confirmNum,
needAddNum: entity.stockNum - entity.confirmNum,
standId: getChildStandIdByIndex(index),
userName: USER_NAME
}
loading.open('处理中...')
confirmMixStock(request).then(res => {
const response = res.data
if (response.code === 0) {
clearConfirmEntity(index)
ElMessage.success(response.message)
pauseGetPickFlag.value = false
//
getMixTasks()
} else if (response.code === 400) {
clearConfirmEntity(index)
warningBox(response.message)
pauseGetPickFlag.value = false
} else {
errorBox(response.message)
}
}).catch(err => {
console.log(err)
errorBox('请求错误,请检查完原因后刷新界面。')
}).finally(() => {
loading.close()
})
}
</script>
<style scoped>
.content {
display: flex;
width: 100%;
}
.work-area {
width: 100%;
}
.confirm-area {
margin: auto;
min-height: fit-content;
max-height: 100%;
margin-bottom: 10px;
min-width: inherit;
border: solid 1px;
border-radius: 10px;
box-shadow: 0px 15px 10px -15px #000;
overflow: auto;
padding: 10px;
}
.el-form-item {
margin: 10px 5px 10px 5px;
}
.el-form-item .el-input {
width: 196px;
}
.table-class {
margin: 5px 5px 5px 5px;
width: inherit;
}
/* 特殊库存字段标红醒目显示 */
.special-inventory-item :deep(.el-form-item__label) {
color: #ff8888;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,343 @@
<template>
<el-container class="chart-container">
<el-main>
<div class="chart-header">
<h2>仓库数据分析图表</h2>
</div>
<!-- 每日出入库数量图表 -->
<el-card class="chart-card">
<template #header>
<div class="card-header">
<span>每日出入库数量统计</span>
<div class="chart-controls">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
@change="fetchDailyData"
/>
<el-button
type="primary"
@click="fetchDailyData"
style="margin-left: 10px;"
>
刷新数据
</el-button>
</div>
</div>
</template>
<div id="daily-chart" class="chart"></div>
</el-card>
<!-- 在库时间数量图表 -->
<el-card class="chart-card">
<template #header>
<div class="card-header">
<span>库存龄期分布统计</span>
<div class="chart-controls">
<!-- <el-select-->
<!-- v-model="agingPeriod"-->
<!-- placeholder="选择统计周期"-->
<!-- @change="fetchAgingData"-->
<!-- style="margin-right: 10px;"-->
<!-- >-->
<!-- <el-option label="按天统计" value="daily" />-->
<!-- <el-option label="按周统计" value="weekly" />-->
<!-- <el-option label="按月统计" value="monthly" />-->
<!-- </el-select>-->
<el-button
type="primary"
@click="fetchAgingData"
>
刷新数据
</el-button>
</div>
</div>
</template>
<div id="aging-chart" class="chart"></div>
</el-card>
</el-main>
</el-container>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue'
import * as echarts from 'echarts'
import { ElMessage } from 'element-plus'
// API
import { queryInOutBoundStatistics, queryStockAgeDistribution } from '@/api/task'
//
const dailyChartRef = ref(null)
const agingChartRef = ref(null)
const dateRange = ref([])
const agingPeriod = ref('daily')
//
let dailyChart = null
let agingChart = null
// 30
const getDefaultDateRange = () => {
const endDate = new Date()
const startDate = new Date()
startDate.setDate(endDate.getDate() - 30)
return [
startDate.toISOString().split('T')[0],
endDate.toISOString().split('T')[0]
]
}
// -
const generateAgingTestData = () => {
return [
{ name: '0-7天', value: Math.floor(Math.random() * 100) + 100 },
{ name: '8-30天', value: Math.floor(Math.random() * 80) + 80 },
{ name: '1-3个月', value: Math.floor(Math.random() * 50) + 40 },
{ name: '3-6个月', value: Math.floor(Math.random() * 30) + 20 },
{ name: '6个月以上', value: Math.floor(Math.random() * 20) + 10 }
]
}
//
onMounted(async () => {
dateRange.value = getDefaultDateRange()
await nextTick()
initCharts()
fetchDailyData()
fetchAgingData()
})
//
const initCharts = () => {
dailyChart = echarts.init(document.getElementById('daily-chart'))
agingChart = echarts.init(document.getElementById('aging-chart'))
}
// 使API
const fetchDailyData = async () => {
try {
const params = {
startDate: dateRange.value[0],
endDate: dateRange.value[1]
}
const response = await queryInOutBoundStatistics(params)
if (response.data.code === 0) {
renderDailyChart(response.data.data)
} else {
ElMessage.error(response.data.message || '获取数据失败')
}
} catch (error) {
console.error('获取每日出入库数据失败:', error)
ElMessage.error('获取数据失败')
}
}
//
const renderDailyChart = (data) => {
const option = {
title: {
text: '每日出入库数量趋势',
subtext: `数据更新时间: ${new Date().toLocaleString()}`,
textStyle: {
fontSize: 16
},
subtextStyle: {
fontSize: 12,
color: '#666'
},
top: '0%', //
left: 'center' //
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow' // : 使
}
},
legend: {
data: ['入库数量', '出库数量'],
top: '12%', //
left: 'center' //
},
toolbox: {
feature: {
saveAsImage: {}
}
},
grid: {
left: '3%',
right: '4%',
top: '20%', //
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
boundaryGap: true, // : true使
data: data.dates
}
],
yAxis: [
{
type: 'value',
name: '数量'
}
],
series: [
{
name: '入库数量',
type: 'bar',
emphasis: {
focus: 'series'
},
data: data.inbound
},
{
name: '出库数量',
type: 'bar',
emphasis: {
focus: 'series'
},
data: data.outbound
}
]
}
dailyChart.setOption(option)
}
// 使
const fetchAgingData = async () => {
try {
// API
const response = await queryStockAgeDistribution({})
if (response.data.code === 0) {
renderAgingChart(response.data.data)
} else {
ElMessage.error(response.data.message || '获取数据失败')
}
} catch (error) {
console.error('获取库存龄期数据失败:', error)
ElMessage.error('获取数据失败')
}
}
//
const renderAgingChart = (data) => {
const option = {
title: {
text: '库存龄期分布统计',
subtext: `统计周期: ${getPeriodText(agingPeriod.value)}`,
textStyle: {
fontSize: 16
},
subtextStyle: {
fontSize: 12,
color: '#666'
},
top: '1%', //
left: 'center' //
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/}{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
top: '8%' //
},
series: [
{
name: '库存龄期',
type: 'pie',
radius: ['40%', '70%'],
center: ['60%', '60%'], // 50%55%
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}: {c} ({d}%)'
},
emphasis: {
label: {
show: true,
fontSize: '18',
fontWeight: 'bold'
}
},
labelLine: {
show: true
},
data: data
}
]
}
agingChart.setOption(option)
}
//
const getPeriodText = (period) => {
switch(period) {
case 'daily': return '按天统计'
case 'weekly': return '按周统计'
case 'monthly': return '按月统计'
default: return '未知周期'
}
}
//
window.addEventListener('resize', () => {
if (dailyChart) dailyChart.resize()
if (agingChart) agingChart.resize()
})
</script>
<style scoped>
.chart-container {
height: 100%;
}
.chart-header {
margin-bottom: 10px; /* 从20px减少到10px */
margin-top: -40px; /* 向上移动10px */
}
.chart-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.chart-controls {
display: flex;
align-items: center;
}
.chart {
height: 400px;
width: 100%;
}
</style>

View File

@ -30,6 +30,10 @@ const routes = [
{path: '/outsMonitor', component: () => import('@/layout/OutsMonitor.vue')},// 任务表单 {path: '/outsMonitor', component: () => import('@/layout/OutsMonitor.vue')},// 任务表单
{path: '/clcKanban', component: () => import('@/layout/clcKanban.vue')},// 需求看板 {path: '/clcKanban', component: () => import('@/layout/clcKanban.vue')},// 需求看板
{path: '/batchInventory', component: () => import('@/layout/batchInventory.vue')},// 批量盘点 {path: '/batchInventory', component: () => import('@/layout/batchInventory.vue')},// 批量盘点
{path: '/excelInventory', component: () => import('@/layout/excelInventory.vue')},// Excel盘点
{path: '/stockMix', component: () => import('@/layout/stockMix.vue')},// 库存合并
{path: '/wmsChart', component: () => import('@/layout/wmsChart.vue')},// 库存合并
{path: '//stockMixConfirm', component: () => import('@/layout//stockMixConfirm.vue')},
{path: '/stockCompare', component: () => import('@/layout/stockCompare.vue')}, {path: '/stockCompare', component: () => import('@/layout/stockCompare.vue')},
{path: '/stockUpdateRecord', component: () => import('@/layout/stockUpdateRecord.vue')},// 库存更新记录 {path: '/stockUpdateRecord', component: () => import('@/layout/stockUpdateRecord.vue')},// 库存更新记录
{path: '/roleUser', component: () => import('@/layout/role_user.vue')},// 角色——用户列表 {path: '/roleUser', component: () => import('@/layout/role_user.vue')},// 角色——用户列表
@ -50,6 +54,16 @@ const routes = [
name: 'login', name: 'login',
component: login component: login
}, },
{
path: '/pda-center',
name: 'PDACenter',
component: () => import('@/views/PDACenter.vue')
},
{
path: '/pda-secondIn',
name: 'pdaSecondIn',
component: () => import('@/layout/pdaSecondIn.vue') // 假设这是具体功能页面
},
{ {
path: '/pda', path: '/pda',
name: 'pda', name: 'pda',

View File

@ -0,0 +1,60 @@
<template>
<body id="pda-login-page">
<el-form class="pda-login-container" label-position="left" label-width="0px">
<h3 class="pda_login_title">PDA系统功能选择</h3>
<el-form-item style="width: 100%">
<el-button color="#87CEFA" style="width: 100%; border: none" @click="goToPDAFunction1">PDA拣选功能</el-button>
</el-form-item>
<el-form-item style="width: 100%">
<el-button color="#87CEFA" style="width: 100%; border: none" @click="goToPDAFunction2">PDA呼叫料箱</el-button>
</el-form-item>
</el-form>
</body>
</template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
// PDA1
const goToPDAFunction1 = () => {
router.push('/pda')
}
// PDA2
const goToPDAFunction2 = () => {
router.push('/pda-secondIn')
}
</script>
<style scoped>
#pda-login-page {
background-position: center;
height: 100%;
width: 100%;
background-size: cover;
position: fixed;
}
body {
margin: 0px;
}
.pda-login-container {
border-radius: 15px;
background-clip: padding-box;
margin: 90px auto;
width: 350px;
padding: 35px 35px 15px 35px;
background: #fff;
border: 1px solid #eaeaea;
box-shadow: 0 0 25px #cac6c6;
}
.pda_login_title {
margin: 0px auto 40px auto;
text-align: center;
color: #505458;
}
</style>

View File

@ -39,7 +39,7 @@ const token = store.getters.getToken// 密码
const loginToWms = () => { const loginToWms = () => {
router.replace({ path: '/home' }) router.replace({ path: '/home' })
} }
// PDA // PDA -
const PdaToWms = () => { const PdaToWms = () => {
// PDA // PDA
const params = { const params = {
@ -53,8 +53,8 @@ const PdaToWms = () => {
// "E"PDA // "E"PDA
const permissionStr = res.data.message || '' const permissionStr = res.data.message || ''
if (permissionStr.includes('E')) { if (permissionStr.includes('E')) {
// PDAPDA // PDAPDA
router.replace({ path: '/pda' }) router.replace({ path: '/pda-center' })
} else { } else {
// PDA // PDA
ElMessage.error('您没有访问PDA系统的权限') ElMessage.error('您没有访问PDA系统的权限')
@ -67,6 +67,7 @@ const PdaToWms = () => {
ElMessage.error('权限检查失败') ElMessage.error('权限检查失败')
}) })
} }
// //
const loginToImage = () => { const loginToImage = () => {
router.replace({ path: '/imageDisplay' }) router.replace({ path: '/imageDisplay' })

View File

@ -10,7 +10,9 @@ import lombok.Getter;
@AllArgsConstructor @AllArgsConstructor
public enum WmsInvTypeEnums { public enum WmsInvTypeEnums {
INV_TYPE_1(1, "明盘"), INV_TYPE_1(1, "明盘"),
INV_TYPE_2(2, "盲盘"); INV_TYPE_2(2, "盲盘"),
MIX_STOCK(3, "合并库存"),
;
private final Integer code; private final Integer code;
private final String desc; private final String desc;
} }

View File

@ -240,4 +240,70 @@ public class DataController {
return "获取拣选任务失败: " + e.getMessage(); return "获取拣选任务失败: " + e.getMessage();
} }
} }
/**
* 查询指定日期范围内的入库和出库统计数据
*/
@PostMapping("/queryInOutBoundStatistics")
public Map<String, Object> queryInOutBoundStatistics(@RequestBody Map<String, String> requestParams) {
try {
// 从请求体中提取参数
String startDate = requestParams.get("startDate");
String endDate = requestParams.get("endDate");
// 验证参数
if (startDate == null || startDate.isEmpty() || endDate == null || endDate.isEmpty()) {
Map<String, Object> response = new HashMap<>();
response.put("code", -1);
response.put("message", "开始日期和结束日期不能为空");
response.put("data", null);
return response;
}
// 调用服务层查询统计数据
Map<String, Object> result = dataControllerService.queryInOutBoundStatistics(startDate, endDate);
Map<String, Object> response = new HashMap<>();
response.put("code", 0);
response.put("message", "success");
response.put("data", result);
return response;
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("code", -1);
response.put("message", "查询失败: " + e.getMessage());
response.put("data", null);
return response;
}
}
/**
* 库存龄期分布统计接口
*/
@GetMapping("/queryStockAgeDistribution")
public Map<String, Object> queryStockAgeDistribution() {
try {
// 调用服务层查询库存龄期分布统计数据
List<Map<String, Object>> result = dataControllerService.queryStockAgeDistribution();
Map<String, Object> response = new HashMap<>();
response.put("code", 0);
response.put("message", "success");
response.put("data", result);
return response;
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("code", -1);
response.put("message", "查询失败: " + e.getMessage());
response.put("data", null);
return response;
}
}
} }

View File

@ -211,4 +211,20 @@ public class ExcelController {
public void exportLocationDetailExcel(HttpServletResponse response) throws IOException { public void exportLocationDetailExcel(HttpServletResponse response) throws IOException {
exportExcelEasyPoi.doExportLocationAndStockExcel(response); exportExcelEasyPoi.doExportLocationAndStockExcel(response);
} }
/**
* 导入物料信息基于模板
* @param file 上传的 Excel 文件
* @param fileVo 文件元数据
* @return 导入结果
* @throws Exception 异常
*/
@PostMapping("/importGoods")
public BaseWmsApiResponse doImportGoods(@RequestPart("file") MultipartFile file, @RequestPart("fileVo") FileVo fileVo) throws Exception {
return importExcelEasyPoi.doImportGoods(file, fileVo);
}
} }

View File

@ -35,6 +35,18 @@ public class StockController {
return stockControllerService.queryStocksByPage(stockQuery); return stockControllerService.queryStocksByPage(stockQuery);
} }
/**
* 查询特殊库存字段为空且数量大于等于2的物料
* @param stockQuery 查询参数
* @return 查询结果
*/
@PostMapping("/querySpecialEmptyStocksForMix")
public WmsApiResponse<PageVo<StockVo>> querySpecialEmptyStocksForMix(@RequestBody StockQuery stockQuery)
{
return stockControllerService.querySpecialEmptyStocksForMix(stockQuery);
}
/* /*
查询库存 查询库存
*/ */
@ -44,6 +56,15 @@ public class StockController {
return stockControllerService.queryStocks(stockQuery); return stockControllerService.queryStocks(stockQuery);
} }
/*
查询非限制库存
*/
@PostMapping("/queryStocksBySpecial")
public WmsApiResponse<PageVo<StockVo>> queryStocksBySpecial(@RequestBody StockQuery stockQuery)
{
return stockControllerService.queryStocksBySpecial(stockQuery);
}
/** /**
* 分页查询库存更新记录 * 分页查询库存更新记录
* @param stockUpdateQuery 查询参数 * @param stockUpdateQuery 查询参数

View File

@ -288,6 +288,16 @@ public class TaskController {
return taskControllerService.confirmInventory(inventoryConfirmRequest); return taskControllerService.confirmInventory(inventoryConfirmRequest);
} }
/**
* 确认合并信息
* @param inventoryConfirmRequest 确认请求
* @return 处理结果
*/
@PostMapping("/confirmMixStock")
public BaseWmsApiResponse confirmMixStock(@RequestBody InventoryConfirmRequest inventoryConfirmRequest) {
return taskControllerService.confirmMixStock(inventoryConfirmRequest);
}
/** /**
* 获取缺料数量当存在库存时返回库存与需求的较小值 * 获取缺料数量当存在库存时返回库存与需求的较小值
* @param goodsId 料号 * @param goodsId 料号
@ -298,4 +308,48 @@ public class TaskController {
public int getLackQty(@RequestParam("goodsId") String goodsId, @RequestParam("workOrder") String workOrder) { public int getLackQty(@RequestParam("goodsId") String goodsId, @RequestParam("workOrder") String workOrder) {
return taskControllerService.getLackQty(goodsId, workOrder); return taskControllerService.getLackQty(goodsId, workOrder);
} }
/**
* 根据混合库存数据创建盘点任务
* 将传入的载具和物料信息转化为盘点任务
* @param mixStockRequest 包含载具号物料号列表和绑定箱号的请求参数
* @return 处理结果
*/
@PostMapping("/createInventoryFromMixStock")
public BaseWmsApiResponse createInventoryFromMixStock(@RequestBody MixStockRequest mixStockRequest) {
return taskControllerService.createInventoryFromMixStock(mixStockRequest);
}
/**
* PDA手动生成出库任务
* @param manualOutTaskRequest 手动出库任务请求参数
* @return 处理结果
*/
@PostMapping("/pdaOutTask")
public BaseWmsApiResponse manualCreateOutTask(@RequestBody InventoryRequest manualOutTaskRequest) {
return taskControllerService.pdaOutTask(manualOutTaskRequest);
}
// /**
// * 测试调用EWM系统验证容器接口
// * @return 调用结果
// */
// @GetMapping("/testEwmCheckContainer")
// public EwmApiBackResponse testEwmCheckContainer() {
// // 创建测试请求对象
// SendEwmCheckContainerNo request = new SendEwmCheckContainerNo();
// request.setCheckKey("送件区域:洪字");
// request.setContainerNo("BASR202509060003");
// request.setFirstOrSecond(false);
// request.setType("GI");
// // 调用EWM服务
// return ewmApiService.sendEwmCheckContainerNo(request);
// }
} }

View File

@ -90,6 +90,17 @@ public class TaskQueryController {
} }
/**
* 更新出库单任务状态
*
* @param batchEditDateVo 出库单信息
* @return 结果
*/
@PostMapping("/updateOutsTaskStatus")
public WmsApiResponse<String> updateOutsTaskStatus(@RequestBody OutsBatchEditDateVo batchEditDateVo) {
return taskQueryControllerService.updateOutsTaskStatus(batchEditDateVo);
}
/** /**
* 出库提高优先级 * 出库提高优先级

View File

@ -0,0 +1,29 @@
package com.wms_main.excel.easypoi.excelTemplate;
import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.Data;
@Data
public class GoodsImportTemplate {
@Excel(name = "物料号", orderNum = "0")
private String goodsId;
@Excel(name = "物料描述", orderNum = "1")
private String goodsDesc;
@Excel(name = "特殊库存标识", orderNum = "2")
private String specialStock;
@Excel(name = "特殊库存编号", orderNum = "3")
private String specialStockNo;
@Excel(name = "销售订单行号", orderNum = "4")
private String specialStockItemNo;
@Excel(name = "账面数量", orderNum = "5")
private Double accountQty;
@Excel(name = "站点", orderNum = "6")
private String stand;
}

View File

@ -45,4 +45,6 @@ public interface IExportExcelEasyPoi extends IBaseExportExcelEasyPoi {
* 导出库位及库存详情 * 导出库位及库存详情
*/ */
void doExportLocationAndStockExcel(HttpServletResponse response) throws IOException; void doExportLocationAndStockExcel(HttpServletResponse response) throws IOException;
} }

View File

@ -69,4 +69,6 @@ public interface IImportExcelEasyPoi extends IBaseImportExcelEasyPoi {
* @throws Exception 异常 * @throws Exception 异常
*/ */
BaseWmsApiResponse doImportInventoryRequest(MultipartFile file, FileVo fileVo) throws Exception; BaseWmsApiResponse doImportInventoryRequest(MultipartFile file, FileVo fileVo) throws Exception;
BaseWmsApiResponse doImportGoods(MultipartFile file, FileVo fileVo);
} }

View File

@ -9,26 +9,30 @@ import com.wms_main.dao.ITAppGoodsService;
import com.wms_main.dao.ITAppStockService; import com.wms_main.dao.ITAppStockService;
import com.wms_main.dao.ITAppStockUpdateService; import com.wms_main.dao.ITAppStockUpdateService;
import com.wms_main.dao.ITAppTaskBakService; import com.wms_main.dao.ITAppTaskBakService;
import com.wms_main.excel.easypoi.excelTemplate.GoodsExcelTemplate; import com.wms_main.excel.easypoi.excelTemplate.*;
import com.wms_main.excel.easypoi.excelTemplate.StockExcelTemplate;
import com.wms_main.excel.easypoi.excelTemplate.StockUpdateExcelTemplate;
import com.wms_main.excel.easypoi.excelTemplate.TaskRecordExcelTemplate;
import com.wms_main.excel.easypoi.service.base.IBaseExportExcelEasyPoi; import com.wms_main.excel.easypoi.service.base.IBaseExportExcelEasyPoi;
import com.wms_main.model.dto.query.GoodsQuery; import com.wms_main.model.dto.query.GoodsQuery;
import com.wms_main.model.dto.query.StockQuery; import com.wms_main.model.dto.query.StockQuery;
import com.wms_main.model.dto.query.StockUpdateQuery; import com.wms_main.model.dto.query.StockUpdateQuery;
import com.wms_main.model.dto.query.WmsTaskQuery; import com.wms_main.model.dto.query.WmsTaskQuery;
import com.wms_main.model.dto.response.wms.BaseWmsApiResponse;
import com.wms_main.model.po.TAppGoods; import com.wms_main.model.po.TAppGoods;
import com.wms_main.model.po.TAppStock; import com.wms_main.model.po.TAppStock;
import com.wms_main.model.po.TAppStockUpdate; import com.wms_main.model.po.TAppStockUpdate;
import com.wms_main.model.po.TAppTaskBak; import com.wms_main.model.po.TAppTaskBak;
import com.wms_main.model.vo.others.FileVo;
import com.wms_main.repository.utils.StringUtils; import com.wms_main.repository.utils.StringUtils;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -314,6 +318,9 @@ public class BaseExportExcelEasyPoi implements IBaseExportExcelEasyPoi {
doWriteExcel("出库记录", response, resultList, TaskRecordExcelTemplate.class); doWriteExcel("出库记录", response, resultList, TaskRecordExcelTemplate.class);
} }
/** /**
* 写入excel * 写入excel
* @param fileDesc 文件描述 * @param fileDesc 文件描述

View File

@ -13,11 +13,13 @@ import com.wms_main.dao.*;
import com.wms_main.excel.easypoi.excelTemplate.*; import com.wms_main.excel.easypoi.excelTemplate.*;
import com.wms_main.excel.easypoi.service.IImportExcelEasyPoi; import com.wms_main.excel.easypoi.service.IImportExcelEasyPoi;
import com.wms_main.excel.easypoi.service.base.impl.BaseImportExcelEasyPoi; import com.wms_main.excel.easypoi.service.base.impl.BaseImportExcelEasyPoi;
import com.wms_main.model.dto.request.wms.BatchInventoryRequest;
import com.wms_main.model.po.*; import com.wms_main.model.po.*;
import com.wms_main.model.vo.others.FileVo; import com.wms_main.model.vo.others.FileVo;
import com.wms_main.model.dto.response.wms.BaseWmsApiResponse; import com.wms_main.model.dto.response.wms.BaseWmsApiResponse;
import com.wms_main.repository.utils.StringUtils; import com.wms_main.repository.utils.StringUtils;
import com.wms_main.repository.utils.UUIDUtils; import com.wms_main.repository.utils.UUIDUtils;
import com.wms_main.service.controller.ITaskControllerService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -25,6 +27,8 @@ import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -42,6 +46,7 @@ public class ImportExcelEasyPoi extends BaseImportExcelEasyPoi implements IImpor
private final ITAppInventoryService tAppInventoryService;// 盘点请求服务 private final ITAppInventoryService tAppInventoryService;// 盘点请求服务
private final ITAppStockService tAppStockService;// 库存服务 private final ITAppStockService tAppStockService;// 库存服务
private final AppCommon appCommon; private final AppCommon appCommon;
private final ITaskControllerService taskControllerService; // 添加任务控制器服务
/** /**
* 导入dbs的实现 * 导入dbs的实现
@ -466,6 +471,116 @@ public class ImportExcelEasyPoi extends BaseImportExcelEasyPoi implements IImpor
} }
} }
/**
* 导入物料信息基于模板
* @param file 上传的 Excel 文件
* @param fileVo 文件信息
* @return 导入结果
*/
public BaseWmsApiResponse doImportGoods(MultipartFile file, FileVo fileVo) {
if (file == null || file.isEmpty()) {
throw new IllegalArgumentException("上传文件不能为空");
}
String originalFilename = file.getOriginalFilename();
if (originalFilename == null ||
(!originalFilename.toLowerCase().endsWith(".xlsx") && !originalFilename.toLowerCase().endsWith(".xls"))) {
throw new IllegalArgumentException("仅支持 .xlsx 或 .xls 格式文件");
}
try (InputStream inputStream = file.getInputStream()) {
ImportParams params = new ImportParams();
params.setTitleRows(0); // 第一行是标题
params.setHeadRows(1); // 数据从第二行开始
List<GoodsImportTemplate> importList = ExcelImportUtil.importExcel(inputStream, GoodsImportTemplate.class, params);
// 数据校验与去重
List<GoodsImportTemplate> validItems = new ArrayList<>();
List<String> errorMessages = new ArrayList<>();
for (GoodsImportTemplate item : importList) {
if (StringUtils.isEmpty(item.getGoodsId())) {
errorMessages.add("物料号不能为空");
continue;
}
if (StringUtils.isEmpty(item.getStand())) {
errorMessages.add("站台号不能为空,物料号:" + item.getGoodsId());
continue;
}
// 检查库存是否存在
LambdaQueryWrapper<TAppStock> wrapper = new LambdaQueryWrapper<TAppStock>()
.eq(TAppStock::getGoodsId, item.getGoodsId())
.eq(StringUtils.isNotEmpty(item.getSpecialStock()), TAppStock::getSpecialStock, item.getSpecialStock())
.eq(StringUtils.isNotEmpty(item.getSpecialStockNo()), TAppStock::getSpecialStockNo, item.getSpecialStockNo())
.eq(StringUtils.isNotEmpty(item.getSpecialStockItemNo()), TAppStock::getSpecialStockItemNo, item.getSpecialStockItemNo());
List<TAppStock> stockList = tAppStockService.list(wrapper);
if (stockList.isEmpty()) {
errorMessages.add("物料号:" + item.getGoodsId() + ",特殊库存:" + item.getSpecialStock() + ",特殊库存编号:" + item.getSpecialStockNo() + ",销售订单行号:" + item.getSpecialStockItemNo() + " 在库存表中不存在" );
continue;
}
validItems.add(item);
}
// 如果有错误返回失败
if (!errorMessages.isEmpty()) {
return BaseWmsApiResponse.error(String.join("\n", errorMessages));
}
// 逐个处理盘点请求
int successCount = 0;
List<String> processErrors = new ArrayList<>();
for (GoodsImportTemplate item : validItems) {
// 创建单个盘点请求
com.wms_main.model.dto.request.wms.InventoryRequest inventoryRequest = new com.wms_main.model.dto.request.wms.InventoryRequest();
inventoryRequest.setStandId(item.getStand()); // 使用Excel中的stand字段作为站台号
inventoryRequest.setGoodsId(item.getGoodsId());
inventoryRequest.setVehicleId(null); // 使用Excel中的vehicleId如果有的话
inventoryRequest.setSpecialStock(item.getSpecialStock());
inventoryRequest.setSpecialStockNo(item.getSpecialStockNo());
inventoryRequest.setSpecialStockItemNo(item.getSpecialStockItemNo());
inventoryRequest.setBatchNo(null);
inventoryRequest.setUserName(fileVo.getUserName()); // 使用文件信息中的用户名
// 调用现有的单个盘点请求方法
BaseWmsApiResponse result = taskControllerService.requestInventory(inventoryRequest);
if (result != null && Objects.equals(result.getCode(), 0)) { // 检查成功状态
successCount++;
} else {
processErrors.add("物料号:" + item.getGoodsId() + " 站台:" + item.getStand() + " 。盘点请求失败,原因:" + result.getMessage());
}
}
// 如果有成功的请求记录文件导入信息
if (successCount > 0) {
saveFileVo(fileVo); // 记录导入文件信息
}
// 返回处理结果
if (!processErrors.isEmpty()) {
String errorMsg = "成功创建 " + successCount + " 条盘点任务,请刷新后查看\n" + String.join("\n", processErrors);
return successCount > 0 ? BaseWmsApiResponse.warn(errorMsg) : BaseWmsApiResponse.error(errorMsg);
} else {
return BaseWmsApiResponse.success("成功创建 " + successCount + " 条盘点任务");
}
} catch (IOException e) {
throw new RuntimeException("文件读取失败:" + e.getMessage(), e);
} catch (Exception e) {
throw new RuntimeException("Excel解析失败" + e.getMessage(), e);
}
}
/** /**
* 导入盘点请求---实现 * 导入盘点请求---实现
* @param file 文件 * @param file 文件
@ -569,9 +684,8 @@ public class ImportExcelEasyPoi extends BaseImportExcelEasyPoi implements IImpor
null, null,
orderId, orderId,
null, null,
null null,
, null,null, null
null,null
); );
inventoryByVehicleMap.put(stock.getVehicleId(), inventoryTask); inventoryByVehicleMap.put(stock.getVehicleId(), inventoryTask);
vehicleList.add(stock.getVehicleId()); vehicleList.add(stock.getVehicleId());
@ -614,7 +728,7 @@ public class ImportExcelEasyPoi extends BaseImportExcelEasyPoi implements IImpor
orderId, orderId,
null, null,
null, null,
null,null null,null, null
); );
inventoryByVehicleMap.put(stock.getVehicleId(), inventoryTask); inventoryByVehicleMap.put(stock.getVehicleId(), inventoryTask);
thisTimeAddVehicleList.add(stock.getVehicleId()); thisTimeAddVehicleList.add(stock.getVehicleId());

View File

@ -58,4 +58,18 @@ public class InventoryConfirmRequest extends BaseWmsRequest {
*/ */
@JsonProperty("specialStockItemNo") @JsonProperty("specialStockItemNo")
private String specialStockItemNo; private String specialStockItemNo;
/**
* 确认数量
*/
@JsonProperty("needAddNum")
private Integer needAddNum;
/**
* 库存合并的目标箱子号
*/
@JsonProperty("targetVehicleId")
private String targetVehicleId;
} }

View File

@ -49,4 +49,10 @@ public class InventoryRequest extends BaseWmsRequest {
@JsonProperty("batchNo") @JsonProperty("batchNo")
private String batchNo; private String batchNo;
/**
* 起点
*/
@JsonProperty("origin")
private String origin;
} }

View File

@ -0,0 +1,23 @@
package com.wms_main.model.dto.request.wms;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MixStockRequest extends BaseWmsRequest {
private List<StockItem> stockItems;
private String bindBoxNo;
@Data
public static class StockItem {
private String vehicleNo; // 载具号
private String materialNo; // 物料号
}
}

View File

@ -114,4 +114,7 @@ public class TAppInventory {
*/ */
@TableField(value = "batch_no") @TableField(value = "batch_no")
private String batchNo; // 批次号 private String batchNo; // 批次号
@TableField(value = "mix_vehicle")
private String mixVehicle;
} }

View File

@ -24,6 +24,10 @@ public class TAppOuts {
*/ */
@TableId(value = "task_id") @TableId(value = "task_id")
private String taskId; private String taskId;
@TableField(value = "task_status")
private int taskStatus;
/** /**
* 料号 * 料号
*/ */

View File

@ -67,4 +67,10 @@ public class InventoryConfirmVo {
*/ */
@JsonProperty("batchNo") @JsonProperty("batchNo")
private String batchNo; private String batchNo;
/**
* 合并目标箱子号
*/
@JsonProperty("mixVehicle")
private String mixVehicle;
} }

View File

@ -24,6 +24,12 @@ public class OutsVo {
*/ */
@JsonProperty("taskId") @JsonProperty("taskId")
private String taskId; private String taskId;
/**
* 任务状态
*/
@JsonProperty("taskStatus")
private int taskStatus;
/** /**
* 料号 * 料号
*/ */
@ -126,6 +132,7 @@ public class OutsVo {
} }
return new OutsVo( return new OutsVo(
po.getTaskId(), po.getTaskId(),
po.getTaskStatus(),
po.getGoodsId(), po.getGoodsId(),
po.getVehicleId(), po.getVehicleId(),
po.getNeedNum(), po.getNeedNum(),
@ -156,6 +163,7 @@ public class OutsVo {
} }
return new OutsVo( return new OutsVo(
po.getTaskId(), po.getTaskId(),
1,
po.getGoodsId(), po.getGoodsId(),
po.getVehicleId(), po.getVehicleId(),
po.getNeedNum(), po.getNeedNum(),

View File

@ -6,6 +6,9 @@ import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.Map;
/** /**
* WCS HTTP 请求类 * WCS HTTP 请求类
*/ */
@ -20,7 +23,7 @@ public class HttpRequest {
/** /**
* 超时时长 * 超时时长
*/ */
private Integer timeout = 5000; private Integer timeout = 10000;
/** /**
* 请求方式 * 请求方式
*/ */
@ -37,6 +40,10 @@ public class HttpRequest {
* token * token
*/ */
private String token; private String token;
/**
* 额外的请求头
*/
private Map<String, String> headers = new HashMap<>();
/** /**
* 构建一个POST请求 * 构建一个POST请求
@ -55,7 +62,30 @@ public class HttpRequest {
if (StringUtils.isEmpty(contentType)) { if (StringUtils.isEmpty(contentType)) {
contentType = "application/json"; contentType = "application/json";
} }
return new HttpRequest(url.trim(), timeout, HttpMethodEnum.POST, contentType, StringUtils.objectToString(data), token); HttpRequest request = new HttpRequest(url.trim(), timeout, HttpMethodEnum.POST, contentType, StringUtils.objectToString(data), token, new HashMap<>());
return request;
}
/**
* 构建一个POST请求带额外请求头
* @param url 接口地址
* @param data 数据
* @param timeout 超时时长
* @param contentType 请求头
* @param token token
* @param headers 额外请求头
* @return post请求体
* @param <T> 数据类型
*/
public static <T> HttpRequest postInstanceOfWithHeaders(String url, T data, Integer timeout, String contentType, String token, Map<String, String> headers) {
if (timeout == null || timeout <= 1000) {
timeout = 5000;
}
if (StringUtils.isEmpty(contentType)) {
contentType = "application/json";
}
HttpRequest request = new HttpRequest(url.trim(), timeout, HttpMethodEnum.POST, contentType, StringUtils.objectToString(data), token, headers);
return request;
} }
/** /**
@ -68,4 +98,16 @@ public class HttpRequest {
public static <T> HttpRequest postInstanceOf(String url, T data) { public static <T> HttpRequest postInstanceOf(String url, T data) {
return postInstanceOf(url.trim(), data, 5000, "application/json", ""); return postInstanceOf(url.trim(), data, 5000, "application/json", "");
} }
/**
* 添加请求头
* @param key 请求头键
* @param value 请求头值
*/
public void addHeader(String key, String value) {
if (this.headers == null) {
this.headers = new HashMap<>();
}
this.headers.put(key, value);
}
} }

View File

@ -4,6 +4,8 @@ import com.alibaba.fastjson2.JSON;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.util.DigestUtils; import org.springframework.util.DigestUtils;
import java.util.Objects;
/** /**
* 字符串工具类 * 字符串工具类
*/ */
@ -268,6 +270,26 @@ public class StringUtils {
return ip; return ip;
} }
/**
* 比较两个字符串是否相等将null和空字符串视为相等
* @param str1 字符串1
* @param str2 字符串2
* @return 是否相等
*/
public static boolean isStringEqual(String str1, String str2) {
// 如果两个都是null返回相等
if (str1 == null && str2 == null) {
return true;
}
// 如果其中一个为null另一个为空字符串返回相等
if ((str1 == null && str2.isEmpty()) || (str2 == null && str1.isEmpty())) {
return true;
}
// 其他情况使用标准equals比较
return Objects.equals(str1, str2);
}
/** /**
* 截取字符串---依据最大程度并去除空白与换行符 * 截取字符串---依据最大程度并去除空白与换行符
* @param originStr 原始字符串 * @param originStr 原始字符串

View File

@ -12,7 +12,16 @@ import com.wms_main.repository.http.entity.HttpResponse;
import com.wms_main.service.api.IEwmApiService; import com.wms_main.service.api.IEwmApiService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.net.http.HttpHeaders;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
/** /**
* Ewm接口服务实现 * Ewm接口服务实现
@ -41,7 +50,7 @@ public class EwmApiServiceImpl implements IEwmApiService {
appCommon.getConfigByKey(AppConfigKeyEnums.EWM_SEND_VEHICLE_URL.getKey()), appCommon.getConfigByKey(AppConfigKeyEnums.EWM_SEND_VEHICLE_URL.getKey()),
request, request,
30000, 30000,
"application/json", "application/json;charset=UTF-8",
"Basic " + encodedAuth); "Basic " + encodedAuth);
// 记录即将发送的请求数据 // 记录即将发送的请求数据
@ -78,7 +87,7 @@ public class EwmApiServiceImpl implements IEwmApiService {
appCommon.getConfigByKey(AppConfigKeyEnums.EWM_SEND_WAREHOUSE_IN_COMPLETED_URL.getKey()), appCommon.getConfigByKey(AppConfigKeyEnums.EWM_SEND_WAREHOUSE_IN_COMPLETED_URL.getKey()),
request, request,
30000, 30000,
"application/json", "application/json;charset=UTF-8",
"Basic " + encodedAuth); "Basic " + encodedAuth);
HttpResponse httpResponse = httpClient.httpPost(httpRequest); HttpResponse httpResponse = httpClient.httpPost(httpRequest);
@ -106,7 +115,7 @@ public class EwmApiServiceImpl implements IEwmApiService {
appCommon.getConfigByKey(AppConfigKeyEnums.EWM_SEND_WAREHOUSE_OUT_COMPLETED_URL.getKey()), appCommon.getConfigByKey(AppConfigKeyEnums.EWM_SEND_WAREHOUSE_OUT_COMPLETED_URL.getKey()),
request, request,
60000, 60000,
"application/json", "application/json;charset=UTF-8",
"Basic " + encodedAuth); "Basic " + encodedAuth);
HttpResponse httpResponse = httpClient.httpPost(httpRequest); HttpResponse httpResponse = httpClient.httpPost(httpRequest);
@ -142,7 +151,7 @@ public class EwmApiServiceImpl implements IEwmApiService {
finalUrl, finalUrl,
request, request,
30000, 30000,
"application/json", "application/json;charset=UTF-8",
"Basic " + encodedAuth); "Basic " + encodedAuth);
HttpResponse httpResponse = httpClient.httpPost(httpRequest); HttpResponse httpResponse = httpClient.httpPost(httpRequest);
@ -170,7 +179,7 @@ public class EwmApiServiceImpl implements IEwmApiService {
appCommon.getConfigByKey(AppConfigKeyEnums.EWM_GET_STOCK_LIST_URL.getKey()), appCommon.getConfigByKey(AppConfigKeyEnums.EWM_GET_STOCK_LIST_URL.getKey()),
request, request,
30000, 30000,
"application/json", "application/json;charset=UTF-8",
"Basic " + encodedAuth); "Basic " + encodedAuth);
HttpResponse httpResponse = httpClient.httpPost(httpRequest); HttpResponse httpResponse = httpClient.httpPost(httpRequest);
@ -185,24 +194,39 @@ public class EwmApiServiceImpl implements IEwmApiService {
} }
} }
@Override @Override
public EwmApiBackResponse sendEwmCheckContainerNo(SendEwmCheckContainerNo request) { public EwmApiBackResponse sendEwmCheckContainerNo(SendEwmCheckContainerNo request) {
try { try {
log.info("发送请求验证EWM系统参数: {}", request);
// 1. 构建请求头
org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON); // 设置Content-Type
headers.add("Accept-Charset", "UTF-8"); // 使用add方法设置Accept-Charset
// 构建Basic Auth认证信息 // 构建Basic Auth认证信息
String auth = "asrs:mom@123456"; String auth = "asrs:mom@123456";
String encodedAuth = java.util.Base64.getEncoder().encodeToString(auth.getBytes()); String encodedAuth = java.util.Base64.getEncoder().encodeToString(auth.getBytes());
headers.set("Authorization", "Basic " + encodedAuth); // Basic Auth
// 设置http请求 // 2. 构建请求体
HttpRequest httpRequest = HttpRequest.postInstanceOf( HttpEntity<SendEwmCheckContainerNo> entity = new HttpEntity<>(request, headers);
// 3. 发送 POST 请求
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<EwmApiBackResponse> response = restTemplate.postForEntity(
appCommon.getConfigByKey(AppConfigKeyEnums.EWM_CHECK_CONTAINER_NO_URL.getKey()), appCommon.getConfigByKey(AppConfigKeyEnums.EWM_CHECK_CONTAINER_NO_URL.getKey()),
request, entity,
30000, EwmApiBackResponse.class
"application/json", );
"Basic " + encodedAuth);
HttpResponse httpResponse = httpClient.httpPost(httpRequest);
if (httpResponse != null && httpResponse.isSuccess()) { // 4. 处理响应
return httpResponse.getData(EwmApiBackResponse.class); if (response.getStatusCode().is2xxSuccessful()) {
EwmApiBackResponse result = response.getBody();
log.info("EWM系统响应: {}", result);
return result;
} }
log.warn("请求验证EWM系统返回空响应或请求失败请求参数: {}", request); log.warn("请求验证EWM系统返回空响应或请求失败请求参数: {}", request);
@ -212,4 +236,64 @@ public class EwmApiServiceImpl implements IEwmApiService {
return EwmApiBackResponse.error("调用EWM系统接口异常: " + e.getMessage()); return EwmApiBackResponse.error("调用EWM系统接口异常: " + e.getMessage());
} }
} }
// @Override
// public EwmApiBackResponse sendEwmCheckContainerNo(SendEwmCheckContainerNo request) {
// try {
// log.info("发送请求验证EWM系统参数: {}", request);
//
// // 构建Basic Auth认证信息
// String auth = "asrs:mom@123456";
// String encodedAuth = java.util.Base64.getEncoder()
// .encodeToString(auth.getBytes(java.nio.charset.StandardCharsets.UTF_8));
//
// // 将请求对象转换为JSON字符串明确指定UTF-8编码
// ObjectMapper objectMapper = new ObjectMapper();
// String requestBody = objectMapper.writeValueAsString(request);
//
// // 设置http请求
// HttpRequest httpRequest = HttpRequest.postInstanceOf(
// appCommon.getConfigByKey(AppConfigKeyEnums.EWM_CHECK_CONTAINER_NO_URL.getKey()),
// requestBody, // 直接传递JSON字符串
// 30000,
// "application/json;charset=UTF-8",
// "Basic " + encodedAuth);
//
// // 添加必要的请求头
// httpRequest.addHeader("Accept-Charset", "UTF-8");
// httpRequest.addHeader("Accept", "application/json;charset=UTF-8");
// httpRequest.addHeader("Content-Type", "application/json;charset=UTF-8");
//
// // 如果HttpClient支持设置字符编码添加以下代码
// if (httpRequest instanceof ConfigurableHttpRequest) {
// ((ConfigurableHttpRequest) httpRequest).setCharset(StandardCharsets.UTF_8);
// }
//
// HttpResponse httpResponse = httpClient.httpPost(httpRequest);
//
// if (httpResponse != null && httpResponse.isSuccess()) {
// // 明确指定使用UTF-8解析响应
// String responseBody = httpResponse.getResponseBody();
// if (responseBody != null) {
// EwmApiBackResponse response = objectMapper.readValue(
// responseBody,
// EwmApiBackResponse.class
// );
// log.info("EWM系统响应: {}", response);
// return response;
// }
// }
//
// log.warn("请求验证EWM系统返回空响应或请求失败请求参数: {}", request);
// return EwmApiBackResponse.error("EWM系统返回空响应或请求失败");
// } catch (Exception e) {
// log.error("调用EWM系统接口异常请求参数: {}", request, e);
// return EwmApiBackResponse.error("调用EWM系统接口异常: " + e.getMessage());
// }
// }
} }

View File

@ -662,6 +662,7 @@ public class ConveyTaskServiceImpl implements IConveyTaskService {
inventoryConfirmVo.setSpecialStockNo(inventory.getSpecialStockNo()); inventoryConfirmVo.setSpecialStockNo(inventory.getSpecialStockNo());
inventoryConfirmVo.setSpecialStockItemNo(inventory.getSpecialStockItemNo()); inventoryConfirmVo.setSpecialStockItemNo(inventory.getSpecialStockItemNo());
inventoryConfirmVo.setBatchNo(inventory.getBatchNo()); inventoryConfirmVo.setBatchNo(inventory.getBatchNo());
inventoryConfirmVo.setMixVehicle(inventory.getMixVehicle());
// 返回结果 // 返回结果
return inventoryConfirmVo; return inventoryConfirmVo;
} }

View File

@ -447,52 +447,86 @@ public class StackerTaskServiceImpl implements IStackerTaskService {
} }
} }
for (TAppTempEwmInboundData tempEwmInboundData : tempEwmInboundDataList){ for (TAppTempEwmInboundData tempEwmInboundData : tempEwmInboundDataList){
// 新库存增加库存信息 // 先查询tempEwmInboundData在这个载具上是否有库存如果有则更新数量没有就作为新库存
TAppStock thisTimeNewStock = new TAppStock( List<TAppStock> existingStockWithSameGoodsId = thisVehicleStocks.stream()
UUIDUtils.getNewUUID(), .filter(stock -> Objects.equals(stock.getGoodsId(), tempEwmInboundData.getMatNo()))
vehicleId, .toList();
thisVehicleInTask.getDestination(),
WmsStockStatusEnums.OK.getCode(), if (!existingStockWithSameGoodsId.isEmpty()) {
tempEwmInboundData.getMatNo(), // 存在相同料号的库存更新数量
WmsGoodsStatusEnums.OK.getCode(), TAppStock matchingStock = existingStockWithSameGoodsId.getFirst();
thisVehicleInTask.getCreateTime(), matchingStock.setRealNum(matchingStock.getRealNum() + tempEwmInboundData.getSkuQty().intValue());
thisVehicleInTask.getOpUser(), matchingStock.setRemainNum(matchingStock.getRemainNum() + tempEwmInboundData.getSkuQty().intValue());
LocalDateTime.now(), matchingStock.setTotalNum(matchingStock.getTotalNum() + tempEwmInboundData.getSkuQty().intValue());
thisVehicleInTask.getOpUser(), matchingStock.setStockStatus(WmsStockStatusEnums.OK.getCode());
tempEwmInboundData.getSkuQty().intValue(), matchingStock.setLastUpdateTime(thisVehicleInTask.getCreateTime());
tempEwmInboundData.getSkuQty().intValue(), matchingStock.setLastUpdateUser(thisVehicleInTask.getOpUser());
tempEwmInboundData.getSkuQty().intValue(), matchingStock.setLocationId(thisVehicleInTask.getDestination());
tempEwmInboundData.getMatDesc(),
expireDate, newStockList.add(matchingStock);
sledDays,
null, // 更新库存更新记录
null, TAppStockUpdate stockUpdate = new TAppStockUpdate(
tempEwmInboundData.getBillNo(), UUIDUtils.getNewUUID(),
tempEwmInboundData.getBillType(), vehicleId,
tempEwmInboundData.getTaskNo(), tempEwmInboundData.getMatNo(),
tempEwmInboundData.getOrderNo(), thisVehicleInTask.getCreateTime(),
tempEwmInboundData.getOrderType(), matchingStock.getRealNum() - tempEwmInboundData.getSkuQty().intValue(),
tempEwmInboundData.getSkuUnit(), matchingStock.getRealNum(),
tempEwmInboundData.getRemark(), thisVehicleInTask.getOrigin() + ":入库合并",
tempEwmInboundData.getSpecialStock(), LocalDateTime.now(),
tempEwmInboundData.getSpecialStockNo(), thisVehicleInTask.getOpUser()
tempEwmInboundData.getSpecialStockItemNo(), );
tempEwmInboundData.getBatchNo() stockUpdateList.add(stockUpdate);
); } else {
newStockList.add(thisTimeNewStock); // 没有找到相同料号的库存创建新的库存记录
// 库存更新记录 TAppStock thisTimeNewStock = new TAppStock(
TAppStockUpdate stockUpdate = new TAppStockUpdate( UUIDUtils.getNewUUID(),
UUIDUtils.getNewUUID(), vehicleId,
thisTimeNewStock.getVehicleId(), thisVehicleInTask.getDestination(),
thisTimeNewStock.getGoodsId(), WmsStockStatusEnums.OK.getCode(),
thisTimeNewStock.getFirstInTime(), tempEwmInboundData.getMatNo(),
0, WmsGoodsStatusEnums.OK.getCode(),
thisTimeNewStock.getRealNum(), thisVehicleInTask.getCreateTime(),
thisVehicleInTask.getOrigin() + ":入库", thisVehicleInTask.getOpUser(),
LocalDateTime.now(), LocalDateTime.now(),
thisVehicleInTask.getOpUser() thisVehicleInTask.getOpUser(),
); tempEwmInboundData.getSkuQty().intValue(),
stockUpdateList.add(stockUpdate); tempEwmInboundData.getSkuQty().intValue(),
tempEwmInboundData.getSkuQty().intValue(),
tempEwmInboundData.getMatDesc(),
expireDate,
sledDays,
null,
null,
tempEwmInboundData.getBillNo(),
tempEwmInboundData.getBillType(),
tempEwmInboundData.getTaskNo(),
tempEwmInboundData.getOrderNo(),
tempEwmInboundData.getOrderType(),
tempEwmInboundData.getSkuUnit(),
tempEwmInboundData.getRemark(),
tempEwmInboundData.getSpecialStock(),
tempEwmInboundData.getSpecialStockNo(),
tempEwmInboundData.getSpecialStockItemNo(),
tempEwmInboundData.getBatchNo()
);
newStockList.add(thisTimeNewStock);
// 库存更新记录
TAppStockUpdate stockUpdate = new TAppStockUpdate(
UUIDUtils.getNewUUID(),
thisTimeNewStock.getVehicleId(),
thisTimeNewStock.getGoodsId(),
thisTimeNewStock.getFirstInTime(),
0,
thisTimeNewStock.getRealNum(),
thisVehicleInTask.getOrigin() + ":入库",
LocalDateTime.now(),
thisVehicleInTask.getOpUser()
);
stockUpdateList.add(stockUpdate);
}
needDeleteTempIds.add(tempEwmInboundData.getId()); needDeleteTempIds.add(tempEwmInboundData.getId());
} }

View File

@ -42,6 +42,11 @@ public interface IDataControllerService {
*/ */
List<TAppPickTask> getPickTaskDataP3(int pageNum, int pageSize); List<TAppPickTask> getPickTaskDataP3(int pageNum, int pageSize);
Map<String, Object> queryInOutBoundStatistics(String startDate, String endDate);
List<Map<String, Object>> queryStockAgeDistribution();
List<Map<String, Object>> queryRecentTask(); List<Map<String, Object>> queryRecentTask();
List<Map<String, Object>> queryStockDetail(String locationId); List<Map<String, Object>> queryStockDetail(String locationId);

View File

@ -18,6 +18,15 @@ public interface IStockControllerService {
*/ */
WmsApiResponse<PageVo<StockVo>> queryStocksByPage(StockQuery stockQuery); WmsApiResponse<PageVo<StockVo>> queryStocksByPage(StockQuery stockQuery);
/**
* 查询特殊库存字段为空且数量大于等于2的物料
* @param stockQuery 查询参数
* @return 查询结果
*/
WmsApiResponse<PageVo<StockVo>> querySpecialEmptyStocksForMix(StockQuery stockQuery);
/** /**
* 查询库存更新记录---分页 * 查询库存更新记录---分页
* @param stockUpdateQuery 查询参数 * @param stockUpdateQuery 查询参数
@ -30,4 +39,11 @@ public interface IStockControllerService {
* @return 所有库存信息 * @return 所有库存信息
*/ */
WmsApiResponse<PageVo<StockVo>> queryStocks(StockQuery stockQuery); WmsApiResponse<PageVo<StockVo>> queryStocks(StockQuery stockQuery);
/**
* 查询所有非限制库存信息
* @return 所有库存信息
*/
WmsApiResponse<PageVo<StockVo>> queryStocksBySpecial(StockQuery stockQuery);
} }

View File

@ -15,6 +15,7 @@ import com.wms_main.model.dto.response.wcs.BaseWcsApiResponse;
import com.wms_main.model.dto.response.wcs.WcsApiResponse; import com.wms_main.model.dto.response.wcs.WcsApiResponse;
import com.wms_main.model.dto.response.wms.BaseWmsApiResponse; import com.wms_main.model.dto.response.wms.BaseWmsApiResponse;
import com.wms_main.model.dto.response.wms.WmsApiResponse; import com.wms_main.model.dto.response.wms.WmsApiResponse;
import com.wms_main.model.po.TAppInventory;
import com.wms_main.model.po.TAppStockCompare; import com.wms_main.model.po.TAppStockCompare;
import com.wms_main.model.vo.wms.InventoryConfirmVo; import com.wms_main.model.vo.wms.InventoryConfirmVo;
import com.wms_main.model.vo.wms.TaskConfirmVo; import com.wms_main.model.vo.wms.TaskConfirmVo;
@ -109,6 +110,8 @@ public interface ITaskControllerService {
*/ */
BaseWmsApiResponse requestInventory(InventoryRequest inventoryRequest); BaseWmsApiResponse requestInventory(InventoryRequest inventoryRequest);
/** /**
* 批量下发盘点请求 * 批量下发盘点请求
* @param batchInventoryRequest 批量盘点请求 * @param batchInventoryRequest 批量盘点请求
@ -131,6 +134,13 @@ public interface ITaskControllerService {
*/ */
BaseWmsApiResponse confirmInventory(InventoryConfirmRequest inventoryConfirmRequest); BaseWmsApiResponse confirmInventory(InventoryConfirmRequest inventoryConfirmRequest);
/**
* 确认合并信息
* @param inventoryConfirmRequest 确认请求
* @return 处理结果
*/
BaseWmsApiResponse confirmMixStock(InventoryConfirmRequest inventoryConfirmRequest);
/** /**
* 获取对应工单的缺料数量 * 获取对应工单的缺料数量
* 最小值为0最大值为库存数量 * 最小值为0最大值为库存数量
@ -149,6 +159,9 @@ public interface ITaskControllerService {
EwmApiBackResponse ewmRequestOutTask(EwmOutTaskRequest ewmOutTaskRequest); EwmApiBackResponse ewmRequestOutTask(EwmOutTaskRequest ewmOutTaskRequest);
//BaseWmsApiResponse requestInventoryByMixStock(InventoryRequest inventoryRequest, List<TAppInventory> inventory);
/** /**
* EWM获取库存信息 * EWM获取库存信息
* @param request 获取库存信息请求 * @param request 获取库存信息请求
@ -170,4 +183,20 @@ public interface ITaskControllerService {
*/ */
EwmApiBackResponse cancelOrderInTasks(SendWarehouseInCompletedRequest request); EwmApiBackResponse cancelOrderInTasks(SendWarehouseInCompletedRequest request);
/**
* 创建混合库存盘点任务
* @param mixStockRequest 盘点请求
* @return 处理结果
*/
BaseWmsApiResponse createInventoryFromMixStock(MixStockRequest mixStockRequest);
/**
* 手动生成出库任务
* @param manualOutTaskRequest 手动出库任务请求参数
* @return 处理结果
*/
BaseWmsApiResponse pdaOutTask(InventoryRequest manualOutTaskRequest);
} }

View File

@ -74,4 +74,7 @@ public interface ITaskQueryControllerService {
* @return 更新结果 * @return 更新结果
*/ */
WmsApiResponse<String> updateOutsInfo(TAppPickTask appTask); WmsApiResponse<String> updateOutsInfo(TAppPickTask appTask);
WmsApiResponse<String> updateOutsTaskStatus(OutsBatchEditDateVo batchEditDateVo);
} }

View File

@ -95,6 +95,144 @@ public class DataControllerServiceImpl implements IDataControllerService {
return tAppPickTaskService.page(new Page<>(pageNum, pageSize), queryWrapper).getRecords(); return tAppPickTaskService.page(new Page<>(pageNum, pageSize), queryWrapper).getRecords();
} }
@Override
public Map<String, Object> queryInOutBoundStatistics(String startDate, String endDate) {
// 验证日期参数
if (startDate == null || endDate == null) {
throw new IllegalArgumentException("开始日期和结束日期不能为空");
}
// 解析日期范围
LocalDate start = LocalDate.parse(startDate);
LocalDate end = LocalDate.parse(endDate);
// 生成日期列表
List<String> dateList = new ArrayList<>();
LocalDate currentDate = start;
while (!currentDate.isAfter(end)) {
dateList.add(currentDate.toString());
currentDate = currentDate.plusDays(1);
}
// 初始化统计映射
Map<String, Integer> inboundMap = new HashMap<>();
Map<String, Integer> outboundMap = new HashMap<>();
// 初始化所有日期的计数为0
for (String date : dateList) {
inboundMap.put(date, 0);
outboundMap.put(date, 0);
}
// 构造查询条件查询指定日期范围内的任务
LambdaQueryWrapper<TAppTaskBak> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.ge(TAppTaskBak::getCreateTime, start.atStartOfDay())
.le(TAppTaskBak::getCreateTime, end.atTime(23, 59, 59));
List<TAppTaskBak> taskList = tAppTaskBakService.list(queryWrapper);
// 统计入库和出库任务数量
for (TAppTaskBak task : taskList) {
// 提取日期部分
String taskDate = task.getCreateTime().toLocalDate().toString();
if (task.getTaskType() != null) {
switch (task.getTaskType()) {
case 1: // 入库任务
inboundMap.put(taskDate, inboundMap.getOrDefault(taskDate, 0) + 1);
break;
case 2: // 出库任务
outboundMap.put(taskDate, outboundMap.getOrDefault(taskDate, 0) + 1);
break;
// 其他任务类型不统计
}
}
}
// 构建结果数据
List<Integer> inboundCounts = new ArrayList<>();
List<Integer> outboundCounts = new ArrayList<>();
for (String date : dateList) {
inboundCounts.add(inboundMap.get(date));
outboundCounts.add(outboundMap.get(date));
}
Map<String, Object> result = new HashMap<>();
result.put("dates", dateList);
result.put("inbound", inboundCounts);
result.put("outbound", outboundCounts);
return result;
}
@Override
public List<Map<String, Object>> queryStockAgeDistribution() {
// 查询所有库存数据
List<TAppStock> stockList = tAppStockService.list();
// 定义时间区间统计变量
int range0To7Days = 0; // 0-7天
int range8To30Days = 0; // 8-30天
int range1To3Months = 0; // 1-3个月
int range3To6Months = 0; // 3-6个月
int rangeAbove6Months = 0; // 6个月以上
LocalDate now = LocalDate.now();
for (TAppStock stock : stockList) {
// 使用 first_in_time 字段计算库存龄期入库时间
if (stock.getFirstInTime() != null) {
LocalDate firstInDate = stock.getFirstInTime().toLocalDate();
long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(firstInDate, now);
if (daysBetween <= 7) {
range0To7Days++;
} else if (daysBetween <= 30) {
range8To30Days++;
} else if (daysBetween <= 90) { // 3个月
range1To3Months++;
} else if (daysBetween <= 180) { // 6个月
range3To6Months++;
} else {
rangeAbove6Months++;
}
}
}
// 构建返回数据
List<Map<String, Object>> result = new ArrayList<>();
Map<String, Object> range0_7 = new HashMap<>();
range0_7.put("name", "0-7天");
range0_7.put("value", range0To7Days);
result.add(range0_7);
Map<String, Object> range8_30 = new HashMap<>();
range8_30.put("name", "8-30天");
range8_30.put("value", range8To30Days);
result.add(range8_30);
Map<String, Object> range1_3 = new HashMap<>();
range1_3.put("name", "1-3个月");
range1_3.put("value", range1To3Months);
result.add(range1_3);
Map<String, Object> range3_6 = new HashMap<>();
range3_6.put("name", "3-6个月");
range3_6.put("value", range3To6Months);
result.add(range3_6);
Map<String, Object> range6Plus = new HashMap<>();
range6Plus.put("name", "6个月以上");
range6Plus.put("value", rangeAbove6Months);
result.add(range6Plus);
return result;
}
@Override @Override
public List<Map<String, Object>> queryRecentTask() { public List<Map<String, Object>> queryRecentTask() {
List<Map<String, Object>> result = new ArrayList<>(); List<Map<String, Object>> result = new ArrayList<>();

View File

@ -18,6 +18,11 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/** /**
* 库存控制类 服务实现 * 库存控制类 服务实现
@ -66,6 +71,99 @@ public class StockControllerServiceImpl implements IStockControllerService {
return WmsApiResponse.success("查询库存数据成功", pageVo); return WmsApiResponse.success("查询库存数据成功", pageVo);
} }
@Override
public WmsApiResponse<PageVo<StockVo>> querySpecialEmptyStocksForMix(StockQuery stockQuery) {
if (stockQuery == null) {
return WmsApiResponse.error("查询参数不能为NULL", null);
}
// 构建查询条件查找特殊库存字段为空的记录
LambdaQueryWrapper<TAppStock> lambdaQueryWrapper = new LambdaQueryWrapper<TAppStock>()
.like(StringUtils.isNotEmpty(stockQuery.getVehicleId()), TAppStock::getVehicleId, stockQuery.getVehicleId())
.like(StringUtils.isNotEmpty(stockQuery.getLocationId()), TAppStock::getLocationId, stockQuery.getLocationId())
.like(StringUtils.isNotEmpty(stockQuery.getGoodsId()), TAppStock::getGoodsId, stockQuery.getGoodsId())
.eq(stockQuery.getStockStatus() != null, TAppStock::getStockStatus, stockQuery.getStockStatus())
.eq(stockQuery.getGoodsStatus() != null, TAppStock::getGoodsStatus, stockQuery.getGoodsStatus());
// 添加特殊库存字段为空的条件specialStockspecialStockNospecialStockItemNobatchNo null empty
lambdaQueryWrapper.and(wrapper -> wrapper
.and(w -> w.isNull(TAppStock::getSpecialStock).or().eq(TAppStock::getSpecialStock, ""))
.or()
.and(w -> w.isNull(TAppStock::getSpecialStockNo).or().eq(TAppStock::getSpecialStockNo, ""))
.or()
.and(w -> w.isNull(TAppStock::getSpecialStockItemNo).or().eq(TAppStock::getSpecialStockItemNo, ""))
.or()
.and(w -> w.isNull(TAppStock::getBatchNo).or().eq(TAppStock::getBatchNo, ""))
);
// 查询所有符合条件的记录
List<TAppStock> allRecords = appStockService.list(lambdaQueryWrapper);
// goodsId 分组并统计
Map<String, List<TAppStock>> groupedByGoodsId = allRecords.stream()
.collect(Collectors.groupingBy(TAppStock::getGoodsId));
// 创建汇总后的记录列表只包含记录数大于1的分组
List<TAppStock> aggregatedRecords = groupedByGoodsId.values().stream()
.filter(records -> records.size() > 1) // 只处理有多条记录的分组
.map(records -> {
// 取第一条记录作为基础创建新对象并复制属性
TAppStock firstRecord = records.getFirst();
TAppStock aggregatedRecord = new TAppStock();
// 复制基础属性根据实际的TAppStock类字段进行调整
aggregatedRecord.setGoodsId(firstRecord.getGoodsId());
aggregatedRecord.setGoodsDesc(firstRecord.getGoodsDesc());
aggregatedRecord.setRealNum(records.size()); // 设置记录条数
// 根据实际需要复制其他必要字段...
// 使用 remainNum 作为统计字段
int totalQuantity = records.stream()
.mapToInt(TAppStock::getRemainNum)
.sum();
// 设置总数量
aggregatedRecord.setRemainNum(totalQuantity);
return aggregatedRecord;
})
.collect(Collectors.toList());
// 获取分页参数
Page<TAppStock> page = stockQuery.toMpPage();
int pageNum = (int) page.getCurrent();
int pageSize = (int) page.getSize();
// 手动分页处理
int start = (pageNum - 1) * pageSize;
int end = Math.min(start + pageSize, aggregatedRecords.size());
List<TAppStock> pagedRecords;
if (start >= aggregatedRecords.size()) {
pagedRecords = new ArrayList<>();
} else {
pagedRecords = aggregatedRecords.subList(start, end);
}
// 创建分页结果
Page<TAppStock> resultPage = new Page<>();
resultPage.setRecords(pagedRecords);
resultPage.setCurrent(pageNum);
resultPage.setSize(pageSize);
resultPage.setTotal(aggregatedRecords.size());
PageVo<StockVo> pageVo = PageVo.of(resultPage, StockVo::ofPo);
return WmsApiResponse.success("查询特殊空库存成功", pageVo);
}
/** /**
* 查询库存更新记录分页---实现 * 查询库存更新记录分页---实现
* @param stockUpdateQuery 查询参数 * @param stockUpdateQuery 查询参数
@ -125,4 +223,43 @@ public class StockControllerServiceImpl implements IStockControllerService {
return WmsApiResponse.success("查询库存数据成功", pageVo); return WmsApiResponse.success("查询库存数据成功", pageVo);
} }
@Override
public WmsApiResponse<PageVo<StockVo>> queryStocksBySpecial(StockQuery stockQuery) {
if (stockQuery == null) {
return WmsApiResponse.error("查询参数不能为NULL", null);
}
Page<TAppStock> page = stockQuery.toMpPage();
LambdaQueryWrapper<TAppStock> lambdaQueryWrapper = new LambdaQueryWrapper<TAppStock>()
.like(StringUtils.isNotEmpty(stockQuery.getVehicleId()), TAppStock::getVehicleId, stockQuery.getVehicleId())
.like(StringUtils.isNotEmpty(stockQuery.getLocationId()), TAppStock::getLocationId, stockQuery.getLocationId())
.like(StringUtils.isNotEmpty(stockQuery.getGoodsId()), TAppStock::getGoodsId, stockQuery.getGoodsId())
.eq(stockQuery.getStockStatus() != null, TAppStock::getStockStatus, stockQuery.getStockStatus())
.eq(stockQuery.getGoodsStatus() != null, TAppStock::getGoodsStatus, stockQuery.getGoodsStatus());
if (stockQuery.getFromDate() != null) {
lambdaQueryWrapper.ge(TAppStock::getFirstInTime, LocalDateTime.of(stockQuery.getFromDate(), LocalDateTime.MIN.toLocalTime()));
}
if (stockQuery.getToDate() != null) {
lambdaQueryWrapper.le(TAppStock::getFirstInTime, LocalDateTime.of(stockQuery.getToDate(), LocalDateTime.MAX.toLocalTime()));
}
// 添加特殊库存字段为空的条件specialStockspecialStockNospecialStockItemNobatchNo null empty
lambdaQueryWrapper.and(wrapper -> wrapper
.and(w -> w.isNull(TAppStock::getSpecialStock).or().eq(TAppStock::getSpecialStock, ""))
.or()
.and(w -> w.isNull(TAppStock::getSpecialStockNo).or().eq(TAppStock::getSpecialStockNo, ""))
.or()
.and(w -> w.isNull(TAppStock::getSpecialStockItemNo).or().eq(TAppStock::getSpecialStockItemNo, ""))
.or()
.and(w -> w.isNull(TAppStock::getBatchNo).or().eq(TAppStock::getBatchNo, ""))
);
Page<TAppStock> stockPage = appStockService.page(page, lambdaQueryWrapper);
PageVo<StockVo> pageVo = PageVo.of(stockPage, StockVo::ofPo);
return WmsApiResponse.success("查询特殊库存数据成功", pageVo);
}
} }

View File

@ -30,7 +30,6 @@ import com.wms_main.model.dto.response.wms.WmsApiResponse;
import com.wms_main.model.po.*; import com.wms_main.model.po.*;
import com.wms_main.model.vo.wms.InventoryConfirmVo; import com.wms_main.model.vo.wms.InventoryConfirmVo;
import com.wms_main.model.vo.wms.TaskConfirmVo; import com.wms_main.model.vo.wms.TaskConfirmVo;
import com.wms_main.model.vo.wms.WorkConfirmVo;
import com.wms_main.repository.utils.ConvertUtils; import com.wms_main.repository.utils.ConvertUtils;
import com.wms_main.repository.utils.DbTransUtils; import com.wms_main.repository.utils.DbTransUtils;
import com.wms_main.repository.utils.StringUtils; import com.wms_main.repository.utils.StringUtils;
@ -48,7 +47,6 @@ import org.springframework.transaction.interceptor.TransactionAspectSupport;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* 任务控制类 服务实现 * 任务控制类 服务实现
@ -76,6 +74,7 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
private final ITAppInventoryRecordService appInventoryRecordService;// 盘点记录服务 private final ITAppInventoryRecordService appInventoryRecordService;// 盘点记录服务
private final ITAppWorkService appWorkService;// 工作详情服务 private final ITAppWorkService appWorkService;// 工作详情服务
private final AppCommon appCommon; private final AppCommon appCommon;
private final ITAppTaskService appTaskService;
private final IEwmApiService ewmApiService; private final IEwmApiService ewmApiService;
// 在其他依赖注入后面添加 // 在其他依赖注入后面添加
private final ITempEwmInboundDataService tempEwmInboundDataService; private final ITempEwmInboundDataService tempEwmInboundDataService;
@ -291,6 +290,7 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
// 添加任务 // 添加任务
TAppOuts task = new TAppOuts( TAppOuts task = new TAppOuts(
UUIDUtils.getNewUUID(), UUIDUtils.getNewUUID(),
0,
stockOutRequest.getGoodsId(), stockOutRequest.getGoodsId(),
stockOutRequest.getVehicleId(), stockOutRequest.getVehicleId(),
stockOutRequest.getNeedNum(), stockOutRequest.getNeedNum(),
@ -466,6 +466,7 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
// 添加任务 // 添加任务
TAppOuts task = new TAppOuts( TAppOuts task = new TAppOuts(
UUIDUtils.getNewUUID(), UUIDUtils.getNewUUID(),
-1,
stockOutRequest.getGoodsId(), stockOutRequest.getGoodsId(),
stockOutRequest.getVehicleId(), stockOutRequest.getVehicleId(),
stockOutRequest.getNeedNum(), stockOutRequest.getNeedNum(),
@ -635,6 +636,7 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
} }
TAppOuts outs = new TAppOuts( TAppOuts outs = new TAppOuts(
task.getTaskNo(), task.getTaskNo(),
-1,
task.getMatNo(), task.getMatNo(),
null, null,
task.getPickingQty().intValue(), task.getPickingQty().intValue(),
@ -680,6 +682,337 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
return response; return response;
} }
@Override
@Transactional(rollbackFor = Exception.class)
public BaseWmsApiResponse createInventoryFromMixStock(MixStockRequest mixStockRequest) {
// 验证请求参数
if (mixStockRequest == null) {
return BaseWmsApiResponse.error("请求参数不能为空");
}
if (mixStockRequest.getStockItems() == null || mixStockRequest.getStockItems().isEmpty()) {
return BaseWmsApiResponse.error("库存项列表不能为空");
}
try {
// 用于存储已创建的盘点任务ID以便在出现错误时进行回滚删除
List<TAppInventory> inventory = new ArrayList<>();
// 遍历混合库存数据逐个创建盘点任务
for (MixStockRequest.StockItem stockItem : mixStockRequest.getStockItems()) {
if (stockItem == null ||
StringUtils.isEmpty(stockItem.getVehicleNo()) ||
StringUtils.isEmpty(stockItem.getMaterialNo())) {
continue; // 跳过无效的库存项
}
// 创建盘点请求对象复用现有的requestInventory方法
InventoryRequest inventoryRequest = new InventoryRequest();
inventoryRequest.setGoodsId(stockItem.getMaterialNo()); // 物料号
inventoryRequest.setVehicleId(stockItem.getVehicleNo()); // 载具号
inventoryRequest.setStandId(mixStockRequest.getStandId()); // 系统默认站台可根据实际需求调整
inventoryRequest.setUserName("SystemByMix"); // 系统用户
inventoryRequest.setSpecialStock(null); // 特殊库存
inventoryRequest.setSpecialStockNo(null); // 特殊库存号
inventoryRequest.setSpecialStockItemNo(null); // 特殊库存项目号
inventoryRequest.setBatchNo(null); // 批次号
// 调用现有的盘点任务生成方法
BaseWmsApiResponse response = requestInventoryByMixStock(inventoryRequest,inventory,mixStockRequest.getBindBoxNo());
if (response.getCode() != 0) {
log.error("创建合并任务失败,载具号: {}, 物料号: {},错误信息: {}",
stockItem.getVehicleNo(), stockItem.getMaterialNo(), response.getMessage());
return BaseWmsApiResponse.error("创建合并任务失败,载具号: " + stockItem.getVehicleNo() +
",物料号: " + stockItem.getMaterialNo() + ",错误信息: " + response.getMessage());
}
}
if (!inventory.isEmpty()){
appInventoryService.saveBatch(inventory);
}
return BaseWmsApiResponse.success("合并库存任务创建完成,共创建 " + inventory.size() + " 个出库任务");
} catch (Exception e) {
log.error("创建混合库存盘点任务时发生异常", e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return BaseWmsApiResponse.error("创建合并任务时发生异常: " + e.getMessage());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public BaseWmsApiResponse pdaOutTask(InventoryRequest manualOutTaskRequest) {
try {
// 验证请求参数
if (manualOutTaskRequest == null) {
return BaseWmsApiResponse.error("请求参数不能为空");
}
if (StringUtils.isEmpty(manualOutTaskRequest.getVehicleId())) {
return BaseWmsApiResponse.error("载具ID不能为空");
}
String vehicleId = manualOutTaskRequest.getVehicleId();
String origin = manualOutTaskRequest.getOrigin();
String remark = "PDA手持呼叫";
// 生成出库任务
TAppTask outTask = new TAppTask(
UUIDUtils.getNewUUID(),
WmsTaskTypeEnums.OUT.getCode(),
0,
9,
vehicleId,
origin,
null,
null,
LocalDateTime.now(),
null,
AppConstant.EMPTY_GOODS_ID,
0,
0,
"",
remark,
"P21",
""
);
boolean taskSaveSuccess = appWmsTaskService.save(outTask);
if (!taskSaveSuccess) {
log.error("保存出库任务失败载具ID: {}", vehicleId);
return BaseWmsApiResponse.error("保存出库任务失败");
}
// 添加到达即删除的拣选任务
TAppPickTask pickTask = new TAppPickTask(
UUIDUtils.getNewUUID(),
"P21", // 与出库任务的目标站台一致
vehicleId,
WmsPickTaskStatusEnum.TEMP.getCode(),
LocalDateTime.now(),
null,
null,
null,
3 // 类型设置为3表示到达即删除
);
boolean pickTaskSaveSuccess = appPickTaskService.save(pickTask);
if (!pickTaskSaveSuccess) {
log.error("保存拣选任务失败载具ID: {}", vehicleId);
// 尝试删除已保存的出库任务以保持数据一致性
try {
appWmsTaskService.removeById(outTask.getTaskId());
} catch (Exception rollbackEx) {
log.error("回滚删除出库任务失败任务ID: {}", outTask.getTaskId(), rollbackEx);
}
return BaseWmsApiResponse.error("保存拣选任务失败");
}
// 更新载具状态为出库中
boolean vehicleUpdateSuccess = appVehicleService.update(
new LambdaUpdateWrapper<TAppVehicle>()
.set(TAppVehicle::getVehicleStatus, WmsVehicleStatusEnums.OUT.getCode())
.eq(TAppVehicle::getVehicleId, vehicleId)
);
if (!vehicleUpdateSuccess) {
log.warn("更新载具状态失败载具ID: {}", vehicleId);
}
// 更新库存状态为出库中
boolean stockUpdateSuccess = appStockService.update(
new LambdaUpdateWrapper<TAppStock>()
.set(TAppStock::getStockStatus, WmsStockStatusEnums.OUTING.getCode())
.eq(TAppStock::getVehicleId, vehicleId)
);
if (!stockUpdateSuccess) {
log.warn("更新库存状态失败载具ID: {}", vehicleId);
}
log.info("PDA手持呼叫出库任务创建成功载具ID: {}", vehicleId);
return BaseWmsApiResponse.success("PDA手持呼叫出库任务创建成功");
} catch (Exception e) {
log.error("PDA手持呼叫出库任务创建过程中发生异常", e);
return BaseWmsApiResponse.error("创建出库任务时发生异常: " + e.getMessage());
}
}
/**
* 请求盘点---实现
*
* @param inventoryRequest 盘点请求
* @return 请求结果
*/
private BaseWmsApiResponse requestInventoryByMixStock(InventoryRequest inventoryRequest, List<TAppInventory> inventory, String bindBoxNo) {
if (inventoryRequest == null) {
return BaseWmsApiResponse.error("请求为NULL。");
}
if (StringUtils.isEmpty(inventoryRequest.getStandId())) {
return BaseWmsApiResponse.error("请求缺少信息,请输入站台号。");
}
if (!appStandService.exists(new LambdaQueryWrapper<TAppStand>().eq(TAppStand::getStandId, inventoryRequest.getStandId())
.eq(TAppStand::getStandType, 1))) {
return BaseWmsApiResponse.error("非拣选站台不允许盘点。");
}
if (StringUtils.isEmpty(inventoryRequest.getGoodsId())) {
return BaseWmsApiResponse.error("请求缺少信息,请输入料号。");
}
// 判断这个料有没有还没盘点完的任务
List<TAppInventory> inventoryList = appInventoryService.list(new LambdaQueryWrapper<TAppInventory>()
.eq(TAppInventory::getGoodsId, inventoryRequest.getGoodsId())
.and(wrapper -> {
// specialStock匹配逻辑
if (StringUtils.isEmpty(inventoryRequest.getSpecialStock())) {
// 传入空值匹配数据库中的NULL或空字符串
wrapper.isNull(TAppInventory::getSpecialStock)
.or()
.eq(TAppInventory::getSpecialStock, "");
} else {
// 传入具体值精确匹配
wrapper.eq(TAppInventory::getSpecialStock, inventoryRequest.getSpecialStock());
}
})
.and(wrapper -> {
// specialStockNo匹配逻辑
if (StringUtils.isEmpty(inventoryRequest.getSpecialStockNo())) {
// 传入空值匹配数据库中的NULL或空字符串
wrapper.isNull(TAppInventory::getSpecialStockNo)
.or()
.eq(TAppInventory::getSpecialStockNo, "");
} else {
// 传入具体值精确匹配
wrapper.eq(TAppInventory::getSpecialStockNo, inventoryRequest.getSpecialStockNo());
}
})
.and(wrapper -> {
// specialStockItemNo匹配逻辑
if (StringUtils.isEmpty(inventoryRequest.getSpecialStockItemNo())) {
// 传入空值匹配数据库中的NULL或空字符串
wrapper.isNull(TAppInventory::getSpecialStockItemNo)
.or()
.eq(TAppInventory::getSpecialStockItemNo, "");
} else {
// 传入具体值精确匹配
wrapper.eq(TAppInventory::getSpecialStockItemNo, inventoryRequest.getSpecialStockItemNo());
}
})
.and(wrapper -> {
// batchNo匹配逻辑
if (StringUtils.isEmpty(inventoryRequest.getBatchNo())) {
// 传入空值匹配数据库中的NULL或空字符串
wrapper.isNull(TAppInventory::getBatchNo)
.or()
.eq(TAppInventory::getBatchNo, "");
} else {
// 传入具体值精确匹配
wrapper.eq(TAppInventory::getBatchNo, inventoryRequest.getBatchNo());
}
})
.eq(StringUtils.isNotEmpty(inventoryRequest.getVehicleId()), TAppInventory::getVehicleId, inventoryRequest.getVehicleId()));
if (inventoryList != null && !inventoryList.isEmpty()) {
String errMsg = StringUtils.isNotEmpty(inventoryRequest.getVehicleId()) ? "载具:" + inventoryRequest.getVehicleId() : "";
return BaseWmsApiResponse.error(errMsg + "该料号还有盘点任务未完成。");
}
// 查询库存创建盘点任务
List<TAppStock> stockList = appStockService.list(new LambdaQueryWrapper<TAppStock>()
.eq(TAppStock::getGoodsId, inventoryRequest.getGoodsId())
.and(wrapper -> {
// specialStock匹配逻辑
if (StringUtils.isEmpty(inventoryRequest.getSpecialStock())) {
// 传入空值匹配数据库中的NULL或空字符串
wrapper.isNull(TAppStock::getSpecialStock)
.or()
.eq(TAppStock::getSpecialStock, "");
} else {
// 传入具体值精确匹配
wrapper.eq(TAppStock::getSpecialStock, inventoryRequest.getSpecialStock());
}
})
.and(wrapper -> {
// specialStockNo匹配逻辑
if (StringUtils.isEmpty(inventoryRequest.getSpecialStockNo())) {
// 传入空值匹配数据库中的NULL或空字符串
wrapper.isNull(TAppStock::getSpecialStockNo)
.or()
.eq(TAppStock::getSpecialStockNo, "");
} else {
// 传入具体值精确匹配
wrapper.eq(TAppStock::getSpecialStockNo, inventoryRequest.getSpecialStockNo());
}
})
.and(wrapper -> {
// specialStockItemNo匹配逻辑
if (StringUtils.isEmpty(inventoryRequest.getSpecialStockItemNo())) {
// 传入空值匹配数据库中的NULL或空字符串
wrapper.isNull(TAppStock::getSpecialStockItemNo)
.or()
.eq(TAppStock::getSpecialStockItemNo, "");
} else {
// 传入具体值精确匹配
wrapper.eq(TAppStock::getSpecialStockItemNo, inventoryRequest.getSpecialStockItemNo());
}
})
.and(wrapper -> {
// batchNo匹配逻辑
if (StringUtils.isEmpty(inventoryRequest.getBatchNo())) {
// 传入空值匹配数据库中的NULL或空字符串
wrapper.isNull(TAppStock::getBatchNo)
.or()
.eq(TAppStock::getBatchNo, "");
} else {
// 传入具体值精确匹配
wrapper.eq(TAppStock::getBatchNo, inventoryRequest.getBatchNo());
}
})
.eq(StringUtils.isNotEmpty(inventoryRequest.getVehicleId()), TAppStock::getVehicleId, inventoryRequest.getVehicleId()));
if (stockList == null || stockList.isEmpty()) {
return BaseWmsApiResponse.error("物料" + inventoryRequest.getGoodsId() + "没有库存。");
}
// 根据载具号map一下
Map<String, TAppInventory> inventoryByVehicleMap = new HashMap<>();
// 盘点单号
String orderId = UUIDUtils.getNewUUID();
for (TAppStock stock : stockList) {
if (!inventoryByVehicleMap.containsKey(stock.getVehicleId())) {
if (stock.getStockStatus() != 0){
return BaseWmsApiResponse.error("载具:" + stock.getVehicleId() + "正在出库。");
}
// 添加盘点任务
TAppInventory inventoryTask = new TAppInventory(
UUIDUtils.getNewUUID(),
inventoryRequest.getGoodsId(),
stock.getVehicleId(),
null,
null,
inventoryRequest.getStandId(),
inventoryRequest.getUserName(),
WmsInvTypeEnums.MIX_STOCK.getCode(),
WmsInvStatusEnums.INIT.getCode(),
WmsInvResultEnums.NONE.getCode(),
LocalDateTime.now(),
null,
orderId,
inventoryRequest.getSpecialStock(),
inventoryRequest.getSpecialStockNo(),
inventoryRequest.getSpecialStockItemNo(),
inventoryRequest.getBatchNo(),
bindBoxNo
);
inventoryByVehicleMap.put(stock.getVehicleId(), inventoryTask);
}
}
if (!inventoryByVehicleMap.isEmpty()) {
inventory.addAll(inventoryByVehicleMap.values());
}
// 保存盘点任务
return BaseWmsApiResponse.success("创建盘点任务成功。");
}
@Override @Override
@ -949,6 +1282,7 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
// 添加任务 // 添加任务
outsList.add(new TAppOuts( outsList.add(new TAppOuts(
UUIDUtils.getNewUUID(), UUIDUtils.getNewUUID(),
-1,
goodsId, goodsId,
"", "",
1, 1,
@ -1308,6 +1642,11 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
} }
} }
if(dataList.size() > 1){
log.info("EWM返回数据中存在多条不同物料号的数据载具号: {}", vehicleNo);
return false;
}
if (!dataList.isEmpty()) { if (!dataList.isEmpty()) {
// 批量插入时指定合理的批次大小 // 批量插入时指定合理的批次大小
boolean saveResult = tempEwmInboundDataService.saveBatch(dataList); boolean saveResult = tempEwmInboundDataService.saveBatch(dataList);
@ -1490,6 +1829,28 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
return WcsApiResponse.error("插入了多条不符合要求的数据。", null); return WcsApiResponse.error("插入了多条不符合要求的数据。", null);
} }
if (response.getContent() != null &&
response.getContent().getTaskDetailInfo() != null &&
!response.getContent().getTaskDetailInfo().isEmpty() &&
!stockList.isEmpty()) {
// 此时才安全地获取数据
TaskDetailInfo taskDetailInfo = response.getContent().getTaskDetailInfo().getFirst();
TAppStock stock = stockList.getFirst();
if (!Objects.equals(taskDetailInfo.getMatNo(), stock.getGoodsId())){
return WcsApiResponse.error("该载具号已存在不同料号的库存,请检查。", null);
}
// 比较特殊库存字段将null和空字符串视为相同
if (!StringUtils.isStringEqual(taskDetailInfo.getSpecialStock(), stock.getSpecialStock()) ||
!StringUtils.isStringEqual(taskDetailInfo.getSpecialStockNo(), stock.getSpecialStockNo()) ||
!StringUtils.isStringEqual(taskDetailInfo.getSpecialStockItemNo(), stock.getSpecialStockItemNo()) ||
!StringUtils.isStringEqual(taskDetailInfo.getBatchNo(), stock.getBatchNo())) {
return WcsApiResponse.error("该载具号已存在不同特殊库存的库存,请检查。", null);
}
}
// 3. 根据临时表数据生成WMS任务 // 3. 根据临时表数据生成WMS任务
generateWmsTasksFromTempData(wcsVehicleInRequest); generateWmsTasksFromTempData(wcsVehicleInRequest);
@ -1616,6 +1977,7 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
} }
/** /**
* 实现 * 实现
* *
@ -1898,20 +2260,22 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
String existingSecondPickingCode = existingCheck.getSecondPickingCode(); String existingSecondPickingCode = existingCheck.getSecondPickingCode();
String newSecondPickingCode = thisOut.getFirst().getSecondPickingCode(); String newSecondPickingCode = thisOut.getFirst().getSecondPickingCode();
boolean isSecondPickingCodeMatch = (StringUtils.isEmpty(existingSecondPickingCode) && StringUtils.isEmpty(newSecondPickingCode)) boolean isSecondPickingCodeEmpty = StringUtils.isEmpty(existingSecondPickingCode) && StringUtils.isEmpty(newSecondPickingCode);
|| Objects.equals(existingSecondPickingCode, newSecondPickingCode); boolean isSecondPickingCodeMatch = isSecondPickingCodeEmpty || Objects.equals(existingSecondPickingCode, newSecondPickingCode);
if (!isSecondPickingCodeMatch) { if (!isSecondPickingCodeMatch) {
return BaseWmsApiResponse.error("当前目标箱号已存在其他颗粒度物料,请检查。"); return BaseWmsApiResponse.error("当前目标箱号已存在其他颗粒度物料,请检查。");
} }
// 2. 如果颗粒度一致包括都为空再检测工单号是否相同 // 2. 只有当颗粒度都为空时才校验工单号
String existingOrderNo = existingCheck.getOrderNo(); if (isSecondPickingCodeEmpty) {
String newOrderNo = thisOut.getFirst().getOrderNo(); String existingOrderNo = existingCheck.getOrderNo();
String newOrderNo = thisOut.getFirst().getOrderNo();
boolean isOrderNoMatch = Objects.equals(existingOrderNo, newOrderNo); boolean isOrderNoMatch = Objects.equals(existingOrderNo, newOrderNo);
if (!isOrderNoMatch) { if (!isOrderNoMatch) {
return BaseWmsApiResponse.error("当前目标箱号已存在其他工单号物料,请检查。"); return BaseWmsApiResponse.error("当前目标箱号已存在其他工单号物料,请检查。");
}
} }
} }
// 当前站台到达的拣选任务 // 当前站台到达的拣选任务
@ -2156,7 +2520,8 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
inventoryRequest.getSpecialStock(), inventoryRequest.getSpecialStock(),
inventoryRequest.getSpecialStockNo(), inventoryRequest.getSpecialStockNo(),
inventoryRequest.getSpecialStockItemNo(), inventoryRequest.getSpecialStockItemNo(),
inventoryRequest.getBatchNo() inventoryRequest.getBatchNo(),
null
); );
inventoryByVehicleMap.put(stock.getVehicleId(), inventoryTask); inventoryByVehicleMap.put(stock.getVehicleId(), inventoryTask);
} }
@ -2168,6 +2533,8 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
return BaseWmsApiResponse.success("创建盘点任务成功。"); return BaseWmsApiResponse.success("创建盘点任务成功。");
} }
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public BaseWmsApiResponse batchRequestInventory(BatchInventoryRequest batchInventoryRequest) { public BaseWmsApiResponse batchRequestInventory(BatchInventoryRequest batchInventoryRequest) {
@ -2336,7 +2703,8 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
item.getSpecialStock(), item.getSpecialStock(),
item.getSpecialStockNo(), item.getSpecialStockNo(),
item.getSpecialStockItemNo(), item.getSpecialStockItemNo(),
item.getBatchNo() item.getBatchNo(),
null
); );
inventoryList.add(inventory); inventoryList.add(inventory);
@ -2454,6 +2822,14 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
.eq(TAppPickTask::getPickType,2) .eq(TAppPickTask::getPickType,2)
); );
if (pickTaskList == null || pickTaskList.isEmpty()) { if (pickTaskList == null || pickTaskList.isEmpty()) {
List<TAppPickTask> pickTaskListType3 = appPickTaskService.list(new LambdaQueryWrapper<TAppPickTask>()
.eq(TAppPickTask::getPickStand, inventoryConfirmRequest.getStandId())
.eq(TAppPickTask::getPickStatus, WmsPickTaskStatusEnum.ARRIVE.getCode())
.eq(TAppPickTask::getPickType,4)
);
if (!pickTaskListType3.isEmpty()) {
return BaseWmsApiResponse.error("当前箱子为合并库存任务, 请转到合并库存确认页面。");
}
return BaseWmsApiResponse.error("当前站台没有箱子到达, 请检查。"); return BaseWmsApiResponse.error("当前站台没有箱子到达, 请检查。");
} }
if (pickTaskList.size() > 1) { if (pickTaskList.size() > 1) {
@ -2562,6 +2938,120 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
} }
} }
/**
* 确认合并信息
*
* @param inventoryConfirmRequest 确认请求
* @return 处理结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public BaseWmsApiResponse confirmMixStock(InventoryConfirmRequest inventoryConfirmRequest) {
if (inventoryConfirmRequest == null) {
return BaseWmsApiResponse.error("请求信息为空。");
}
if (StringUtils.isEmpty(inventoryConfirmRequest.getInventoryId())) {
return BaseWmsApiResponse.error("请求信息缺少关键信息,请刷新后重试!");
}
if (StringUtils.isEmpty(inventoryConfirmRequest.getStandId())) {
return BaseWmsApiResponse.error("请求信息缺少站台号。");
}
if (Objects.equals(inventoryConfirmRequest.getVehicleId(), inventoryConfirmRequest.getTargetVehicleId())){
// 查找是否还存在盘点任务
List<TAppInventory> inventoryList = appInventoryService.list(new LambdaQueryWrapper<TAppInventory>()
.eq(TAppInventory::getInvType, 3)
.eq(TAppInventory::getMixVehicle, inventoryConfirmRequest.getTargetVehicleId())
);
if (inventoryList.size() > 1){
return BaseWmsApiResponse.error("当前载具作为合并母容器!还存在 " + (inventoryList.size() - 1) + " 个合并任务!请先处理合并子容器!");
}
}
// 查询当前站台上的已到达的拣选任务
List<TAppPickTask> pickTaskList = appPickTaskService.list(new LambdaQueryWrapper<TAppPickTask>()
.eq(TAppPickTask::getPickStand, inventoryConfirmRequest.getStandId())
.eq(TAppPickTask::getPickStatus, WmsPickTaskStatusEnum.ARRIVE.getCode())
.eq(TAppPickTask::getPickType,4)
);
if (pickTaskList == null || pickTaskList.isEmpty()) {
List<TAppPickTask> pickTaskListType3 = appPickTaskService.list(new LambdaQueryWrapper<TAppPickTask>()
.eq(TAppPickTask::getPickStand, inventoryConfirmRequest.getStandId())
.eq(TAppPickTask::getPickStatus, WmsPickTaskStatusEnum.ARRIVE.getCode())
.eq(TAppPickTask::getPickType,2)
);
if (!pickTaskListType3.isEmpty()) {
return BaseWmsApiResponse.error("当前箱子为盘点任务, 请转到盘点确认页面。");
}
return BaseWmsApiResponse.error("当前站台没有箱子到达, 请检查。");
}
if (pickTaskList.size() > 1) {
return BaseWmsApiResponse.error("当前站台有多个箱子到达,请检查。");
}
// 当前站台到达的拣选任务
TAppPickTask thisPickTask = pickTaskList.getFirst();
if (StringUtils.isNotEmpty(inventoryConfirmRequest.getInventoryId())) {
TAppInventory targetInventory = appInventoryService.getById(inventoryConfirmRequest.getInventoryId());
if (targetInventory != null) {
if (!Objects.equals(targetInventory.getVehicleId(), thisPickTask.getVehicleId())) {
// 盘点的载具号与当前站台到达的拣选任务载具号不一致请检查
return BaseWmsApiResponse.error("盘点的载具号与当前站台到达的载具号不一致,请检查。");
}
if (inventoryConfirmRequest.getConfirmNum() == null || inventoryConfirmRequest.getConfirmNum() < 0) {
return BaseWmsApiResponse.error("请求信息中的盘点确认数量不正确。");
}
// 移除任务表
appInventoryService.removeById(inventoryConfirmRequest.getInventoryId());
// 根据盘点确认信息更新库存
StockConfirmEntity stockConfirm = new StockConfirmEntity();
stockConfirm.setVehicleId(targetInventory.getVehicleId());
stockConfirm.setGoodsId(targetInventory.getGoodsId());
stockConfirm.setRealRemainQty(inventoryConfirmRequest.getConfirmNum());
stockConfirm.setSpecialStock(inventoryConfirmRequest.getSpecialStock());
stockConfirm.setSpecialStockNo(inventoryConfirmRequest.getSpecialStockNo());
stockConfirm.setSpecialStockItemNo(inventoryConfirmRequest.getSpecialStockItemNo());
stockConfirm.setBatchNo(inventoryConfirmRequest.getBatchNo());
// 更新库存信息
boolean updateStockInfo = stockDataService.updateStockInfo(stockConfirm, inventoryConfirmRequest.getStandId(), inventoryConfirmRequest.getUserName(), "库存合并确认", targetInventory.getInventoryId());
if (!updateStockInfo) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return BaseWmsApiResponse.error("库存发生变化,更新失败,请联系管理员确认!");
}else {
if (!Objects.equals(inventoryConfirmRequest.getVehicleId(), inventoryConfirmRequest.getTargetVehicleId())){
// 需要找到目标箱号的库存信息将库存全部加过去
List<TAppStock> targetStocks = appStockService.list(
new LambdaQueryWrapper<TAppStock>()
.eq(TAppStock::getVehicleId, inventoryConfirmRequest.getTargetVehicleId())
.eq(TAppStock::getGoodsId, targetInventory.getGoodsId())
);
if (targetStocks.size() == 1) {
// 更新库存数量
int newRealNum = targetStocks.getFirst().getRealNum() + inventoryConfirmRequest.getNeedAddNum();
targetStocks.getFirst().setRealNum(newRealNum);
// 保存更新
appStockService.updateById(targetStocks.getFirst());
} else if (targetStocks.isEmpty()){
return BaseWmsApiResponse.error("目标容器已丢失,更新失败,请联系管理员确认!");
}else {
return BaseWmsApiResponse.error("目标容器已存在多个库存,更新失败,请联系管理员确认!");
}
}
}
} else {
return BaseWmsApiResponse.error("请求的盘点任务号未查到对应的盘点任务。");
}
}
// 放行
if (conveyTaskService.releaseStandVehicle(thisPickTask)) {
return BaseWmsApiResponse.success("确认成功。");
} else {
// 回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return BaseWmsApiResponse.error("释放箱子失败。");
}
}
/** /**
* 获取对应工单对应物料的缺料数量---实现 * 获取对应工单对应物料的缺料数量---实现
* @param goodsId 料号 * @param goodsId 料号

View File

@ -1,6 +1,7 @@
package com.wms_main.service.controller.serviceImpl; package com.wms_main.service.controller.serviceImpl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wms_main.constant.enums.wms.WmsTaskTypeEnums; import com.wms_main.constant.enums.wms.WmsTaskTypeEnums;
import com.wms_main.dao.*; import com.wms_main.dao.*;
@ -16,6 +17,9 @@ import com.wms_main.service.controller.ITaskQueryControllerService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
/** /**
* 任务查询控制类 服务实现 * 任务查询控制类 服务实现
*/ */
@ -297,6 +301,50 @@ public class TaskQueryControllerServiceImpl implements ITaskQueryControllerServi
} }
} }
@Override
public WmsApiResponse<String> updateOutsTaskStatus(OutsBatchEditDateVo batchEditDateVo) {
if (batchEditDateVo == null || batchEditDateVo.getTaskIds() == null || batchEditDateVo.getTaskIds().isEmpty()) {
return WmsApiResponse.error("任务ID列表不能为空", null);
}
List<String> taskIds = batchEditDateVo.getTaskIds();
// 查询所有有效的任务
List<TAppOuts> existingOuts = appOutsService.list(
new LambdaQueryWrapper<TAppOuts>()
.in(TAppOuts::getTaskId, taskIds)
);
if (existingOuts.isEmpty()) {
return WmsApiResponse.error("未找到任何对应的出库单", null);
}
// 过滤出状态不是-1的任务
List<String> validTaskIds = existingOuts.stream()
.filter(outs -> outs.getTaskStatus() != 0)
.map(TAppOuts::getTaskId)
.collect(Collectors.toList());
if (validTaskIds.isEmpty()) {
return WmsApiResponse.error("所有任务状态已经为已下发,无需重复下发", null);
}
// 批量更新
LambdaUpdateWrapper<TAppOuts> updateWrapper = new LambdaUpdateWrapper<TAppOuts>()
.set(TAppOuts::getTaskStatus, 0)
.in(TAppOuts::getTaskId, validTaskIds);
boolean updateSuccess = appOutsService.update(updateWrapper);
if (updateSuccess) {
return WmsApiResponse.success("批量更新出库单状态成功,共更新" + validTaskIds.size() + "条记录", null);
} else {
return WmsApiResponse.error("批量更新出库单状态失败", null);
}
}
@Override @Override
public WmsApiResponse<String> batchEditDate(OutsBatchEditDateVo batchEditDateVo) { public WmsApiResponse<String> batchEditDate(OutsBatchEditDateVo batchEditDateVo) {
// 1. 参数校验 // 1. 参数校验
@ -390,4 +438,7 @@ public class TaskQueryControllerServiceImpl implements ITaskQueryControllerServi
} }
} }
} }

View File

@ -56,6 +56,7 @@ public class OutsExecutorServiceImpl implements IOutsExecutorService {
List<TAppOuts> appOutsList = appOutsService.list( List<TAppOuts> appOutsList = appOutsService.list(
new LambdaQueryWrapper<TAppOuts>() new LambdaQueryWrapper<TAppOuts>()
.le(TAppOuts::getPickingDate, LocalDate.now()) .le(TAppOuts::getPickingDate, LocalDate.now())
.eq(TAppOuts::getTaskStatus, 0)
.orderByDesc(TAppOuts::getOutType) .orderByDesc(TAppOuts::getOutType)
).stream() ).stream()
.filter(appOuts -> appOuts.getOutType() == 1 || appOuts.getDistributeNum() < appOuts.getNeedNum()) .filter(appOuts -> appOuts.getOutType() == 1 || appOuts.getDistributeNum() < appOuts.getNeedNum())
@ -1306,13 +1307,15 @@ public class OutsExecutorServiceImpl implements IOutsExecutorService {
// 生成拣选任务 // 生成拣选任务
if (oldPickTasks.isEmpty() && newOldPickTasks.isEmpty()) { if (oldPickTasks.isEmpty() && newOldPickTasks.isEmpty()) {
// 根据inventory的类型设置pickType
int pickType = inventory.getInvType() != null && inventory.getInvType() == 3 ? 4 : 2;
newPickTasks.add(new TAppPickTask( newPickTasks.add(new TAppPickTask(
UUIDUtils.getNewUUID(), UUIDUtils.getNewUUID(),
getOptimalSubStand(inventory.getInvStand()), getOptimalSubStand(inventory.getInvStand()),
stock.getVehicleId(), stock.getVehicleId(),
pickTaskStatus, pickTaskStatus,
LocalDateTime.now(), LocalDateTime.now(),
null, null, null, 2 null, null, null, pickType
)); ));
} }
// 设置盘点状态 // 设置盘点状态

View File

@ -8,12 +8,14 @@ spring:
# url: jdbc:mysql://112.4.208.194:3001/wms_kate_wuxi?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai # url: jdbc:mysql://112.4.208.194:3001/wms_kate_wuxi?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
# username: developer # username: developer
# password: developer # password: developer
# 本地 # 本地12315
url: jdbc:mysql://localhost:3306/wms_fengshang_yangzhou?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&rewriteBatchedStatements=true url: jdbc:mysql://localhost:3306/wms_fengshang_yangzhou?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&rewriteBatchedStatements=true
# username: wms
# password: Admin123
username: root username: root
password: 123456 password: 123456
# 测试环境12306
# url: jdbc:mysql://localhost:3306/wms_test?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&rewriteBatchedStatements=true
# username: root
# password: 123456
#在线 #在线
# url: jdbc:mysql://172.18.222.253:3306/wms_fengshang_yangzhou?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&rewriteBatchedStatements=true # url: jdbc:mysql://172.18.222.253:3306/wms_fengshang_yangzhou?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&rewriteBatchedStatements=true
# username: root # username: root