Browse Source

feat(check): 添加年度盘点报告功能并优化盘点结果页面

- 新增年度盘点报告API接口和后端服务
- 在盘点查询页面添加年中/年末盘点类型选项
- 实现年度盘点报告弹窗界面和提交逻辑
- 为盘点结果页面添加Excel导出功能
- 重构盘点结果页面表格列配置为动态模式
- 移除盘点结果页面的盘点结果显示字段
- 优化changeHUSpecialItem页面的数据加载逻辑
- 修复物料编码验证和页面初始化问题
master
常熟吴彦祖 2 weeks ago
parent
commit
f4ceef8d31
  1. 3
      src/api/check/physicalInventory.js
  2. 319
      src/views/modules/check/rfidDailyCountResult.vue
  3. 79
      src/views/modules/check/searchPhysicalInventory.vue
  4. 7
      src/views/modules/warehouse/changeHUSpecialItem.vue

3
src/api/check/physicalInventory.js

@ -17,6 +17,9 @@ export const createCycleCount = data => createAPI(`/check/physicalInventory/crea
// 创建空的手工盘点单(只有表头)
export const createManualCount = data => createAPI(`/check/physicalInventory/createManualCount`, 'post', data)
// 年度盘点报告(年中/年末)- rqrq
export const generateYearInventoryReport = data => createAPI(`/check/physicalInventory/generateYearInventoryReport`, 'post', data)
// 手工盘点 - 查询物料汇总(用于添加物料弹框)
export const queryMaterialForManualCount = data => createAPI(`/check/physicalInventory/queryMaterialForManualCount`, 'post', data)

319
src/views/modules/check/rfidDailyCountResult.vue

@ -12,13 +12,6 @@
<el-form-item :label="'托盘编码'">
<el-input v-model="searchData.palletId" style="width: 120px" clearable></el-input>
</el-form-item>
<!-- <el-form-item label="盘点结果">-->
<!-- <el-select v-model="searchData.countResult" placeholder="请选择" style="width: 120px" clearable>-->
<!-- <el-option label="全部" value=""></el-option>-->
<!-- <el-option label="盘点成功" value="盘点成功"></el-option>-->
<!-- <el-option label="盘点失败" value="盘点失败"></el-option>-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<el-form-item :label="'盘点日期'">
<el-date-picker
style="width: 120px"
@ -42,20 +35,26 @@
</el-form-item>
<el-form-item :label="' '">
<el-button type="primary" @click="searchTable()">查询</el-button>
</el-form-item>
<el-form-item :label="' '">
<el-button
type="primary"
class="yzzButtonAn"
@click="generateCountResult()"
:disabled="generateLoading">
{{ generateLoading ? '生成中...' : '生成盘点结果' }}
</el-button>
<download-excel
:fields="fields()"
:data="exportData"
type="xls"
:name="exportName"
:header="exportHeader"
:footer="exportFooter"
:defaultValue="exportDefaultValue"
:fetch="createExportData"
:before-generate="startDownload"
:before-finish="finishDownload"
worksheet="导出信息"
class="el-button el-button--primary el-button--medium">
导出
</download-excel>
</el-form-item>
</el-form>
</el-form>
<!-- 主表格rfid_count_snapshot - rqrq -->
<!-- 主表格rfid_count_snapshot列配置与盘点等页面一致 - rqrq -->
<el-table
:data="dataList"
:height="height"
@ -64,113 +63,22 @@
v-loading="dataListLoading"
style="width: 100%;">
<el-table-column
prop="site"
header-align="center"
align="center"
width="80"
label="站点">
</el-table-column>
<el-table-column
prop="unitId"
header-align="center"
align="center"
min-width="150"
show-overflow-tooltip
label="标签编码">
</el-table-column>
<el-table-column
prop="partNo"
header-align="center"
align="center"
min-width="120"
show-overflow-tooltip
label="物料编码">
</el-table-column>
<el-table-column
prop="qty"
header-align="center"
align="center"
width="100"
label="数量">
</el-table-column>
<el-table-column
prop="batchNo"
header-align="center"
align="center"
min-width="120"
show-overflow-tooltip
label="批次号">
</el-table-column>
<el-table-column
prop="locationId"
header-align="center"
align="center"
min-width="120"
show-overflow-tooltip
label="库位">
</el-table-column>
<el-table-column
prop="warehouseId"
header-align="center"
align="center"
width="100"
label="仓库">
</el-table-column>
<el-table-column
prop="wdr"
header-align="center"
align="center"
width="100"
label="WDR">
</el-table-column>
<el-table-column
prop="lastCountDate"
header-align="center"
align="center"
width="160"
label="最后盘点日期">
</el-table-column>
<el-table-column
prop="palletId"
header-align="center"
align="center"
min-width="120"
show-overflow-tooltip
label="托盘编码">
</el-table-column>
<!-- <el-table-column-->
<!-- prop="countResult"-->
<!-- header-align="center"-->
<!-- align="center"-->
<!-- width="100"-->
<!-- label="盘点结果">-->
<!-- <template slot-scope="scope">-->
<!-- <span :style="{color: scope.row.countResult === '盘点成功' ? '#67C23A' : '#F56C6C'}">-->
<!-- {{ scope.row.countResult }}-->
<!-- </span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column
prop="countTimes"
header-align="center"
align="center"
width="100"
label="盘点次数">
</el-table-column>
<el-table-column
prop="remark"
header-align="center"
align="center"
min-width="150"
show-overflow-tooltip
label="备注">
</el-table-column>
<el-table-column
prop="updatedDate"
header-align="center"
align="center"
width="160"
label="更新时间">
v-for="(item, index) in mainColumnList"
:key="index"
:sortable="item.columnSortable"
:prop="item.columnProp"
:header-align="item.headerAlign"
:align="item.align"
:show-overflow-tooltip="item.showOverflowTooltip"
:fixed="item.fixed === '' ? false : item.fixed"
:min-width="item.columnWidth"
:label="item.columnLabel">
<template slot-scope="scope">
<span v-if="item.columnProp === 'lastCountDate'">{{ formatDateTime(scope.row.lastCountDate) }}</span>
<span v-else-if="item.columnProp === 'updatedDate'">{{ formatDateTime(scope.row.updatedDate) }}</span>
<span v-else-if="item.columnProp === 'createdDate'">{{ formatDateTime(scope.row.createdDate) }}</span>
<span v-else>{{ scope.row[item.columnProp] }}</span>
</template>
</el-table-column>
</el-table>
@ -189,25 +97,46 @@
</template>
<script>
import {
import {
searchRfidCountSnapshotList,
generateCountResultFromSnapshot
} from "@/api/check/rfidCount.js"
} from '@/api/check/rfidCount.js'
export default {
const EXPORT_MAX_ROWS = 50000
export default {
name: 'rfidDailyCountResult',
data() {
return {
// - rqrq
height: 200,
// - rqrq
dataListLoading: false,
exportLoading: false,
exportData: [],
exportName: 'RFID日常盘点快照_' + this.dayjs().format('YYYYMMDDHHmmss'),
exportHeader: ['RFID日常盘点快照'],
exportFooter: [],
exportDefaultValue: '',
generateLoading: false,
// - rqrq
dataList: [],
// - rqrq
// searchPhysicalInventory columnList - rqrq
mainColumnList: [
{ columnProp: 'site', headerAlign: 'center', align: 'center', columnLabel: '站点', columnWidth: 80, columnSortable: false, showOverflowTooltip: true, fixed: '' },
{ columnProp: 'unitId', headerAlign: 'center', align: 'center', columnLabel: '标签编码', columnWidth: 150, columnSortable: false, showOverflowTooltip: true, fixed: '' },
{ columnProp: 'partNo', headerAlign: 'center', align: 'center', columnLabel: '物料编码', columnWidth: 120, columnSortable: false, showOverflowTooltip: true, fixed: '' },
{ columnProp: 'qty', headerAlign: 'center', align: 'center', columnLabel: '数量', columnWidth: 100, columnSortable: false, showOverflowTooltip: true, fixed: '' },
{ columnProp: 'batchNo', headerAlign: 'center', align: 'center', columnLabel: '批次号', columnWidth: 120, columnSortable: false, showOverflowTooltip: true, fixed: '' },
{ columnProp: 'locationId', headerAlign: 'center', align: 'center', columnLabel: '库位', columnWidth: 120, columnSortable: false, showOverflowTooltip: true, fixed: '' },
{ columnProp: 'warehouseId', headerAlign: 'center', align: 'center', columnLabel: '仓库', columnWidth: 100, columnSortable: false, showOverflowTooltip: true, fixed: '' },
{ columnProp: 'wdr', headerAlign: 'center', align: 'center', columnLabel: 'WDR', columnWidth: 100, columnSortable: false, showOverflowTooltip: true, fixed: '' },
{ columnProp: 'lastCountDate', headerAlign: 'center', align: 'center', columnLabel: '最后盘点日期', columnWidth: 160, columnSortable: false, showOverflowTooltip: true, fixed: '' },
{ columnProp: 'palletId', headerAlign: 'center', align: 'center', columnLabel: '托盘编码', columnWidth: 120, columnSortable: false, showOverflowTooltip: true, fixed: '' },
{ columnProp: 'countResult', headerAlign: 'center', align: 'center', columnLabel: '盘点结果', columnWidth: 100, columnSortable: false, showOverflowTooltip: true, fixed: '' },
{ columnProp: 'remark', headerAlign: 'center', align: 'center', columnLabel: '备注', columnWidth: 150, columnSortable: false, showOverflowTooltip: true, fixed: '' },
{ columnProp: 'createdDate', headerAlign: 'center', align: 'center', columnLabel: '创建时间', columnWidth: 160, columnSortable: false, showOverflowTooltip: true, fixed: '' },
{ columnProp: 'updatedDate', headerAlign: 'center', align: 'center', columnLabel: '更新时间', columnWidth: 160, columnSortable: false, showOverflowTooltip: true, fixed: '' }
],
searchData: {
site: this.$store.state.user.site,
unitId: '',
@ -220,7 +149,6 @@
limit: 20
},
// - rqrq
pageIndex: 1,
pageSize: 20,
totalPage: 0
@ -228,26 +156,36 @@
},
mounted() {
// - rqrq
this.$nextTick(() => {
this.height = window.innerHeight - 250
})
// - rqrq
this.getDataList()
},
methods: {
/**
* @Description 查询数据 - rqrq
* @Title getDataList
* @author rqrq
* @Description 组装与当前查询条件一致的请求体导出/列表共用- rqrq
*/
buildQuery(overrides) {
return {
site: this.searchData.site,
unitId: this.searchData.unitId,
partNo: this.searchData.partNo,
palletId: this.searchData.palletId,
countResult: this.searchData.countResult,
startDate: this.searchData.startDate,
endDate: this.searchData.endDate,
page: this.pageIndex,
limit: this.pageSize,
...overrides
}
},
getDataList() {
this.dataListLoading = true
this.searchData.page = this.pageIndex
this.searchData.limit = this.pageSize
const params = this.buildQuery({ page: this.pageIndex, limit: this.pageSize })
searchRfidCountSnapshotList(this.searchData).then(({data}) => {
searchRfidCountSnapshotList(params).then(({ data }) => {
this.dataListLoading = false
if (data && data.code === 0) {
this.dataList = data.page.list || []
@ -263,23 +201,78 @@
})
},
/**
* @Description 查询按钮点击事件 - rqrq
* @Title searchTable
* @author rqrq
*/
searchTable() {
this.pageIndex = 1
this.getDataList()
},
/**
* @Description 生成盘点结果 - rqrq
* @Title generateCountResult
* @author rqrq
* @Description 导出按当前筛选拉取快照列表 mainColumnList / fields 一致- rqrq
*/
async createExportData() {
try {
const queryParams = this.buildQuery({ page: 1, limit: EXPORT_MAX_ROWS })
const { data } = await searchRfidCountSnapshotList(queryParams)
if (data && data.code === 0) {
const list = data.page.list || []
const totalAll = data.page.totalCount || 0
if (totalAll > EXPORT_MAX_ROWS) {
this.$message.warning(`${totalAll} 条,本次最多导出前 ${EXPORT_MAX_ROWS}`)
}
const dateProps = ['lastCountDate', 'updatedDate', 'createdDate']
return list.map(row => {
const o = { ...row }
dateProps.forEach(p => {
if (o[p]) {
o[p] = this.formatDateTime(o[p])
}
})
return o
})
}
this.$message.error((data && data.msg) || '导出查询失败')
return []
} catch (e) {
console.error(e)
this.$message.error('导出失败')
return []
}
},
startDownload() {
this.exportLoading = true
},
finishDownload() {
this.exportLoading = false
},
fields() {
let json = '{'
this.mainColumnList.forEach((item, index) => {
if (index === this.mainColumnList.length - 1) {
json += '"' + item.columnLabel + '":"' + item.columnProp + '"'
} else {
json += '"' + item.columnLabel + '":"' + item.columnProp + '",'
}
})
json += '}'
return eval('(' + json + ')')
},
formatDateTime(dateTime) {
if (!dateTime) return ''
const date = new Date(dateTime)
if (isNaN(date.getTime())) return String(dateTime)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}`
},
generateCountResult() {
// - rqrq
this.$confirm('确认将RFID盘点快照生成为正式盘点单吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@ -287,16 +280,15 @@
}).then(() => {
this.generateLoading = true
let params = {
const params = {
site: this.$store.state.user.site,
username: this.$store.state.user.name
}
generateCountResultFromSnapshot(params).then(({data}) => {
generateCountResultFromSnapshot(params).then(({ data }) => {
this.generateLoading = false
if (data && data.code === 0) {
this.$message.success(data.msg || '生成成功')
// - rqrq
this.getDataList()
} else {
this.$alert(data.msg, '错误', {
@ -307,37 +299,22 @@
this.generateLoading = false
this.$message.error('生成失败')
})
}).catch(() => {
// - rqrq
})
}).catch(() => {})
},
/**
* @Description 每页数变化 - rqrq
* @Title sizeChangeHandle
* @param {Number} val 每页数量
* @author rqrq
*/
sizeChangeHandle(val) {
this.pageSize = val
this.pageIndex = 1
this.getDataList()
},
/**
* @Description 当前页变化 - rqrq
* @Title currentChangeHandle
* @param {Number} val 当前页码
* @author rqrq
*/
currentChangeHandle(val) {
this.pageIndex = val
this.getDataList()
}
}
}
}
</script>
<style scoped>
/* 样式使用全局样式,无需额外定义 - rqrq */
</style>

79
src/views/modules/check/searchPhysicalInventory.vue

@ -32,6 +32,8 @@
<el-option label="循环盘点" value="CYCLE"></el-option>
<el-option label="手工盘点" value="MANUAL"></el-option>
<el-option label="日常盘点" value="DAILY"></el-option>
<el-option label="年中盘点" value="MID_YEAR"></el-option>
<el-option label="年末盘点" value="YEAR_END"></el-option>
</el-select>
</el-form-item>
<el-form-item label="状态" style="margin-right: 10px;">
@ -55,6 +57,7 @@
<el-button @click="resetQuery">重置</el-button>
<el-button type="success" @click="showCycleCountDialog">循环盘点</el-button>
<el-button type="warning" @click="handleCreateManualCount">手工盘点</el-button>
<el-button type="primary" plain @click="openYearReportDialog">年度盘点报告</el-button>
</el-form-item>
</el-form>
</el-form>
@ -312,6 +315,26 @@
</el-footer>
</el-dialog>
<!-- 年度盘点报告弹窗 - rqrq -->
<el-dialog title="年度盘点报告" :visible.sync="yearReportDialogVisible" width="460px" :close-on-click-modal="false" v-drag @close="resetYearReportForm">
<el-form :model="yearReportForm" label-width="120px">
<el-form-item label="盘点报告类型" required>
<el-select v-model="yearReportForm.countType" placeholder="请选择" style="width: 280px;">
<el-option label="年中盘点" value="MID_YEAR"></el-option>
<el-option label="年末盘点" value="YEAR_END"></el-option>
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="yearReportForm.remark" type="textarea" :rows="3" placeholder="可选" style="width: 280px;"></el-input>
</el-form-item>
</el-form>
<el-footer style="height:30px;margin-top: 60px;text-align:center">
<el-button type="primary" @click="submitYearReport(false)" :loading="yearReportLoading"> </el-button>
<el-button @click="yearReportDialogVisible = false" :disabled="yearReportLoading"> </el-button>
</el-footer>
</el-dialog>
<!-- 添加物料弹窗显示已有标签列表-->
<el-dialog :title="'添加物料 - ' + (addMaterialRow ? addMaterialRow.countNo : '')" :visible.sync="addMaterialDialogVisible" width="1000px" :close-on-click-modal="false" v-drag>
<div style="margin-bottom: 10px; display: flex; align-items: center; gap: 15px;">
@ -408,7 +431,7 @@
</template>
<script>
import { searchCountHeaderList, createCycleCount, createManualCount, queryMaterialForManualCount, addMaterialToCount, quickAddMaterialByPartNo, deleteMaterialByPartNo,
import { searchCountHeaderList, createCycleCount, createManualCount, generateYearInventoryReport, queryMaterialForManualCount, addMaterialToCount, quickAddMaterialByPartNo, deleteMaterialByPartNo,
releaseCount, pushCountToWcs, completeCount, cancelCount, deleteCount, searchCountLabelList,
searchCountPalletList, searchCountResultList, searchMaterialSummary, searchOrderTaskByCountNo,
searchOrderTaskDetail, getSysIfCount, updateSysIfCount, queryAdjustmentTransList,
@ -558,6 +581,14 @@ export default {
nowPercent:null,//
createLoading: false,
// - rqrq
yearReportDialogVisible: false,
yearReportLoading: false,
yearReportForm: {
countType: 'MID_YEAR',
remark: ''
},
// - rqrq
addMaterialDialogVisible: false,
addMaterialRow: null,
@ -963,6 +994,52 @@ export default {
} )
},
// - rqrq
openYearReportDialog() {
this.yearReportForm = { countType: 'MID_YEAR', remark: '' }
this.yearReportDialogVisible = true
},
resetYearReportForm() {
this.yearReportForm = { countType: 'MID_YEAR', remark: '' }
},
submitYearReport(forceContinue) {
if (!this.yearReportForm.countType) {
this.$message.warning('请选择盘点报告类型')
return
}
this.yearReportLoading = true
const params = {
site: this.$store.state.user.site,
username: this.$store.state.user.name,
countType: this.yearReportForm.countType,
remark: this.yearReportForm.remark,
forceContinue: forceContinue === true
}
generateYearInventoryReport(params).then(({ data }) => {
if (data && data.code === 0) {
const row = data.row || {}
this.$message.success('生成成功,盘点单号:' + (row.countNo || '') + '(标签 ' + (row.labelCount || 0) + ' 条)')
this.yearReportDialogVisible = false
this.search()
} else if (data && data.code === 460) {
const msg = data.msg || '存在未盘点在库标签'
this.$confirm(msg, '存在未盘点在库标签', {
confirmButtonText: '仍要生成',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.submitYearReport(true)
}).catch(() => {})
} else {
this.$alert((data && data.msg) || '生成失败', '提示', { type: 'error' })
}
}).catch(() => {
this.$alert('网络错误', '错误', { confirmButtonText: '确定' })
}).finally(() => {
this.yearReportLoading = false
})
},
// - rqrq
createCycleCount() {
if (!this.cycleCountForm.countPercent || this.cycleCountForm.countPercent < 1 || this.cycleCountForm.countPercent > 50) {

7
src/views/modules/warehouse/changeHUSpecialItem.vue

@ -607,16 +607,17 @@ export default {
})
// - rqrq
// - rqrq
this.getDataList()
},
methods: {
// - rqrq
getDataList() {
this.dataListLoading = true
if(this.queryHeaderData.partNo==''||this.queryHeaderData.partNo==null){
this.$message.warning('物料编码必填。')
return false
}
this.dataListLoading = true
const params = {
...this.queryHeaderData,
page: this.pageIndex,

Loading…
Cancel
Save