Commit 6a357ea1 by 杨子

feat(出库执行): 新增智能批次推荐功能并优化界面

refactor(库存查询): 调整库存汇总表格显示和排序逻辑

style(表单): 统一调整表单标签宽度为80px

fix(预警规则): 修复阈值输入和启用状态显示问题

perf(库存可视化): 优化库存信息显示布局和字段标签

chore(物资选择): 移除安全库存字段并调整状态切换方式
parent 94f94f90
...@@ -83,3 +83,14 @@ export function cancelByRelationId(data, operatorId) { ...@@ -83,3 +83,14 @@ export function cancelByRelationId(data, operatorId) {
} }
}) })
} }
/**
* 按策略查询库存数据
*/
export function getInventoryByStrategy(data) {
return request({
url: '/ware/wmsOutboundOrder/getInventoryByStrategy',
method: 'post',
data: data
})
}
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="8"> <el-col :span="8">
<el-form-item label="仓库"> <el-form-item label="仓库">
<el-select v-model="form.warehouseId" placeholder="请选择仓库" @change="handleWarehouseChange" size="small"> <el-select v-model="form.warehouseId" placeholder="请选择仓库" @change="handleWarehouseChange" size="small" filterable clearable>
<el-option <el-option
v-for="warehouse in warehouseList" v-for="warehouse in warehouseList"
:key="warehouse.warehouseId" :key="warehouse.warehouseId"
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="库区"> <el-form-item label="库区">
<el-select v-model="form.areaId" placeholder="请选择库区" @change="handleAreaChange" :disabled="!form.warehouseId" size="small"> <el-select v-model="form.areaId" placeholder="请选择库区" @change="handleAreaChange" :disabled="!form.warehouseId" size="small" filterable clearable>
<el-option <el-option
v-for="area in areaList" v-for="area in areaList"
:key="area.areaId" :key="area.areaId"
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="货架"> <el-form-item label="货架">
<el-select v-model="form.locationId" placeholder="请选择货架" :disabled="!form.areaId" size="small"> <el-select v-model="form.locationId" placeholder="请选择货架" :disabled="!form.areaId" size="small" filterable clearable>
<el-option <el-option
v-for="location in locationList" v-for="location in locationList"
:key="location.locationId" :key="location.locationId"
......
...@@ -59,7 +59,6 @@ ...@@ -59,7 +59,6 @@
<el-table-column label="规格型号" align="center" prop="specification" /> <el-table-column label="规格型号" align="center" prop="specification" />
<el-table-column label="计量单位" align="center" prop="unit" /> <el-table-column label="计量单位" align="center" prop="unit" />
<el-table-column label="生产厂家" align="center" prop="manufacturer" /> <el-table-column label="生产厂家" align="center" prop="manufacturer" />
<el-table-column label="安全库存" align="center" prop="safetyStock" />
<el-table-column label="状态" align="center" prop="status" > <el-table-column label="状态" align="center" prop="status" >
<template #default="scope"> <template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" /> <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
...@@ -131,13 +130,8 @@ ...@@ -131,13 +130,8 @@
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="安全库存" prop="safetyStock">
<el-input v-model.number="addMaterialForm.safetyStock" placeholder="请输入安全库存" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
<el-switch v-model="addMaterialForm.status" /> <el-switch v-model="addMaterialForm.status" active-value="0" inactive-value="1" active-text="正常" inactive-text="停用" inline-prompt />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
...@@ -209,7 +203,7 @@ const addMaterialForm = reactive({ ...@@ -209,7 +203,7 @@ const addMaterialForm = reactive({
unit: '', unit: '',
manufacturer: '', manufacturer: '',
safetyStock: null, safetyStock: null,
status: true status: '0'
}) })
// 新增物资表单验证规则 // 新增物资表单验证规则
...@@ -293,8 +287,6 @@ const handleAddMaterial = () => { ...@@ -293,8 +287,6 @@ const handleAddMaterial = () => {
if (addMaterialRef.value) { if (addMaterialRef.value) {
addMaterialRef.value.resetFields() addMaterialRef.value.resetFields()
} }
// 设置默认状态为启用
addMaterialForm.status = true
addMaterialVisible.value = true addMaterialVisible.value = true
} }
......
...@@ -17,14 +17,6 @@ ...@@ -17,14 +17,6 @@
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="阈值" prop="thresholdValue">
<el-input
v-model="queryParams.thresholdValue"
placeholder="请输入阈值"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="预警级别" prop="alertLevel"> <el-form-item label="预警级别" prop="alertLevel">
<el-select <el-select
...@@ -149,7 +141,7 @@ ...@@ -149,7 +141,7 @@
</el-form-item> </el-form-item>
<el-form-item label="阈值" prop="thresholdValue"> <el-form-item label="阈值" prop="thresholdValue">
<el-input v-model="form.thresholdValue" placeholder="请输入阈值" /> <el-input-number v-model="form.thresholdValue" placeholder="请输入阈值" :precision="2" />
</el-form-item> </el-form-item>
<el-form-item label="比较运算符" prop="comparisonOperator"> <el-form-item label="比较运算符" prop="comparisonOperator">
<el-select v-model="form.comparisonOperator" placeholder="请选择比较运算符"> <el-select v-model="form.comparisonOperator" placeholder="请选择比较运算符">
...@@ -163,9 +155,7 @@ ...@@ -163,9 +155,7 @@
</el-form-item> </el-form-item>
<el-form-item label="是否启用" prop="isEnabled"> <el-form-item label="是否启用" prop="isEnabled">
<el-select v-model="form.isEnabled" placeholder="请选择是否启用"> <el-switch v-model="form.isEnabled" active-value="1" inactive-value="2" active-text="启用" inactive-text="禁用" inline-prompt />
<el-option v-for="item in is_enabled" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="备注" prop="remark"> <el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /> <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
...@@ -238,7 +228,7 @@ const data = reactive({ ...@@ -238,7 +228,7 @@ const data = reactive({
alertLevel: null, alertLevel: null,
notificationType: null, notificationType: null,
notificationReceivers: null, notificationReceivers: null,
isEnabled: null, isEnabled: '1',
}, },
rules: { rules: {
ruleName: [ ruleName: [
...@@ -301,7 +291,7 @@ function reset() { ...@@ -301,7 +291,7 @@ function reset() {
alertLevel: null, alertLevel: null,
notificationType: null, notificationType: null,
notificationReceivers: null, notificationReceivers: null,
isEnabled: null, isEnabled: '1',
remark: null, remark: null,
createBy: null, createBy: null,
createTime: null, createTime: null,
...@@ -343,6 +333,7 @@ function handleUpdate(row) { ...@@ -343,6 +333,7 @@ function handleUpdate(row) {
const _ruleId = row.ruleId || ids.value const _ruleId = row.ruleId || ids.value
getWmsAlertRule(_ruleId).then(response => { getWmsAlertRule(_ruleId).then(response => {
form.value = response.data form.value = response.data
form.value.materialName = row.material?.materialName || ''
open.value = true open.value = true
title.value = "修改预警规则" title.value = "修改预警规则"
}) })
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-table ref="inventoryTableRef" :data="inventoryDetailList" row-key="itemDetailId" border height="600px" <el-table ref="inventoryTableRef" :data="inventoryDetailList" row-key="itemDetailId" border height="calc(100vh - 200px)"
@expand-change="handleExpandChange"> @expand-change="handleExpandChange">
<el-table-column type="expand" fixed="left"> <el-table-column type="expand" fixed="left">
<template #default="props"> <template #default="props">
......
<template> <template>
<div class="app-container"> <div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="120px"> <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="入库单号" prop="orderNo"> <el-form-item label="入库单号" prop="orderNo">
<el-input v-model="queryParams.orderNo" placeholder="请输入入库单号" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.orderNo" placeholder="请输入入库单号" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<!-- 第一列:库存基础信息 --> <!-- 第一列:库存基础信息 -->
<div class="visual-column location-info"> <div class="visual-column location-info">
<h3>库存基础信息</h3> <h3>库存基础信息</h3>
<el-form :model="inventoryInfo" label-width="80px" class="three-col-form"> <el-form :model="inventoryInfo" label-width="80px" label-position="top" class="three-col-form">
<div class="form-row"> <div class="form-row">
<el-form-item label="物资名称"> <el-form-item label="物资名称">
<span>{{ inventoryInfo.materialName || '-' }}</span> <span>{{ inventoryInfo.materialName || '-' }}</span>
...@@ -42,10 +42,10 @@ ...@@ -42,10 +42,10 @@
<el-form-item label="锁定数量"> <el-form-item label="锁定数量">
<span>{{ inventoryInfo.lockedQuantity || 0 }}</span> <span>{{ inventoryInfo.lockedQuantity || 0 }}</span>
</el-form-item> </el-form-item>
<el-form-item label="入库单价"> <el-form-item label="入库单价(元)">
<span>{{ inventoryInfo.unitCost || 0 }}</span> <span>{{ inventoryInfo.unitCost || 0 }}</span>
</el-form-item> </el-form-item>
<el-form-item label="总成本"> <el-form-item label="成本(元)">
<span>{{ inventoryInfo.totalCost || 0 }}</span> <span>{{ inventoryInfo.totalCost || 0 }}</span>
</el-form-item> </el-form-item>
</div> </div>
...@@ -136,7 +136,7 @@ const closeDialog = () => { ...@@ -136,7 +136,7 @@ const closeDialog = () => {
} }
.location-info { .location-info {
flex: 0 0 400px; flex: 0 0 600px;
} }
.visual-column h3 { .visual-column h3 {
......
...@@ -67,18 +67,18 @@ ...@@ -67,18 +67,18 @@
<!-- 是否显示0库存 --> <!-- 是否显示0库存 -->
<el-form-item label="是否显示0库存"> <el-form-item label="是否显示0库存">
<el-select v-model="queryParams.showzerostock" placeholder="请选择" clearable > <el-select v-model="queryParams.showzerostock" placeholder="请选择" clearable >
<el-option label="显示" value="true" /> <el-option label="显示" :value="true" />
<el-option label="不显示" value="false" /> <el-option label="不显示" :value="false" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<!-- 排序方式 --> <!-- 排序方式 -->
<el-form-item label="排序方式"> <el-form-item label="排序方式">
<el-select v-model="queryParams.orderBy" placeholder="请选择" clearable > <el-select v-model="queryParams.orderBy" placeholder="请选择" clearable >
<el-option label="总库存降序" value="totalquantity DESC" /> <el-option label="总库存降序" value="totalQuantity DESC" />
<el-option label="总库存升序" value="totalquantity ASC" /> <el-option label="总库存升序" value="totalQuantity ASC" />
<el-option label="总金额降序" value="totalAmount DESC" /> <el-option label="总金额降序" value="totalCost DESC" />
<el-option label="总金额升序" value="totalAmount ASC" /> <el-option label="总金额升序" value="totalCost ASC" />
</el-select> </el-select>
</el-form-item> </el-form-item>
...@@ -96,16 +96,18 @@ ...@@ -96,16 +96,18 @@
<template #default="props"> <template #default="props">
<el-table :data="props.row.inventoryList" border style="width: 100%" size="small"> <el-table :data="props.row.inventoryList" border style="width: 100%" size="small">
<el-table-column prop="batchNo" label="批次号" width="150" /> <el-table-column prop="batchNo" label="批次号" width="150" />
<el-table-column prop="warehouse.warehouseName" label="仓库" /> <el-table-column label="仓库/库区/货架">
<el-table-column prop="area.areaName" label="库区" /> <template #default="scope">
<el-table-column prop="location.locationName" label="货架"/> <div>{{ scope.row.warehouse.warehouseName || '-' }} / {{ scope.row.area.areaName || '-' }}/ {{ scope.row.location.locationName || '-' }}</div>
</template>
</el-table-column>
<el-table-column label="批次号" align="center" prop="batchNo" width="180" /> <el-table-column label="批次号" align="center" prop="batchNo" width="180" />
<el-table-column label="生产日期" align="center" prop="productionDate" width="180"> <el-table-column label="生产日期" align="center" prop="productionDate">
<template #default="scope"> <template #default="scope">
<span>{{ parseTime(scope.row.productionDate, '{y}-{m}-{d}') }}</span> <span>{{ parseTime(scope.row.productionDate, '{y}-{m}-{d}') }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="有效日期" align="center" prop="expirationDate" width="180"> <el-table-column label="有效日期" align="center" prop="expirationDate">
<template #default="scope"> <template #default="scope">
<span>{{ parseTime(scope.row.expirationDate, '{y}-{m}-{d}') }}</span> <span>{{ parseTime(scope.row.expirationDate, '{y}-{m}-{d}') }}</span>
</template> </template>
...@@ -113,19 +115,19 @@ ...@@ -113,19 +115,19 @@
<el-table-column label="库存数量" align="center" prop="quantity" /> <el-table-column label="库存数量" align="center" prop="quantity" />
<el-table-column label="可用数量" align="center" prop="availableQuantity" /> <el-table-column label="可用数量" align="center" prop="availableQuantity" />
<el-table-column label="锁定数量" align="center" prop="lockedQuantity" /> <el-table-column label="锁定数量" align="center" prop="lockedQuantity" />
<el-table-column label="入库单价" align="center" prop="unitCost" /> <el-table-column label="入库单价(元)" align="center" prop="unitCost" />
<el-table-column label="总成本" align="center" prop="totalCost" /> <el-table-column label="成本(元)" align="center" prop="totalCost" />
<el-table-column label="库存状态" align="center" prop="inventoryStatus"> <el-table-column label="库存状态" align="center" prop="inventoryStatus">
<template #default="scope"> <template #default="scope">
<dict-tag :options="inventory_status" :value="scope.row.inventoryStatus" /> <dict-tag :options="inventory_status" :value="scope.row.inventoryStatus" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="最后入库时间" align="center" prop="lastInboundTime" width="180"> <el-table-column label="最后入库时间" align="center" prop="lastInboundTime" >
<template #default="scope"> <template #default="scope">
<span>{{ parseTime(scope.row.lastInboundTime, '{y}-{m}-{d}') }}</span> <span>{{ parseTime(scope.row.lastInboundTime, '{y}-{m}-{d}') }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="最后出库时间" align="center" prop="lastOutboundTime" width="180"> <el-table-column label="最后出库时间" align="center" prop="lastOutboundTime">
<template #default="scope"> <template #default="scope">
<span>{{ parseTime(scope.row.lastOutboundTime, '{y}-{m}-{d}') }}</span> <span>{{ parseTime(scope.row.lastOutboundTime, '{y}-{m}-{d}') }}</span>
</template> </template>
...@@ -135,7 +137,6 @@ ...@@ -135,7 +137,6 @@
<span>{{ scope.row.rfidTag || '-' }}</span> <span>{{ scope.row.rfidTag || '-' }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="RFID绑定数量" align="center" prop="rfidBindingCount" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180" fixed="right"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180" fixed="right">
<template #default="scope"> <template #default="scope">
<el-button link type="primary" icon="Document" @click="handleFlowQuery(scope.row)" v-hasPermi="['ware:wmsInventory:flow']">库存履历</el-button> <el-button link type="primary" icon="Document" @click="handleFlowQuery(scope.row)" v-hasPermi="['ware:wmsInventory:flow']">库存履历</el-button>
...@@ -153,7 +154,7 @@ ...@@ -153,7 +154,7 @@
<el-table-column prop="specification" label="规格型号" width="150" /> <el-table-column prop="specification" label="规格型号" width="150" />
<el-table-column prop="unit" label="计量单位" width="100" /> <el-table-column prop="unit" label="计量单位" width="100" />
<el-table-column prop="totalQuantity" label="总库存" /> <el-table-column prop="totalQuantity" label="总库存" />
<!-- <el-table-column prop="totalAmount" label="总金额" /> --> <el-table-column prop="totalCost" label="总成本(元)" />
<el-table-column prop="locationCount" label="存储货架数" /> <el-table-column prop="locationCount" label="存储货架数" />
<el-table-column prop="batchCount" label="批次数量"/> <el-table-column prop="batchCount" label="批次数量"/>
</el-table> </el-table>
...@@ -187,6 +188,7 @@ import { listWmsArea } from '@/api/ware/wmsArea' ...@@ -187,6 +188,7 @@ import { listWmsArea } from '@/api/ware/wmsArea'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import LocationVisualDialog from './components/LocationVisualDialog.vue' import LocationVisualDialog from './components/LocationVisualDialog.vue'
import InventoryFlowDialog from '@/components/InventoryFlowDialog.vue' import InventoryFlowDialog from '@/components/InventoryFlowDialog.vue'
import { listWmsLocation } from '@/api/ware/wmsLocation'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const { sys_yes_no, inventory_status } = proxy.useDict('sys_yes_no', 'inventory_status') const { sys_yes_no, inventory_status } = proxy.useDict('sys_yes_no', 'inventory_status')
...@@ -206,8 +208,8 @@ const queryParams = reactive({ ...@@ -206,8 +208,8 @@ const queryParams = reactive({
materialName: null, materialName: null,
minQuantity: null, minQuantity: null,
maxQuantity: null, maxQuantity: null,
showzerostock: null, showzerostock: false,
orderBy: 'totalquantity DESC' orderBy: 'totalQuantity DESC'
}) })
// 物资分类选项 // 物资分类选项
...@@ -263,7 +265,7 @@ const resetQuery = () => { ...@@ -263,7 +265,7 @@ const resetQuery = () => {
queryParams.minQuantity = null queryParams.minQuantity = null
queryParams.maxQuantity = null queryParams.maxQuantity = null
queryParams.showzerostock = null queryParams.showzerostock = null
queryParams.orderBy = 'totalquantity DESC' queryParams.orderBy = 'totalQuantity DESC'
queryParams.pageNum = 1 queryParams.pageNum = 1
// 重置库区列表 // 重置库区列表
...@@ -373,25 +375,21 @@ const handleLocationVisualization = (row) => { ...@@ -373,25 +375,21 @@ const handleLocationVisualization = (row) => {
// 设置当前位置信息 // 设置当前位置信息
currentLocation.value = { currentLocation.value = {
locationId: row.location.locationId, locationId: row.locationId,
locationName: row.location.locationName, locationName: row.location?.locationName,
locationCode: row.location.locationCode
}
// 获取所有相关位置信息(这里需要根据实际情况调整,可能需要调用API)
// 暂时使用模拟数据
locations.value = [
{
locationId: row.location.locationId,
locationName: row.location.locationName,
locationCode: row.location.locationCode
} }
]
// 显示对话框 // 显示对话框
locationVisualVisible.value = true locationVisualVisible.value = true
} }
const getLocations = () => {
// 获取所有货架列表
listWmsLocation({ pageNum: 1, pageSize: 1000 }).then(response => {
locations.value = response.rows;
})
}
// 库存履历 // 库存履历
const handleFlowQuery = (row) => { const handleFlowQuery = (row) => {
// 设置选中的库存ID // 设置选中的库存ID
...@@ -406,6 +404,7 @@ onMounted(() => { ...@@ -406,6 +404,7 @@ onMounted(() => {
initWarehouseList() initWarehouseList()
initCategoryTree() initCategoryTree()
getInventoryList() getInventoryList()
getLocations()
}) })
</script> </script>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<el-dialog <el-dialog
v-model="visible" v-model="visible"
title="智能批次推荐" title="智能批次推荐"
width="700px" width="1000px"
:close-on-click-modal="false" :close-on-click-modal="false"
> >
<!-- 推荐策略选择 --> <!-- 推荐策略选择 -->
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
<el-radio-group v-model="selectedStrategy" size="small"> <el-radio-group v-model="selectedStrategy" size="small">
<el-radio-button label="FIFO">先进先出</el-radio-button> <el-radio-button label="FIFO">先进先出</el-radio-button>
<el-radio-button label="FEFO">先过期先出</el-radio-button> <el-radio-button label="FEFO">先过期先出</el-radio-button>
<el-radio-button label="NEAREST">最近库位</el-radio-button> <el-radio-button label="LIFO">后进先出</el-radio-button>
<el-radio-button label="MANUAL">手动选择</el-radio-button> <el-radio-button label="BATCH">批次选择</el-radio-button>
</el-radio-group> </el-radio-group>
<div class="strategy-description"> <div class="strategy-description">
...@@ -56,14 +56,14 @@ ...@@ -56,14 +56,14 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="batchNo" label="批次号" width="150"> <el-table-column prop="batchNo" label="批次号" width="240">
<template #default="{ row }"> <template #default="{ row }">
<div class="batch-info"> <div class="batch-info">
<span class="batch-no">{{ row.batchNo }}</span> <span class="batch-no">{{ row.batchNo }}</span>
<el-tag <el-tag
v-if="isNearExpiry(row.expiryDate)" v-if="isNearExpiry(row.expiryDate)"
type="warning" type="warning"
size="mini" size="small"
> >
临期 临期
</el-tag> </el-tag>
...@@ -72,35 +72,35 @@ ...@@ -72,35 +72,35 @@
</el-table-column> </el-table-column>
<el-table-column label="库存信息" width="120"> <el-table-column label="库存信息" width="120">
<template #default="{ row }"> <template #default="scope">
<div class="stock-info"> <div class="stock-info">
<div class="current-stock"> <div class="current-stock">
库存: <strong>{{ row.currentStock }}</strong> 库存: <strong>{{ scope.row.quantity || 0 }}</strong>
</div> </div>
<div class="recommended"> <div class="recommended">
推荐: <strong class="recommended-quantity">{{ row.recommendedQuantity }}</strong> 可用: <strong class="recommended-quantity">{{ scope.row.availableQuantity || 0 }}</strong>
</div> </div>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="location" label="库位" width="100"> <el-table-column label="库位" width="100">
<template #default="{ row }"> <template #default="scope">
<el-tag size="small" type="primary"> <el-tag size="small" type="primary">
{{ row.location }} {{ scope.row.location.locationName || '-' }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="效期" width="120"> <el-table-column label="效期" width="120">
<template #default="{ row }"> <template #default="scope">
<div class="expiry-info" :class="{ <div class="expiry-info" :class="{
'expiry-near': isNearExpiry(row.expiryDate), 'expiry-near': isNearExpiry(scope.row.expirationDate),
'expiry-urgent': isUrgentExpiry(row.expiryDate) 'expiry-urgent': isUrgentExpiry(scope.row.expirationDate)
}"> }">
{{ formatDate(row.expiryDate) }} {{ formatDate(scope.row.expirationDate) }}
<el-icon <el-icon
v-if="isNearExpiry(row.expiryDate)" v-if="isNearExpiry(scope.row.expirationDate)"
color="#e6a23c" color="#e6a23c"
size="12" size="12"
> >
...@@ -110,24 +110,24 @@ ...@@ -110,24 +110,24 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="priorityScore" label="推荐度" width="100" align="center"> <el-table-column label="推荐度" width="100" align="center">
<template #default="{ row }"> <template #default="scope">
<el-progress <el-progress
:percentage="row.priorityScore" :percentage="calculatePriorityScore(scope.row)"
:stroke-width="10" :stroke-width="10"
:show-text="false" :show-text="false"
/> />
<span class="score-text">{{ row.priorityScore }}</span> <span class="score-text">{{ calculatePriorityScore(scope.row) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="推荐理由"> <el-table-column label="推荐理由">
<template #default="{ row }"> <template #default="scope">
<div class="reasons"> <div class="reasons">
<el-tag <el-tag
v-for="(reason, index) in row.reason" v-for="(reason, index) in generateRecommendReasons(scope.row)"
:key="index" :key="index"
size="mini" size="small"
type="info" type="info"
effect="plain" effect="plain"
class="reason-tag" class="reason-tag"
...@@ -182,12 +182,17 @@ ...@@ -182,12 +182,17 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, watch } from 'vue' import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { InfoFilled, Warning } from '@element-plus/icons-vue' import { InfoFilled, Warning } from '@element-plus/icons-vue'
import { getInventoryByStrategy } from '@/api/ware/wmsOutboundOrder'
const props = defineProps() const props = defineProps({
const emit = defineEmits() modelValue: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:modelValue', 'select-batch-all', 'select-batch'])
// 响应式数据 // 响应式数据
const visible = computed({ const visible = computed({
...@@ -195,102 +200,84 @@ const visible = computed({ ...@@ -195,102 +200,84 @@ const visible = computed({
set: (value) => emit('update:modelValue', value) set: (value) => emit('update:modelValue', value)
}) })
const selectedStrategy = ref('FEFO') const selectedStrategy = ref('FIFO')
const loading = ref(false) const loading = ref(false)
const applying = ref(false) const applying = ref(false)
const activeExplanation = ref(['explanation']) const activeExplanation = ref(['explanation'])
// 当前详情数据
const currentDetail = ref({})
// 模拟推荐数据 // 模拟推荐数据
const recommendations = ref([ const recommendations = ref([])
{
batchId: 'B001', const getInventoryRecommendations = async () => {
batchNo: 'BATCH-202312-001', if (!currentDetail.value) return
materialId: 'MAT001', loading.value = true
materialName: '不锈钢螺丝', getInventoryByStrategy({
recommendedQuantity: 250, materialId: currentDetail.value.materialId,
currentStock: 300, warehouseId: currentDetail.value.warehouseId,
location: 'A-01-02', strategy: selectedStrategy.value
expiryDate: '2026-11-15', }).then(response => {
productionDate: '2023-11-15', // 处理返回的数据,计算推荐数量、推荐度和推荐理由
supplierName: '上海五金', const data = response.data || []
priorityScore: 95, recommendations.value = data.map((item, index) => ({
reason: ['先过期先出', '库位最近'] ...item,
}, recommendedQuantity: Math.min(item.availableQuantity, currentDetail.value.plannedQuantity - currentDetail.value.actualQuantity || 0)
{ }))
batchId: 'B002', loading.value = false
batchNo: 'BATCH-202311-015', })
materialId: 'MAT001', }
materialName: '不锈钢螺丝',
recommendedQuantity: 180, // 打开对话框方法
currentStock: 200, const open = (detail) => {
location: 'A-02-01', if (detail) {
expiryDate: '2026-10-20', currentDetail.value = detail
productionDate: '2023-10-20', getInventoryRecommendations()
supplierName: '浙江紧固件',
priorityScore: 88,
reason: ['先进先出', '库存充足']
},
{
batchId: 'B003',
batchNo: 'BATCH-202312-003',
materialId: 'MAT001',
materialName: '不锈钢螺丝',
recommendedQuantity: 70,
currentStock: 100,
location: 'B-01-03',
expiryDate: '2027-01-10',
productionDate: '2023-12-01',
supplierName: '江苏五金',
priorityScore: 75,
reason: ['新批次', '库存较少']
} }
]) }
// 暴露方法给父组件
defineExpose({
open
})
// 计算属性 // 计算属性
const strategyDescription = computed(() => { const strategyDescription = computed(() => {
const descriptions = { const descriptions = {
'FIFO': '优先出库生产日期早的批次,减少库存积压', 'FIFO': '优先出库生产日期早的批次,减少库存积压',
'FEFO': '优先出库效期近的批次,减少过期损失', 'FEFO': '优先出库效期近的批次,减少过期损失',
'NEAREST': '优先出库距离操作台近的批次,提高作业效率', 'LIFO': '优先出库距离操作台近的批次,提高作业效率',
'MANUAL': '不进行自动推荐,由操作员自主选择' 'BATCH': '不进行自动推荐,由操作员自主选择'
} }
return descriptions[selectedStrategy.value] || '' return descriptions[selectedStrategy.value] || ''
}) })
// 计算推荐度
const calculatePriorityScore = (row) => {
// 根据策略和批次信息计算推荐度
let score = 100 - (row.expirationDate ? new Date(row.expirationDate) - new Date() : 0) / (1000 * 60 * 60 * 24 * 30)
return Math.max(0, Math.min(100, Math.round(score)))
}
// 生成推荐理由
const generateRecommendReasons = (row) => {
const reasons = []
if (row.isFifo === 'Y') reasons.push('FIFO策略')
if (row.availableQuantity >= row.recommendedQuantity) reasons.push('库存充足')
if (isNearExpiry(row.expirationDate)) reasons.push('临期优先')
return reasons.length > 0 ? reasons : ['符合推荐策略']
}
const canApplyAll = computed(() => { const canApplyAll = computed(() => {
if (!props.detail) return false if (!currentDetail.value) return false
const remainingNeed = props.detail.plannedQuantity - props.detail.actualQuantity const remainingNeed = currentDetail.value.planQuantity - currentDetail.value.actualQuantity
const totalRecommended = recommendations.value.reduce( const totalRecommended = recommendations.value.reduce(
(sum, r) => sum + r.recommendedQuantity, 0 (sum, r) => sum + r.availableQuantity, 0
) )
return remainingNeed >= totalRecommended return remainingNeed >= totalRecommended
}) })
// 监听策略变化
watch(selectedStrategy, (newStrategy) => {
if (newStrategy && newStrategy !== 'MANUAL') {
loading.value = true
// 模拟API调用
setTimeout(() => {
// 根据策略重新排序
if (newStrategy === 'FEFO') {
recommendations.value.sort((a, b) => {
const dateA = new Date(a.expiryDate).getTime()
const dateB = new Date(b.expiryDate).getTime()
return dateA - dateB
})
} else if (newStrategy === 'FIFO') {
recommendations.value.sort((a, b) => {
const dateA = new Date(a.productionDate).getTime()
const dateB = new Date(b.productionDate).getTime()
return dateA - dateB
})
}
loading.value = false
}, 500)
}
})
// 方法 // 方法
const getRankTagType = (index) => { const getRankTagType = (index) => {
const types = ['danger', 'warning', 'primary'] const types = ['danger', 'warning', 'primary']
...@@ -298,6 +285,7 @@ const getRankTagType = (index) => { ...@@ -298,6 +285,7 @@ const getRankTagType = (index) => {
} }
const isNearExpiry = (expiryDate) => { const isNearExpiry = (expiryDate) => {
if (!expiryDate) return false
const expiry = new Date(expiryDate) const expiry = new Date(expiryDate)
const now = new Date() const now = new Date()
const diffDays = Math.ceil((expiry.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)) const diffDays = Math.ceil((expiry.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
...@@ -305,6 +293,7 @@ const isNearExpiry = (expiryDate) => { ...@@ -305,6 +293,7 @@ const isNearExpiry = (expiryDate) => {
} }
const isUrgentExpiry = (expiryDate) => { const isUrgentExpiry = (expiryDate) => {
if (!expiryDate) return false
const expiry = new Date(expiryDate) const expiry = new Date(expiryDate)
const now = new Date() const now = new Date()
const diffDays = Math.ceil((expiry.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)) const diffDays = Math.ceil((expiry.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
...@@ -312,6 +301,7 @@ const isUrgentExpiry = (expiryDate) => { ...@@ -312,6 +301,7 @@ const isUrgentExpiry = (expiryDate) => {
} }
const formatDate = (dateString) => { const formatDate = (dateString) => {
if (!dateString) return '-'
const date = new Date(dateString) const date = new Date(dateString)
return date.toLocaleString('zh-CN', { return date.toLocaleString('zh-CN', {
year: 'numeric', year: 'numeric',
...@@ -326,27 +316,9 @@ const selectBatch = (batch) => { ...@@ -326,27 +316,9 @@ const selectBatch = (batch) => {
} }
const applyAllRecommendations = async () => { const applyAllRecommendations = async () => {
if (!props.detail) return if (!currentDetail.value) return
emit('select-batch-all', recommendations.value)
applying.value = true
try {
// 模拟批量应用推荐
await new Promise(resolve => setTimeout(resolve, 1000))
ElMessage.success('已应用所有推荐批次')
// 触发批次选择事件(选择第一个)
if (recommendations.value.length > 0) {
emit('select-batch', recommendations.value[0])
}
visible.value = false visible.value = false
} catch (error) {
ElMessage.error('应用失败')
} finally {
applying.value = false
}
} }
const applyRecommendations = () => { const applyRecommendations = () => {
...@@ -354,6 +326,16 @@ const applyRecommendations = () => { ...@@ -354,6 +326,16 @@ const applyRecommendations = () => {
selectBatch(recommendations.value[0]) selectBatch(recommendations.value[0])
} }
} }
// 监听策略变化
watch(selectedStrategy, (newStrategy) => {
if (newStrategy && newStrategy !== 'BATCH') {
loading.value = true
getInventoryRecommendations()
}
})
</script> </script>
<style scoped> <style scoped>
......
...@@ -24,14 +24,16 @@ ...@@ -24,14 +24,16 @@
</div> </div>
<el-table ref="tableRef" :data="orderDetails" stripe highlight-current-row row-key="itemId" <el-table ref="tableRef" :data="orderDetails" stripe highlight-current-row row-key="itemId"
height="calc(100vh - 320px)" v-loading="loadingDetails" @expand-change="handleExpandChange" preserve-expanded-content> height="calc(100vh - 320px)" v-loading="loadingDetails" @expand-change="handleExpandChange"
preserve-expanded-content>
<el-table-column type="expand"> <el-table-column type="expand">
<template #default="props"> <template #default="props">
<el-table :data="props.row.inventoryRecords"> <el-table :data="props.row.inventoryRecords" height="400px">
<el-table-column label="批次号" align="center" prop="batchNo" width="120" /> <el-table-column label="批次号" align="center" prop="batchNo" width="120" />
<el-table-column label="仓库/库区/货架" align="center"> <el-table-column label="仓库/库区/货架" align="center">
<template #default="scope"> <template #default="scope">
{{ scope.row.warehouse?.warehouseName || '-' }}/{{ scope.row.area?.areaName || '-' }}/{{ scope.row.location?.locationName || '-' }} {{ scope.row.warehouse?.warehouseName || '-' }}/{{ scope.row.area?.areaName || '-' }}/{{
scope.row.location?.locationName || '-' }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="RFID标签" align="center" prop="rfidCode" /> <el-table-column label="RFID标签" align="center" prop="rfidCode" />
...@@ -55,7 +57,8 @@ ...@@ -55,7 +57,8 @@
<!-- <el-table-column label="撤销人" align="center" prop="cancelBy" /> --> <!-- <el-table-column label="撤销人" align="center" prop="cancelBy" /> -->
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right">
<template #default="scope"> <template #default="scope">
<el-button link v-if="scope.row.isCanceled === '0'" type="primary" @click="handleCancelByRelation(scope.row)">撤销</el-button> <el-button link v-if="scope.row.isCanceled === '0'" type="primary"
@click="handleCancelByRelation(scope.row)">撤销</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
...@@ -73,11 +76,22 @@ ...@@ -73,11 +76,22 @@
<dict-tag :options="out_order_status" :value="scope.row.pickingStatus" /> <dict-tag :options="out_order_status" :value="scope.row.pickingStatus" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="100" align="center" fixed="right"> <el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="scope"> <template #default="scope">
<el-button v-if="scope.row.pickingStatus !== '0'" type="primary" size="small" @click="handleCancel(scope.row)"> <div>
<el-button v-if="scope.row.pickingStatus !== '0'" type="primary" size="small"
@click="handleCancel(scope.row)">
撤销 撤销
</el-button> </el-button>
</div>
<div>
<el-button v-if="scope.row.pickingStatus !== '2'" type="success" size="small" @click="showBatchRecommendation(scope.row)">
<el-icon>
<MagicStick />
</el-icon>
智能推荐批次
</el-button>
</div>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
...@@ -90,18 +104,13 @@ ...@@ -90,18 +104,13 @@
<!-- <el-icon><Scan /></el-icon> --> <!-- <el-icon><Scan /></el-icon> -->
扫码出库 扫码出库
</h3> </h3>
<el-button type="info" size="small" @click="showBatchRecommendation">
<el-icon>
<MagicStick />
</el-icon>
智能推荐批次
</el-button>
</div> </div>
<!-- 扫码输入区域 --> <!-- 扫码输入区域 -->
<div class="scan-input-area"> <div class="scan-input-area">
<div class="input-wrapper"> <div class="input-wrapper">
<el-input v-model="scanInput" placeholder="请扫描二维码" size="large" clearable @keyup.enter="handleScan" <el-input v-model="scanInput" placeholder="请扫描RFID标签" size="large" clearable @keyup.enter="handleScan"
@clear="clearScanResult" ref="scanInputRef"> @clear="clearScanResult" ref="scanInputRef">
<template #prepend> <template #prepend>
<el-icon> <el-icon>
...@@ -240,7 +249,7 @@ ...@@ -240,7 +249,7 @@
{{ row.message }} {{ row.message }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="locationId" label="货架" width="100" /> <!-- <el-table-column prop="locationId" label="货架" width="100" /> -->
</el-table> </el-table>
</div> </div>
</div> </div>
...@@ -250,8 +259,8 @@ ...@@ -250,8 +259,8 @@
<BatchDetailDialog v-model="showBatchDialog" :detail="selectedDetail" :transactions="detailTransactions" /> <BatchDetailDialog v-model="showBatchDialog" :detail="selectedDetail" :transactions="detailTransactions" />
<!-- 批次推荐对话框 --> <!-- 批次推荐对话框 -->
<BatchRecommendationDialog v-model="showRecommendationDialog" :detail="selectedDetail" <BatchRecommendationDialog ref="recommendationDialogRef" v-model="showRecommendationDialog" :detail="selectedDetail"
@select-batch="handleSelectRecommendedBatch" /> @select-batch="handleSelectRecommendedBatch" @select-batch-all="handleSelectAllRecommendations" />
</div> </div>
</el-dialog> </el-dialog>
</template> </template>
...@@ -282,7 +291,7 @@ import { updateWmsOutboundOrder, confirmOutbound } from '@/api/ware/wmsOutboundO ...@@ -282,7 +291,7 @@ import { updateWmsOutboundOrder, confirmOutbound } from '@/api/ware/wmsOutboundO
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { cancelByDetailId, cancelByRelationId } from '@/api/ware/wmsOutboundOrder' import { cancelByDetailId, cancelByRelationId } from '@/api/ware/wmsOutboundOrder'
import { listWmsInventory } from "@/api/ware/wmsInventory" import { listWmsInventory } from "@/api/ware/wmsInventory"
import {listWmsOutboundItemInventory} from "@/api/ware/wmsOutboundItemInventory" import { listWmsOutboundItemInventory } from "@/api/ware/wmsOutboundItemInventory"
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
...@@ -334,7 +343,7 @@ const loadingTransactions = ref(false) ...@@ -334,7 +343,7 @@ const loadingTransactions = ref(false)
// 对话框控制 // 对话框控制
const showBatchDialog = ref(false) const showBatchDialog = ref(false)
const showRecommendationDialog = ref(false) const showRecommendationDialog = ref(false)
const recommendationDialogRef = ref(null)
// 计算属性 // 计算属性
const totalPlanned = computed(() => { const totalPlanned = computed(() => {
return orderDetails.value.reduce((sum, detail) => sum + (detail.planQuantity || 0), 0) return orderDetails.value.reduce((sum, detail) => sum + (detail.planQuantity || 0), 0)
...@@ -489,8 +498,6 @@ const handleOutbound = async () => { ...@@ -489,8 +498,6 @@ const handleOutbound = async () => {
lastTransaction.value = confirmResponse.data?.relation; lastTransaction.value = confirmResponse.data?.relation;
tableRef.value?.toggleRowExpansion(currentDetail, true)
// 检查当前行是否已展开 // 检查当前行是否已展开
if (expandedRows.value.has(currentDetail.itemId)) { if (expandedRows.value.has(currentDetail.itemId)) {
// 先收缩行 // 先收缩行
...@@ -499,6 +506,8 @@ const handleOutbound = async () => { ...@@ -499,6 +506,8 @@ const handleOutbound = async () => {
nextTick(() => { nextTick(() => {
tableRef.value?.toggleRowExpansion(currentDetail, true) tableRef.value?.toggleRowExpansion(currentDetail, true)
}) })
}else{
tableRef.value?.toggleRowExpansion(currentDetail, true)
} }
getWmsOutboundOrderItem(currentDetail.itemId).then(res => { getWmsOutboundOrderItem(currentDetail.itemId).then(res => {
...@@ -581,21 +590,16 @@ const handleUndoLast = async () => { ...@@ -581,21 +590,16 @@ const handleUndoLast = async () => {
} }
} }
const showBatchRecommendation = async () => { const showBatchRecommendation = async (row) => {
// if (!selectedDetail.value) { row.warehouseId = props.order.warehouseId
// const detail = orderDetails.value.find(d => d.id === currentDetailId.value) selectedDetail.value = row
// if (!detail) {
// ElMessage.warning('请先选择需要出库的明细行')
// return
// }
// selectedDetail.value = detail
// }
showRecommendationDialog.value = true showRecommendationDialog.value = true
recommendationDialogRef.value?.open(row)
} }
const handleSelectRecommendedBatch = (batch) => { const handleSelectRecommendedBatch = (batch) => {
currentBatch.value = batch currentBatch.value = batch
scanInput.value = batch.batchNo scanInput.value = batch.rfidTag
showRecommendationDialog.value = false showRecommendationDialog.value = false
// 自动触发扫描 // 自动触发扫描
nextTick(() => { nextTick(() => {
...@@ -603,6 +607,11 @@ const handleSelectRecommendedBatch = (batch) => { ...@@ -603,6 +607,11 @@ const handleSelectRecommendedBatch = (batch) => {
}) })
} }
const handleSelectAllRecommendations = (batches) => {
}
const clearScanResult = () => { const clearScanResult = () => {
currentBatch.value = null currentBatch.value = null
outboundQuantity.value = 0 outboundQuantity.value = 0
......
<template> <template>
<div class="app-container"> <div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="120px"> <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="出库单号" prop="orderNo"> <el-form-item label="出库单号" prop="orderNo">
<el-input <el-input
v-model="queryParams.orderNo" v-model="queryParams.orderNo"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment