Files
FTC25009/ruoyi-ui/src/views/out/locationOutbound/index.vue

473 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
<el-form-item label="仓库名称" prop="warehouseId">
<el-select v-model="queryParams.warehouseId" placeholder="请选择仓库" clearable filterable>
<el-option
v-for="warehouse in warehouseOptions"
:key="warehouse.warehouseId"
:label="warehouse.warehouseName"
:value="warehouse.warehouseId"
/>
</el-select>
</el-form-item>
<el-form-item label="货位编码" prop="cargoSpaceId">
<el-input
v-model="queryParams.cargoSpaceId"
placeholder="请输入货位编码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="货位名称" prop="cargoSpaceName">
<el-input
v-model="queryParams.cargoSpaceName"
placeholder="请输入货位名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="货位状态" prop="cargoSpaceStatus">
<el-select v-model="queryParams.cargoSpaceStatus" placeholder="请选择货位状态" clearable>
<el-option
v-for="dict in dict.type.bas_cargo_space_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="托盘条码" prop="trayCode">
<el-input
v-model="queryParams.trayCode"
placeholder="请输入托盘条码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="物料编码" prop="productId">
<el-input
v-model="queryParams.productId"
placeholder="请输入物料编码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="物料名称" prop="productName">
<el-input
v-model="queryParams.productName"
placeholder="请输入物料名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="批次号" prop="lotNo">
<el-input
v-model="queryParams.lotNo"
placeholder="请输入批次号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="货位范围" prop="showEmpty">
<el-switch v-model="queryParams.showEmpty" active-text="所有货位" inactive-text="有库存" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-position"
size="mini"
:disabled="multiple"
@click="openBatchGenerateDialog"
v-hasPermi="['out:locationOutbound:generate']"
>批量生成作业</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table
v-loading="loading"
:data="locationList"
border
stripe
highlight-current-row
row-key="cargoSpaceId"
@selection-change="handleSelectionChange"
@row-click="handleRowClick"
>
<el-table-column type="selection" width="55" align="center" :selectable="canGenerate" />
<el-table-column label="货位编码" align="center" prop="cargoSpaceId" width="150" show-overflow-tooltip />
<el-table-column label="货位名称" align="center" prop="cargoSpaceName" min-width="150" show-overflow-tooltip />
<el-table-column label="货位状态" align="center" prop="cargoSpaceStatus" width="110">
<template slot-scope="scope">
<dict-tag :options="dict.type.bas_cargo_space_status" :value="scope.row.cargoSpaceStatus" />
</template>
</el-table-column>
<el-table-column label="仓库" align="center" prop="warehouseName" width="120" show-overflow-tooltip />
<el-table-column label="巷道" align="center" prop="roadwayId" width="80" />
<el-table-column label="排" align="center" prop="thePlatoon" width="70" />
<el-table-column label="列" align="center" prop="theColumn" width="70" />
<el-table-column label="层" align="center" prop="theFloor" width="70" />
<el-table-column label="库存ID" align="center" prop="stockId" width="160" show-overflow-tooltip />
<el-table-column label="托盘条码" align="center" prop="trayCode" width="140" show-overflow-tooltip />
<el-table-column label="托盘类型" align="center" prop="trayType" width="90" />
<el-table-column label="明细数" align="center" prop="detailCount" width="80" />
<el-table-column label="总库存" align="center" prop="quantity" width="90" />
<el-table-column label="已分配" align="center" prop="outQuantity" width="90" />
<el-table-column label="可用库存" align="center" prop="availableQuantity" width="100" />
<el-table-column label="入库时间" align="center" prop="inDate" width="160">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.inDate, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-position"
:disabled="!canGenerate(scope.row)"
@click="openGenerateDialog(scope.row)"
v-hasPermi="['out:locationOutbound:generate']"
>生成作业</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<div class="detail-panel">
<div class="detail-header">
<span class="detail-title">库存明细</span>
<el-tag v-if="currentLocation" type="primary" size="small">
货位:{{ currentLocation.cargoSpaceName || currentLocation.cargoSpaceId }}
</el-tag>
<span v-else class="detail-hint">(请点击上方货位行查看明细)</span>
</div>
<el-table
:data="locationDetailList"
border
stripe
size="small"
style="width: 100%;"
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
>
<el-table-column label="库存明细ID" align="center" prop="detailId" width="180" show-overflow-tooltip />
<el-table-column label="物料编码" align="center" prop="productId" width="130" show-overflow-tooltip />
<el-table-column label="物料名称" align="center" prop="productName" min-width="160" show-overflow-tooltip />
<el-table-column label="批次号" align="center" prop="lotNo" width="130" show-overflow-tooltip />
<el-table-column label="库存数量" align="center" prop="quantity" width="100" />
<el-table-column label="已分配" align="center" prop="outQuantity" width="100" />
<el-table-column label="可用库存" align="center" prop="availableQuantity" width="110" />
<el-table-column label="入库时间" align="center" prop="inDate" width="160">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.inDate, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<template slot="empty">
<div class="detail-empty">
{{ currentLocation ? (currentLocation.stockId ? '暂无库存明细' : '当前货位无库存') : '请先选择上方货位行' }}
</div>
</template>
</el-table>
</div>
<el-dialog
title="指定货位出库"
:visible.sync="generateDialogVisible"
width="520px"
append-to-body
>
<el-descriptions v-if="currentRow" :column="1" size="small" border class="mb12">
<el-descriptions-item label="货位">{{ currentRow.cargoSpaceName || currentRow.cargoSpaceId }}</el-descriptions-item>
<el-descriptions-item label="托盘">{{ currentRow.trayCode || '-' }}</el-descriptions-item>
<el-descriptions-item label="可用库存">{{ currentRow.availableQuantity || 0 }}</el-descriptions-item>
</el-descriptions>
<el-form label-width="90px" size="small">
<el-form-item label="出库口" required>
<el-select
v-model="selectedExitId"
placeholder="请选择出库口"
filterable
clearable
style="width: 100%;"
:loading="exitLoading"
>
<el-option
v-for="point in exitOptions"
:key="point.pointId"
:label="formatExitLabel(point)"
:value="point.pointId"
/>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="generateDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="generating" @click="confirmGenerate">确认生成</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listLocationOutbound, generateLocationOutbound } from '@/api/out/locationOutbound'
import { listStationPoint } from '@/api/bas/stationpoint'
import { listWarehouse } from '@/api/bas/warehouse'
export default {
name: 'LocationOutbound',
dicts: ['bas_cargo_space_status'],
data() {
return {
loading: true,
showSearch: true,
total: 0,
locationList: [],
currentLocation: null,
locationDetailList: [],
selectedRows: [],
multiple: true,
isBatchGenerate: false,
batchGenerating: false,
warehouseOptions: [],
queryParams: {
pageNum: 1,
pageSize: 10,
warehouseId: null,
warehouseName: null,
cargoSpaceId: null,
cargoSpaceName: null,
cargoSpaceStatus: null,
trayCode: null,
productId: null,
productName: null,
lotNo: null,
showEmpty: false
},
generateDialogVisible: false,
generating: false,
exitLoading: false,
currentRow: null,
selectedExitId: null,
exitOptions: []
}
},
created() {
this.loadWarehouseOptions()
this.getList()
},
methods: {
loadWarehouseOptions() {
listWarehouse({ pageNum: 1, pageSize: 1000 }).then(response => {
this.warehouseOptions = response.rows || []
}).catch(() => {
this.warehouseOptions = []
})
},
getList() {
this.loading = true
listLocationOutbound(this.queryParams).then(response => {
this.locationList = response.rows || []
this.total = response.total || 0
this.refreshCurrentLocation()
this.loading = false
}).catch(() => {
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.resetForm('queryForm')
this.queryParams.showEmpty = false
this.resetDetailQuery()
this.handleQuery()
},
resetDetailQuery() {
this.currentLocation = null
this.locationDetailList = []
},
refreshCurrentLocation() {
if (!this.currentLocation) {
this.locationDetailList = []
return
}
const current = this.locationList.find(row => row.cargoSpaceId === this.currentLocation.cargoSpaceId)
if (!current) {
this.resetDetailQuery()
return
}
this.handleRowClick(current)
},
handleRowClick(row) {
this.currentLocation = row
this.locationDetailList = row && row.details ? row.details : []
},
handleSelectionChange(selection) {
this.selectedRows = selection
this.multiple = selection.length === 0
},
canGenerate(row) {
if (!row) {
return false
}
if (!row.stockId) {
return ['1', '6'].includes(String(row.cargoSpaceStatus))
}
const availableQuantity = Number(row.availableQuantity || 0)
return String(row.cargoSpaceStatus) === '2' && availableQuantity > 0
},
openGenerateDialog(row) {
if (!this.canGenerate(row)) {
this.$modal.msgWarning('当前货位不能生成出库作业')
return
}
this.isBatchGenerate = false
this.currentRow = row
this.selectedExitId = null
this.generateDialogVisible = true
this.loadExitOptions(row)
},
openBatchGenerateDialog() {
if (this.selectedRows.length === 0) {
this.$modal.msgWarning('请选择可生成作业的货位')
return
}
const invalidRow = this.selectedRows.find(row => !this.canGenerate(row))
if (invalidRow) {
this.$modal.msgWarning('选中货位包含无可用库存或已生成出库作业的记录')
return
}
const rows = this.selectedRows
const warehouseIds = Array.from(new Set(rows.map(row => row.warehouseId).filter(Boolean)))
if (warehouseIds.length > 1) {
this.$modal.msgWarning('批量生成作业请选择同一仓库的货位')
return
}
this.isBatchGenerate = true
this.currentRow = rows[0]
this.selectedExitId = null
this.generateDialogVisible = true
this.loadExitOptions(rows[0])
},
loadExitOptions(row) {
this.exitLoading = true
listStationPoint({
warehouseId: row.warehouseId,
status: '0'
}).then(response => {
const rows = response.rows || response.data || []
this.exitOptions = rows.filter(point => {
return ['2', '3'].includes(String(point.pointType)) &&
String(point.pointStatus || '0') === '0' &&
this.pointSupportsTrayType(point, row.trayType)
})
if (this.exitOptions.length === 1) {
this.selectedExitId = this.exitOptions[0].pointId
}
this.exitLoading = false
}).catch(() => {
this.exitOptions = []
this.exitLoading = false
})
},
pointSupportsTrayType(point, trayType) {
if (!trayType || !point.trayType) {
return true
}
return String(point.trayType).split(',').map(item => item.trim()).includes(String(trayType))
},
formatExitLabel(point) {
const typeName = String(point.pointType) === '3' ? '入出库' : '出库'
const trayType = point.trayType ? `,${point.trayType}类托盘` : ''
return `${point.pointName || point.pointId}(${point.pointId},${typeName}${trayType})`
},
confirmGenerate() {
if (!this.currentRow || !this.currentRow.cargoSpaceId) {
this.$modal.msgWarning('请选择要出库的货位')
return
}
if (!this.selectedExitId) {
this.$modal.msgWarning('请选择出库口')
return
}
const rows = this.isBatchGenerate ? this.selectedRows.filter(row => this.canGenerate(row)) : [this.currentRow]
const confirmText = this.isBatchGenerate
? `确认将选中的 ${rows.length} 个货位生成出库作业?`
: this.currentRow.stockId
? `确认将货位 ${this.currentRow.cargoSpaceName || this.currentRow.cargoSpaceId} 的整托库存生成出库作业?`
: `确认将空货位 ${this.currentRow.cargoSpaceName || this.currentRow.cargoSpaceId} 生成出库作业?`
this.$modal.confirm(confirmText).then(() => {
this.generating = true
return Promise.all(rows.map(row => generateLocationOutbound({
stockId: row.stockId,
cargoSpaceId: row.cargoSpaceId,
exitId: this.selectedExitId
})))
}).then(response => {
const first = Array.isArray(response) && response.length > 0 ? response[0] : response
const message = this.isBatchGenerate ? '批量生成成功' : (first.msg || '生成成功')
this.$modal.msgSuccess(message)
this.generateDialogVisible = false
this.generating = false
this.getList()
}).catch(() => {
this.generating = false
})
}
}
}
</script>
<style scoped>
.detail-panel {
margin-top: 20px;
}
.detail-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.detail-title {
font-size: 14px;
font-weight: 600;
color: #303133;
}
.detail-hint {
margin-left: 4px;
color: #909399;
font-size: 12px;
}
.detail-empty {
padding: 18px 24px;
color: #909399;
background: #f8fafc;
}
.mb12 {
margin-bottom: 12px;
}
</style>