You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
besure_web/src/views/erp/stock/out/components/StockOutItemForm.vue

1018 lines
40 KiB
Vue

<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-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-select>
</el-form-item>
</template>
</el-table-column>
<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"
@change="setStockCount(row)"
>
<el-option
v-for="item in getAreaOptions(row.warehouseId)" :key="item.id" :label="getAreaLabel(item)"
:value="item.id" />
</el-select>
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Item.barcode')" min-width="160">
<!-- <template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.productBarCode" />
</el-form-item>
</template>-->
<template #default="{ row, $index }">
<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
v-if="isProductMaterialStockOut" :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>
<el-table-column :label="t('ErpStock.Item.stock')" min-width="100">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.stockCount" :formatter="erpCountInputFormatter" />
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Item.inventoryUnit')" min-width="80">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.productUnitName" />
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isPurchaseUnitStockOut" :label="t('ErpStock.Item.purchaseUnit')" 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="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') }}
<el-tooltip effect="dark" placement="top">
<template #content>
{{ purchaseUnitConvertTipText }}
</template>
<Icon icon="ep:question-filled" class="ml-4px" style="vertical-align: middle; color: #909399; cursor: pointer;" />
</el-tooltip>
</template>
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.purchaseUnitConvertQuantity" />
</el-form-item>
</template>
</el-table-column>
<el-table-column v-if="isSparePartStockOut" :label="t('ErpStock.Item.outboundPurpose')" min-width="140">
<template #default="{ row, $index }">
<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="showRelatedRepairColumn"
:label="t('ErpStock.Item.relatedRepairOrder')"
min-width="190"
>
<template #default="{ row, $index }">
<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
v-if="showRelatedMaintainColumn"
:label="t('ErpStock.Item.relatedMaintainRecord')"
min-width="190"
>
<template #default="{ row, $index }">
<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>
<!-- <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
v-model="row.productPrice"
controls-position="right"
:min="0.00"
:precision="2"
class="!w-100%"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Item.totalPrice')" prop="totalPrice" fixed="right" min-width="100">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.totalPrice`" class="mb-0px!">
<el-input disabled v-model="row.totalPrice" :formatter="erpPriceInputFormatter" />
</el-form-item>
</template>
</el-table-column>-->
<el-table-column :label="t('ErpStock.Item.remark')" fixed="right" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.remark`" class="mb-0px!">
<el-input v-model="row.remark" :placeholder="t('ErpStock.Out.placeholderRemark')" />
</el-form-item>
</template>
</el-table-column>
<el-table-column align="center" fixed="right" :label="t('ErpStock.Item.action')" width="60">
<template #default="{ $index }">
<el-button type="danger" @click="handleDelete($index)" link>
<Icon icon="ep:delete" />
</el-button>
</template>
</el-table-column>
</el-table>
</el-form>
<el-row justify="center" class="mt-3" v-if="!disabled">
<el-button @click="handleAdd" round>+ {{ t('ErpStock.Out.addItem') }}</el-button>
</el-row>
<Dialog :title="t('ErpStock.Item.selectCode')" v-model="productDialogVisible" width="900px">
<el-form :model="productQueryParams" :inline="true" class="-mb-15px">
<el-form-item :label="t('ErpStock.Item.code')">
<el-input
v-model="productQueryParams.barCode" clearable :placeholder="t('ErpStock.Item.placeholderCode')"
@keyup.enter="handleProductDialogQuery" />
</el-form-item>
<el-form-item :label="t('ErpStock.Item.name')">
<el-input
v-model="productQueryParams.name" clearable :placeholder="t('ErpStock.Item.placeholderName')"
@keyup.enter="handleProductDialogQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleProductDialogQuery">
<Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}
</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="t('ErpStock.Item.code')" prop="barCode" min-width="160" />
<el-table-column :label="t('ErpStock.Item.name')" prop="name" min-width="160" />
<el-table-column :label="t('ErpStock.Item.spec')" prop="standard" min-width="120" />
<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">
<Pagination
:total="productDialogTotal" v-model:page="productQueryParams.pageNo"
v-model:limit="productQueryParams.pageSize" @pagination="getProductDialogList" />
</div>
<template #footer>
<el-button type="primary" @click="confirmProductSelect">{{ t('common.ok') }}</el-button>
<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'
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,
erpPriceMultiply,
getSumValue
} from '@/utils'
const props = defineProps<{
items: any[] | undefined
outType?: string
disabled: boolean
}>()
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
const productList = ref<ProductVO[]>([]) // 产品列表
const warehouseList = ref<WarehouseVO[]>([]) // 仓库列表
const defaultWarehouse = ref<WarehouseVO>(undefined) // 默认仓库
const warehouseAreaMap = ref<Record<number, WarehouseAreaVO[]>>({})
const resolveStockOutCategoryType = (outType?: string) => {
if (!outType) return undefined
if (outType.includes('产品')) return 1
if (outType.includes('物料') || outType.includes('原料') || outType.includes('领料')) return 2
if (outType.includes('备件')) return 3
return undefined
}
const activeCategoryType = computed(() => resolveStockOutCategoryType(props.outType))
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)
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 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,
value: 'value',
label: 'label',
children: 'children'
}
const productCascaderOptions = computed(() => {
const map = new Map<number | string, { value: number | string; label: string; children: any[] }>()
for (const item of productList.value) {
const categoryKey = item.categoryId ?? item.categoryName ?? ''
const categoryLabel = item.categoryName ?? String(categoryKey)
if (!map.has(categoryKey)) {
map.set(categoryKey, {
value: categoryKey,
label: categoryLabel,
children: []
})
}
const group = map.get(categoryKey)
if (group) {
group.children.push({
value: item.id,
label: item.name
})
}
}
return Array.from(map.values())
})
/** 初始化设置入库项 */
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)
}
},
{ immediate: true }
)
watch(
() => props.outType,
async () => {
if (isProductMaterialStockOut.value) {
formData.value.forEach((row) => {
normalizeRow(row)
row.inputCount = row.inputCount ?? row.count ?? 1
if (isProductStockOut.value) {
row.inputUnitType = row.inputUnitType || '个'
}
syncCountByInputCount(row)
})
await loadRowsWarehouseAreas(formData.value)
}
}
)
/** 监听合同产品变化,计算合同产品总价 */
watch(
() => formData.value,
(val) => {
if (!val || val.length === 0) {
return
}
// 循环处理
val.forEach((item) => {
item.totalPrice = erpPriceMultiply(item.productPrice, item.count)
})
},
{ deep: true }
)
/** 合计 */
const getSummaries = (param: SummaryMethodProps) => {
const { columns, data } = param
const sums: string[] = []
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = t('common.total')
return
}
if (['inputCount', 'count', 'totalPrice'].includes(column.property)) {
const sum = getSumValue(data.map((item) => Number(item[column.property])))
sums[index] =
column.property === 'inputCount' || column.property === 'count'
? erpCountInputFormatter(sum)
: erpPriceInputFormatter(sum)
} else {
sums[index] = ''
}
})
return sums
}
/** 新增按钮操作 */
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,
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
}
syncCountByInputCount(row)
formData.value.push(row)
if (row.warehouseId && isProductMaterialStockOut.value) {
loadWarehouseAreas(row.warehouseId)
}
}
/** 删除按钮操作 */
const handleDelete = (index) => {
formData.value.splice(index, 1)
}
/** 处理产品变更 */
const onChangeProductCode = (productBarCode, row) => {
const product = productList.value.find((item) => item.barCode === productBarCode)
product ? fillRowByProduct(row, product) : clearProduct(row)
// 加载库存
setStockCount(row)
}
/** 处理仓库变更 */
const onChangeWarehouse = (warehouseId, row) => {
row.areaId = undefined
if (warehouseId && isProductMaterialStockOut.value) {
loadWarehouseAreas(warehouseId)
}
// 加载库存
setStockCount(row)
}
/** 处理产品变更 */
const onChangeProduct = (productId, row) => {
const product = productList.value.find((item) => item.id === productId)
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.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
row.productPrice = undefined
row.productId = undefined
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
}
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
}
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]),
() => {
if (!isProductMaterialStockOut.value) return
formData.value.forEach(syncCountByInputCount)
},
{ deep: true }
)
const syncCountByInputCount = (row: any) => {
if (!isProductMaterialStockOut.value) return
const inputCount = Number(row.inputCount)
const convertQuantity = Number(row.purchaseUnitConvertQuantity)
if (!Number.isFinite(inputCount)) {
row.count = undefined
return
}
if (!row.purchaseUnitId && !row.purchaseUnitName) {
row.count = inputCount
return
}
row.count = Number.isFinite(convertQuantity) ? inputCount * convertQuantity : inputCount
}
const 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({
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.supplierName = row.supplierName ?? getDefaultSupplierName(product)
row.purchaseUnitConvertQuantity = row.purchaseUnitConvertQuantity ?? (product as any).purchaseUnitConvertQuantity
syncCountByInputCount(row)
})
}
/** 加载库存 */
const setStockCount = async (row) => {
if (!row.productId || !row.warehouseId) {
return
}
const stock = await StockApi.getStock2(row.productId, row.warehouseId, row.areaId)
row.stockCount = stock ? stock.count : 0
}
/** 表单校验 */
const validate = () => {
return formRef.value.validate()
}
const resetItems = () => {
formData.value.splice(0, formData.value.length)
if (!props.disabled) {
handleAdd()
}
}
defineExpose({ validate, resetItems })
/** 初始化 */
onMounted(async () => {
productList.value = await ProductApi.getProductSimpleList()
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
defaultWarehouse.value = warehouseList.value.find((item) => item.defaultStatus)
fillProductNames(formData.value)
if (isProductMaterialStockOut.value) {
await loadRowsWarehouseAreas(formData.value)
}
// 默认添加一个
if (formData.value.length === 0) {
handleAdd()
}
})
</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>