Merge remote-tracking branch 'origin/main'

main
liutao 6 days ago
commit 0631dd1693

@ -2,15 +2,28 @@ import request from '@/config/axios'
// ERP 产品库存明细 VO
export interface StockRecordVO {
id: number // 编号
productId: number // 产品编号
warehouseId: number // 仓库编号
count: number // 出入库数量
totalCount: number // 总库存量
bizType: number // 业务类型
bizId: number // 业务编号
bizItemId: number // 业务项编号
bizNo: string // 业务单号
id: number
productId: number
bizDirection?: string
bizDocType?: number
categoryType?: number
warehouseId: number
areaId?: number
areaName?: string
count: number
totalCount: number
bizType: number
bizId: number
bizItemId: number
bizNo: string
createTime?: string
creator?: string
productName: string
categoryName: string
unitName: string
warehouseName: string
creatorName: string
recordTime?: string
}
// ERP 产品库存明细 API

@ -6,10 +6,34 @@ export interface StockVO {
id: number
// 产品编号
productId: number
name?: string
productName?: string
barCode?: string
categoryName?: string
unitName?: string
// 产品分类类型
categoryType?: number
// 仓库编号
warehouseId: number
warehouseName?: string
areaId?: number
areaName?: string
// 包装/换算规则
packagingRule?: string
// 库存展示
stockDisplay?: string
// 库存数量
count: number
latestInTime?: number | string
lastInTime?: number | string
recentInTime?: number | string
latestStockInTime?: number | string
lastStockInTime?: number | string
latestOutTime?: number | string
lastOutTime?: number | string
recentOutTime?: number | string
latestStockOutTime?: number | string
lastStockOutTime?: number | string
}
// ERP 产品库存 API
@ -25,8 +49,8 @@ export const StockApi = {
},
// 查询产品库存详情
getStock2: async (productId: number, warehouseId: number) => {
return await request.get({ url: `/erp/stock/get`, params: { productId, warehouseId } })
getStock2: async (productId: number, warehouseId: number, areaId?: number) => {
return await request.get({ url: `/erp/stock/get`, params: { productId, warehouseId, areaId } })
},
// 获得产品库存数量

@ -345,12 +345,19 @@
Stock: {
product: 'Product',
warehouse: 'Warehouse',
code: 'Code',
name: 'Name',
category: 'Category',
code: 'Material Code',
name: 'Material Name',
category: 'Material Category',
subCategory: 'Material Subcategory',
area: 'Area',
packagingRule: 'Packaging / Conversion Rule',
stockDisplay: 'Stock Display',
unit: 'Unit',
count: 'Stock Quantity',
count: 'Base Quantity',
latestInTime: 'Latest Inbound',
latestOutTime: 'Latest Outbound',
placeholderProduct: 'Please select product',
placeholderCategory: 'Please select material category',
placeholderWarehouse: 'Please select warehouse',
exportName: 'Product Stock.xls'
},
@ -487,8 +494,15 @@
inventoryUnit: 'Inventory Unit',
purchaseUnit: 'Purchase Unit',
purchaseUnitConvertQuantity: 'Purchase Convert Qty',
defaultSupplier: 'Supplier (Default)',
outboundPurpose: 'Outbound Purpose',
relatedOrder: 'Related Order',
relatedRepairOrder: 'Related Repair Order',
relatedMaintainRecord: 'Related Maintenance Record',
selectRelatedRepairOrder: 'Select Repair Order',
selectRelatedMaintainRecord: 'Select Maintenance Record',
inputUnitType: 'Unit Input Type',
inputCount: 'Input Count',
inputCount: 'Purchase Unit Quantity',
count: 'Quantity',
price: 'Price',
totalPrice: 'Total Price',
@ -503,13 +517,17 @@
placeholderArea: 'Please select area',
placeholderProduct: 'Please select product',
placeholderBarcode: 'Please select barcode',
placeholderOutboundPurpose: 'Please select outbound purpose',
placeholderRelatedRepairOrder: 'Please select related repair order',
placeholderRelatedMaintainRecord: 'Please select related maintenance record',
placeholderInputUnitType: 'Please select',
placeholderCode: 'Please enter code',
placeholderName: 'Please enter name',
validatorWarehouseRequired: 'Warehouse is required',
validatorAreaRequired: 'Please select area',
validatorProductRequired: 'Product is required',
validatorCountRequired: 'Quantity is required'
validatorCountRequired: 'Quantity is required',
stockCountExceededWarning: 'Quantity cannot exceed stock. It has been adjusted to stock quantity.'
},
Check: {
no: 'Check No',
@ -549,10 +567,20 @@
}
},
Record: {
id: 'ID',
product: 'Product',
warehouse: 'Warehouse',
bizType: 'Type',
bizNo: 'Business No',
bizDirection: 'Direction',
categoryType: 'Business Type',
materialCategory: 'Material Category',
productId: 'Product ID',
warehouseId: 'Warehouse ID',
areaId: 'Area ID',
bizId: 'Business ID',
bizItemId: 'Business Item ID',
creator: 'Creator',
categoryProduct: 'Product',
categoryMaterial: 'Material',
categorySpare: 'Spare Part',
@ -562,13 +590,15 @@
productName: 'Product Name',
categoryName: 'Product Category',
unitName: 'Unit',
warehouseName: 'Warehouse Code',
warehouseName: 'Warehouse Name',
areaName: 'Area Name',
count: 'In/Out Quantity',
totalCount: 'Stock Quantity',
recordTime: 'In/Out Date',
recordTime: 'Operation Time',
createTime: 'Create Date',
creatorName: 'Operator',
placeholderProduct: 'Please select product',
placeholderMaterialCategory: 'Please select material category',
placeholderWarehouse: 'Please select warehouse',
placeholderBizType: 'Please select type',
placeholderBizNo: 'Please input business no',
@ -2712,6 +2742,7 @@
tabInspection: 'Inspection Records',
tabRepair: 'Repair Records',
tabMaintain: 'Maintenance Records',
pressureNetRecord: 'Pressure Net Record',
tabInstall: 'Installation Records',
tabDrawings: 'Drawings',
tabOperationManual: 'Operation Manual',
@ -2763,6 +2794,10 @@
createTime: 'Operation Time',
noInstallRecords: 'No installation records',
noRecords: 'No data',
moldBrandName: 'Mold Group Name',
subMoldName: 'Sub Mold Name',
pressureNetTime: 'Pressure Net Time',
placeholderPressureNetTime: 'Please select pressure net time',
pending: 'Pending',
pass: 'Pass',
fail: 'Fail',

@ -345,12 +345,19 @@
Stock: {
product: '产品',
warehouse: '仓库',
code: '编码',
name: '名称',
category: '分类',
code: '物料编码',
name: '物料名称',
category: '物料大类',
subCategory: '物料小类',
area: '库区',
packagingRule: '包装/换算规则',
stockDisplay: '库存展示',
unit: '单位',
count: '库存量',
count: '基本数量',
latestInTime: '最近入库',
latestOutTime: '最近出库',
placeholderProduct: '请选择产品',
placeholderCategory: '请选择物料大类',
placeholderWarehouse: '请选择仓库',
exportName: '产品库存.xls'
},
@ -487,8 +494,15 @@
inventoryUnit: '库存单位',
purchaseUnit: '采购单位',
purchaseUnitConvertQuantity: '采购换算数量',
defaultSupplier: '供应商(默认)',
outboundPurpose: '出库用途',
relatedOrder: '关联单据',
relatedRepairOrder: '关联维修单',
relatedMaintainRecord: '关联保养记录',
selectRelatedRepairOrder: '选择维修单',
selectRelatedMaintainRecord: '选择保养记录',
inputUnitType: '单位输入方式',
inputCount: '录入数量',
inputCount: '采购单位数量',
count: '数量',
price: '产品单价',
totalPrice: '合计金额',
@ -503,13 +517,17 @@
placeholderArea: '请选择库区',
placeholderProduct: '请选择产品',
placeholderBarcode: '请选择编码',
placeholderOutboundPurpose: '请选择出库用途',
placeholderRelatedRepairOrder: '请选择关联维修单',
placeholderRelatedMaintainRecord: '请选择关联保养记录',
placeholderInputUnitType: '请选择',
placeholderCode: '请输入编码',
placeholderName: '请输入名称',
validatorWarehouseRequired: '仓库不能为空',
validatorAreaRequired: '请选择库区',
validatorProductRequired: '产品不能为空',
validatorCountRequired: '产品数量不能为空'
validatorCountRequired: '产品数量不能为空',
stockCountExceededWarning: '数量不能超出库存,已自动调整为库存数量'
},
Check: {
no: '盘点单号',
@ -549,10 +567,20 @@
}
},
Record: {
id: '编号',
product: '产品',
warehouse: '仓库',
bizType: '类型',
bizNo: '业务单号',
bizDirection: '方向',
categoryType: '业务类型',
materialCategory: '物料大类',
productId: '产品编号',
warehouseId: '仓库编号',
areaId: '库区编号',
bizId: '业务编号',
bizItemId: '业务项编号',
creator: '创建人',
categoryProduct: '产品',
categoryMaterial: '原料',
categorySpare: '备件',
@ -562,13 +590,15 @@
productName: '产品名称',
categoryName: '产品分类',
unitName: '产品单位',
warehouseName: '仓库编号',
warehouseName: '仓库名称',
areaName: '库区名称',
count: '出入库数量',
totalCount: '库存量',
recordTime: '出入库日期',
recordTime: '操作时间',
createTime: '创建日期',
creatorName: '操作人',
placeholderProduct: '请选择产品',
placeholderMaterialCategory: '请选择物料大类',
placeholderWarehouse: '请选择仓库',
placeholderBizType: '请选择类型',
placeholderBizNo: '请输入业务单号',
@ -2191,6 +2221,7 @@
tabInspection: '点检记录',
tabRepair: '维修记录',
tabMaintain: '保养记录',
pressureNetRecord: '压网记录',
tabInstall: '安装记录',
tabDrawings: '图纸',
tabOperationManual: '操作手册',
@ -2247,6 +2278,10 @@
createTime: '操作时间',
noInstallRecords: '暂无安装记录',
noRecords: '暂无数据',
moldBrandName: '模具组名称',
subMoldName: '子模具名称',
pressureNetTime: '压网时间',
placeholderPressureNetTime: '请选择压网时间',
// 检测结果
pending: '待检测',
pass: '通过',

@ -22,6 +22,20 @@ export interface DictState {
isSetDict: boolean
}
const normalizeDictMap = (dictMap: Recordable = {}) => {
const normalizedDictMap: Recordable = {}
Object.keys(dictMap || {}).forEach((dictType) => {
const normalizedDictType = dictType.trim()
const dictList = dictMap[dictType]
if (Array.isArray(normalizedDictMap[normalizedDictType]) && Array.isArray(dictList)) {
normalizedDictMap[normalizedDictType].push(...dictList)
} else {
normalizedDictMap[normalizedDictType] = dictList
}
})
return normalizedDictMap
}
export const useDictStore = defineStore('dict', {
state: (): DictState => ({
dictMap: new Map<string, any>(),
@ -31,7 +45,7 @@ export const useDictStore = defineStore('dict', {
getDictMap(): Recordable {
const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)
if (dictMap) {
this.dictMap = dictMap
this.dictMap = normalizeDictMap(dictMap)
}
return this.dictMap
},
@ -43,13 +57,15 @@ export const useDictStore = defineStore('dict', {
async setDictMap() {
const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)
if (dictMap) {
const dictTypes = Object.keys(dictMap || {})
const normalizedDictMap = normalizeDictMap(dictMap)
const dictTypes = Object.keys(normalizedDictMap || {})
const firstDictType = dictTypes[0]
const firstList = firstDictType ? dictMap[firstDictType] : undefined
const firstList = firstDictType ? normalizedDictMap[firstDictType] : undefined
const needsUpgrade =
Array.isArray(firstList) && firstList.length > 0 && !('labelEn' in (firstList[0] || {}))
if (!needsUpgrade) {
this.dictMap = dictMap
this.dictMap = normalizedDictMap
wsCache.set(CACHE_KEY.DICT_CACHE, normalizedDictMap, { exp: 60 })
this.isSetDict = true
return
}
@ -58,11 +74,12 @@ export const useDictStore = defineStore('dict', {
const res = await getSimpleDictDataList()
const dictDataMap = new Map<string, any>()
res.forEach((dictData: DictDataVO) => {
const enumValueObj = dictDataMap[dictData.dictType]
const dictType = dictData.dictType.trim()
const enumValueObj = dictDataMap[dictType]
if (!enumValueObj) {
dictDataMap[dictData.dictType] = []
dictDataMap[dictType] = []
}
dictDataMap[dictData.dictType].push({
dictDataMap[dictType].push({
value: dictData.value,
label: dictData.label,
labelEn: (dictData as any).labelEn,
@ -91,11 +108,12 @@ export const useDictStore = defineStore('dict', {
const res = await getSimpleDictDataList()
const dictDataMap = new Map<string, any>()
res.forEach((dictData: DictDataVO) => {
const enumValueObj = dictDataMap[dictData.dictType]
const dictType = dictData.dictType.trim()
const enumValueObj = dictDataMap[dictType]
if (!enumValueObj) {
dictDataMap[dictData.dictType] = []
dictDataMap[dictType] = []
}
dictDataMap[dictData.dictType].push({
dictDataMap[dictType].push({
value: dictData.value,
label: dictData.label,
labelEn: (dictData as any).labelEn,

@ -228,6 +228,11 @@ export enum DICT_TYPE {
ERP_AUDIT_STATUS = 'erp_audit_status',// ERP 审批状态
ERP_STOCK_RECORD_BIZ_TYPE = 'erp_stock_record_biz_type',// 库存明细的业务类型
ERP_MOLD_STATUS = 'erp_mold_status',// ERP 模具状态
WAREHOUSE_DOCUMENT_TYPES = 'warehouse_document_types', // 入库单据类型
WAREHOUSE_DOCUMENT_OUT_TYPES = 'warehouse_document_out_types', // 出库单据类型
WAREHOUSE_RECEIVING_STATUS = 'warehouse_receiving_status', // 入库状态
WAREHOUSE_OUTBOUND_STATUS = 'Warehouse_outbound_status', // 出库状态
WAREHOUSE_OUTBOUND_PURPOSE = 'warehouse_outbound_purpose', // 出库用途
SUBMOLD_TYPE = 'submold_type', // 子模具类型
ERP_MAINTAIN_TYPE = 'maintain_type',// ERP 保养类型

@ -252,6 +252,22 @@
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.maintainTime') }}</span><span
class="device-ledger-history-item-value">{{
String(formatHistoryTime(item.taskTime)).split(' ')[0] }}</span></div>
<div class="device-ledger-history-item-row device-ledger-history-item-row--images"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.images') }}</span><span
class="device-ledger-history-item-value">
<div v-if="parseImages(item.images).length" class="device-ledger-repair-images">
<el-image
v-for="image in parseImages(item.images)"
:key="image"
:src="image"
:preview-src-list="parseImages(item.images)"
preview-teleported
fit="cover"
class="device-ledger-repair-image"
/>
</div>
<div v-else class="device-ledger-repair-image-placeholder">-</div>
</span></div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.remark') }}</span><span
class="device-ledger-history-item-value">{{ item.remark ?? '-' }}</span></div>
@ -263,6 +279,44 @@
</el-steps>
</div>
</el-tab-pane>
<el-tab-pane :label="t('MoldManagement.MoldBrandDetail.pressureNetRecord')" name="pressureNet">
<div v-loading="pressureNetLoading">
<el-form :inline="true" class="device-ledger-tab-toolbar">
<el-form-item :label="t('MoldManagement.MoldBrandDetail.pressureNetTime')">
<el-date-picker
v-model="pressureNetTime"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
:start-placeholder="t('MoldManagement.MoldBrandDetail.startTime')"
:end-placeholder="t('MoldManagement.MoldBrandDetail.endTime')"
:range-separator="t('MoldManagement.MoldBrandDetail.to')"
:unlink-panels="true"
style="width: 340px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" plain @click="handleQueryPressureNet">{{ t('MoldManagement.MoldBrandDetail.query') }}</el-button>
<el-button @click="handleResetPressureNet">{{ t('MoldManagement.MoldBrandDetail.reset') }}</el-button>
</el-form-item>
</el-form>
<el-table :data="pressureNetRecordList" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('MoldManagement.MoldBrandDetail.moldBrandName')" prop="moldBrandName" min-width="140" />
<el-table-column :label="t('MoldManagement.MoldBrandDetail.subMoldName')" prop="moldName" min-width="120" />
<el-table-column :label="t('MoldManagement.MoldBrandDetail.pressureNetTime')" prop="pressureNetTime" min-width="160">
<template #default="scope">{{ formatHistoryTime(scope.row.pressureNetTime) }}</template>
</el-table-column>
<el-table-column :label="t('MoldManagement.MoldBrandDetail.createTime')" prop="createTime" min-width="160">
<template #default="scope">{{ formatHistoryTime(scope.row.createTime) }}</template>
</el-table-column>
<el-table-column :label="t('MoldManagement.MoldBrandDetail.remark')" prop="remark" min-width="160">
<template #default="scope">{{ scope.row.remark || '-' }}</template>
</el-table-column>
</el-table>
<el-empty v-if="!pressureNetRecordList.length" :description="t('MoldManagement.MoldBrandDetail.noRecords')" />
<Pagination :total="pressureNetTotal" v-model:page="pressureNetPageNo" v-model:limit="pressureNetPageSize"
@pagination="fetchPressureNetRecords" />
</div>
</el-tab-pane>
<el-tab-pane :label="t('MoldManagement.MoldBrandDetail.tabInstall')" name="install">
<div v-loading="installLoading">
<el-form :inline="true" class="device-ledger-tab-toolbar">
@ -374,10 +428,15 @@ const maintainHistory = ref<any[]>([])
const repairList = ref<any[]>([])
const repairQueryCode = ref<string | undefined>()
const repairQueryStatus = ref<string | undefined>()
const pressureNetRecordList = ref<any[]>([])
const installRecords = ref<any[]>([])
const installTotal = ref(0)
const installPageNo = ref(1)
const installPageSize = ref(10)
const pressureNetTotal = ref(0)
const pressureNetPageNo = ref(1)
const pressureNetPageSize = ref(10)
const pressureNetTime = ref<string[] | undefined>()
const installMoldId = ref<number | undefined>()
const installRemark = ref<string | undefined>()
const installDateRange = ref<string[] | undefined>()
@ -393,6 +452,7 @@ const repairExportLoading = ref(false)
const inspectionLoading = ref(false)
const repairLoading = ref(false)
const maintainLoading = ref(false)
const pressureNetLoading = ref(false)
const installLoading = ref(false)
const imageList = computed(() => parseImages(detailData.value?.images))
@ -727,6 +787,38 @@ const fetchRepairHistory = async () => {
}
}
const fetchPressureNetRecords = async () => {
if (!brandId.value) return
pressureNetLoading.value = true
try {
const params: any = {
pageNo: pressureNetPageNo.value,
pageSize: pressureNetPageSize.value,
moldBrandId: brandId.value
}
if (pressureNetTime.value && pressureNetTime.value.length === 2) {
params['pressureNetTime[0]'] = pressureNetTime.value[0]
params['pressureNetTime[1]'] = pressureNetTime.value[1]
}
const data = await MoldBrandApi.getPressureNetRecordPage(params)
pressureNetRecordList.value = data?.list ?? []
pressureNetTotal.value = data?.total ?? 0
} finally {
pressureNetLoading.value = false
}
}
const handleQueryPressureNet = () => {
pressureNetPageNo.value = 1
fetchPressureNetRecords()
}
const handleResetPressureNet = () => {
pressureNetTime.value = undefined
pressureNetPageNo.value = 1
fetchPressureNetRecords()
}
const fetchInstallRecords = async () => {
if (!brandId.value) return
installLoading.value = true
@ -836,6 +928,9 @@ const loadTabData = async (tab: string) => {
await getChildMolds()
fetchMaintainHistory()
break
case 'pressureNet':
fetchPressureNetRecords()
break
case 'install':
await getChildMolds()
fetchInstallRecords()

@ -73,6 +73,18 @@
/>
</el-select>
</el-form-item>
</el-col>
<el-col v-if="formData.categoryType === 2 || formData.categoryType === 3" :span="12">
<el-form-item :label="t('FactoryModeling.ProductInformation.dialogPurchaseUnitLabel')" prop="purchaseUnitId">
<el-select v-model="formData.purchaseUnitId" clearable :placeholder="t('FactoryModeling.ProductInformation.dialogUnitPlaceholder')" class="w-1/1" @change="handlePurchaseUnitChange">
<el-option
v-for="unit in purchaseUnitOptions"
:key="unit.id"
:label="unit.name"
:value="unit.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('FactoryModeling.ProductInformation.dialogStandardLabel')" prop="standard">
@ -165,18 +177,6 @@
<el-input v-model="formData.brand" :placeholder="t('FactoryModeling.ProductInformation.dialogBrandPlaceholder')" />
</el-form-item>
</el-col>
<el-col v-if="formData.categoryType === 2 || formData.categoryType === 3" :span="12">
<el-form-item :label="t('FactoryModeling.ProductInformation.dialogPurchaseUnitLabel')" prop="purchaseUnitId">
<el-select v-model="formData.purchaseUnitId" clearable :placeholder="t('FactoryModeling.ProductInformation.dialogUnitPlaceholder')" class="w-1/1" @change="handlePurchaseUnitChange">
<el-option
v-for="unit in purchaseUnitOptions"
:key="unit.id"
:label="unit.name"
:value="unit.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col v-if="formData.categoryType === 2 || formData.categoryType === 3" :span="12">
<el-form-item :label="t('FactoryModeling.ProductInformation.dialogPurchaseUnitConvertLabel')" prop="purchaseUnitConvertQuantity">
<el-input-number v-model="formData.purchaseUnitConvertQuantity" :min="1" :precision="0" class="!w-1/1" />

@ -16,7 +16,14 @@
<el-table-column v-if="isProductMaterialStockIn" :label="t('ErpStock.Item.area')" min-width="140">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.areaId`" :rules="formRules.areaId" class="mb-0px!">
<el-select v-model="row.areaId" clearable filterable :placeholder="t('ErpStock.Item.placeholderArea')" :disabled="!row.warehouseId">
<el-select
v-model="row.areaId"
clearable
filterable
:placeholder="t('ErpStock.Item.placeholderArea')"
:disabled="!row.warehouseId"
@change="setStockCount(row)"
>
<el-option v-for="item in getAreaOptions(row.warehouseId)" :key="item.id" :label="getAreaLabel(item)"
:value="item.id" />
</el-select>
@ -74,6 +81,13 @@
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isPurchaseUnitStockIn" :label="t('ErpStock.Item.defaultSupplier')" min-width="130">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.supplierName" />
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isPurchaseUnitStockIn" min-width="120">
<template #header>
{{ t('ErpStock.Item.purchaseUnitConvertQuantity') }}
@ -183,6 +197,11 @@
<el-table-column :label="t('ErpStock.Item.category')" prop="subCategoryName" min-width="120" />
<el-table-column :label="t('ErpStock.Item.unit')" prop="unitName" min-width="80" />
<el-table-column v-if="isPurchaseUnitStockIn" :label="t('ErpStock.Item.purchaseUnit')" prop="purchaseUnitName" min-width="100" />
<el-table-column v-if="isPurchaseUnitStockIn" :label="t('ErpStock.Item.defaultSupplier')" min-width="130">
<template #default="{ row }">
{{ getDefaultSupplierName(row) || '-' }}
</template>
</el-table-column>
<el-table-column v-if="isPurchaseUnitStockIn" :label="t('ErpStock.Item.purchaseUnitConvertQuantity')" prop="purchaseUnitConvertQuantity" min-width="120" />
</el-table>
<div class="product-dialog-pagination">
@ -360,6 +379,7 @@ const handleAdd = () => {
productPrice: undefined,
purchaseUnitId: undefined,
purchaseUnitName: undefined,
supplierName: undefined,
purchaseUnitConvertQuantity: undefined,
inputUnitType: isProductStockIn.value ? '个' : undefined,
inputCount: isProductMaterialStockIn.value ? 1 : undefined,
@ -414,10 +434,18 @@ const fillRowByProduct = (row: any, product: any) => {
row.productName = product.name
row.purchaseUnitId = product.purchaseUnitId
row.purchaseUnitName = product.purchaseUnitName
row.supplierName = getDefaultSupplierName(product)
row.purchaseUnitConvertQuantity = product.purchaseUnitConvertQuantity
syncCountByInputCount(row)
}
const getDefaultSupplierName = (product: any) => {
if (!product) return undefined
if (product.supplierName) return product.supplierName
const suppliers = Array.isArray(product.suppliers) ? product.suppliers : []
return suppliers.find((item) => Number(item?.defaultStatus) === 1)?.supplierName
}
const clearProduct = (row: any) => {
row.productUnitName = undefined
row.productBarCode = undefined
@ -426,6 +454,7 @@ const clearProduct = (row: any) => {
row.productName = undefined
row.purchaseUnitId = undefined
row.purchaseUnitName = undefined
row.supplierName = undefined
row.purchaseUnitConvertQuantity = undefined
row.inputCount = isProductMaterialStockIn.value ? undefined : row.inputCount
row.count = isProductMaterialStockIn.value ? undefined : row.count
@ -539,6 +568,7 @@ const fillProductNames = (rows: any[]) => {
row.productPrice = row.productPrice ?? product.minPrice
row.purchaseUnitId = row.purchaseUnitId ?? (product as any).purchaseUnitId
row.purchaseUnitName = row.purchaseUnitName ?? (product as any).purchaseUnitName
row.supplierName = row.supplierName ?? getDefaultSupplierName(product)
row.purchaseUnitConvertQuantity = row.purchaseUnitConvertQuantity ?? (product as any).purchaseUnitConvertQuantity
syncCountByInputCount(row)
})
@ -549,7 +579,7 @@ const setStockCount = async (row) => {
if (!row.productId || !row.warehouseId) {
return
}
const stock = await StockApi.getStock2(row.productId, row.warehouseId)
const stock = await StockApi.getStock2(row.productId, row.warehouseId, row.areaId)
row.stockCount = stock ? stock.count : 0
}

@ -86,7 +86,7 @@
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.ERP_AUDIT_STATUS)"
v-for="dict in getIntDictOptions(DICT_TYPE.WAREHOUSE_RECEIVING_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
@ -197,7 +197,7 @@
prop="status"
sortable>
<template #default="scope">
<dict-tag :type="DICT_TYPE.ERP_AUDIT_STATUS" :value="scope.row.status" />
<dict-tag :type="DICT_TYPE.WAREHOUSE_RECEIVING_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
@ -357,12 +357,12 @@ const queryParams = reactive({
productId: undefined,
supplierId: undefined,
inTime: [],
status: undefined,
remark: undefined,
inType: undefined,
creator: undefined
})
const WAREHOUSE_DOCUMENT_TYPES = 'warehouse_document_types'
const stockInTypeOptions = computed(() => getStrDictOptions(WAREHOUSE_DOCUMENT_TYPES))
const stockInTypeOptions = computed(() => getStrDictOptions(DICT_TYPE.WAREHOUSE_DOCUMENT_TYPES))
const inTypeToInfoKey = (inType: string) => {
const map: Record<string, string> = {
'产品入库': 'productInfo',

@ -18,7 +18,14 @@ v-model="row.warehouseId" clearable filterable
<el-table-column v-if="isProductMaterialStockOut" :label="t('ErpStock.Item.area')" min-width="140">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.areaId`" :rules="formRules.areaId" class="mb-0px!">
<el-select v-model="row.areaId" clearable filterable :placeholder="t('ErpStock.Item.placeholderArea')" :disabled="!row.warehouseId">
<el-select
v-model="row.areaId"
clearable
filterable
:placeholder="t('ErpStock.Item.placeholderArea')"
:disabled="!row.warehouseId"
@change="setStockCount(row)"
>
<el-option
v-for="item in getAreaOptions(row.warehouseId)" :key="item.id" :label="getAreaLabel(item)"
:value="item.id" />
@ -80,6 +87,13 @@ v-model="row.productId" :options="productCascaderOptions" :props="productCascade
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isPurchaseUnitStockOut" :label="t('ErpStock.Item.defaultSupplier')" min-width="130">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.supplierName" />
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isPurchaseUnitStockOut" min-width="120">
<template #header>
{{ t('ErpStock.Item.purchaseUnitConvertQuantity') }}
@ -96,28 +110,83 @@ v-model="row.productId" :options="productCascaderOptions" :props="productCascade
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isProductStockOut" :label="t('ErpStock.Item.inputUnitType')" min-width="120">
<el-table-column v-if="isSparePartStockOut" :label="t('ErpStock.Item.outboundPurpose')" min-width="140">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.inputUnitType`" class="mb-0px!">
<el-select v-model="row.inputUnitType" clearable :placeholder="t('ErpStock.Item.placeholderInputUnitType')">
<el-option v-for="item in inputUnitTypeOptions" :key="item" :label="item" :value="item" />
<el-form-item :prop="`${$index}.outUsageType`" class="mb-0px!">
<el-select
v-model="row.outUsageType"
clearable
:placeholder="t('ErpStock.Item.placeholderOutboundPurpose')"
@change="handleOutboundPurposeChange(row)"
>
<el-option
v-for="dict in outboundPurposeOptions"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isProductMaterialStockOut" :label="t('ErpStock.Item.inputCount')" prop="inputCount" min-width="140">
<el-table-column
v-if="showRelatedRepairColumn"
:label="t('ErpStock.Item.relatedRepairOrder')"
min-width="190"
>
<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"
class="!w-100%" />
<el-form-item
v-if="isRepairPurpose(row.outUsageType)"
:prop="`${$index}.relatedOrderNo`"
class="mb-0px!"
>
<el-input
v-model="row.relatedOrderNo"
readonly
:placeholder="getRelatedOrderPlaceholder(row.outUsageType)"
>
<template #append>
<el-button @click="openRelatedOrderDialog(row)">
<Icon icon="ep:search" />
</el-button>
</template>
</el-input>
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Item.count')" prop="count" fixed="right" min-width="140">
<el-table-column
v-if="showRelatedMaintainColumn"
:label="t('ErpStock.Item.relatedMaintainRecord')"
min-width="190"
>
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.count`" :rules="formRules.count" class="mb-0px!">
<el-input disabled v-model="row.count" :formatter="erpCountInputFormatter" />
<el-form-item
v-if="isMaintainPurpose(row.outUsageType)"
:prop="`${$index}.relatedMaintenanceNo`"
class="mb-0px!"
>
<el-input
v-model="row.relatedMaintenanceNo"
readonly
:placeholder="getRelatedOrderPlaceholder(row.outUsageType)"
>
<template #append>
<el-button @click="openRelatedOrderDialog(row)">
<Icon icon="ep:search" />
</el-button>
</template>
</el-input>
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isProductMaterialStockOut" :label="t('ErpStock.Item.count')" prop="inputCount" min-width="140">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.inputCount`" :rules="formRules.inputCount" class="mb-0px!">
<el-input-number
v-model="row.inputCount" controls-position="right" :min="0.001" :precision="3"
class="!w-100%"
@blur="handleInputCountBlur(row)"
/>
</el-form-item>
</template>
</el-table-column>
@ -193,6 +262,11 @@ v-loading="productDialogLoading" :data="productDialogList" row-key="id" :stripe=
<el-table-column :label="t('ErpStock.Item.category')" prop="subCategoryName" min-width="120" />
<el-table-column :label="t('ErpStock.Item.unit')" prop="unitName" min-width="80" />
<el-table-column v-if="isPurchaseUnitStockOut" :label="t('ErpStock.Item.purchaseUnit')" prop="purchaseUnitName" min-width="100" />
<el-table-column v-if="isPurchaseUnitStockOut" :label="t('ErpStock.Item.defaultSupplier')" min-width="130">
<template #default="{ row }">
{{ getDefaultSupplierName(row) || '-' }}
</template>
</el-table-column>
<el-table-column v-if="isPurchaseUnitStockOut" :label="t('ErpStock.Item.purchaseUnitConvertQuantity')" prop="purchaseUnitConvertQuantity" min-width="120" />
</el-table>
<div class="product-dialog-pagination">
@ -205,6 +279,103 @@ v-loading="productDialogLoading" :data="productDialogList" row-key="id" :stripe=
<el-button @click="productDialogVisible = false">{{ t('common.cancel') }}</el-button>
</template>
</Dialog>
<Dialog :title="relatedOrderDialogTitle" v-model="relatedOrderDialogVisible" width="1100px">
<el-table
v-loading="relatedOrderLoading"
:data="relatedOrderList"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
@row-click="handleRelatedOrderRowClick"
>
<el-table-column width="55" align="center">
<template #default="{ row }">
<el-radio v-model="selectedRelatedOrderId" :label="row.id">&nbsp;</el-radio>
</template>
</el-table-column>
<template v-if="relatedOrderType === 'repair'">
<el-table-column :label="t('EquipmentManagement.DvRepair.repairCode')" align="center" prop="repairCode" min-width="180" />
<el-table-column :label="t('EquipmentManagement.DvRepair.repairName')" align="center" prop="repairName" min-width="160" />
<el-table-column :label="t('EquipmentManagement.DvRepair.status')" align="center" prop="status" width="110">
<template #default="scope">
<el-tag :type="getRepairStatusTagType(scope.row.status)" effect="light">
{{ getRepairStatusLabel(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column :label="t('EquipmentManagement.DvRepair.repairStatus')" align="center" prop="repairStatus" min-width="120">
<template #default="scope">
<el-tag :type="getRepairResultTagType(scope.row.repairStatus)" effect="light">
{{ getRepairResultLabel(scope.row.repairStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column :label="t('EquipmentManagement.DvRepair.machineryName')" align="center" prop="machineryName" min-width="180" />
<el-table-column :label="t('EquipmentManagement.DvRepair.machineryCode')" align="center" prop="machineryCode" min-width="180" />
<el-table-column :label="t('EquipmentManagement.DvRepair.machinerySpec')" align="center" prop="machinerySpec" min-width="120" />
<el-table-column :label="t('EquipmentManagement.DvRepair.machineryType')" align="center" prop="machineryTypeId" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.MES_MACHINE_TYPE" :value="scope.row.machineryTypeId" />
</template>
</el-table-column>
<el-table-column :label="t('EquipmentManagement.DvRepair.requireDate')" align="center" prop="requireDate" :formatter="dateFormatter2" width="110" />
<el-table-column :label="t('EquipmentManagement.DvRepair.finishDate')" align="center" prop="finishDate" :formatter="dateFormatter2" width="110" />
<el-table-column :label="t('EquipmentManagement.DvRepair.confirmDate')" align="center" prop="confirmDate" :formatter="dateFormatter2" width="110" />
<el-table-column :label="t('EquipmentManagement.DvRepair.acceptedBy')" align="center" prop="acceptedBy" min-width="150" />
<el-table-column :label="t('EquipmentManagement.DvRepair.confirmBy')" align="center" prop="confirmBy" min-width="150" />
<el-table-column :label="t('EquipmentManagement.DvRepair.createTime')" align="center" prop="createTime" :formatter="dateFormatter" width="170" />
</template>
<template v-else>
<el-table-column :label="t('EquipmentManagement.WorkOrderManagement.planNo')" align="center" prop="planNo" min-width="160" />
<el-table-column :label="t('EquipmentManagement.WorkOrderManagement.deviceName')" align="center" prop="deviceName" min-width="160" />
<el-table-column :label="t('EquipmentManagement.WorkOrderManagement.planType')" align="center" prop="planType" width="90">
<template #default="scope">
<el-tag v-if="String(scope.row.planType) === '1'" type="primary">
{{ t('EquipmentManagement.WorkOrderManagement.planTypeInspect') }}
</el-tag>
<el-tag v-else-if="String(scope.row.planType) === '2'" type="success">
{{ t('EquipmentManagement.WorkOrderManagement.planTypeMaintain') }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column :label="t('EquipmentManagement.WorkOrderManagement.configName')" align="center" prop="configName" min-width="160" />
<el-table-column :label="t('EquipmentManagement.WorkOrderManagement.jobStatus')" align="center" prop="jobStatus" width="120">
<template #default="scope">
<dict-tag :type="'job_status'" :value="scope.row.jobStatus" />
</template>
</el-table-column>
<el-table-column :label="t('EquipmentManagement.WorkOrderManagement.operatorName')" align="center" prop="operatorName" width="140" />
<el-table-column :label="t('EquipmentManagement.WorkOrderManagement.taskTime')" align="center" prop="taskTime" :formatter="dateFormatter" width="180" />
<el-table-column :label="t('EquipmentManagement.WorkOrderManagement.taskEndTime')" align="center" prop="taskEndTime" :formatter="dateFormatter" width="180" />
<el-table-column :label="t('EquipmentManagement.WorkOrderManagement.jobResult')" align="center" prop="jobResult" width="90">
<template #default="scope">
<el-tag v-if="scope.row.jobResult == '1'" type="success">
{{ t('EquipmentManagement.WorkOrderManagement.jobResultOk') }}
</el-tag>
<el-tag v-else-if="scope.row.jobResult == '2'" type="danger">
{{ t('EquipmentManagement.WorkOrderManagement.jobResultNg') }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column :label="t('EquipmentManagement.WorkOrderManagement.createTime')" align="center" prop="createTime" :formatter="dateFormatter" width="180" />
</template>
</el-table>
<div class="product-dialog-pagination">
<Pagination
:total="relatedOrderTotal"
v-model:page="relatedOrderQueryParams.pageNo"
v-model:limit="relatedOrderQueryParams.pageSize"
@pagination="getRelatedOrderList"
/>
</div>
<template #footer>
<el-button type="primary" @click="confirmRelatedOrderSelect">{{ t('common.ok') }}</el-button>
<el-button @click="relatedOrderDialogVisible = false">{{ t('common.cancel') }}</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { Icon } from '@/components/Icon'
@ -212,6 +383,10 @@ import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
import { WarehouseAreaApi, WarehouseAreaVO } from '@/api/erp/stock/warehousearea'
import { StockApi } from '@/api/erp/stock/stock'
import { DvRepairApi } from '@/api/mes/dvrepair'
import { TicketManagementApi } from '@/api/mes/ticketManagement'
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
import {
erpCountInputFormatter,
erpPriceInputFormatter,
@ -227,10 +402,12 @@ const props = defineProps<{
const formLoading = ref(false) //
const formData = ref([])
const { t } = useI18n()
const message = useMessage()
const formRules = reactive({
warehouseId: [{ required: true, message: t('ErpStock.Item.validatorWarehouseRequired'), trigger: 'blur' }],
areaId: [{ required: true, message: t('ErpStock.Item.validatorAreaRequired'), trigger: 'change' }],
productId: [{ required: true, message: t('ErpStock.Item.validatorProductRequired'), trigger: 'blur' }],
inputCount: [{ required: true, message: t('ErpStock.Item.validatorCountRequired'), trigger: 'blur' }],
count: [{ required: true, message: t('ErpStock.Item.validatorCountRequired'), trigger: 'blur' }]
})
const formRef = ref([]) // Ref
@ -238,7 +415,6 @@ const productList = ref<ProductVO[]>([]) // 产品列表
const warehouseList = ref<WarehouseVO[]>([]) //
const defaultWarehouse = ref<WarehouseVO>(undefined) //
const warehouseAreaMap = ref<Record<number, WarehouseAreaVO[]>>({})
const inputUnitTypeOptions = ['个', '包', '托']
const resolveStockOutCategoryType = (outType?: string) => {
if (!outType) return undefined
if (outType.includes('产品')) return 1
@ -250,6 +426,8 @@ const activeCategoryType = computed(() => resolveStockOutCategoryType(props.outT
const isProductMaterialStockOut = computed(() => Boolean(activeCategoryType.value))
const isProductStockOut = computed(() => activeCategoryType.value === 1)
const isPurchaseUnitStockOut = computed(() => activeCategoryType.value === 2 || activeCategoryType.value === 3)
const isSparePartStockOut = computed(() => activeCategoryType.value === 3)
const outboundPurposeOptions = computed(() => getStrDictOptions(DICT_TYPE.WAREHOUSE_OUTBOUND_PURPOSE))
const purchaseUnitConvertTipText = computed(() => t('FactoryModeling.ProductInformation.dialogPurchaseUnitConvertTip'))
const productDialogVisible = ref(false)
const productDialogLoading = ref(false)
@ -264,6 +442,31 @@ const productQueryParams = reactive({
})
const currentSelectRow = ref<any>()
const selectedProductId = ref<number>()
const RELATED_PURPOSE_REPAIR = '1'
const RELATED_PURPOSE_MAINTAIN = '2'
const relatedOrderDialogVisible = ref(false)
const relatedOrderLoading = ref(false)
const relatedOrderType = ref<'repair' | 'maintain'>('repair')
const relatedOrderList = ref<any[]>([])
const relatedOrderTotal = ref(0)
const currentRelatedRow = ref<any>()
const selectedRelatedOrderId = ref<number>()
const repairCodeCache = new Map<number | string, string>()
const relatedOrderQueryParams = reactive({
pageNo: 1,
pageSize: 10
})
const relatedOrderDialogTitle = computed(() =>
relatedOrderType.value === 'repair'
? t('ErpStock.Item.selectRelatedRepairOrder')
: t('ErpStock.Item.selectRelatedMaintainRecord')
)
const showRelatedRepairColumn = computed(
() => isSparePartStockOut.value && formData.value.some((row) => isRepairPurpose(row.outUsageType))
)
const showRelatedMaintainColumn = computed(
() => isSparePartStockOut.value && formData.value.some((row) => isMaintainPurpose(row.outUsageType))
)
const productCascaderProps = {
emitPath: false,
@ -300,6 +503,8 @@ watch(
() => props.items,
async (val) => {
formData.value = val || []
normalizeRows(formData.value)
await fillRelatedRepairCodes(formData.value)
fillProductNames(formData.value)
if (isProductMaterialStockOut.value) {
await loadRowsWarehouseAreas(formData.value)
@ -313,6 +518,7 @@ watch(
async () => {
if (isProductMaterialStockOut.value) {
formData.value.forEach((row) => {
normalizeRow(row)
row.inputCount = row.inputCount ?? row.count ?? 1
if (isProductStockOut.value) {
row.inputUnitType = row.inputUnitType || '个'
@ -348,10 +554,12 @@ const getSummaries = (param: SummaryMethodProps) => {
sums[index] = t('common.total')
return
}
if (['count', 'totalPrice'].includes(column.property)) {
if (['inputCount', 'count', 'totalPrice'].includes(column.property)) {
const sum = getSumValue(data.map((item) => Number(item[column.property])))
sums[index] =
column.property === 'count' ? erpCountInputFormatter(sum) : erpPriceInputFormatter(sum)
column.property === 'inputCount' || column.property === 'count'
? erpCountInputFormatter(sum)
: erpPriceInputFormatter(sum)
} else {
sums[index] = ''
}
@ -373,11 +581,21 @@ const handleAdd = () => {
productPrice: undefined,
purchaseUnitId: undefined,
purchaseUnitName: undefined,
supplierName: undefined,
purchaseUnitConvertQuantity: undefined,
inputUnitType: isProductStockOut.value ? '个' : undefined,
inputCount: isProductMaterialStockOut.value ? 1 : undefined,
stockCount: undefined,
count: isProductMaterialStockOut.value ? undefined : 1,
outUsageType: undefined,
repairId: undefined,
repairDeviceId: undefined,
maintenanceId: undefined,
relatedOrderNo: undefined,
relatedOrderName: undefined,
relatedMaintenanceNo: undefined,
relatedMaintenanceName: undefined,
relatedOrderType: undefined,
totalPrice: undefined,
remark: undefined
}
@ -427,10 +645,69 @@ const fillRowByProduct = (row: any, product: any) => {
row.productName = product.name
row.purchaseUnitId = product.purchaseUnitId
row.purchaseUnitName = product.purchaseUnitName
row.supplierName = getDefaultSupplierName(product)
row.purchaseUnitConvertQuantity = product.purchaseUnitConvertQuantity
syncCountByInputCount(row)
}
const getDefaultSupplierName = (product: any) => {
if (!product) return undefined
if (product.supplierName) return product.supplierName
const suppliers = Array.isArray(product.suppliers) ? product.suppliers : []
return suppliers.find((item) => Number(item?.defaultStatus) === 1)?.supplierName
}
const normalizeRows = (rows: any[]) => {
;(rows || []).forEach(normalizeRow)
}
const normalizeRow = (row: any) => {
if (!row) return
if (row.outUsageType !== undefined && row.outUsageType !== null) {
row.outUsageType = String(row.outUsageType)
}
row.inputCount = row.inputCount ?? row.count
if (row.repairId) {
row.relatedOrderNo = row.relatedOrderNo || row.repairCode || row.repairNo || String(row.repairId)
row.relatedOrderName = row.relatedOrderName || row.repairName
row.relatedOrderType = row.relatedOrderType || 'repair'
}
if (row.maintenanceId) {
row.relatedMaintenanceNo =
row.relatedMaintenanceNo ||
row.maintenanceCode ||
row.maintenanceNo ||
row.planNo ||
row.maintenanceName ||
String(row.maintenanceId)
row.relatedMaintenanceName = row.relatedMaintenanceName || row.maintenanceName
row.relatedOrderType = row.relatedOrderType || 'maintain'
}
}
const fillRelatedRepairCodes = async (rows: any[]) => {
const repairRows = (rows || []).filter((row) => row?.repairId)
await Promise.all(
repairRows.map(async (row) => {
const repairId = row.repairId
let repairCode = repairCodeCache.get(repairId)
if (!repairCode) {
try {
const repair = await DvRepairApi.getDvRepair(repairId)
repairCode = repair?.repairCode
if (repairCode) {
repairCodeCache.set(repairId, repairCode)
}
row.repairDeviceId = row.repairDeviceId ?? repair?.machineryId
} catch {
repairCode = undefined
}
}
row.relatedOrderNo = repairCode || row.repairCode || row.repairNo || String(repairId)
})
)
}
const clearProduct = (row: any) => {
row.productUnitName = undefined
row.productBarCode = undefined
@ -439,6 +716,7 @@ const clearProduct = (row: any) => {
row.productName = undefined
row.purchaseUnitId = undefined
row.purchaseUnitName = undefined
row.supplierName = undefined
row.purchaseUnitConvertQuantity = undefined
row.inputCount = isProductMaterialStockOut.value ? undefined : row.inputCount
row.count = isProductMaterialStockOut.value ? undefined : row.count
@ -490,6 +768,118 @@ const confirmProductSelect = () => {
productDialogVisible.value = false
}
const isRepairPurpose = (purpose: any) => String(purpose ?? '') === RELATED_PURPOSE_REPAIR
const isMaintainPurpose = (purpose: any) => String(purpose ?? '') === RELATED_PURPOSE_MAINTAIN
const isRelatedPurpose = (purpose: any) => {
const value = String(purpose ?? '')
return value === RELATED_PURPOSE_REPAIR || value === RELATED_PURPOSE_MAINTAIN
}
const clearRelatedOrder = (row: any) => {
row.repairId = undefined
row.repairDeviceId = undefined
row.maintenanceId = undefined
row.relatedOrderNo = undefined
row.relatedOrderName = undefined
row.relatedMaintenanceNo = undefined
row.relatedMaintenanceName = undefined
row.relatedOrderType = undefined
}
const handleOutboundPurposeChange = (row: any) => {
clearRelatedOrder(row)
}
const getRelatedOrderPlaceholder = (purpose: any) => {
return String(purpose ?? '') === RELATED_PURPOSE_REPAIR
? t('ErpStock.Item.placeholderRelatedRepairOrder')
: t('ErpStock.Item.placeholderRelatedMaintainRecord')
}
const openRelatedOrderDialog = async (row: any) => {
const purpose = String(row.outUsageType ?? '')
if (!isRelatedPurpose(purpose)) return
currentRelatedRow.value = row
relatedOrderType.value = purpose === RELATED_PURPOSE_REPAIR ? 'repair' : 'maintain'
selectedRelatedOrderId.value =
relatedOrderType.value === 'repair' ? row.repairId : row.maintenanceId
relatedOrderQueryParams.pageNo = 1
relatedOrderDialogVisible.value = true
await getRelatedOrderList()
}
const getRelatedOrderList = async () => {
relatedOrderLoading.value = true
try {
if (relatedOrderType.value === 'repair') {
const data = await DvRepairApi.getDvRepairPage(relatedOrderQueryParams)
relatedOrderList.value = data?.list || []
relatedOrderTotal.value = data?.total || 0
return
}
const data = await TicketManagementApi.getTicketManagementPage({
...relatedOrderQueryParams,
planType: 2
})
relatedOrderList.value = data?.list || []
relatedOrderTotal.value = data?.total || 0
} finally {
relatedOrderLoading.value = false
}
}
const handleRelatedOrderRowClick = (row: any) => {
selectedRelatedOrderId.value = row.id
}
const confirmRelatedOrderSelect = () => {
const selected = relatedOrderList.value.find((item) => item.id === selectedRelatedOrderId.value)
if (!selected || !currentRelatedRow.value) return
if (relatedOrderType.value === 'repair') {
currentRelatedRow.value.repairId = selected.id
currentRelatedRow.value.repairDeviceId = selected.machineryId
currentRelatedRow.value.relatedOrderNo = selected.repairCode
currentRelatedRow.value.relatedOrderName = selected.repairName
} else {
currentRelatedRow.value.maintenanceId = selected.id
currentRelatedRow.value.relatedMaintenanceNo = selected.planNo
currentRelatedRow.value.relatedMaintenanceName = selected.configName
}
currentRelatedRow.value.relatedOrderType = relatedOrderType.value
relatedOrderDialogVisible.value = false
}
const getRepairStatusLabel = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (v === '0') return t('EquipmentManagement.DvRepair.statusPending')
if (v === '1') return t('EquipmentManagement.DvRepair.statusFinished')
return '-'
}
const getRepairStatusTagType = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (v === '1') return 'success'
if (v === '0') return 'warning'
return 'info'
}
const getRepairResultLabel = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (v == '0') return t('EquipmentManagement.DvRepair.repairResultPending')
if (v == '1') return t('EquipmentManagement.DvRepair.repairResultOk')
if (v == '2') return t('EquipmentManagement.DvRepair.repairResultNg')
return '-'
}
const getRepairResultTagType = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (v == '1') return 'success'
if (v == '2') return 'danger'
if (v == '0') return 'info'
return 'info'
}
watch(
() => formData.value.map((row) => [row.inputCount, row.purchaseUnitConvertQuantity]),
() => {
@ -514,6 +904,17 @@ const syncCountByInputCount = (row: any) => {
row.count = Number.isFinite(convertQuantity) ? inputCount * convertQuantity : inputCount
}
const handleInputCountBlur = (row: any) => {
if (!isProductMaterialStockOut.value) return
const inputCount = Number(row.inputCount)
const stockCount = Number(row.stockCount)
if (!Number.isFinite(inputCount) || !Number.isFinite(stockCount)) return
if (inputCount <= stockCount) return
row.inputCount = stockCount
syncCountByInputCount(row)
message.warning(t('ErpStock.Item.stockCountExceededWarning'))
}
const loadWarehouseAreas = async (warehouseId: number) => {
if (!warehouseId || warehouseAreaMap.value[warehouseId]) return
const data = await WarehouseAreaApi.getWarehouseAreaPage({
@ -552,6 +953,7 @@ const fillProductNames = (rows: any[]) => {
row.productPrice = row.productPrice ?? product.minPrice
row.purchaseUnitId = row.purchaseUnitId ?? (product as any).purchaseUnitId
row.purchaseUnitName = row.purchaseUnitName ?? (product as any).purchaseUnitName
row.supplierName = row.supplierName ?? getDefaultSupplierName(product)
row.purchaseUnitConvertQuantity = row.purchaseUnitConvertQuantity ?? (product as any).purchaseUnitConvertQuantity
syncCountByInputCount(row)
})
@ -562,7 +964,7 @@ const setStockCount = async (row) => {
if (!row.productId || !row.warehouseId) {
return
}
const stock = await StockApi.getStock2(row.productId, row.warehouseId)
const stock = await StockApi.getStock2(row.productId, row.warehouseId, row.areaId)
row.stockCount = stock ? stock.count : 0
}

@ -86,7 +86,7 @@
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.ERP_AUDIT_STATUS)"
v-for="dict in getIntDictOptions(DICT_TYPE.WAREHOUSE_OUTBOUND_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
@ -197,7 +197,7 @@
prop="status"
sortable>
<template #default="scope">
<dict-tag :type="DICT_TYPE.ERP_AUDIT_STATUS" :value="scope.row.status" />
<dict-tag :type="DICT_TYPE.WAREHOUSE_OUTBOUND_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
@ -359,8 +359,7 @@ const queryParams = reactive({
outType: undefined,
creator: undefined
})
const WAREHOUSE_DOCUMENT_TYPES = 'warehouse_document_out_types'
const stockOutTypeOptions = computed(() => getStrDictOptions(WAREHOUSE_DOCUMENT_TYPES))
const stockOutTypeOptions = computed(() => getStrDictOptions(DICT_TYPE.WAREHOUSE_DOCUMENT_OUT_TYPES))
const outTypeToInfoKey = (outType: string) => {
const map: Record<string, string> = {
'产品出库': 'productInfo',

@ -3,79 +3,41 @@
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="auto"
>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="auto">
<el-form-item :label="t('ErpStock.Record.product')" prop="productId">
<el-select
v-model="queryParams.productId"
clearable
filterable
:placeholder="t('ErpStock.Record.placeholderProduct')"
class="!w-240px"
>
<el-option
v-for="item in productList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
<el-select v-model="queryParams.productId" clearable filterable
:placeholder="t('ErpStock.Record.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.Record.warehouse')" prop="warehouseId">
<el-select
v-model="queryParams.warehouseId"
clearable
filterable
:placeholder="t('ErpStock.Record.placeholderWarehouse')"
class="!w-240px"
>
<el-option
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
<el-select v-model="queryParams.warehouseId" clearable filterable
:placeholder="t('ErpStock.Record.placeholderWarehouse')" class="!w-240px">
<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.Record.bizType')" prop="bizType">
<el-select
v-model="queryParams.bizType"
:placeholder="t('ErpStock.Record.placeholderBizType')"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.ERP_STOCK_RECORD_BIZ_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
<el-form-item :label="t('ErpStock.Record.materialCategory')" prop="categoryType">
<el-select v-model="queryParams.categoryType" clearable
:placeholder="t('ErpStock.Record.placeholderMaterialCategory')" class="!w-240px">
<el-option v-for="item in categoryTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.Record.bizNo')" prop="bizNo" v-show="showAllFilters">
<el-input
v-model="queryParams.bizNo"
:placeholder="t('ErpStock.Record.placeholderBizNo')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
<el-input v-model="queryParams.bizNo" :placeholder="t('ErpStock.Record.placeholderBizNo')" clearable
@keyup.enter="handleQuery" class="!w-240px" />
</el-form-item>
<el-form-item :label="t('ErpStock.Record.bizType')" prop="bizType">
<el-select v-model="queryParams.bizType" :placeholder="t('ErpStock.Record.placeholderBizType')" clearable
class="!w-240px">
<el-option v-for="dict in getIntDictOptions(DICT_TYPE.ERP_STOCK_RECORD_BIZ_TYPE)" :key="dict.value"
:label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item :label="t('common.createTime')" prop="createTime" v-show="showAllFilters">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
<el-date-picker v-model="queryParams.createTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
:start-placeholder="t('ErpStock.Record.placeholderCreateTimeStart')"
:end-placeholder="t('ErpStock.Record.placeholderCreateTimeEnd')"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-220px" />
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
@ -85,23 +47,17 @@
</el-button>
</el-form-item>
<el-form-item>
<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="primary"
plain
@click="openForm('create')"
v-hasPermi="['erp:stock-record:create']"
>
<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="primary" plain @click="openForm('create')" v-hasPermi="['erp:stock-record:create']">
<Icon icon="ep:plus" class="mr-5px" /> {{ t('common.add') }}
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['erp:stock-record:export']"
>
<el-button type="success" plain @click="handleExport" :loading="exportLoading"
v-hasPermi="['erp:stock-record:export']">
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
</el-button>
</el-form-item>
@ -110,96 +66,91 @@
<!-- 列表 -->
<ContentWrap>
<el-tabs v-model="activeName" @tab-click="handleTabClick">
<el-tab-pane v-for="item in categoryTabs" :key="item.id" :label="item.name" :name="String(item.id)" />
</el-tabs>
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
@selection-change="handleSelectionChange"
>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" row-key="id"
@selection-change="handleSelectionChange">
<el-table-column width="30" :label="t('action.select')" type="selection" />
<el-table-column :label="t('ErpStock.Record.productName')" align="left" sortable prop="productName" width="210px" />
<el-table-column :label="t('ErpStock.Record.categoryName')" align="center" prop="categoryName" sortable />
<el-table-column :label="t('ErpStock.Record.unitName')" align="center" prop="unitName" sortable />
<el-table-column :label="t('ErpStock.Record.warehouseName')" align="center" prop="warehouseName" sortable />
<el-table-column :label="t('ErpStock.Record.bizType')" align="center" prop="bizType" min-width="100" sortable>
<el-table-column :label="t('ErpStock.Record.bizNo')" align="center" prop="bizNo" min-width="180" sortable />
<el-table-column :label="t('ErpStock.Record.bizDirection')" align="center" prop="bizDirection" min-width="90"
sortable>
<template #default="scope">
<dict-tag :type="DICT_TYPE.ERP_STOCK_RECORD_BIZ_TYPE" :value="scope.row.bizType" />
<el-tag v-if="scope.row.bizDirection" :type="getDirectionTagType(scope.row.bizDirection)">
{{ scope.row.bizDirection || '-' }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Record.bizNo')" align="center" prop="bizNo" width="200" sortable />
<el-table-column
:label="t('ErpStock.Record.count')"
align="right"
sortable
prop="count"
width="120px"
:formatter="erpCountTableColumnFormatter"
/>
<el-table-column
:label="t('ErpStock.Record.totalCount')"
align="right"
sortable
prop="totalCount"
width="120px"
:formatter="erpCountTableColumnFormatter"
/>
<el-table-column
:label="t('ErpStock.Record.recordTime')"
align="center"
sortable
prop="recordTime"
:formatter="dateFormatter2"
width="120px"
/>
<el-table-column
:label="t('ErpStock.Record.createTime')"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
sortable />
<el-table-column :label="t('ErpStock.Record.creatorName')" align="center" prop="creatorName" sortable />
<el-table-column :label="t('ErpStock.Record.categoryType')" align="center" prop="bizDocType" min-width="120"
sortable />
<el-table-column :label="t('ErpStock.Record.productName')" align="center" sortable prop="productName"
min-width="160" />
<el-table-column :label="t('ErpStock.Record.materialCategory')" align="center" prop="categoryType" min-width="120"
sortable>
<template #default="scope">
<dict-tag :type="DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE" :value="scope.row.categoryType" />
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Record.categoryName')" align="center" prop="categoryName" min-width="120"
sortable />
<el-table-column :label="t('ErpStock.Record.warehouseName')" align="center" prop="warehouseName" min-width="140"
sortable />
<el-table-column :label="t('ErpStock.Record.areaName')" align="center" prop="areaName" min-width="120" sortable />
<el-table-column :label="t('ErpStock.Record.count')" align="center" sortable prop="count" min-width="120">
<template #default="scope">
<span :class="getDirectionClass(scope.row.bizDirection)">
{{ formatCountWithUnit(scope.row, true) }}
</span>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Record.totalCount')" align="center" sortable prop="totalCount"
min-width="120">
<template #default="scope">
{{ formatCountWithUnit(scope.row) }}
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Record.creatorName')" align="center" prop="creatorName" min-width="100"
sortable />
<el-table-column :label="t('ErpStock.Record.recordTime')" align="center" sortable prop="recordTime"
:formatter="dateFormatter" width="180px" />
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</ContentWrap>
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import {dateFormatter, dateFormatter2} from '@/utils/formatTime'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { StockRecordApi, StockRecordVO } from '@/api/erp/stock/record'
import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
import { ProductCategoryApi, ProductCategoryVO } from '@/api/erp/product/category'
import { erpCountTableColumnFormatter } from '@/utils'
import { useDictStoreWithOut } from '@/store/modules/dict'
/** ERP 产品库存明细列表 */
defineOptions({ name: 'ErpStockRecord' })
const message = useMessage() //
const { t } = useI18n() //
const dictStore = useDictStoreWithOut()
const loading = ref(true) //
const list = ref<StockRecordVO[]>([]) //
const total = ref(0) //
const queryParams = reactive({
const queryParams = reactive<{
pageNo: number
pageSize: number
productId?: number
categoryType?: number
warehouseId?: number
bizType?: number
bizNo?: string
createTime: string[]
}>({
pageNo: 1,
pageSize: 10,
productId: undefined,
categoryId: undefined,
categoryType: undefined,
warehouseId: undefined,
bizType: undefined,
bizNo: undefined,
@ -214,13 +165,54 @@ const toggleFilters = () => {
}
const productList = ref<ProductVO[]>([]) //
const warehouseList = ref<WarehouseVO[]>([]) //
const categoryTabs = ref<ProductCategoryVO[]>([])
const categoryTypeOptions = computed(() => getIntDictOptions(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE))
const isStockIn = (direction?: string) => direction === '入库'
const isStockOut = (direction?: string) => direction === '出库'
const getDirectionClass = (direction?: string) => {
if (isStockIn(direction)) return 'stock-record-in'
if (isStockOut(direction)) return 'stock-record-out'
return ''
}
const getDirectionTagType = (direction?: string) => {
if (isStockIn(direction)) return 'success'
if (isStockOut(direction)) return 'danger'
return 'info'
}
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 formatCountWithUnit = (row: StockRecordVO, withDirectionSign = false) => {
const value = withDirectionSign ? row.count : row.totalCount
const unit = row.unitName ? ` ${row.unitName}` : ''
if (value === undefined || value === null) return `-${unit}`
if (!withDirectionSign) return `${formatNumber(value)}${unit}`
const num = Number(value)
if (!Number.isFinite(num)) return `${value}${unit}`
const absValue = Math.abs(num).toLocaleString()
if (isStockIn(row.bizDirection)) return `+${absValue}${unit}`
if (isStockOut(row.bizDirection)) return `-${absValue}${unit}`
return `${formatNumber(value)}${unit}`
}
const buildQueryParams = () => {
return Object.fromEntries(
Object.entries(queryParams).filter(([, value]) => value !== undefined && value !== null && value !== '')
)
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await StockRecordApi.getStockRecordPage(queryParams)
const data = await StockRecordApi.getStockRecordPage(buildQueryParams())
list.value = data.list
total.value = data.total
} finally {
@ -256,7 +248,7 @@ const handleDelete = async (id: number) => {
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
} catch { }
}
/** 导出按钮操作 */
@ -268,7 +260,7 @@ const handleExport = async () => {
exportLoading.value = true
const ids = selectionList.value.map((item) => item.id).filter((v) => v != null)
const params = {
...queryParams,
...buildQueryParams(),
ids: ids.length ? ids.join(',') : undefined
}
const data = await StockRecordApi.exportStockRecord(params)
@ -291,32 +283,22 @@ onActivated(() => {
})
onMounted(async () => {
await loadCategoryTabs()
await dictStore.setDictMap()
await getList()
//
productList.value = await ProductApi.getProductSimpleList()
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
})
</script>
/** tab 切换 */
const activeName = ref('')
const handleTabClick = (tab: TabsPaneContext) => {
queryParams.categoryId = tab.paneName ? Number(tab.paneName) : undefined
handleQuery()
<style scoped lang="scss">
.stock-record-in {
color: var(--el-color-success);
font-weight: 600;
}
const loadCategoryTabs = async () => {
try {
const data = await ProductCategoryApi.getProductCategoryList({})
const roots = (data || []).filter((item: any) => item && (item.parentId === 0 || item.parentId === null || item.parentId === undefined))
categoryTabs.value = roots.sort((a: any, b: any) => Number(a?.sort ?? 0) - Number(b?.sort ?? 0))
const defaultId = categoryTabs.value.find((v) => String(v.id) === '2')?.id ?? categoryTabs.value[0]?.id
queryParams.categoryId = defaultId
activeName.value = defaultId !== undefined && defaultId !== null ? String(defaultId) : ''
} catch {
categoryTabs.value = []
queryParams.categoryId = undefined
activeName.value = ''
}
.stock-record-out {
color: var(--el-color-danger);
font-weight: 600;
}
</script>
</style>

@ -27,6 +27,22 @@
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.Stock.category')" prop="categoryType">
<el-select
v-model="queryParams.categoryType"
clearable
:placeholder="t('ErpStock.Stock.placeholderCategory')"
class="!w-240px"
>
<el-option
v-for="item in categoryTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.Stock.warehouse')" prop="warehouseId">
<el-select
v-model="queryParams.warehouseId"
@ -69,23 +85,6 @@
<!-- 列表 -->
<ContentWrap>
<!-- <el-tabs v-model="activeName" @tab-click="handleTabClick">
<el-tab-pane label="产品" name="2" />
<el-tab-pane label="原料" name="1" />
<el-tab-pane label="备件" name="5" />
<el-tab-pane label="工具" name="3" />
<el-tab-pane label="耗材" name="4" />
<el-tab-pane label="其他" name="0" />
</el-tabs> -->
<el-tabs v-model="activeName" @tab-click="handleTabClick">
<!-- 使用 v-for 动态生成 el-tab-pane -->
<el-tab-pane
v-for="item in parentList"
:key="item.id"
:label="item.name"
:name="item.id.toString()"
/>
</el-tabs>
<el-table
v-loading="loading"
:data="list"
@ -95,19 +94,55 @@
@selection-change="handleSelectionChange"
>
<el-table-column width="30" :label="t('action.select')" type="selection" />
<el-table-column :label="t('ErpStock.Stock.code')" align="left" sortable prop="barCode" />
<el-table-column :label="t('ErpStock.Stock.name')" align="left" sortable prop="productName" />
<el-table-column :label="t('ErpStock.Stock.category')" align="center" prop="categoryName" sortable />
<el-table-column :label="t('ErpStock.Stock.unit')" align="center" prop="unitName" sortable />
<el-table-column
:label="t('ErpStock.Stock.count')"
align="right"
sortable
prop="count"
:formatter="erpCountTableColumnFormatter"
/>
<el-table-column :label="t('ErpStock.Stock.warehouse')" align="center" prop="warehouseName" sortable />
<el-table-column :label="t('ErpStock.Stock.code')" align="center" sortable prop="barCode" min-width="160" />
<el-table-column :label="t('ErpStock.Stock.name')" align="center" sortable prop="name" min-width="180">
<template #default="{ row }">
{{ row.name || row.productName || '-' }}
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Stock.category')" align="center" prop="categoryType" min-width="120" sortable>
<template #default="{ row }">
<dict-tag :type="DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE" :value="row.categoryType" />
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Stock.subCategory')" align="center" prop="categoryName" min-width="120" sortable />
<el-table-column :label="t('ErpStock.Stock.packagingRule')" align="center" prop="packagingRule" min-width="180" />
<el-table-column :label="t('ErpStock.Stock.warehouse')" align="center" prop="warehouseName" min-width="140" sortable />
<el-table-column :label="t('ErpStock.Stock.area')" align="center" prop="areaName" min-width="120" sortable />
<el-table-column :label="t('ErpStock.Stock.stockDisplay')" align="center" prop="stockDisplay" min-width="220">
<template #default="{ row }">
<span
v-if="formatStockDisplay(row.stockDisplay).length"
class="stock-display"
:style="getStockDisplayStyle(row.categoryType)"
>
<span
v-for="item in formatStockDisplay(row.stockDisplay)"
:key="item"
class="stock-display__item"
>
{{ item }}
</span>
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Stock.count')" align="center" sortable prop="count" min-width="120">
<template #default="{ row }">
{{ formatStockCount(row.count) }}
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Stock.unit')" align="center" prop="unitName" min-width="90" sortable />
<el-table-column :label="t('ErpStock.Stock.latestInTime')" align="center" min-width="180">
<template #default="{ row }">
{{ formatStockTime(getLatestInTime(row)) }}
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Stock.latestOutTime')" align="center" min-width="180">
<template #default="{ row }">
{{ formatStockTime(getLatestOutTime(row)) }}
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
@ -121,40 +156,90 @@
<script setup lang="ts">
import download from '@/utils/download'
import { DICT_TYPE, getDictObj, getIntDictOptions } from '@/utils/dict'
import { StockApi, StockVO } from '@/api/erp/stock/stock'
import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
import { erpCountTableColumnFormatter } from '@/utils'
import { ProductCategoryApi, ProductCategoryVO } from '@/api/erp/product/category'
import { handleTree } from '@/utils/tree'
import { useDictStoreWithOut } from '@/store/modules/dict'
import { formatDate } from '@/utils/formatTime'
import { isHexColor } from '@/utils/color'
/** ERP 产品库存列表 */
defineOptions({ name: 'ErpStock' })
const message = useMessage() //
const { t } = useI18n() //
const dictStore = useDictStoreWithOut()
const loading = ref(true) //
const list = ref<StockVO[]>([]) //
const total = ref(0) //
const queryParams = reactive({
const queryParams = reactive<{
pageNo: number
pageSize: number
productId?: number
warehouseId?: number
categoryType?: number
}>({
pageNo: 1,
pageSize: 10,
productId: undefined,
warehouseId: undefined,
categoryId: undefined
categoryType: undefined
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
const productList = ref<ProductVO[]>([]) //
const warehouseList = ref<WarehouseVO[]>([]) //
const categoryList = ref<ProductCategoryVO[]>([]) //
const parentList = ref<ProductCategoryVO[]>([])
const categoryTypeOptions = computed(() => getIntDictOptions(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE))
const formatStockCount = (value: number | string | undefined) => {
if (value === undefined || value === null || value === '') return '-'
const num = Number(value)
return Number.isFinite(num) ? num.toLocaleString() : String(value)
}
const formatStockTime = (value: any) => {
return value ? formatDate(value, 'YYYY-MM-DD HH:mm:ss') : '-'
}
const formatStockDisplay = (value: string | undefined) => {
if (!value) return []
return value
.split(',')
.map((item) => item.trim())
.filter(Boolean)
}
const getStockDisplayStyle = (categoryType: number | string | undefined) => {
const dict = getDictObj(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE, categoryType)
if (dict?.cssClass && isHexColor(dict.cssClass)) {
return { color: dict.cssClass }
}
const colorType = dict?.colorType && !['primary', 'default'].includes(String(dict.colorType))
? dict.colorType
: 'primary'
return { color: `var(--el-color-${colorType})` }
}
const getLatestInTime = (row: any) => {
return row.latestInTime ?? row.lastInTime ?? row.recentInTime ?? row.latestStockInTime ?? row.lastStockInTime
}
const getLatestOutTime = (row: any) => {
return row.latestOutTime ?? row.lastOutTime ?? row.recentOutTime ?? row.latestStockOutTime ?? row.lastStockOutTime
}
const buildQueryParams = () => {
return Object.fromEntries(
Object.entries(queryParams).filter(([, value]) => value !== undefined && value !== null && value !== '')
)
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await StockApi.getStockPage(queryParams)
const data = await StockApi.getStockPage(buildQueryParams())
list.value = data.list
total.value = data.total
} finally {
@ -202,7 +287,7 @@ const handleExport = async () => {
exportLoading.value = true
const ids = selectionList.value.map((item) => item.id).filter((v) => v != null)
const params = {
...queryParams,
...buildQueryParams(),
ids: ids.length ? ids.join(',') : undefined
}
const data = await StockApi.exportStock(params)
@ -221,28 +306,25 @@ const handleSelectionChange = (rows: StockVO[]) => {
/** 初始化 **/
onMounted(async () => {
queryParams.categoryId = 2
await dictStore.setDictMap()
await getList()
//
productList.value = await ProductApi.getProductSimpleList()
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
//
const categoryData = await ProductCategoryApi.getProductCategorySimpleList()
categoryList.value = handleTree(categoryData, 'id', 'parentId')
//
for (let i = 0; i < categoryData.length; i++) {
if (categoryData[i].parentId === 0) {
parentList.value.push(categoryData[i]);
}
}
//
parentList.value.sort((a, b) => a.sort - b.sort);
})
</script>
/** tab 切换 */
let activeName = '2'
const handleTabClick = (tab: TabsPaneContext) => {
queryParams.categoryId = tab.paneName
handleQuery()
<style scoped>
.stock-display {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 18px;
color: var(--el-color-primary);
font-weight: 500;
}
</script>
.stock-display__item {
white-space: nowrap;
}
</style>

Loading…
Cancel
Save