heli-mes/mes-ui/mes-ui-admin-vue3/src/views/heli/xzdstoragelog/index.vue

614 lines
21 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>
<el-card class="hl-card">
<!-- <template #header>-->
<!-- <span>入出库单据生成</span>-->
<!-- </template>-->
<ContentWrap class="borderxx">
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="120px"
>
<el-form-item label="单据日期" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
class="!w-280px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="物料编号" prop="matCode">
<el-input
v-model="queryParams.matCode"
placeholder="物料编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="物料名称" prop="matName">
<el-input
v-model="queryParams.matName"
placeholder="物料名称"
clearable
@keyup.enter="handleQuery"
class="!w-260px"
/>
</el-form-item>
<el-form-item label="单据类型" prop="stockType">
<el-select
v-model="queryParams.stockType"
placeholder="请选择单据类型"
clearable
class="!w-280px"
>
<el-option
v-for="item in HeliStockTypeDict"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="单据状态" prop="isExport">
<el-select
v-model="queryParams.isExport"
placeholder="请选择状态"
clearable
class="!w-240px"
>
<el-option
v-for="item in HeliStorageIsExportDict"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery" type="primary">
<Icon icon="ep:search" class="mr-5px" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" />
重置
</el-button>
<el-checkbox style="margin-left: 18px" v-model="queryParams.isSameMat">
相同物料
</el-checkbox>
<el-button style="margin-left: 18px" @click="getMatCode()" type="primary" size="large">
获取编码
</el-button>
<el-button
style="margin-left: 18px"
@click="submitForm()"
type="primary"
size="large"
:disabled="!multipleSelection || multipleSelection.length === 0"
>
保存编码
</el-button>
<el-button
style="margin-left: 18px"
@click="generateBill()"
type="primary"
size="large"
:disabled="!multipleSelection || multipleSelection.length === 0"
>
单据生成
</el-button>
<el-button style="margin-left: 18px" @click="openPnForm()" type="success" size="large">
新增物料
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<div class="section-title">出入库单据信息</div>
<el-table
v-loading="loading"
:data="list"
:show-overflow-tooltip="true"
class="hl-table"
ref="multipleTable"
height="calc(100vh - 410px)"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="50" :selectable="checkSelectable" />
<el-table-column type="index" width="60" label="序号" align="center" fixed="left" />
<el-table-column
label="单据类型"
align="center"
prop="stockMode"
min-width="110"
fixed="left"
>
<template #default="scope">
<dict-tag :type="DICT_TYPE.HELI_STORAGE_TYPE" :value="scope.row.stockType" />
</template>
</el-table-column>
<el-table-column
label="单据状态"
align="center"
prop="stockMode"
min-width="110"
fixed="left"
>
<template #default="scope">
<el-tag v-if="scope.row.isExport == 0 || scope.row.isExport == null" type="danger">
未生成
</el-tag>
<el-tag v-else-if="scope.row.isExport == 1" type="success">已生成</el-tag>
<el-tag v-else type="success">其他</el-tag>
</template>
</el-table-column>
<el-table-column
label="单据编号"
align="center"
prop="xzdStockNo"
min-width="220"
/>
<el-table-column label="物料编码" align="center" prop="matCode" min-width="200">
<template #default="scope">
<template v-if="scope.row.goodsType == 2">
<el-form-item class="mb-0px!">
<el-select
v-model="scope.row.matId"
placeholder=""
style="width: 100%"
filterable
clearable
@change="(val) => handleMatIdChange(val, scope.row)"
>
<el-option
v-for="dict in scope.row.materialDOList"
:key="dict.id"
:label="dict.code + ' ' + dict.name"
:value="dict.id"
/>
</el-select>
</el-form-item>
</template>
<template v-else>
{{ scope.row.matCode }}
</template>
</template>
</el-table-column>
<el-table-column label="价格类型" align="center" prop="priceType" min-width="190">
<template #default="scope">
<el-select v-model="scope.row.priceType" placeholder="请选择" class="!w-full" clearable>
<el-option
v-for="item in getIntDictOptions(DICT_TYPE.HELI_PRICE_TYPE)"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="发票编码" align="center" prop="incoiceCode" min-width="160">
<template #default="scope">
<el-input
v-model="scope.row.incoiceCode"
placeholder="请输入发票编码"
clearable
class="!w-full"
/>
</template>
</el-table-column>
<el-table-column label="物料名称" align="center" prop="matName" min-width="120" />
<el-table-column label="单价" align="center" prop="price" min-width="120" />
<el-table-column label="供应商简称" align="center" prop="brief" min-width="200" />
<el-table-column label="数量" align="center" prop="storageOkQty" min-width="140" />
<!-- <el-table-column label="单据类型" align="center" prop="stockMode" min-width="180" >-->
<!-- <template #default="scope">-->
<!-- <dict-tag :type="DICT_TYPE.HELI_STOCK_MODE" :value="scope.row.stockMode" />-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="规格型号" align="center" prop="matSpec" min-width="100" />
<el-table-column label="仓库" align="center" prop="whName" min-width="100" />
<!-- <el-table-column label="库区" align="center" prop="rgName" min-width="100" />-->
<el-table-column label="库区/库位" align="center" prop="pnName" min-width="140" />
<el-table-column label="单位" align="center" prop="matUnit" min-width="120">
<template #default="scope">
<dict-tag :type="DICT_TYPE.HELI_MATERIAL_UNIT" :value="scope.row.matUnit" />
</template>
</el-table-column>
<el-table-column label="出入库单号" align="center" prop="codeNo" min-width="180" />
<el-table-column
label="入/出库日期"
align="center"
prop="createTime"
:formatter="dateFormatter1"
min-width="170"
/>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</el-card>
<MaterialForm ref="materialOpenFormRef" @success="getList" />
</template>
<script setup lang="ts">
import * as StorageLogApi from '@/api/heli/xzdstoragelog'
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import * as WarehouseApi from '@/api/heli/warehouse'
import routeParamsCache from '@/utils/routeParamsCache'
import { ElButton, ElTableColumn } from 'element-plus'
import { ref } from 'vue'
import { HeliStockTypeDict, HeliStorageStatusDict } from '@/api/heli/wmsstorage'
import { HeliStorageIsExportDict } from '@/api/heli/xzdstoragelog'
import { dateFormatter, dateFormatter1 } from '@/utils/formatTime'
import MaterialForm from "@/views/heli/material/MaterialForm.vue";
defineOptions({ name: 'Xzdstoragelog' })
const whList = ref([])
const multipleSelection = ref([])
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const exportLoading = ref(false) // 导出的加载中
const loading = ref(true) // 列表的加载中
const list = ref([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
matCode: undefined,
matName: undefined,
matType: undefined,
whId: undefined,
lotNo: undefined,
pnId: undefined,
stockNo: undefined,
stockType: undefined,
isExport: undefined,
createTime: [],
ids: []
})
const queryFormRef = ref() // 搜索的表单
const handleSelectionChange = (val) => {
// 更新 multipleSelection
multipleSelection.value = val
}
// 判断行是否可选择:已生成的单据不能选择
const checkSelectable = (row) => {
return row.isExport == 0 || row.isExport == null
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await StorageLogApi.getStorageLogPage(queryParams)
list.value = data.list
total.value = data.total
// 如果没有价格类型,默认选择 1|临时暂估价
list.value.forEach((item) => {
if (!item.priceType) {
item.priceType = '1|临时暂估价'
}
})
} finally {
loading.value = false
}
}
const materialOpenFormRef = ref()
const openPnForm = () => {
materialOpenFormRef.value.open("create")
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
queryParams.createTime = getDefaultDateRange()
handleQuery()
}
//仓库列表
const init_page_wh = async () => {
whList.value = await WarehouseApi.getSimpList()
}
// //库区列表
// const init_page_rg = (async ()=>{
// rgList.value = await RgApi.getSimpList()
// })
// //库位列表
// const init_page_pn = (async ()=>{
// pnList.value = await PnApi.getSimpList()
// })
const getMatCode = async () => {
try {
const selectedList = multipleSelection.value || [] // 安全获取数据
// 1. 检查空数据
if (!selectedList || selectedList.length == null) {
message.error('提交明细不能为空,请确认')
return
}
queryParams.ids = selectedList.map((item) => item.id)
// 保存当前的下拉框选择值和 supplierName
const currentSelections = selectedList.map((row) => ({
id: row.id,
matId: row.matId,
priceType: row.priceType,
incoiceCode: row.incoiceCode,
supplierName: row.supplierName
}))
await getList()
// 恢复下拉框的值和 supplierName
currentSelections.forEach((selection) => {
const row = list.value.find((r) => r.id === selection.id)
if (row) {
row.matId = selection.matId
row.priceType = selection.priceType
row.incoiceCode = selection.incoiceCode
row.supplierName = selection.supplierName
}
})
queryParams.ids = []
message.success('获取成功')
emit('success')
} catch (error) {
console.error('获取失败:', error)
// message.error(`操作失败: ${error.message || "未知错误"}`);
} finally {
loading.value = false
}
}
const generateBill = async () => {
// 1. 检查是否选择了数据
if (multipleSelection.value.length === 0) {
message.warning('请选择要生成的数据')
return
}
// 2. 必须选择同一类型的单据
if (
multipleSelection.value.some((row) => row.stockType !== multipleSelection.value[0].stockType)
) {
message.warning('请选择同一单据类型的数据')
return
}
try {
// 3. 调用后端生成单据接口
await StorageLogApi.generateBill(multipleSelection.value)
message.success(`成功生成 ${multipleSelection.value.length} 条单据`)
// 4. 刷新列表,显示最新状态
await getList()
} catch (e: any) {
// 获取错误码和错误信息中的 ID 列表
let code = 0
let idStr = ''
if (typeof e === 'string') {
idStr = e
} else if (e.code) {
code = e.code
idStr = e.msg || ''
} else if (e.response?.data) {
code = e.response.data.code
idStr = e.response.data.msg || ''
}
// 移除 msg 中可能存在的中文后缀
idStr = idStr.replace('存在', '')
// 分割msg获取ID数组
const msg = idStr.split(',')
// 在list中找到对应ID的序号集合当前页的行号
const errorIndices = msg
.map((id) => {
const index = list.value.findIndex((row) => row.id === parseInt(id))
if (index === -1) return 0 // 未找到返回0
// 使用当前页的行号
return index + 1
})
.filter((index) => index > 0) // 过滤掉未找到的
// 转换为字符串用逗号分割errorIndices从小到大排序
errorIndices.sort((a, b) => a - b)
const errorIndicesStr = errorIndices.join(',')
// 根据不同的错误码显示不同的提示
if (code === 11115) {
message.error(`${errorIndicesStr} )没有选择物料编码,请确认!`)
} else if (code === 11111) {
message.error(`${errorIndicesStr} )存在已删除的数据,请刷新界面!`)
} else if (code === 111102) {
message.error(`${errorIndicesStr} )已生成入库单的记录,请刷新界面。`)
} else if (code === 11112) {
message.error(`${errorIndicesStr} )价格类型没有输入,请确认。`)
} else if (code === 11113) {
message.error(`${errorIndicesStr} )价格类型是'临时暂估价',发票编码不空,请确认。`)
} else if (code === 11114) {
message.error(`${errorIndicesStr} )价格类型是'3|实价',发票编码为空,请确认`)
} else {
message.error(`${errorIndicesStr} )存在异常数据,请刷新界面。`)
}
// 生成失败时,不清空多选状态,保留下拉框的值
return
}
}
const submitForm = async () => {
try {
const selectedList = multipleSelection.value || [] // 安全获取数据
// 1. 检查空数据
if (!selectedList || selectedList.length == null) {
message.error('提交明细不能为空,请确认')
return
}
// 2.物料编码不能为空 提示 行(具体序列号)没有选择物料编码,请确认! 使用表格中的实际行号
if (selectedList.some((row) => !row.matId)) {
const rowWithoutMatId = selectedList.find((row) => !row.matId)
const tableRowIndex = list.value.findIndex((r) => r.id === rowWithoutMatId.id) + 1
message.error(`${tableRowIndex} )没有选择物料编码,请确认!`)
return
}
loading.value = true
// 5. 提交数据(添加超时处理)
const res = await Promise.race([
StorageLogApi.submitForm(selectedList),
new Promise((_, reject) => setTimeout(() => reject(new Error('请求超时')), 30000))
])
message.success('保存成功')
// 不立即刷新列表,保持当前的下拉框选择状态
// 用户可以通过点击搜索按钮手动刷新
await getList() // 注释掉自动刷新
// emit('success')
} catch (e: any) {
console.log(e)
// 获取错误码和错误信息中的 ID 列表
let code = 0
let idStr = ''
if (typeof e === 'string') {
idStr = e
} else if (e.code) {
code = e.code
idStr = e.msg || ''
} else if (e.response?.data) {
code = e.response.data.code
idStr = e.response.data.msg || ''
}
// 分割msg获取ID数组
const msg = idStr.split(',')
// 在list中找到对应ID的序号集合当前页的行号
const errorIndices = msg
.map((id) => {
const index = list.value.findIndex((row) => row.id === parseInt(id))
if (index === -1) return 0 // 未找到返回0
// 使用当前页的行号
return index + 1
})
.filter((index) => index > 0) // 过滤掉未找到的
// 转换为字符串用逗号分割errorIndices从小到大排序
errorIndices.sort((a, b) => a - b)
const errorIndicesStr = errorIndices.join(',')
if (code === 10086) {
message.error(`${errorIndicesStr} )存在已删除的数据,请刷新界面!`)
} else if (code === 10087) {
message.error(`${errorIndicesStr} )已生成入库单的记录,请刷新界面。`)
} else if (code === 10088) {
message.error(`${errorIndicesStr} )没有选择物料编码,请确认!`)
} else {
message.error(`${errorIndicesStr} )存在异常数据,请刷新界面。`)
}
// console.error('获取失败:', error)
// message.error(`操作失败: ${error.message || "未知错误"}`);
} finally {
loading.value = false
}
}
/** 初始化 **/
const route = useRoute()
const routeValue = ref('')
onMounted(async () => {
let params = routeParamsCache.get(route.path)
routeValue.value = route.path
if (params) {
Object.assign(queryParams, params)
}
await init_page_wh()
// await init_page_rg()
// await init_page_pn()
await getList()
})
onBeforeUnmount(() => {
const plainParams = JSON.parse(JSON.stringify(queryParams))
routeParamsCache.set(routeValue.value, plainParams)
})
window.addEventListener('beforeunload', () => {
const plainParams = JSON.parse(JSON.stringify(queryParams))
routeParamsCache.set(routeValue.value, plainParams)
})
/** 初始化 **/
onMounted(() => {
// 设置默认日期范围当月1号到当天
queryParams.createTime = getDefaultDateRange()
// 自动加载数据
// getList()
})
/** 获取默认日期范围 */
const getDefaultDateRange = () => {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
const startDate = `${year}-${month}-01 00:00:00`
const endDate = `${year}-${month}-${day} 23:59:59`
return [startDate, endDate]
}
/** 物料编码变更处理 */
const handleMatIdChange = (val, row) => {
if (val && queryParams.isSameMat) {
// 当勾选了"相同物料"选项时,更新所有选中行的物料编码
const selectedIds = multipleSelection.value.map((item) => item.id)
list.value.forEach((item) => {
if (selectedIds.includes(item.id)) {
// 更新物料 ID
item.matId = val
// 自动设置价格类型为"1|临时暂估价"
if (!item.priceType) {
item.priceType = '1|临时暂估价'
}
// 同步更新物料编号和名称
const selectedMat = item.materialDOList?.find((m) => m.id === val)
if (selectedMat) {
item.matCode = selectedMat.code
item.matName = selectedMat.name
}
}
})
} else if (val) {
// 没有勾选"相同物料"时,只更新当前行
const selectedMat = row.materialDOList?.find((m) => m.id === val)
if (selectedMat) {
row.matCode = selectedMat.code
row.matName = selectedMat.name
}
// 自动设置价格类型为"1|临时暂估价"
if (!row.priceType) {
row.priceType = '1|临时暂估价'
}
}
}
</script>