Merge remote-tracking branch 'origin/main'

main
liutao 2 weeks ago
commit 5e9940478f

@ -7,6 +7,7 @@ export interface StockInVO {
supplierId: number // 供应商编号
inTime: Date // 入库时间
inType: string //入库类型
stockUserId?: string // 经办人编号
totalCount: number // 合计数量
totalPrice: number // 合计金额,单位:元
status: number // 状态

@ -364,6 +364,8 @@ export default {
remark: 'Remark',
supplier: 'Supplier',
productInfo: 'Product Info',
materialInfo: 'Material Info',
sparePartInfo: 'Spare Part Info',
count: 'Quantity',
price: 'Amount',
totalPrice: 'Total Price',
@ -2070,8 +2072,8 @@ export default {
dialogNamePlaceholder: 'Please enter name',
dialogCategoryLabel: 'Category',
dialogCategoryPlaceholder: 'Please select category',
dialogUnitLabel: 'Unit',
dialogUnitPlaceholder: 'Please select unit',
dialogUnitLabel: 'Inventory Unit',
dialogUnitPlaceholder: 'Please select inventory unit',
dialogStandardLabel: 'Specification',
dialogStandardPlaceholder: 'Please enter specification',
dialogExpiryDayLabel: 'Shelf Life (Days)',
@ -2106,11 +2108,16 @@ export default {
dialogSupplierTitle: 'Select Supplier',
dialogSupplierPlaceholder: 'Please select supplier',
validatorSupplierRequired: 'Supplier is required',
validatorPurchaseUnitRequired: 'Purchase unit is required',
validatorPurchaseUnitConvertRequired: 'Purchase conversion quantity is required',
dialogFragileFlagLabel: 'Fragile',
dialogSparePartLevelLabel: 'Spare Part Level',
dialogPurchaseCycleLabel: 'Purchase Cycle (Days)',
dialogBrandLabel: 'Brand',
dialogBrandPlaceholder: 'Please enter brand',
dialogPurchaseUnitLabel: 'Purchase Unit',
dialogPurchaseUnitConvertLabel: 'Purchase Conversion Qty',
dialogPurchaseUnitConvertTip: 'Inventory Unit = Conversion Qty × Purchase Unit',
images: 'Image',
categoryTree: 'Material Category',
addCategory: 'Add Category',
@ -2508,6 +2515,7 @@ export default {
MoldBrandPage: {
all: 'All',
onMachine: 'On Machine',
inStock: 'In Stock',
standby: 'Standby',
repairing: 'Repairing',
scrapped: 'Scrapped',

@ -364,6 +364,8 @@ export default {
remark: '备注',
supplier: '供应商',
productInfo: '产品信息',
materialInfo: '物料信息',
sparePartInfo: '备件信息',
count: '数量',
price: '金额',
totalPrice: '合计金额',
@ -1990,6 +1992,7 @@ export default {
MoldBrandPage: {
all: '全部',
onMachine: '在机',
inStock: '在库',
standby: '待用',
repairing: '维修',
scrapped: '报废',
@ -3263,8 +3266,8 @@ export default {
dialogCategoryTypeLabel: '类型',
dialogCategoryLabel: '分类',
dialogCategoryPlaceholder: '请选择分类',
dialogUnitLabel: '单位',
dialogUnitPlaceholder: '请选择单位',
dialogUnitLabel: '库存单位',
dialogUnitPlaceholder: '请选择库存单位',
dialogStandardLabel: '规格',
dialogStandardPlaceholder: '请输入规格',
dialogExpiryDayLabel: '保质期天数',
@ -3299,11 +3302,16 @@ export default {
dialogSupplierTitle: '选择供应商',
dialogSupplierPlaceholder: '请选择供应商',
validatorSupplierRequired: '供应商不能为空',
validatorPurchaseUnitRequired: '采购单位不能为空',
validatorPurchaseUnitConvertRequired: '采购换算数量不能为空',
dialogFragileFlagLabel: '是否易损件',
dialogSparePartLevelLabel: '备件等级',
dialogPurchaseCycleLabel: '采购周期(天)',
dialogBrandLabel: '品牌',
dialogBrandPlaceholder: '请输入品牌',
dialogPurchaseUnitLabel: '采购单位',
dialogPurchaseUnitConvertLabel: '采购换算数量',
dialogPurchaseUnitConvertTip: '库存单位 = 换算数量 × 采购单位',
images: '图片',
categoryTree: '物料分类',
addCategory: '新增分类',

@ -175,6 +175,7 @@ const statusOptions = computed(() => getIntDictOptions(DICT_TYPE.ERP_MOLD_STATUS
const counters = reactive({
allCount: 0,
onMachineCount: 0,
inStockCount: 0,
standbyCount: 0,
repairingCount: 0,
scrappedCount: 0
@ -216,6 +217,7 @@ const onKeywordClear = () => {
const statusCards = computed(() => [
{ key: 'all', label: t('MoldManagement.MoldBrandPage.all'), value: counters.allCount, status: undefined },
{ key: 'onMachine', label: t('MoldManagement.MoldBrandPage.onMachine'), value: counters.onMachineCount, status: 1 },
{ key: 'inStock', label: t('MoldManagement.MoldBrandPage.inStock'), value: counters.inStockCount, status: 4 },
{ key: 'standby', label: t('MoldManagement.MoldBrandPage.standby'), value: counters.standbyCount, status: 0 },
{ key: 'repairing', label: t('MoldManagement.MoldBrandPage.repairing'), value: counters.repairingCount, status: 2 },
{ key: 'scrapped', label: t('MoldManagement.MoldBrandPage.scrapped'), value: counters.scrappedCount, status: 3 }
@ -235,6 +237,7 @@ const normalizePageResult = (data: any) => {
total.value = pageResult.total ?? 0
counters.allCount = Number(data?.allCount ?? pageResult.total ?? 0)
counters.onMachineCount = Number(data?.onMachineCount ?? 0)
counters.inStockCount = Number(data?.inStockCount ?? 0)
counters.standbyCount = Number(data?.standbyCount ?? 0)
counters.repairingCount = Number(data?.repairingCount ?? 0)
counters.scrappedCount = Number(data?.scrappedCount ?? 0)
@ -398,7 +401,7 @@ onMounted(async () => {
<style scoped>
.mold-brand-page__header {
display: grid;
grid-template-columns: repeat(5, 10vw);
grid-template-columns: repeat(6, 10vw);
gap: 12px;
}

@ -66,7 +66,7 @@
<el-form-item :label="t('FactoryModeling.ProductInformation.dialogUnitLabel')" prop="unitId">
<el-select v-model="formData.unitId" clearable :placeholder="t('FactoryModeling.ProductInformation.dialogUnitPlaceholder')" class="w-1/1">
<el-option
v-for="unit in unitList"
v-for="unit in inventoryUnitOptions"
:key="unit.id"
:label="unit.name"
:value="unit.id"
@ -165,6 +165,24 @@
<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" />
<div class="form-item-tip">{{ purchaseUnitConvertTipText }}</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('FactoryModeling.ProductInformation.dialogStatusLabel')" prop="status">
<el-radio-group v-model="formData.status">
@ -458,7 +476,10 @@ const formData = ref({
purchaseCycle: undefined as number | undefined,
brand: undefined as string | undefined,
images: undefined as string | undefined,
sparePartLevel: undefined as number | undefined
sparePartLevel: undefined as number | undefined,
purchaseUnitId: undefined as string | undefined,
purchaseUnitName: undefined as string | undefined,
purchaseUnitConvertQuantity: undefined as number | undefined
})
const selectedDeviceRows = ref<any[]>([])
const selectedMoldRows = ref<any[]>([])
@ -884,12 +905,30 @@ const formRules = reactive({
categoryId: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorCategoryRequired'), trigger: 'blur' }],
unitId: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorUnitRequired'), trigger: 'blur' }],
status: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorStatusRequired'), trigger: 'blur' }],
packagingSchemes: [{ validator: validatePackagingSchemes, trigger: 'change' }],
suppliers: [{ validator: validateSuppliers, trigger: 'change' }]
packagingSchemes: [{ required: true, validator: validatePackagingSchemes, trigger: ['change', 'blur'] }],
suppliers: [{ required: true, validator: validateSuppliers, trigger: ['change', 'blur'] }],
purchaseUnitId: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorPurchaseUnitRequired'), trigger: 'change' }],
purchaseUnitConvertQuantity: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorPurchaseUnitConvertRequired'), trigger: 'blur' }]
})
const formRef = ref()
const categoryList = ref<ProductCategoryVO[]>([])
const unitList = ref<ProductUnitVO[]>([])
const inventoryUnitOptions = computed(() => {
if (!formData.value.purchaseUnitId) return unitList.value
return unitList.value.filter((u: any) => u.id !== formData.value.purchaseUnitId)
})
const purchaseUnitOptions = computed(() => {
if (!formData.value.unitId) return unitList.value
return unitList.value.filter((u: any) => u.id !== formData.value.unitId)
})
const purchaseUnitConvertTipText = computed(() => {
const inventoryUnitName = unitList.value.find((u: any) => u.id === formData.value.unitId)?.name
const purchaseUnitName = unitList.value.find((u: any) => u.id === formData.value.purchaseUnitId)?.name
const qty = formData.value.purchaseUnitConvertQuantity
const base = t('FactoryModeling.ProductInformation.dialogPurchaseUnitConvertTip')
if (!inventoryUnitName || !purchaseUnitName || !qty) return base
return `${base}${inventoryUnitName} = ${qty} × ${purchaseUnitName}`
})
const packagingSchemeList = ref<any[]>([])
const supplierList = ref<any[]>([])
@ -981,10 +1020,22 @@ const handleCategoryTypeChange = async () => {
formData.value.purchaseCycle = undefined
formData.value.brand = undefined
formData.value.sparePartLevel = undefined
formData.value.purchaseUnitId = undefined
formData.value.purchaseUnitName = undefined
formData.value.purchaseUnitConvertQuantity = undefined
const categoryData = await ProductCategoryApi.getProductCategorySimpleList(formData.value.categoryType)
categoryList.value = handleTree(categoryData, 'id', 'parentId')
}
const handlePurchaseUnitChange = (val: any) => {
if (!val) {
formData.value.purchaseUnitName = undefined
return
}
const unit = unitList.value.find((u: any) => u.id === val)
formData.value.purchaseUnitName = unit?.name ?? undefined
}
const getQrcodeRefreshUrl = () => {
if (!formData.value.id || !formData.value.barCode) return ''
return `/erp/product/regenerate-code?id=${formData.value.id}&code=${encodeURIComponent(String(formData.value.barCode))}`
@ -1088,7 +1139,10 @@ const resetForm = () => {
fragileFlag: undefined,
purchaseCycle: undefined,
brand: undefined,
sparePartLevel: undefined
sparePartLevel: undefined,
purchaseUnitId: undefined,
purchaseUnitName: undefined,
purchaseUnitConvertQuantity: undefined
}
selectedDeviceRows.value = []
selectedMoldRows.value = []
@ -1170,4 +1224,11 @@ watch(
padding: 16px;
}
}
.form-item-tip {
font-size: 12px;
color: var(--el-text-color-placeholder);
line-height: 1.4;
margin-top: 4px;
}
</style>

@ -136,13 +136,13 @@
<div class="detail-card">
<div class="detail-image-box">
<div class="detail-image-item">
<div class="detail-image-label">{{ t('FactoryModeling.ProductInformation.qrcode') }}</div>
<div class="detail-image-label">{{ t('FactoryModeling.ProductInformation.images') }}</div>
<el-image
v-if="productDetail.qrcodeUrl"
v-if="productDetail.images"
class="detail-image"
:src="productDetail.qrcodeUrl"
:src="productDetail.images"
fit="contain"
:preview-src-list="[productDetail.qrcodeUrl]"
:preview-src-list="[productDetail.images]"
preview-teleported
/>
<el-empty v-else :image-size="64" :description="t('FactoryModeling.ProductInformation.qrcodeEmpty')" />
@ -195,6 +195,14 @@
</template>
<!-- 物料/备件类型供应商 -->
<template v-if="productDetail.categoryType === 2 || productDetail.categoryType === 3">
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogPurchaseUnitLabel') }}</span>
<span class="field-value">{{ formatValue(productDetail.purchaseUnitName) }}</span>
</div>
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogPurchaseUnitConvertLabel') }}</span>
<span class="field-value">{{ formatPurchaseUnitConvert(productDetail) }}</span>
</div>
<div class="detail-field">
<span class="field-label">{{ t('FactoryModeling.ProductInformation.dialogSupplierLabel') }}</span>
<span class="field-value">{{ formatSupplierText(productDetail) }}</span>
@ -249,17 +257,17 @@
<span>默认供应商{{ defaultSupplierText }}</span>
</div>
</template>
<div class="detail-image-item" style="margin-top: 12px;">
<div class="detail-image-label">{{ t('FactoryModeling.ProductInformation.images') }}</div>
<div class="detail-qrcode-box" style="margin-top: 12px;">
<div class="detail-image-label">{{ t('FactoryModeling.ProductInformation.qrcode') }}</div>
<el-image
v-if="productDetail.images"
class="detail-image"
:src="productDetail.images"
v-if="productDetail.qrcodeUrl"
class="detail-qrcode"
:src="productDetail.qrcodeUrl"
fit="contain"
:preview-src-list="[productDetail.images]"
:preview-src-list="[productDetail.qrcodeUrl]"
preview-teleported
/>
<el-empty v-else :image-size="64" description="暂无图片" />
<el-empty v-else :image-size="48" :description="t('FactoryModeling.ProductInformation.qrcodeEmpty')" />
</div>
</div>
@ -559,6 +567,13 @@ const formatSparePartLevel = (value: any) => {
return dictItem?.label || formatValue(value)
}
const formatPurchaseUnitConvert = (detail: any) => {
if (!detail?.purchaseUnitConvertQuantity) return '-'
const unitName = detail.unitName || ''
const purchaseUnitName = detail.purchaseUnitName || ''
return `${unitName} = ${detail.purchaseUnitConvertQuantity} × ${purchaseUnitName}`
}
const sparePartLevelDict = computed(() => getIntDictOptions(DICT_TYPE.SPARE_PARTS_LEVEL))
const formatValue = (value: any) => {
@ -640,6 +655,21 @@ const formatDateTime = (value: any) => {
grid-column: 1 / -1;
}
.detail-qrcode-box {
display: flex;
flex-direction: column;
gap: 6px;
}
.detail-qrcode {
width: 80px;
height: 80px;
flex-shrink: 0;
border: 1px solid var(--el-border-color-lighter);
border-radius: 4px;
cursor: pointer;
}
.field-label {
flex: 0 0 auto;
color: var(--el-text-color-regular);

@ -87,11 +87,11 @@
prop="primaryFlag"
/>
<!-- <el-table-column label="关联主单位" align="center" prop="primaryId" /> -->
<el-table-column
<!-- <el-table-column
:label="t('FactoryModeling.ProductUnit.tableChangeRateColumn')"
align="center"
prop="changeRate"
/>
/> -->
<el-table-column :label="t('FactoryModeling.ProductUnit.tableStatusColumn')" align="center" prop="status" sortable>
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />

@ -72,6 +72,24 @@
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="经办人" prop="stockUserId">
<el-select
v-model="formData.stockUserId"
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-col>
<el-col :span="8">
<el-form-item :label="t('ErpStock.In.remark')" prop="remark">
<el-input
@ -93,7 +111,12 @@
<ContentWrap>
<el-tabs v-model="subTabsName" class="-mt-15px -mb-10px">
<el-tab-pane :label="t('ErpStock.In.list')" name="item">
<StockInItemForm ref="itemFormRef" :items="formData.items" :disabled="disabled" />
<StockInItemForm
ref="itemFormRef"
:items="formData.items"
:in-type="formData.inType"
:disabled="disabled"
/>
</el-tab-pane>
</el-tabs>
</ContentWrap>
@ -107,9 +130,12 @@
</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 StockInItemForm from './components/StockInItemForm.vue'
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
import { getSimpleUserList, type UserVO } from '@/api/system/user'
/** ERP 其它入库单 表单 */
defineOptions({ name: 'StockInForm' })
@ -129,29 +155,25 @@ const formData = ref({
fileUrl: '',
items: [],
isCode: undefined,
no: undefined
no: undefined,
stockUserId: undefined
})
const formRules = reactive({
inTime: [{ required: true, message: t('ErpStock.In.validatorInTimeRequired'), trigger: 'blur' }],
inType: [{ required: true, message: t('ErpStock.In.validatorInTypeRequired'), trigger: 'blur' }]
inType: [{ required: true, message: t('ErpStock.In.validatorInTypeRequired'), trigger: 'blur' }],
stockUserId: [{ required: true, message: '请选择经办人', trigger: 'change' }]
})
const disabled = computed(() => formType.value === 'detail')
const formRef = ref() // Ref
const supplierList = ref<SupplierVO[]>([]) //
const options = [
{
value: '其他入库',
label: t('ErpStock.In.tabOther')
},
{
value: '备件入库',
label: t('ErpStock.In.tabPart')
},
{
value: '产品入库',
label: t('ErpStock.In.tabProduct')
},
]
const userList = ref<UserVO[]>([]) //
const WAREHOUSE_DOCUMENT_TYPES = 'warehouse_document_types'
const options = computed(() => getStrDictOptions(WAREHOUSE_DOCUMENT_TYPES))
watch(options, (items) => {
if (formType.value === 'create' && !formData.value.inType && items.length) {
formData.value.inType = items[0].value
}
})
/** 子表的表单 */
const subTabsName = ref('item')
const itemFormRef = ref()
@ -176,13 +198,24 @@ const open = async (type: string, id?: number) => {
formLoading.value = true
try {
formData.value = await StockInApi.getStockIn(id)
formData.value.stockUserId = normalizeUserId(formData.value.stockUserId)
if (currentRequestId !== openRequestId.value) return
} finally {
formLoading.value = false
}
}
//
supplierList.value = await SupplierApi.getSupplierSimpleList()
const [suppliers, users] = await Promise.all([
SupplierApi.getSupplierSimpleList(),
getSimpleUserList()
])
supplierList.value = suppliers
userList.value = users ?? []
}
const normalizeUserId = (value: any) => {
if (value === undefined || value === null || value === '') return undefined
return String(value)
}
defineExpose({ open }) // open
@ -215,12 +248,14 @@ const resetForm = () => {
formData.value = {
id: undefined,
supplierId: undefined,
inTime: undefined,
inTime: getNowDateTime().valueOf(),
inType: options.value[0]?.value,
remark: undefined,
fileUrl: undefined,
items: [],
isCode: true,
no: undefined
no: undefined,
stockUserId: undefined
}
formRef.value?.resetFields()
}

@ -1,35 +1,24 @@
<template>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
v-loading="formLoading"
label-width="0px"
:inline-message="true"
:disabled="disabled"
>
<el-table :data="formData" size="small" show-summary :summary-method="getSummaries" class="-mt-10px" >
<el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="0px"
:inline-message="true" :disabled="disabled">
<el-table :data="formData" size="small" show-summary :summary-method="getSummaries" class="-mt-10px">
<el-table-column :label="t('common.index')" type="index" align="center" width="60" />
<el-table-column :label="t('ErpStock.Item.warehouse')" min-width="125">
<template #default="{ row, $index }">
<el-form-item
:prop="`${$index}.warehouseId`"
:rules="formRules.warehouseId"
class="mb-0px!"
>
<el-select
v-model="row.warehouseId"
clearable
filterable
:placeholder="t('ErpStock.Item.placeholderWarehouse')"
@change="onChangeWarehouse($event, row)"
>
<el-option
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
<el-form-item :prop="`${$index}.warehouseId`" :rules="formRules.warehouseId" class="mb-0px!">
<el-select v-model="row.warehouseId" clearable filterable
:placeholder="t('ErpStock.Item.placeholderWarehouse')" @change="onChangeWarehouse($event, row)">
<el-option v-for="item in warehouseList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isProductMaterialStockIn" label="库区" 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="请选择库区" :disabled="!row.warehouseId">
<el-option v-for="item in getAreaOptions(row.warehouseId)" :key="item.id" :label="getAreaLabel(item)"
:value="item.id" />
</el-select>
</el-form-item>
</template>
@ -41,38 +30,25 @@
</el-form-item>
</template>-->
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productBarCode`" class="mb-0px!">
<el-select
v-model="row.productBarCode"
clearable
filterable
@change="onChangeProductCode($event, row)"
:placeholder="t('ErpStock.Item.placeholderBarcode')"
>
<el-option
v-for="item in productList"
:key="item.barCode"
:label="item.barCode"
:value="item.barCode"
/>
</el-select>
<el-form-item :prop="`${$index}.productBarCode`" class="mb-0px!">
<div class="product-code-select">
<el-input v-model="row.productBarCode" readonly clearable
:placeholder="t('ErpStock.Item.placeholderBarcode')" @click="openProductSelectDialog(row)"
@clear="handleProductClear(row)" />
</div>
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Item.product')" min-width="180">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productId`" :rules="formRules.productId" class="mb-0px!">
<el-cascader
v-model="row.productId"
:options="productCascaderOptions"
:props="productCascaderProps"
:show-all-levels="false"
clearable
filterable
@change="onChangeProduct($event, row)"
:placeholder="t('ErpStock.Item.placeholderProduct')"
class="!w-100%"
/>
<el-form-item v-if="isProductMaterialStockIn" :prop="`${$index}.productId`" :rules="formRules.productId"
class="mb-0px!">
<el-input v-model="row.productName" disabled :placeholder="t('ErpStock.Item.placeholderProduct')" />
</el-form-item>
<el-form-item v-else :prop="`${$index}.productId`" :rules="formRules.productId" class="mb-0px!">
<el-cascader v-model="row.productId" :options="productCascaderOptions" :props="productCascaderProps"
:show-all-levels="false" clearable filterable @change="onChangeProduct($event, row)"
:placeholder="t('ErpStock.Item.placeholderProduct')" class="!w-100%" />
</el-form-item>
</template>
</el-table-column>
@ -91,20 +67,45 @@
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isPurchaseUnitStockIn" label="采购单位" min-width="100">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.purchaseUnitName" />
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isPurchaseUnitStockIn" label="采购换算数量" min-width="120">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.purchaseUnitConvertQuantity" />
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isProductStockIn" label="单位输入方式" min-width="120">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.inputUnitType`" class="mb-0px!">
<el-select v-model="row.inputUnitType" clearable placeholder="请选择">
<el-option v-for="item in inputUnitTypeOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isPurchaseUnitStockIn" 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"
class="!w-100%" />
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Item.count')" prop="count" fixed="right" min-width="140">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.count`" :rules="formRules.count" class="mb-0px!">
<el-input-number
v-model="row.count"
controls-position="right"
:min="0.001"
:precision="3"
class="!w-100%"
/>
<el-input disabled v-model="row.count" :formatter="erpCountInputFormatter" />
</el-form-item>
</template>
</el-table-column>
<!-- <el-table-column :label="t('ErpStock.Item.price')" fixed="right" min-width="120">
<!-- <el-table-column :label="t('ErpStock.Item.price')" fixed="right" min-width="120">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productPrice`" class="mb-0px!">
<el-input-number
@ -143,10 +144,52 @@
<el-row justify="center" class="mt-3" v-if="!disabled">
<el-button @click="handleAdd" round>+ {{ t('ErpStock.In.addItem') }}</el-button>
</el-row>
<Dialog title="选择编码" v-model="productDialogVisible" width="900px">
<el-form :model="productQueryParams" :inline="true" class="-mb-15px">
<el-form-item label="编码">
<el-input v-model="productQueryParams.barCode" clearable placeholder="请输入编码"
@keyup.enter="handleProductDialogQuery" />
</el-form-item>
<el-form-item label="名称">
<el-input v-model="productQueryParams.name" clearable placeholder="请输入名称"
@keyup.enter="handleProductDialogQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleProductDialogQuery">
<Icon icon="ep:search" class="mr-5px" /> 查询
</el-button>
</el-form-item>
</el-form>
<el-table v-loading="productDialogLoading" :data="productDialogList" row-key="id" :stripe="true"
:show-overflow-tooltip="true" @row-click="handleProductDialogRowClick">
<el-table-column width="55" align="center">
<template #default="{ row }">
<el-radio v-model="selectedProductId" :label="row.id">&nbsp;</el-radio>
</template>
</el-table-column>
<el-table-column label="编码" prop="barCode" min-width="160" />
<el-table-column label="名称" prop="name" min-width="160" />
<el-table-column label="规格型号" prop="standard" min-width="120" />
<el-table-column label="分类" prop="subCategoryName" min-width="120" />
<el-table-column label="单位" prop="unitName" min-width="80" />
<el-table-column v-if="isPurchaseUnitStockIn" label="采购单位" prop="purchaseUnitName" min-width="100" />
<el-table-column v-if="isPurchaseUnitStockIn" label="采购换算数量" prop="purchaseUnitConvertQuantity" min-width="120" />
</el-table>
<div class="product-dialog-pagination">
<Pagination :total="productDialogTotal" v-model:page="productQueryParams.pageNo"
v-model:limit="productQueryParams.pageSize" @pagination="getProductDialogList" />
</div>
<template #footer>
<el-button type="primary" @click="confirmProductSelect"></el-button>
<el-button @click="productDialogVisible = false">取消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
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 {
erpCountInputFormatter,
@ -156,14 +199,16 @@ import {
} from '@/utils'
const props = defineProps<{
items: undefined
disabled: false
items: any[] | undefined
inType?: string
disabled: boolean
}>()
const formLoading = ref(false) //
const formData = ref([])
const { t } = useI18n()
const formRules = reactive({
warehouseId: [{ required: true, message: t('ErpStock.Item.validatorWarehouseRequired'), trigger: 'blur' }],
areaId: [{ required: true, message: '请选择库区', trigger: 'change' }],
productId: [{ required: true, message: t('ErpStock.Item.validatorProductRequired'), trigger: 'blur' }],
count: [{ required: true, message: t('ErpStock.Item.validatorCountRequired'), trigger: 'blur' }]
})
@ -171,6 +216,30 @@ const formRef = ref([]) // 表单 Ref
const productList = ref<ProductVO[]>([]) //
const warehouseList = ref<WarehouseVO[]>([]) //
const defaultWarehouse = ref<WarehouseVO>(undefined) //
const warehouseAreaMap = ref<Record<number, WarehouseAreaVO[]>>({})
const inputUnitTypeOptions = ['个', '包', '托']
const stockInCategoryTypeMap: Record<string, number> = {
产品入库: 1,
物料入库: 2,
备件入库: 3
}
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 productDialogVisible = ref(false)
const productDialogLoading = ref(false)
const productDialogList = ref<any[]>([])
const productDialogTotal = ref(0)
const productQueryParams = reactive({
pageNo: 1,
pageSize: 10,
categoryType: 1,
barCode: undefined,
name: undefined
})
const currentSelectRow = ref<any>()
const selectedProductId = ref<number>()
const productCascaderProps = {
emitPath: false,
@ -206,11 +275,29 @@ const productCascaderOptions = computed(() => {
watch(
() => props.items,
async (val) => {
formData.value = val
formData.value = val || []
fillProductNames(formData.value)
if (isProductMaterialStockIn.value) {
await loadRowsWarehouseAreas(formData.value)
}
},
{ immediate: true }
)
watch(
() => props.inType,
async () => {
if (isProductMaterialStockIn.value) {
formData.value.forEach((row) => {
if (isProductStockIn.value) {
row.inputUnitType = row.inputUnitType || '个'
}
})
await loadRowsWarehouseAreas(formData.value)
}
}
)
/** 监听合同产品变化,计算合同产品总价 */
watch(
() => formData.value,
@ -252,16 +339,26 @@ const handleAdd = () => {
const row = {
id: undefined,
warehouseId: defaultWarehouse.value?.id,
areaId: undefined,
productId: undefined,
productName: undefined,
productUnitName: undefined, //
productBarCode: undefined, //
productPrice: undefined,
purchaseUnitId: undefined,
purchaseUnitName: undefined,
purchaseUnitConvertQuantity: undefined,
inputUnitType: isProductStockIn.value ? '个' : undefined,
inputCount: isPurchaseUnitStockIn.value ? 1 : undefined,
stockCount: undefined,
count: 1,
count: isPurchaseUnitStockIn.value ? undefined : 1,
totalPrice: undefined,
remark: undefined
}
formData.value.push(row)
if (row.warehouseId && isProductMaterialStockIn.value) {
loadWarehouseAreas(row.warehouseId)
}
}
/** 删除按钮操作 */
@ -272,18 +369,17 @@ const handleDelete = (index) => {
/** 处理产品变更 */
const onChangeProductCode = (productBarCode, row) => {
const product = productList.value.find((item) => item.barCode === productBarCode)
if (product) {
row.productUnitName = product.unitName
row.productBarCode = product.barCode
row.productPrice = product.minPrice
row.productId = product.id
}
product ? fillRowByProduct(row, product) : clearProduct(row)
//
setStockCount(row)
}
/** 处理仓库变更 */
const onChangeWarehouse = (warehouseId, row) => {
row.areaId = undefined
if (warehouseId && isProductMaterialStockIn.value) {
loadWarehouseAreas(warehouseId)
}
//
setStockCount(row)
}
@ -291,15 +387,143 @@ const onChangeWarehouse = (warehouseId, row) => {
/** 处理产品变更 */
const onChangeProduct = (productId, row) => {
const product = productList.value.find((item) => item.id === productId)
if (product) {
row.productUnitName = product.unitName
row.productBarCode = product.barCode
row.productPrice = product.minPrice
}
product ? fillRowByProduct(row, product) : clearProduct(row)
//
setStockCount(row)
}
const fillRowByProduct = (row: any, product: any) => {
row.productUnitName = product.unitName
row.productBarCode = product.barCode
row.productPrice = product.minPrice
row.productId = product.id
row.productName = product.name
row.purchaseUnitId = product.purchaseUnitId
row.purchaseUnitName = product.purchaseUnitName
row.purchaseUnitConvertQuantity = product.purchaseUnitConvertQuantity
syncCountByInputCount(row)
}
const clearProduct = (row: any) => {
row.productUnitName = undefined
row.productBarCode = undefined
row.productPrice = undefined
row.productId = undefined
row.productName = undefined
row.purchaseUnitId = undefined
row.purchaseUnitName = undefined
row.purchaseUnitConvertQuantity = undefined
row.inputCount = isPurchaseUnitStockIn.value ? undefined : row.inputCount
row.count = isPurchaseUnitStockIn.value ? undefined : row.count
}
const handleProductClear = (row: any) => {
clearProduct(row)
setStockCount(row)
}
const openProductSelectDialog = async (row: any) => {
if (!activeCategoryType.value) return
currentSelectRow.value = row
selectedProductId.value = row.productId
productQueryParams.pageNo = 1
productQueryParams.categoryType = activeCategoryType.value
productQueryParams.barCode = undefined
productQueryParams.name = undefined
productDialogVisible.value = true
await getProductDialogList()
}
const getProductDialogList = async () => {
productDialogLoading.value = true
try {
productQueryParams.categoryType = activeCategoryType.value || 1
const data = await ProductApi.getProductPage(productQueryParams)
productDialogList.value = data?.list || []
productDialogTotal.value = data?.total || 0
} finally {
productDialogLoading.value = false
}
}
const handleProductDialogQuery = () => {
productQueryParams.pageNo = 1
getProductDialogList()
}
const handleProductDialogRowClick = (row: any) => {
selectedProductId.value = row.id
}
const confirmProductSelect = () => {
const product = productDialogList.value.find((item) => item.id === selectedProductId.value)
if (!product || !currentSelectRow.value) return
fillRowByProduct(currentSelectRow.value, product)
setStockCount(currentSelectRow.value)
productDialogVisible.value = false
}
watch(
() => formData.value.map((row) => [row.inputCount, row.purchaseUnitConvertQuantity]),
() => {
if (!isPurchaseUnitStockIn.value) return
formData.value.forEach(syncCountByInputCount)
},
{ deep: true }
)
const syncCountByInputCount = (row: any) => {
if (!isPurchaseUnitStockIn.value) return
const inputCount = Number(row.inputCount)
const convertQuantity = Number(row.purchaseUnitConvertQuantity)
row.count = Number.isFinite(inputCount) && Number.isFinite(convertQuantity)
? inputCount * convertQuantity
: undefined
}
const loadWarehouseAreas = async (warehouseId: number) => {
if (!warehouseId || warehouseAreaMap.value[warehouseId]) return
const data = await WarehouseAreaApi.getWarehouseAreaPage({
pageNo: 1,
pageSize: 100,
warehouseId
})
warehouseAreaMap.value = {
...warehouseAreaMap.value,
[warehouseId]: data?.list || []
}
}
const loadRowsWarehouseAreas = async (rows: any[]) => {
const warehouseIds = Array.from(new Set((rows || []).map((row) => row.warehouseId).filter(Boolean)))
await Promise.all(warehouseIds.map((warehouseId) => loadWarehouseAreas(warehouseId)))
}
const getAreaOptions = (warehouseId: number) => {
return warehouseId ? warehouseAreaMap.value[warehouseId] || [] : []
}
const getAreaLabel = (item: WarehouseAreaVO) => {
return item.areaCode ? `${item.areaCode} - ${item.areaName}` : item.areaName
}
const fillProductNames = (rows: any[]) => {
if (!productList.value.length) return
rows.forEach((row) => {
const product = productList.value.find((item) => item.id === row.productId || item.barCode === row.productBarCode)
if (!product) return
row.productId = row.productId || product.id
row.productBarCode = row.productBarCode || product.barCode
row.productName = row.productName || product.name
row.productUnitName = row.productUnitName || product.unitName
row.productPrice = row.productPrice ?? product.minPrice
row.purchaseUnitId = row.purchaseUnitId ?? (product as any).purchaseUnitId
row.purchaseUnitName = row.purchaseUnitName ?? (product as any).purchaseUnitName
row.purchaseUnitConvertQuantity = row.purchaseUnitConvertQuantity ?? (product as any).purchaseUnitConvertQuantity
syncCountByInputCount(row)
})
}
/** 加载库存 */
const setStockCount = async (row) => {
if (!row.productId || !row.warehouseId) {
@ -321,6 +545,10 @@ onMounted(async () => {
productList.value = await ProductApi.getProductSimpleList()
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
defaultWarehouse.value = warehouseList.value.find((item) => item.defaultStatus)
fillProductNames(formData.value)
if (isProductMaterialStockIn.value) {
await loadRowsWarehouseAreas(formData.value)
}
//
if (formData.value.length === 0) {
handleAdd()
@ -329,3 +557,18 @@ onMounted(async () => {
</script>
<style scoped lang="scss">
.product-code-select {
width: 100%;
:deep(.el-input__wrapper) {
cursor: pointer;
}
}
.product-dialog-pagination {
display: flex;
justify-content: flex-end;
padding: 12px 0 0;
}
</style>

@ -145,9 +145,12 @@
<!-- 列表 -->
<ContentWrap>
<el-tabs v-model="activeName" @tab-click="handleTabClick">
<el-tab-pane :label="t('ErpStock.In.tabProduct')" name="产品入库" />
<el-tab-pane :label="t('ErpStock.In.tabPart')" name="备件入库" />
<el-tab-pane :label="t('ErpStock.In.tabOther')" name="其他入库" />
<el-tab-pane
v-for="dict in stockInTypeOptions"
:key="dict.value"
:label="dict.label"
:name="dict.value"
/>
</el-tabs>
<el-table
@ -260,7 +263,7 @@
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { getIntDictOptions, getStrDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter2 } from '@/utils/formatTime'
import download from '@/utils/download'
import { StockInApi, StockInVO } from '@/api/erp/stock/in'
@ -292,6 +295,9 @@ const queryParams = reactive({
inType: undefined,
creator: undefined
})
const WAREHOUSE_DOCUMENT_TYPES = 'warehouse_document_types'
const stockInTypeOptions = computed(() => getStrDictOptions(WAREHOUSE_DOCUMENT_TYPES))
const activeName = ref('')
const queryFormRef = ref() //
const exportLoading = ref(false) //
const showAllFilters = ref(false)
@ -327,6 +333,7 @@ const handleQuery = () => {
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
queryParams.inType = activeName.value || stockInTypeOptions.value[0]?.value
handleQuery()
}
@ -394,9 +401,28 @@ const handleSelectionChange = (rows: StockInVO[]) => {
selectionList.value = rows
}
const syncDefaultInType = () => {
if (queryParams.inType) {
activeName.value = queryParams.inType
return
}
const defaultInType = stockInTypeOptions.value[0]?.value
if (defaultInType) {
queryParams.inType = defaultInType
activeName.value = defaultInType
}
}
watch(stockInTypeOptions, (options) => {
if (!queryParams.inType && options.length) {
syncDefaultInType()
handleQuery()
}
})
/** 初始化 **/
onMounted(async () => {
queryParams.inType = '产品入库'
syncDefaultInType()
await getList()
//
productList.value = await ProductApi.getProductSimpleList()
@ -404,9 +430,9 @@ onMounted(async () => {
supplierList.value = await SupplierApi.getSupplierSimpleList()
userList.value = await UserApi.getSimpleUserList()
})
let activeName = '产品入库'
const handleTabClick = (tab: TabsPaneContext) => {
queryParams.inType = tab.paneName
queryParams.inType = String(tab.paneName || '')
activeName.value = queryParams.inType
handleQuery()
}
</script>

@ -137,6 +137,7 @@ const getList = async () => {
pageSize: queryParams.pageSize,
code: queryParams.code,
name: queryParams.name,
status: 4,
})
const pageResult = data?.pageResult ?? data ?? {}
list.value = pageResult.list ?? []

@ -138,6 +138,7 @@ const getList = async () => {
pageSize: queryParams.pageSize,
code: queryParams.code,
name: queryParams.name,
excludeStatus: 4,
})
const pageResult = data?.pageResult ?? data ?? {}
list.value = pageResult.list ?? []

Loading…
Cancel
Save