feat:盘点记录页面

main
黄伟杰 3 days ago
parent 056862f6c0
commit 176f373e0f

@ -40,6 +40,37 @@ export interface StockCheckItemVO {
productUnitName: string // 产品单位名称
}
export interface StockCheckRecordVO {
id: number
stockCheckId?: number
stockCheckItemId?: number
no?: string
checkTime?: Date | string
sourceType?: number | string
categoryType?: number | string
status?: number | string
checkStatus?: number | string
productId?: number
productName?: string
productBarCode?: string
barCode?: string
standard?: string
productUnitName?: string
unitName?: string
warehouseId?: number
warehouseName?: string
areaId?: number
areaName?: string
stockCount?: number | string
actualCount?: number | string
count?: number | string
remark?: string
creator?: string
creatorName?: string
createTime?: Date | string
[key: string]: any
}
export interface StockCheckApproveRecordVO {
id?: number // 编号
stockCheckId?: number // 盘点单编号
@ -61,6 +92,16 @@ export const StockCheckApi = {
return await request.get({ url: `/erp/stock-check/page`, params })
},
// 查询库存盘点记录分页
getStockCheckRecordPage: async (params: any) => {
return await request.get({ url: `/erp/stock-check/record-page`, params })
},
// 导出库存盘点记录 Excel
exportStockCheckRecord: async (params) => {
return await request.download({ url: `/erp/stock-check/record-export-excel`, params })
},
// 查询库存盘点单详情
getStockCheck: async (id: number) => {
return await request.get({ url: `/erp/stock-check/get?id=` + id })

@ -716,6 +716,18 @@ export default {
validatorWarehouseRequired: 'Warehouse name is required'
}
},
CheckRecord: {
area: 'Area',
productBarCode: 'Material Code',
productName: 'Material Name',
warehouseName: 'Warehouse Name',
areaName: 'Area Name',
placeholderSourceType: 'Please select source type',
placeholderCategoryType: 'Please select product category',
placeholderArea: 'Please select area',
placeholderCheckStatus: 'Please select check status',
exportName: 'Stock Check Record.xls'
},
Record: {
id: 'ID',
product: 'Product',

@ -716,6 +716,18 @@ export default {
validatorWarehouseRequired: '仓库名字不能为空'
}
},
CheckRecord: {
area: '库区',
productBarCode: '物料编码',
productName: '物料名称',
warehouseName: '仓库名称',
areaName: '库区名称',
placeholderSourceType: '请选择生成来源',
placeholderCategoryType: '请选择产品分类',
placeholderArea: '请选择库区',
placeholderCheckStatus: '请选择盘点状态',
exportName: '盘点记录.xls'
},
Record: {
id: '编号',
product: '产品',

@ -178,8 +178,8 @@
<el-button
link
type="primary"
:disabled="scope.row.status === 20"
@click="openForm('update', scope.row.id)"
:disabled="isEditDisabled(scope.row)"
@click="openForm('update', scope.row.id, scope.row)"
>
{{ t('action.edit') }}
</el-button>
@ -363,7 +363,14 @@ const resetQuery = () => {
handleQuery()
}
const openForm = (type: string, id?: number) => {
const isEditDisabled = (row: StockCheckVO) => {
return Number(row.status) === 20 || Number(row.checkStatus) === 1
}
const openForm = (type: string, id?: number, row?: StockCheckVO) => {
if (type === 'update' && row && isEditDisabled(row)) {
return
}
formRef.value.open(type, id)
}

@ -0,0 +1,430 @@
<template>
<ContentWrap>
<el-form
ref="queryFormRef"
:model="queryParams"
:inline="true"
label-width="auto"
class="-mb-15px"
>
<el-form-item :label="t('ErpStock.Check.no')" prop="no">
<el-input
v-model="queryParams.no"
:placeholder="t('ErpStock.Check.placeholderNo')"
clearable
class="!w-240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item :label="t('ErpStock.Check.checkTime')" prop="checkTime">
<el-date-picker
v-model="queryParams.checkTime"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
:start-placeholder="t('common.startTimeText')"
:end-placeholder="t('common.endTimeText')"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('ErpStock.Check.sourceType')" prop="sourceType">
<el-select
v-model="queryParams.sourceType"
:placeholder="t('ErpStock.CheckRecord.placeholderSourceType')"
clearable
class="!w-240px"
>
<el-option
v-for="item in sourceTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.Check.categoryType')" prop="categoryType">
<el-select
v-model="queryParams.categoryType"
:placeholder="t('ErpStock.CheckRecord.placeholderCategoryType')"
clearable
class="!w-240px"
@change="handleCategoryTypeChange"
>
<el-option
v-for="dict in categoryTypeOptions"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.Check.product')" prop="productId">
<el-select
v-model="queryParams.productId"
clearable
filterable
:placeholder="t('ErpStock.Check.placeholderProduct')"
class="!w-240px"
>
<el-option
v-for="item in productList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.Check.warehouse')" prop="warehouseId">
<el-select
v-model="queryParams.warehouseId"
clearable
filterable
:placeholder="t('ErpStock.Check.placeholderWarehouse')"
class="!w-240px"
@change="handleWarehouseChange"
>
<el-option
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.CheckRecord.area')" prop="areaId" v-show="showAllFilters">
<el-select
v-model="queryParams.areaId"
clearable
filterable
:placeholder="t('ErpStock.CheckRecord.placeholderArea')"
class="!w-240px"
>
<el-option
v-for="item in filteredAreaList"
:key="item.id"
:label="item.areaName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.Check.creator')" prop="creator" v-show="showAllFilters">
<el-select
v-model="queryParams.creator"
clearable
filterable
:placeholder="t('ErpStock.Check.placeholderCreator')"
class="!w-240px"
>
<el-option
v-for="item in userList"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{
showAllFilters
? t('FactoryModeling.FactoryStructure.collapseText')
: t('FactoryModeling.FactoryStructure.expandText')
}}
</el-button>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" />
{{ t('common.query') }}
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" />
{{ t('common.reset') }}
</el-button>
<el-button
type="success"
plain
:loading="exportLoading"
@click="handleExport"
v-hasPermi="['erp:stock-check:export']"
>
<Icon icon="ep:download" class="mr-5px" />
{{ t('action.export') }}
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
@selection-change="handleSelectionChange"
>
<el-table-column width="45" :label="t('action.select')" type="selection" />
<el-table-column :label="t('ErpStock.Check.no')" prop="no" align="center" min-width="160" sortable />
<el-table-column :label="t('ErpStock.Check.sourceType')" prop="sourceType" align="center" min-width="100">
<template #default="{ row }">
{{ getSourceTypeLabel(row.sourceType) }}
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Check.categoryType')" prop="categoryType" align="center" min-width="110">
<template #default="{ row }">
<dict-tag :type="DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE" :value="row.categoryType" />
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.CheckRecord.productBarCode')" align="center" min-width="140">
<template #default="{ row }">
{{ getRowValue(row, ['productBarCode', 'barCode']) }}
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.CheckRecord.productName')" align="left" min-width="180">
<template #default="{ row }">
{{ getRowValue(row, ['productName', 'name']) }}
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.CheckRecord.warehouseName')" align="center" prop="warehouseName" min-width="130" />
<el-table-column :label="t('ErpStock.CheckRecord.areaName')" align="center" prop="areaName" min-width="120" />
<el-table-column :label="t('ErpStock.Check.stockCount')" align="right" prop="stockCount" min-width="110">
<template #default="{ row }">
{{ formatCount(row.stockCount, row) }}
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Check.actualCount')" align="right" prop="actualCount" min-width="110">
<template #default="{ row }">
{{ formatCount(row.actualCount, row) }}
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Check.differenceCount')" align="right" prop="count" min-width="100">
<template #default="{ row }">
<span :class="getDifferenceClass(row.count)">{{ formatDifference(row.count, row) }}</span>
</template>
</el-table-column>
<el-table-column
:label="t('ErpStock.Check.checkTime')"
prop="checkTime"
align="center"
:formatter="dateFormatter"
min-width="170"
sortable
/>
<el-table-column :label="t('ErpStock.Check.creator')" prop="creatorName" align="center" min-width="110" />
<el-table-column
:label="t('common.createTime')"
prop="createTime"
align="center"
:formatter="dateFormatter"
min-width="170"
/>
<el-table-column :label="t('ErpStock.Check.remark')" prop="remark" min-width="160" />
</el-table>
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { StockCheckApi, type StockCheckRecordVO } from '@/api/erp/stock/check'
import { ProductApi, type ProductVO } from '@/api/erp/product/product'
import { WarehouseApi, type WarehouseVO } from '@/api/erp/stock/warehouse'
import { WarehouseAreaApi, type WarehouseAreaVO } from '@/api/erp/stock/warehousearea'
import * as UserApi from '@/api/system/user'
import { useDictStoreWithOut } from '@/store/modules/dict'
defineOptions({ name: 'ErpStockCheckRecord' })
const { t } = useI18n()
const message = useMessage()
const dictStore = useDictStoreWithOut()
const loading = ref(false)
const list = ref<StockCheckRecordVO[]>([])
const total = ref(0)
const queryFormRef = ref()
const productList = ref<ProductVO[]>([])
const warehouseList = ref<WarehouseVO[]>([])
const areaList = ref<WarehouseAreaVO[]>([])
const userList = ref<UserApi.UserVO[]>([])
const showAllFilters = ref(false)
const exportLoading = ref(false)
const selectionList = ref<StockCheckRecordVO[]>([])
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
no: undefined as string | undefined,
checkTime: [] as string[],
sourceType: undefined as number | undefined,
categoryType: undefined as number | undefined,
productId: undefined as number | undefined,
warehouseId: undefined as number | undefined,
areaId: undefined as number | undefined,
creator: undefined as string | number | undefined
})
const sourceTypeOptions = computed(() => [
{ value: 1, label: t('ErpStock.Check.sourceTypeStock') },
{ value: 2, label: t('ErpStock.Check.sourceTypeProduct') }
])
const categoryTypeOptions = computed(() => getIntDictOptions(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE))
const filteredAreaList = computed(() => {
if (!queryParams.warehouseId) return areaList.value
return areaList.value.filter((item) => item.warehouseId === queryParams.warehouseId)
})
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const buildQueryParams = () => {
return Object.fromEntries(
Object.entries(queryParams).filter(([, value]) => {
if (Array.isArray(value)) return value.length > 0
return value !== undefined && value !== null && value !== ''
})
)
}
const getList = async () => {
loading.value = true
try {
const data = await StockCheckApi.getStockCheckRecordPage(buildQueryParams())
list.value = data?.list || []
total.value = data?.total || 0
} finally {
loading.value = false
}
}
const getProductList = async () => {
productList.value = await ProductApi.getProductSimpleList(
queryParams.categoryType !== undefined ? { categoryType: queryParams.categoryType } : undefined
)
if (queryParams.productId && !productList.value.some((item) => item.id === queryParams.productId)) {
queryParams.productId = undefined
}
}
const handleCategoryTypeChange = async () => {
await getProductList()
}
const handleWarehouseChange = () => {
queryParams.areaId = undefined
}
const handleSelectionChange = (rows: StockCheckRecordVO[]) => {
selectionList.value = rows
}
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
const handleExport = async () => {
try {
await message.exportConfirm()
exportLoading.value = true
const ids = selectionList.value.map((item) => item.id).filter((id) => id != null)
const params = {
...buildQueryParams(),
ids: ids.length ? ids.join(',') : undefined
}
const data = await StockCheckApi.exportStockCheckRecord(params)
download.excel(data, t('ErpStock.CheckRecord.exportName'))
} catch {
} finally {
exportLoading.value = false
}
}
const resetQuery = async () => {
queryFormRef.value?.resetFields()
await getProductList()
handleQuery()
}
const getRowValue = (row: StockCheckRecordVO, keys: string[]) => {
for (const key of keys) {
const value = row[key]
if (value !== undefined && value !== null && value !== '') return value
}
return '-'
}
const getSourceTypeLabel = (value: number | string | undefined) => {
const option = sourceTypeOptions.value.find((item) => Number(item.value) === Number(value))
return option?.label || '-'
}
const getUnitName = (row: StockCheckRecordVO) => {
return row.productUnitName || row.unitName || ''
}
const formatNumber = (value: number | string | undefined) => {
if (value === undefined || value === null || value === '') return '-'
const num = Number(value)
return Number.isFinite(num) ? num.toLocaleString() : String(value)
}
const formatCount = (value: number | string | undefined, row: StockCheckRecordVO) => {
const unitName = getUnitName(row)
const formatted = formatNumber(value)
return unitName && formatted !== '-' ? formatted + ' ' + unitName : formatted
}
const formatDifference = (value: number | string | undefined, row: StockCheckRecordVO) => {
const unitName = getUnitName(row)
const num = Number(value)
if (!Number.isFinite(num)) return formatCount(value, row)
const sign = num > 0 ? '+' : ''
const formatted = sign + num.toLocaleString()
return unitName ? formatted + ' ' + unitName : formatted
}
const getDifferenceClass = (value: number | string | undefined) => {
const num = Number(value)
if (num > 0) return 'stock-check-record-profit'
if (num < 0) return 'stock-check-record-loss'
return ''
}
onMounted(async () => {
await dictStore.setDictMap()
await Promise.all([
getProductList(),
WarehouseApi.getWarehouseSimpleList().then((data) => {
warehouseList.value = data || []
}),
WarehouseAreaApi.getWarehouseAreaPage({ pageNo: 1, pageSize: 100 }).then((data) => {
areaList.value = data?.list || []
}),
UserApi.getSimpleUserList().then((data) => {
userList.value = data || []
})
])
await getList()
})
</script>
<style scoped>
.stock-check-record-profit {
color: var(--el-color-success);
font-weight: 600;
}
.stock-check-record-loss {
color: var(--el-color-danger);
font-weight: 600;
}
</style>
Loading…
Cancel
Save