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",
"axios": "^1.3.3",
"core-js": "^3.8.3",
"echarts": "^5.4.3",
"element-plus": "^2.9.8",
"file-saver": "^2.0.5",
"moment": "^2.29.4",
@ -5558,6 +5559,20 @@
"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": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz",
@ -12223,6 +12238,19 @@
"engines": {
"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": {
@ -16416,6 +16444,22 @@
"integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==",
"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": {
"version": "1.1.1",
"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",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"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",
"axios": "^1.3.3",
"core-js": "^3.8.3",
"echarts": "^5.4.3",
"element-plus": "^2.9.8",
"file-saver": "^2.0.5",
"moment": "^2.29.4",

View File

@ -173,3 +173,12 @@ export const exportLocationDetailExcel = (data) => {
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";
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) => {
return request({
url: '/stock/queryStocks',
@ -16,6 +24,22 @@ 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) => {
return request({
url: '/stock/queryStockUpdateByPage',
@ -23,3 +47,4 @@ export const queryStockUpdateByPage = (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) => {
return request({
@ -115,6 +123,39 @@ export const confirmInventory = (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) => {
return request({

View File

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

View File

@ -274,6 +274,7 @@ const handleSelectionChange = (row) => {
}
}
//
//
const handleSelectAllChange = (val) => {
displayStocks.value.forEach(row => {
@ -281,12 +282,15 @@ const handleSelectAllChange = (val) => {
})
if (val) {
//
// -
selectedRows.value = [...displayStocks.value]
} else {
//
// -
selectedRows.value = []
}
//
displayStocks.value = [...displayStocks.value]
}
//
const handleBatchInventory = () => {
@ -320,14 +324,13 @@ const confirmBatchInventory = () => {
if (res.data.code === 0) {
ElMessage.success('批量盘点成功');
showBatchInventoryDialog.value = false;
search(); //
//
selectedRows.value.forEach(row => {
row.checked = false;
});
selectedRows.value = [];
isSelectAll.value = false;
//
selectedRows.value = []
isSelectAll.value = false
//
search(); //
} else {
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 = () => {
@ -356,11 +371,21 @@ const search = () => {
const data = response.data
console.log(data)
if (data != null) {
displayStocks.value = data.lists
// checked
displayStocks.value = data.lists.map(item => ({
...item,
checked: false
}))
baseTableQuery.total = data.total
//
selectedRows.value = []
isSelectAll.value = false
} else {
displayStocks.value = []
baseTableQuery.total = 0
selectedRows.value = []
isSelectAll.value = false
}
} else {
ElMessage.error(response.message)
@ -381,6 +406,7 @@ const clearQuery = () => {
stockQuery.fromDate = null,
stockQuery.toDate = null,
stockQuery.noUseDays = null
resetSelection()
}
const loadAllGoodsInfo = () => {
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()"
clearable/>
</el-form-item>
<!-- <el-form-item label="工单号">-->
<!-- <el-input v-model="searchQueryFormEntity.goodsDesc" @keyup.enter="search()"-->
<!-- clearable/>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="工单号">-->
<!-- <el-input v-model="searchQueryFormEntity.goodsDesc" @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="handleBatchIssue"
:disabled="selectedRows.length === 0">
批量下发({{ selectedRows.length }})
</el-button>
</el-row>
<!-- <el-row>-->
<!-- <el-button style="background-color: #00CED1;" class="btn-search"-->
<!-- @click="openUploadDialog()">导入数据-->
<!-- </el-button>-->
<!-- <el-button type="success" class="btn-search"-->
<!-- @click="exportExcel()">导出excel-->
<!-- </el-button>-->
<!-- </el-row>-->
<!-- <el-row>-->
<!-- <el-button style="background-color: #00CED1;" class="btn-search"-->
<!-- @click="openUploadDialog()">导入数据-->
<!-- </el-button>-->
<!-- <el-button type="success" class="btn-search"-->
<!-- @click="exportExcel()">导出excel-->
<!-- </el-button>-->
<!-- </el-row>-->
</div>
</div>
</el-form>
@ -46,6 +52,7 @@
<template #header>
<el-checkbox
v-model="isSelectAll"
:indeterminate="isSelectIndeterminate"
@change="handleSelectAllChange">
&nbsp;
</el-checkbox>
@ -64,6 +71,7 @@
show-overflow-tooltip/>
<el-table-column prop="goodsId" label="料号" fixed="left" min-width="120px" sortable="custom"
show-overflow-tooltip/>
<el-table-column prop="needNum" label="需求数量" min-width="120px" sortable="custom"
show-overflow-tooltip/>
<el-table-column prop="distributeNum" label="已分配数量" min-width="120px" sortable="custom"
@ -92,7 +100,18 @@
sortable="custom"
:formatter="(row, column, cellValue) => formatDateToDay(cellValue)"
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">
<template v-slot="scope">
<div style="display: flex; gap: 5px; justify-content: center;">
@ -107,8 +126,7 @@
size="small"
type="primary"
@click="handleBatchEdit"
:disabled="selectedRows.length === 0"
style="margin-top: 5px;">
:disabled="selectedRows.length === 0" style="margin-top: 5px;">
批量修改时间({{ selectedRows.length }})
</el-button>
</div>
@ -118,9 +136,15 @@
</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"
v-model:page-size="baseTableQuery.pageSize"
:page-sizes="[20, 50, 100]"
: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"/>
</div>
<el-dialog v-model="showEditDialog" title="编辑执行日期" width="30%" draggable>
@ -170,9 +194,9 @@
<script setup>
import store from '@/store'
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 {ElMessage} from 'element-plus'
import {ElMessage, ElMessageBox} from 'element-plus'
import {genTableRequest} from '@/utils/generator.js'
import {labelPosition} from '@/constant/form.js'
import UploadExcelBaseGoods from '@/excel/UploadExcelBaseGoods.vue'
@ -180,12 +204,36 @@ import UploadExcelKanban from '@/excel/UploadExcelKanban.vue'
import {exportGoodsExcel} from "@/api/excel";
import {dateFormatter} from "@/utils/formatter";
import { getUserPermission } from '@/api/user.js'
import {computed} from 'vue' // computed
/**
* 常量定义
*/
const STAND_ID = store.getters.getStandId
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 baseTableQuery = reactive({
currentPage: 1,
pageSize: 10,
pageSize: 20, // 1020
total: 0,
sortBy: new Map([['goodsId', true]]),//
sortBy: new Map([['goodsId', true]]),
standId: STAND_ID,
userName: USER_NAME
})
@ -234,18 +282,45 @@ const resizeHeight = () => {
maxHeight.value = window.innerHeight * 0.55
}
//
const handleSelectionChange = (row) => {
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)
}
} 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 permissionParams = {
@ -280,21 +355,109 @@ const handleBatchEdit = () => {
})
}
const handleSelectAllChange = (val) => {
tableData.value.forEach(row => {
row.checked = val
})
if (val) {
//
selectedRows.value = [...tableData.value]
} else {
//
selectedRows.value = []
//
const handleBatchIssue = () => {
const permissionParams = {
loginAccountUpdate: store.getters.getUser.loginAccount,
roleIdOp: store.getters.getUser.roleId,
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) => {
if (!form.pickingDate) {
@ -308,16 +471,13 @@ const saveBatchEdit = (form) => {
pickingDate: form.pickingDate
}
// API
batchEditDate(request).then((res) => {
if (res.data.code === 0) {
ElMessage.success('批量修改成功')
showBatchEditDialog.value = false
resetSelection()
search()
//
selectedRows.value.forEach(row => {
row.checked = false
})
//
selectedRows.value = []
} else {
ElMessage.error(res.data.message)
@ -327,6 +487,7 @@ const saveBatchEdit = (form) => {
})
}
//
const saveEdit = (editForm) => {
let request = {
@ -340,6 +501,7 @@ const saveEdit = (editForm) => {
type: 'success',
});
search()
clearSelection()
} else {
ElMessage({
message: res.data.message,
@ -422,7 +584,7 @@ const handleEdit = (row) => {
return
}
editForm.taskId = row.taskId
// 使 formatDateToDay
// 使 formatDateToDay
editForm.pickingDate = row.pickingDate ? formatDateToDay(row.pickingDate) : ''
showEditDialog.value = true
}
@ -457,8 +619,16 @@ const search = () => {
if (response.code === 0) {
const data = response.data
if (data != null) {
tableData.value = data.lists
// checked
const lists = data.lists.map(item => ({
...item,
checked: false
}))
tableData.value = lists
baseTableQuery.total = data.total
//
selectedRows.value = []
} else {
tableData.value = []
baseTableQuery.total = 0
@ -615,4 +785,37 @@ const exportExcel = () => {
margin: auto 5px 5px auto;
color: black;
}
/* 任务状态样式 */
.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 class="loading-spinner">
<i class="el-icon-loading"></i>
<p>处理中...</p>
<div class="loading-spinner-modern">
<div class="spinner"></div>
<div class="text">数据请求中...</div>
</div>
</div>
</el-container>
@ -228,7 +228,7 @@ export default {
//
this.showLoading = true;
confirmCurrentTask(request, { timeout: 15000 }).then(res => {
confirmCurrentTask(request, { timeout: 60000 }).then(res => {
const responseData = res.data;
if (responseData.code === 0) {
if (responseData.message === "继续拣选"){
@ -489,6 +489,34 @@ export default {
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 {
background-color: #dbeeff !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: '/clcKanban', component: () => import('@/layout/clcKanban.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: '/stockUpdateRecord', component: () => import('@/layout/stockUpdateRecord.vue')},// 库存更新记录
{path: '/roleUser', component: () => import('@/layout/role_user.vue')},// 角色——用户列表
@ -50,6 +54,16 @@ const routes = [
name: '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',
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 = () => {
router.replace({ path: '/home' })
}
// PDA
// PDA -
const PdaToWms = () => {
// PDA
const params = {
@ -53,8 +53,8 @@ const PdaToWms = () => {
// "E"PDA
const permissionStr = res.data.message || ''
if (permissionStr.includes('E')) {
// PDAPDA
router.replace({ path: '/pda' })
// PDAPDA
router.replace({ path: '/pda-center' })
} else {
// PDA
ElMessage.error('您没有访问PDA系统的权限')
@ -67,6 +67,7 @@ const PdaToWms = () => {
ElMessage.error('权限检查失败')
})
}
//
const loginToImage = () => {
router.replace({ path: '/imageDisplay' })

View File

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

View File

@ -240,4 +240,70 @@ public class DataController {
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 {
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);
}
/**
* 查询特殊库存字段为空且数量大于等于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);
}
/*
查询非限制库存
*/
@PostMapping("/queryStocksBySpecial")
public WmsApiResponse<PageVo<StockVo>> queryStocksBySpecial(@RequestBody StockQuery stockQuery)
{
return stockControllerService.queryStocksBySpecial(stockQuery);
}
/**
* 分页查询库存更新记录
* @param stockUpdateQuery 查询参数

View File

@ -288,6 +288,16 @@ public class TaskController {
return taskControllerService.confirmInventory(inventoryConfirmRequest);
}
/**
* 确认合并信息
* @param inventoryConfirmRequest 确认请求
* @return 处理结果
*/
@PostMapping("/confirmMixStock")
public BaseWmsApiResponse confirmMixStock(@RequestBody InventoryConfirmRequest inventoryConfirmRequest) {
return taskControllerService.confirmMixStock(inventoryConfirmRequest);
}
/**
* 获取缺料数量当存在库存时返回库存与需求的较小值
* @param goodsId 料号
@ -298,4 +308,48 @@ public class TaskController {
public int getLackQty(@RequestParam("goodsId") String goodsId, @RequestParam("workOrder") String 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;
}

View File

@ -69,4 +69,6 @@ public interface IImportExcelEasyPoi extends IBaseImportExcelEasyPoi {
* @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.ITAppStockUpdateService;
import com.wms_main.dao.ITAppTaskBakService;
import com.wms_main.excel.easypoi.excelTemplate.GoodsExcelTemplate;
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.excelTemplate.*;
import com.wms_main.excel.easypoi.service.base.IBaseExportExcelEasyPoi;
import com.wms_main.model.dto.query.GoodsQuery;
import com.wms_main.model.dto.query.StockQuery;
import com.wms_main.model.dto.query.StockUpdateQuery;
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.TAppStock;
import com.wms_main.model.po.TAppStockUpdate;
import com.wms_main.model.po.TAppTaskBak;
import com.wms_main.model.vo.others.FileVo;
import com.wms_main.repository.utils.StringUtils;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Workbook;
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.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
@ -314,6 +318,9 @@ public class BaseExportExcelEasyPoi implements IBaseExportExcelEasyPoi {
doWriteExcel("出库记录", response, resultList, TaskRecordExcelTemplate.class);
}
/**
* 写入excel
* @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.service.IImportExcelEasyPoi;
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.vo.others.FileVo;
import com.wms_main.model.dto.response.wms.BaseWmsApiResponse;
import com.wms_main.repository.utils.StringUtils;
import com.wms_main.repository.utils.UUIDUtils;
import com.wms_main.service.controller.ITaskControllerService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -25,6 +27,8 @@ import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@ -42,6 +46,7 @@ public class ImportExcelEasyPoi extends BaseImportExcelEasyPoi implements IImpor
private final ITAppInventoryService tAppInventoryService;// 盘点请求服务
private final ITAppStockService tAppStockService;// 库存服务
private final AppCommon appCommon;
private final ITaskControllerService taskControllerService; // 添加任务控制器服务
/**
* 导入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 文件
@ -569,9 +684,8 @@ public class ImportExcelEasyPoi extends BaseImportExcelEasyPoi implements IImpor
null,
orderId,
null,
null
,
null,null
null,
null,null, null
);
inventoryByVehicleMap.put(stock.getVehicleId(), inventoryTask);
vehicleList.add(stock.getVehicleId());
@ -614,7 +728,7 @@ public class ImportExcelEasyPoi extends BaseImportExcelEasyPoi implements IImpor
orderId,
null,
null,
null,null
null,null, null
);
inventoryByVehicleMap.put(stock.getVehicleId(), inventoryTask);
thisTimeAddVehicleList.add(stock.getVehicleId());

View File

@ -58,4 +58,18 @@ public class InventoryConfirmRequest extends BaseWmsRequest {
*/
@JsonProperty("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")
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")
private String batchNo; // 批次号
@TableField(value = "mix_vehicle")
private String mixVehicle;
}

View File

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

View File

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

View File

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

View File

@ -6,6 +6,9 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.Map;
/**
* 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
*/
private String token;
/**
* 额外的请求头
*/
private Map<String, String> headers = new HashMap<>();
/**
* 构建一个POST请求
@ -55,7 +62,30 @@ public class HttpRequest {
if (StringUtils.isEmpty(contentType)) {
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) {
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 org.springframework.util.DigestUtils;
import java.util.Objects;
/**
* 字符串工具类
*/
@ -268,6 +270,26 @@ public class StringUtils {
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 原始字符串

View File

@ -12,7 +12,16 @@ import com.wms_main.repository.http.entity.HttpResponse;
import com.wms_main.service.api.IEwmApiService;
import lombok.RequiredArgsConstructor;
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.web.client.RestTemplate;
import java.net.http.HttpHeaders;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* Ewm接口服务实现
@ -41,7 +50,7 @@ public class EwmApiServiceImpl implements IEwmApiService {
appCommon.getConfigByKey(AppConfigKeyEnums.EWM_SEND_VEHICLE_URL.getKey()),
request,
30000,
"application/json",
"application/json;charset=UTF-8",
"Basic " + encodedAuth);
// 记录即将发送的请求数据
@ -78,7 +87,7 @@ public class EwmApiServiceImpl implements IEwmApiService {
appCommon.getConfigByKey(AppConfigKeyEnums.EWM_SEND_WAREHOUSE_IN_COMPLETED_URL.getKey()),
request,
30000,
"application/json",
"application/json;charset=UTF-8",
"Basic " + encodedAuth);
HttpResponse httpResponse = httpClient.httpPost(httpRequest);
@ -106,7 +115,7 @@ public class EwmApiServiceImpl implements IEwmApiService {
appCommon.getConfigByKey(AppConfigKeyEnums.EWM_SEND_WAREHOUSE_OUT_COMPLETED_URL.getKey()),
request,
60000,
"application/json",
"application/json;charset=UTF-8",
"Basic " + encodedAuth);
HttpResponse httpResponse = httpClient.httpPost(httpRequest);
@ -142,7 +151,7 @@ public class EwmApiServiceImpl implements IEwmApiService {
finalUrl,
request,
30000,
"application/json",
"application/json;charset=UTF-8",
"Basic " + encodedAuth);
HttpResponse httpResponse = httpClient.httpPost(httpRequest);
@ -170,7 +179,7 @@ public class EwmApiServiceImpl implements IEwmApiService {
appCommon.getConfigByKey(AppConfigKeyEnums.EWM_GET_STOCK_LIST_URL.getKey()),
request,
30000,
"application/json",
"application/json;charset=UTF-8",
"Basic " + encodedAuth);
HttpResponse httpResponse = httpClient.httpPost(httpRequest);
@ -185,24 +194,39 @@ public class EwmApiServiceImpl implements IEwmApiService {
}
}
@Override
public EwmApiBackResponse sendEwmCheckContainerNo(SendEwmCheckContainerNo request) {
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认证信息
String auth = "asrs:mom@123456";
String encodedAuth = java.util.Base64.getEncoder().encodeToString(auth.getBytes());
headers.set("Authorization", "Basic " + encodedAuth); // Basic Auth
// 设置http请求
HttpRequest httpRequest = HttpRequest.postInstanceOf(
// 2. 构建请求体
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()),
request,
30000,
"application/json",
"Basic " + encodedAuth);
HttpResponse httpResponse = httpClient.httpPost(httpRequest);
entity,
EwmApiBackResponse.class
);
if (httpResponse != null && httpResponse.isSuccess()) {
return httpResponse.getData(EwmApiBackResponse.class);
// 4. 处理响应
if (response.getStatusCode().is2xxSuccessful()) {
EwmApiBackResponse result = response.getBody();
log.info("EWM系统响应: {}", result);
return result;
}
log.warn("请求验证EWM系统返回空响应或请求失败请求参数: {}", request);
@ -212,4 +236,64 @@ public class EwmApiServiceImpl implements IEwmApiService {
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.setSpecialStockItemNo(inventory.getSpecialStockItemNo());
inventoryConfirmVo.setBatchNo(inventory.getBatchNo());
inventoryConfirmVo.setMixVehicle(inventory.getMixVehicle());
// 返回结果
return inventoryConfirmVo;
}

View File

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

View File

@ -42,6 +42,11 @@ public interface IDataControllerService {
*/
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>> queryStockDetail(String locationId);

View File

@ -18,6 +18,15 @@ public interface IStockControllerService {
*/
WmsApiResponse<PageVo<StockVo>> queryStocksByPage(StockQuery stockQuery);
/**
* 查询特殊库存字段为空且数量大于等于2的物料
* @param stockQuery 查询参数
* @return 查询结果
*/
WmsApiResponse<PageVo<StockVo>> querySpecialEmptyStocksForMix(StockQuery stockQuery);
/**
* 查询库存更新记录---分页
* @param stockUpdateQuery 查询参数
@ -30,4 +39,11 @@ public interface IStockControllerService {
* @return 所有库存信息
*/
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.wms.BaseWmsApiResponse;
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.vo.wms.InventoryConfirmVo;
import com.wms_main.model.vo.wms.TaskConfirmVo;
@ -109,6 +110,8 @@ public interface ITaskControllerService {
*/
BaseWmsApiResponse requestInventory(InventoryRequest inventoryRequest);
/**
* 批量下发盘点请求
* @param batchInventoryRequest 批量盘点请求
@ -131,6 +134,13 @@ public interface ITaskControllerService {
*/
BaseWmsApiResponse confirmInventory(InventoryConfirmRequest inventoryConfirmRequest);
/**
* 确认合并信息
* @param inventoryConfirmRequest 确认请求
* @return 处理结果
*/
BaseWmsApiResponse confirmMixStock(InventoryConfirmRequest inventoryConfirmRequest);
/**
* 获取对应工单的缺料数量
* 最小值为0最大值为库存数量
@ -149,6 +159,9 @@ public interface ITaskControllerService {
EwmApiBackResponse ewmRequestOutTask(EwmOutTaskRequest ewmOutTaskRequest);
//BaseWmsApiResponse requestInventoryByMixStock(InventoryRequest inventoryRequest, List<TAppInventory> inventory);
/**
* EWM获取库存信息
* @param request 获取库存信息请求
@ -170,4 +183,20 @@ public interface ITaskControllerService {
*/
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 更新结果
*/
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();
}
@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
public List<Map<String, Object>> queryRecentTask() {
List<Map<String, Object>> result = new ArrayList<>();

View File

@ -18,6 +18,11 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
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);
}
@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 查询参数
@ -125,4 +223,43 @@ public class StockControllerServiceImpl implements IStockControllerService {
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.vo.wms.InventoryConfirmVo;
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.DbTransUtils;
import com.wms_main.repository.utils.StringUtils;
@ -48,7 +47,6 @@ import org.springframework.transaction.interceptor.TransactionAspectSupport;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 任务控制类 服务实现
@ -76,6 +74,7 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
private final ITAppInventoryRecordService appInventoryRecordService;// 盘点记录服务
private final ITAppWorkService appWorkService;// 工作详情服务
private final AppCommon appCommon;
private final ITAppTaskService appTaskService;
private final IEwmApiService ewmApiService;
// 在其他依赖注入后面添加
private final ITempEwmInboundDataService tempEwmInboundDataService;
@ -291,6 +290,7 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
// 添加任务
TAppOuts task = new TAppOuts(
UUIDUtils.getNewUUID(),
0,
stockOutRequest.getGoodsId(),
stockOutRequest.getVehicleId(),
stockOutRequest.getNeedNum(),
@ -466,6 +466,7 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
// 添加任务
TAppOuts task = new TAppOuts(
UUIDUtils.getNewUUID(),
-1,
stockOutRequest.getGoodsId(),
stockOutRequest.getVehicleId(),
stockOutRequest.getNeedNum(),
@ -635,6 +636,7 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
}
TAppOuts outs = new TAppOuts(
task.getTaskNo(),
-1,
task.getMatNo(),
null,
task.getPickingQty().intValue(),
@ -680,6 +682,337 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
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
@ -949,6 +1282,7 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
// 添加任务
outsList.add(new TAppOuts(
UUIDUtils.getNewUUID(),
-1,
goodsId,
"",
1,
@ -1308,6 +1642,11 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
}
}
if(dataList.size() > 1){
log.info("EWM返回数据中存在多条不同物料号的数据载具号: {}", vehicleNo);
return false;
}
if (!dataList.isEmpty()) {
// 批量插入时指定合理的批次大小
boolean saveResult = tempEwmInboundDataService.saveBatch(dataList);
@ -1490,6 +1829,28 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
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任务
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 newSecondPickingCode = thisOut.getFirst().getSecondPickingCode();
boolean isSecondPickingCodeMatch = (StringUtils.isEmpty(existingSecondPickingCode) && StringUtils.isEmpty(newSecondPickingCode))
|| Objects.equals(existingSecondPickingCode, newSecondPickingCode);
boolean isSecondPickingCodeEmpty = StringUtils.isEmpty(existingSecondPickingCode) && StringUtils.isEmpty(newSecondPickingCode);
boolean isSecondPickingCodeMatch = isSecondPickingCodeEmpty || Objects.equals(existingSecondPickingCode, newSecondPickingCode);
if (!isSecondPickingCodeMatch) {
return BaseWmsApiResponse.error("当前目标箱号已存在其他颗粒度物料,请检查。");
}
// 2. 如果颗粒度一致包括都为空再检测工单号是否相同
String existingOrderNo = existingCheck.getOrderNo();
String newOrderNo = thisOut.getFirst().getOrderNo();
// 2. 只有当颗粒度都为空时才校验工单号
if (isSecondPickingCodeEmpty) {
String existingOrderNo = existingCheck.getOrderNo();
String newOrderNo = thisOut.getFirst().getOrderNo();
boolean isOrderNoMatch = Objects.equals(existingOrderNo, newOrderNo);
if (!isOrderNoMatch) {
return BaseWmsApiResponse.error("当前目标箱号已存在其他工单号物料,请检查。");
boolean isOrderNoMatch = Objects.equals(existingOrderNo, newOrderNo);
if (!isOrderNoMatch) {
return BaseWmsApiResponse.error("当前目标箱号已存在其他工单号物料,请检查。");
}
}
}
// 当前站台到达的拣选任务
@ -2156,7 +2520,8 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
inventoryRequest.getSpecialStock(),
inventoryRequest.getSpecialStockNo(),
inventoryRequest.getSpecialStockItemNo(),
inventoryRequest.getBatchNo()
inventoryRequest.getBatchNo(),
null
);
inventoryByVehicleMap.put(stock.getVehicleId(), inventoryTask);
}
@ -2168,6 +2533,8 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
return BaseWmsApiResponse.success("创建盘点任务成功。");
}
@Override
@Transactional(rollbackFor = Exception.class)
public BaseWmsApiResponse batchRequestInventory(BatchInventoryRequest batchInventoryRequest) {
@ -2336,7 +2703,8 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
item.getSpecialStock(),
item.getSpecialStockNo(),
item.getSpecialStockItemNo(),
item.getBatchNo()
item.getBatchNo(),
null
);
inventoryList.add(inventory);
@ -2454,6 +2822,14 @@ public class TaskControllerServiceImpl implements ITaskControllerService {
.eq(TAppPickTask::getPickType,2)
);
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("当前站台没有箱子到达, 请检查。");
}
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 料号

View File

@ -1,6 +1,7 @@
package com.wms_main.service.controller.serviceImpl;
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.wms_main.constant.enums.wms.WmsTaskTypeEnums;
import com.wms_main.dao.*;
@ -16,6 +17,9 @@ import com.wms_main.service.controller.ITaskQueryControllerService;
import lombok.RequiredArgsConstructor;
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
public WmsApiResponse<String> batchEditDate(OutsBatchEditDateVo batchEditDateVo) {
// 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(
new LambdaQueryWrapper<TAppOuts>()
.le(TAppOuts::getPickingDate, LocalDate.now())
.eq(TAppOuts::getTaskStatus, 0)
.orderByDesc(TAppOuts::getOutType)
).stream()
.filter(appOuts -> appOuts.getOutType() == 1 || appOuts.getDistributeNum() < appOuts.getNeedNum())
@ -1306,13 +1307,15 @@ public class OutsExecutorServiceImpl implements IOutsExecutorService {
// 生成拣选任务
if (oldPickTasks.isEmpty() && newOldPickTasks.isEmpty()) {
// 根据inventory的类型设置pickType
int pickType = inventory.getInvType() != null && inventory.getInvType() == 3 ? 4 : 2;
newPickTasks.add(new TAppPickTask(
UUIDUtils.getNewUUID(),
getOptimalSubStand(inventory.getInvStand()),
stock.getVehicleId(),
pickTaskStatus,
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
# username: developer
# password: developer
# 本地
# 本地12315
url: jdbc:mysql://localhost:3306/wms_fengshang_yangzhou?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&rewriteBatchedStatements=true
# username: wms
# password: Admin123
username: root
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
# username: root