Commit 6a357ea1 by 杨子

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

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

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

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

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

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