feat:入库单据-新增/编辑逻辑调整

main
黄伟杰 2 weeks ago
parent 66c7674c8f
commit c5b8b16d91

@ -14,6 +14,20 @@ export interface StockInVO {
remark: string // 备注
}
export interface StockInApproveRecordVO {
id: number
stockInId: number
actionType: string
fromStatus: number
toStatus: number
targetUserId: number
targetUserName: string
remark: string
creator: string
creatorName: string
createTime: string
}
// ERP 其它入库单 API
export const StockInApi = {
// 查询其它入库单分页
@ -47,6 +61,21 @@ export const StockInApi = {
})
},
// 提交入库单审核
submitStockIn: async (data: { id: string; auditUserId: string; remark?: string }) => {
return await request.put({ url: `/erp/stock-in/submit`, data })
},
// 审核入库单
auditStockIn: async (data: { id: string; status: string; remark?: string }) => {
return await request.put({ url: `/erp/stock-in/audit`, data })
},
// 查询入库单审核记录
getApproveRecordList: async (id: number) => {
return await request.get({ url: `/erp/stock-in/approve-record-list`, params: { id } })
},
// 删除其它入库单
deleteStockIn: async (ids: number[]) => {
return await request.delete({

@ -1,7 +1,15 @@
<template>
<div class="dv-repair-panel">
<div class="dv-repair-panel__header">
<div class="dv-repair-panel__title">{{ dialogTitle }}</div>
<div class="dv-repair-panel__title">
<span>{{ dialogTitle }}</span>
<dict-tag
v-if="formType === 'detail' && formData.status !== undefined"
class="ml-8px"
:type="DICT_TYPE.ERP_AUDIT_STATUS"
:value="formData.status"
/>
</div>
<el-button text @click="closeForm">
<Icon icon="ep:close" />
</el-button>
@ -50,6 +58,7 @@
filterable
:placeholder="t('ErpStock.In.placeholderInType')"
class="!w-1/1"
@change="handleInTypeChange"
>
<el-option
v-for="item in options"
@ -118,6 +127,24 @@
:disabled="disabled"
/>
</el-tab-pane>
<el-tab-pane v-if="showAuditRecordTab" label="审核记录" name="auditRecord">
<el-table
v-loading="auditRecordLoading"
:data="auditRecordList"
:stripe="true"
:show-overflow-tooltip="true"
>
<el-table-column type="index" label="序号" align="center" width="80" />
<el-table-column label="操作人" align="center" prop="creatorName" min-width="120" />
<el-table-column label="操作时间" align="center" prop="createTime" :formatter="dateFormatter" min-width="180" />
<el-table-column label="操作动作" align="center" min-width="120">
<template #default="{ row }">
{{ formatAuditAction(row) }}
</template>
</el-table-column>
<el-table-column label="操作说明" align="center" prop="remark" min-width="180" />
</el-table>
</el-tab-pane>
</el-tabs>
</ContentWrap>
</div>
@ -130,9 +157,9 @@
</div>
</template>
<script setup lang="ts">
import { getStrDictOptions } from '@/utils/dict'
import { getNowDateTime } from '@/utils/formatTime'
import { StockInApi, StockInVO } from '@/api/erp/stock/in'
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
import { dateFormatter, getNowDateTime } from '@/utils/formatTime'
import { StockInApi, StockInApproveRecordVO, StockInVO } from '@/api/erp/stock/in'
import StockInItemForm from './components/StockInItemForm.vue'
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
import { getSimpleUserList, type UserVO } from '@/api/system/user'
@ -156,7 +183,8 @@ const formData = ref({
items: [],
isCode: undefined,
no: undefined,
stockUserId: undefined
stockUserId: undefined,
status: undefined
})
const formRules = reactive({
inTime: [{ required: true, message: t('ErpStock.In.validatorInTimeRequired'), trigger: 'blur' }],
@ -177,6 +205,9 @@ watch(options, (items) => {
/** 子表的表单 */
const subTabsName = ref('item')
const itemFormRef = ref()
const auditRecordLoading = ref(false)
const auditRecordList = ref<StockInApproveRecordVO[]>([])
const showAuditRecordTab = computed(() => formType.value === 'detail' && Number(formData.value.status) !== 0)
//
const openRequestId = ref(0)
@ -192,6 +223,7 @@ const open = async (type: string, id?: number) => {
const currentRequestId = ++openRequestId.value
dialogTitle.value = t('action.' + type)
formType.value = type
subTabsName.value = 'item'
resetForm()
//
if (id) {
@ -199,6 +231,9 @@ const open = async (type: string, id?: number) => {
try {
formData.value = await StockInApi.getStockIn(id)
formData.value.stockUserId = normalizeUserId(formData.value.stockUserId)
if (type === 'detail' && Number(formData.value.status) !== 0) {
await getAuditRecordList(id)
}
if (currentRequestId !== openRequestId.value) return
} finally {
formLoading.value = false
@ -213,12 +248,37 @@ const open = async (type: string, id?: number) => {
userList.value = users ?? []
}
const getAuditRecordList = async (id: number) => {
auditRecordLoading.value = true
try {
auditRecordList.value = await StockInApi.getApproveRecordList(id)
} finally {
auditRecordLoading.value = false
}
}
const formatAuditAction = (row: StockInApproveRecordVO) => {
if (row.toStatus === 10) return '提交'
if (row.toStatus === 20) return '审核通过'
if (row.toStatus === 1) return '驳回'
if (row.actionType === 'SUBMIT') return '提交'
if (row.actionType === 'AUDIT') return '审核'
return row.actionType || '-'
}
const normalizeUserId = (value: any) => {
if (value === undefined || value === null || value === '') return undefined
return String(value)
}
defineExpose({ open }) // open
const handleInTypeChange = () => {
formData.value.items = []
nextTick(() => {
itemFormRef.value?.resetItems?.()
})
}
/** 提交表单 */
const emit = defineEmits(['success', 'closed']) // success
const submitForm = async () => {
@ -255,8 +315,10 @@ const resetForm = () => {
items: [],
isCode: true,
no: undefined,
stockUserId: undefined
stockUserId: undefined,
status: undefined
}
auditRecordList.value = []
formRef.value?.resetFields()
}

@ -60,7 +60,7 @@
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Item.unit')" min-width="80">
<el-table-column label="库存单位" min-width="80">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.productUnitName" />
@ -74,7 +74,16 @@
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isPurchaseUnitStockIn" label="采购换算数量" min-width="120">
<el-table-column v-if="isPurchaseUnitStockIn" min-width="120">
<template #header>
采购换算数量
<el-tooltip effect="dark" placement="top">
<template #content>
{{ purchaseUnitConvertTipText }}
</template>
<Icon icon="ep:question-filled" class="ml-4px" style="vertical-align: middle; color: #909399; cursor: pointer;" />
</el-tooltip>
</template>
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.purchaseUnitConvertQuantity" />
@ -90,7 +99,7 @@
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isPurchaseUnitStockIn" label="录入数量" prop="inputCount" min-width="140">
<el-table-column v-if="isProductMaterialStockIn" label="录入数量" prop="inputCount" min-width="140">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.inputCount`" class="mb-0px!">
<el-input-number v-model="row.inputCount" controls-position="right" :min="0.001" :precision="3"
@ -187,6 +196,7 @@
</Dialog>
</template>
<script setup lang="ts">
import { Icon } from '@/components/Icon'
import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
import { WarehouseAreaApi, WarehouseAreaVO } from '@/api/erp/stock/warehousearea'
@ -227,6 +237,7 @@ const activeCategoryType = computed(() => stockInCategoryTypeMap[props.inType ||
const isProductMaterialStockIn = computed(() => Boolean(activeCategoryType.value))
const isProductStockIn = computed(() => activeCategoryType.value === 1)
const isPurchaseUnitStockIn = computed(() => activeCategoryType.value === 2 || activeCategoryType.value === 3)
const purchaseUnitConvertTipText = computed(() => t('FactoryModeling.ProductInformation.dialogPurchaseUnitConvertTip'))
const productDialogVisible = ref(false)
const productDialogLoading = ref(false)
const productDialogList = ref<any[]>([])
@ -289,9 +300,11 @@ watch(
async () => {
if (isProductMaterialStockIn.value) {
formData.value.forEach((row) => {
row.inputCount = row.inputCount ?? row.count ?? 1
if (isProductStockIn.value) {
row.inputUnitType = row.inputUnitType || '个'
}
syncCountByInputCount(row)
})
await loadRowsWarehouseAreas(formData.value)
}
@ -349,12 +362,13 @@ const handleAdd = () => {
purchaseUnitName: undefined,
purchaseUnitConvertQuantity: undefined,
inputUnitType: isProductStockIn.value ? '个' : undefined,
inputCount: isPurchaseUnitStockIn.value ? 1 : undefined,
inputCount: isProductMaterialStockIn.value ? 1 : undefined,
stockCount: undefined,
count: isPurchaseUnitStockIn.value ? undefined : 1,
count: isProductMaterialStockIn.value ? undefined : 1,
totalPrice: undefined,
remark: undefined
}
syncCountByInputCount(row)
formData.value.push(row)
if (row.warehouseId && isProductMaterialStockIn.value) {
loadWarehouseAreas(row.warehouseId)
@ -413,8 +427,8 @@ const clearProduct = (row: any) => {
row.purchaseUnitId = undefined
row.purchaseUnitName = undefined
row.purchaseUnitConvertQuantity = undefined
row.inputCount = isPurchaseUnitStockIn.value ? undefined : row.inputCount
row.count = isPurchaseUnitStockIn.value ? undefined : row.count
row.inputCount = isProductMaterialStockIn.value ? undefined : row.inputCount
row.count = isProductMaterialStockIn.value ? undefined : row.count
}
const handleProductClear = (row: any) => {
@ -466,19 +480,25 @@ const confirmProductSelect = () => {
watch(
() => formData.value.map((row) => [row.inputCount, row.purchaseUnitConvertQuantity]),
() => {
if (!isPurchaseUnitStockIn.value) return
if (!isProductMaterialStockIn.value) return
formData.value.forEach(syncCountByInputCount)
},
{ deep: true }
)
const syncCountByInputCount = (row: any) => {
if (!isPurchaseUnitStockIn.value) return
if (!isProductMaterialStockIn.value) return
const inputCount = Number(row.inputCount)
const convertQuantity = Number(row.purchaseUnitConvertQuantity)
row.count = Number.isFinite(inputCount) && Number.isFinite(convertQuantity)
? inputCount * convertQuantity
: undefined
if (!Number.isFinite(inputCount)) {
row.count = undefined
return
}
if (!row.purchaseUnitId && !row.purchaseUnitName) {
row.count = inputCount
return
}
row.count = Number.isFinite(convertQuantity) ? inputCount * convertQuantity : inputCount
}
const loadWarehouseAreas = async (warehouseId: number) => {
@ -537,7 +557,15 @@ const setStockCount = async (row) => {
const validate = () => {
return formRef.value.validate()
}
defineExpose({ validate })
const resetItems = () => {
formData.value.splice(0, formData.value.length)
if (!props.disabled) {
handleAdd()
}
}
defineExpose({ validate, resetItems })
/** 初始化 */

@ -193,14 +193,20 @@
:label="t('ErpStock.In.status')"
align="center"
fixed="right"
width="90"
width="100"
prop="status"
sortable>
<template #default="scope">
<dict-tag :type="DICT_TYPE.ERP_AUDIT_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column :label="t('common.operate')" align="center" fixed="right" width="220">
<el-table-column
label="审核人"
align="right"
sortable
prop="auditUserName"
/>
<el-table-column :label="t('common.operate')" align="center" fixed="right" width="300">
<template #default="scope">
<el-button
link
@ -219,22 +225,31 @@
{{ t('action.edit') }}
</el-button>
<el-button
v-if="[0, 1].includes(Number(scope.row.status))"
link
type="primary"
@click="openSubmitDialog(scope.row)"
v-hasPermi="['erp:stock-in:update-status']"
>
提交
</el-button>
<el-button
v-if="Number(scope.row.status) === 10"
link
type="primary"
@click="handleUpdateStatus(scope.row.id, 20)"
@click="openAuditDialog(scope.row, 20)"
v-hasPermi="['erp:stock-in:update-status']"
v-if="scope.row.status === 10"
>
{{ t('action.approve') }}
通过
</el-button>
<el-button
v-if="Number(scope.row.status) === 10"
link
type="danger"
@click="handleUpdateStatus(scope.row.id, 10)"
@click="openAuditDialog(scope.row, 1)"
v-hasPermi="['erp:stock-in:update-status']"
v-else
>
{{ t('action.unapprove') }}
驳回
</el-button>
<el-button
link
@ -259,6 +274,56 @@
<!-- 表单面板添加/修改 -->
<StockInForm v-else ref="formRef" @success="getList" @closed="formVisible = false" />
<Dialog title="提交审核" v-model="submitDialogVisible" width="520px">
<el-form ref="submitFormRef" :model="submitFormData" :rules="submitFormRules" label-width="90px">
<el-form-item label="审核人" prop="auditUserId">
<el-select
v-model="submitFormData.auditUserId"
clearable
filterable
placeholder="请选择审核人"
class="!w-1/1"
>
<el-option
v-for="item in userList"
:key="String(item.id)"
:label="item.nickname"
:value="String(item.id)"
/>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="submitFormData.remark"
type="textarea"
:rows="3"
placeholder="请输入提交备注"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="actionLoading" @click="handleSubmitStockIn"></el-button>
</template>
</Dialog>
<Dialog :title="auditDialogTitle" v-model="auditDialogVisible" width="520px">
<el-form ref="auditFormRef" :model="auditFormData" label-width="90px">
<el-form-item label="备注" prop="remark">
<el-input
v-model="auditFormData.remark"
type="textarea"
:rows="3"
:placeholder="auditRemarkPlaceholder"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="auditDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="actionLoading" @click="handleAuditStockIn"></el-button>
</template>
</Dialog>
</div>
</template>
@ -311,6 +376,26 @@ const supplierList = ref<SupplierVO[]>([]) // 供应商列表
const userList = ref<UserVO[]>([]) //
const formVisible = ref(false) //
const actionLoading = ref(false)
const submitDialogVisible = ref(false)
const submitFormRef = ref()
const submitFormData = reactive({
id: '',
auditUserId: '',
remark: ''
})
const submitFormRules = reactive({
auditUserId: [{ required: true, message: '请选择审核人', trigger: 'change' }]
})
const auditDialogVisible = ref(false)
const auditFormRef = ref()
const auditFormData = reactive({
id: '',
status: '',
remark: ''
})
const auditDialogTitle = computed(() => auditFormData.status === '20' ? '审核通过' : '审核驳回')
const auditRemarkPlaceholder = computed(() => auditFormData.status === '20' ? '请输入审核通过备注' : '请输入驳回备注')
/** 查询列表 */
const getList = async () => {
@ -360,19 +445,52 @@ const handleDelete = async (ids: number[]) => {
} catch {}
}
/** 审批/反审批操作 */
const handleUpdateStatus = async (id: number, status: number) => {
const openSubmitDialog = (row: StockInVO) => {
submitFormData.id = String(row.id)
submitFormData.auditUserId = ''
submitFormData.remark = ''
submitDialogVisible.value = true
nextTick(() => submitFormRef.value?.clearValidate?.())
}
const handleSubmitStockIn = async () => {
await submitFormRef.value.validate()
actionLoading.value = true
try {
//
const confirmText = status === 20 ? t('ErpStock.In.confirmApprove') : t('ErpStock.In.confirmReverseApprove')
await message.confirm(confirmText)
//
await StockInApi.updateStockInStatus(id, status)
const successText = status === 20 ? t('ErpStock.In.approveSuccess') : t('ErpStock.In.reverseApproveSuccess')
message.success(successText)
//
await StockInApi.submitStockIn({
id: submitFormData.id,
auditUserId: submitFormData.auditUserId,
remark: submitFormData.remark
})
message.success('提交成功')
submitDialogVisible.value = false
await getList()
} catch {}
} finally {
actionLoading.value = false
}
}
const openAuditDialog = (row: StockInVO, status: 20 | 1) => {
auditFormData.id = String(row.id)
auditFormData.status = String(status)
auditFormData.remark = status === 20 ? '审核通过' : ''
auditDialogVisible.value = true
}
const handleAuditStockIn = async () => {
actionLoading.value = true
try {
await StockInApi.auditStockIn({
id: auditFormData.id,
status: auditFormData.status,
remark: auditFormData.remark
})
message.success(auditFormData.status === '20' ? '审核通过成功' : '驳回成功')
auditDialogVisible.value = false
await getList()
} finally {
actionLoading.value = false
}
}
/** 导出按钮操作 */

Loading…
Cancel
Save