|
|
@@ -247,7 +247,68 @@
|
|
|
<el-table-column label="序号" type="index" min-width="60" align="center" header-align="center" />
|
|
|
<el-table-column label="产品名称" prop="productName" min-width="160" :show-overflow-tooltip="true">
|
|
|
<template #default="scope">
|
|
|
- <el-input v-model="scope.row.productName" placeholder="请输入产品名称" clearable size="small" readonly />
|
|
|
+ <el-select
|
|
|
+ v-if="(scope.row.nameOptions || []).length > 1"
|
|
|
+ v-model="scope.row.productName"
|
|
|
+ placeholder="选择展示名称"
|
|
|
+ filterable
|
|
|
+ allow-create
|
|
|
+ default-first-option
|
|
|
+ size="small"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="name in scope.row.nameOptions"
|
|
|
+ :key="name"
|
|
|
+ :label="name"
|
|
|
+ :value="name"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ <el-input
|
|
|
+ v-else
|
|
|
+ v-model="scope.row.productName"
|
|
|
+ placeholder="请输入产品名称"
|
|
|
+ clearable
|
|
|
+ size="small"
|
|
|
+ readonly
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="品牌要求" min-width="120">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-select
|
|
|
+ v-if="(scope.row.brandOptions || []).length"
|
|
|
+ v-model="scope.row.brandMode"
|
|
|
+ placeholder="品牌要求"
|
|
|
+ size="small"
|
|
|
+ style="width: 100%"
|
|
|
+ @change="(val) => handleBrandModeChange(scope.row, val)"
|
|
|
+ >
|
|
|
+ <el-option label="不限品牌" value="any" />
|
|
|
+ <el-option label="指定品牌" value="required" />
|
|
|
+ </el-select>
|
|
|
+ <span v-else class="text-muted">—</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="品牌" min-width="120">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-select
|
|
|
+ v-if="scope.row.brandMode === 'required' && (scope.row.brandOptions || []).length"
|
|
|
+ v-model="scope.row.brandId"
|
|
|
+ placeholder="选择品牌"
|
|
|
+ size="small"
|
|
|
+ style="width: 100%"
|
|
|
+ @change="(val) => handleBrandIdChange(scope.row, val)"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="b in scope.row.brandOptions"
|
|
|
+ :key="b.id"
|
|
|
+ :label="b.brandName"
|
|
|
+ :value="b.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ <span v-else-if="scope.row.brandMode === 'any'">不限</span>
|
|
|
+ <span v-else class="text-muted">—</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="型号/规格" prop="productModel" min-width="130" :show-overflow-tooltip="true">
|
|
|
@@ -546,6 +607,13 @@ import CustomerSelector from '@/components/CustomerSelector.vue'
|
|
|
import ContactSelector from '@/components/ContactSelector.vue'
|
|
|
import QuoteSelector from '@/components/QuoteSelector.vue'
|
|
|
import { removeProductsNotAllowedByHazard } from '@/utils/crm-supplier-hazard'
|
|
|
+import {
|
|
|
+ applyBrandModeChange,
|
|
|
+ applyBrandSelectChange,
|
|
|
+ buildProductNameOptions,
|
|
|
+ initLineBrandMetaFromProduct
|
|
|
+} from '@/utils/crm-product-brand'
|
|
|
+import { fetchProductDetail } from '@/api/crm/product'
|
|
|
import ApprovalDialog from './ApprovalDialog.vue'
|
|
|
import { getContactByCustomerId } from '@/api/crm/customer'
|
|
|
import type { CustomerVO, ContactVO } from '@/api/crm/customer/types'
|
|
|
@@ -890,6 +958,11 @@ const loadContractDetail = async (id: number | string) => {
|
|
|
productModel: product.model || '',
|
|
|
isToxic: Number(product.isToxic || 0),
|
|
|
isExplosive: Number(product.isExplosive || 0),
|
|
|
+ brandMode: product.brandMode || undefined,
|
|
|
+ brandId: product.brandId || undefined,
|
|
|
+ brandName: product.brandName || undefined,
|
|
|
+ nameOptions: product.productName ? [product.productName] : [],
|
|
|
+ brandOptions: [],
|
|
|
priceTiers: (product.priceTiers || []).map((t: any) => {
|
|
|
let ids: string[] = []
|
|
|
if (Array.isArray(t.letterOssIds) && t.letterOssIds.length) {
|
|
|
@@ -909,6 +982,8 @@ const loadContractDetail = async (id: number | string) => {
|
|
|
deliveryMethod: product.deliveryMethod || ''
|
|
|
}))
|
|
|
|
|
|
+ await enrichContractProductBrandMeta(form.value.products)
|
|
|
+
|
|
|
if (contractData.contractType === 'framework') {
|
|
|
form.value.products.forEach((product: any) => {
|
|
|
product.price = 0
|
|
|
@@ -1139,7 +1214,7 @@ const handleQuoteConfirm = (quote: QuoteVO) => {
|
|
|
if (quote.productList && quote.productList.length > 0) {
|
|
|
$modal
|
|
|
.confirm('是否要从报价单中带入产品明细?')
|
|
|
- .then(() => {
|
|
|
+ .then(async () => {
|
|
|
const isFw = form.value.contractType === 'framework'
|
|
|
form.value.products = quote.productList!.map((item: any) => ({
|
|
|
productId: item.productId,
|
|
|
@@ -1161,8 +1236,14 @@ const handleQuoteConfirm = (quote: QuoteVO) => {
|
|
|
description: item.description || '',
|
|
|
remark: item.remark || '',
|
|
|
sort: item.sort,
|
|
|
+ brandMode: item.brandMode || undefined,
|
|
|
+ brandId: item.brandId || undefined,
|
|
|
+ brandName: item.brandName || undefined,
|
|
|
+ nameOptions: item.productName ? [item.productName] : [],
|
|
|
+ brandOptions: [],
|
|
|
priceTiers: isFw ? [] : undefined
|
|
|
}))
|
|
|
+ await enrichContractProductBrandMeta(form.value.products)
|
|
|
calculateTotalAmount()
|
|
|
})
|
|
|
.catch(() => {})
|
|
|
@@ -1490,6 +1571,7 @@ const handleAddProduct = () => {
|
|
|
const handleProductSelected = (products: any[]) => {
|
|
|
const isFw = form.value.contractType === 'framework'
|
|
|
products.forEach((product) => {
|
|
|
+ const brandMeta = initLineBrandMetaFromProduct(product)
|
|
|
form.value.products.push({
|
|
|
productId: product.id,
|
|
|
productName: product.productName,
|
|
|
@@ -1508,6 +1590,7 @@ const handleProductSelected = (products: any[]) => {
|
|
|
remark: '',
|
|
|
isToxic: Number(product.isToxic || 0),
|
|
|
isExplosive: Number(product.isExplosive || 0),
|
|
|
+ ...brandMeta,
|
|
|
priceTiers: isFw ? [] : undefined
|
|
|
})
|
|
|
})
|
|
|
@@ -1517,6 +1600,43 @@ const handleProductSelected = (products: any[]) => {
|
|
|
calculateTotalAmount()
|
|
|
}
|
|
|
|
|
|
+const handleBrandModeChange = (row: ExtendedContractProductForm, val: string) => {
|
|
|
+ applyBrandModeChange(row, val)
|
|
|
+}
|
|
|
+
|
|
|
+const handleBrandIdChange = (row: ExtendedContractProductForm, val: string) => {
|
|
|
+ applyBrandSelectChange(row, val, row.brandOptions || [])
|
|
|
+}
|
|
|
+
|
|
|
+async function enrichContractProductBrandMeta(lines: ExtendedContractProductForm[]) {
|
|
|
+ await Promise.all(
|
|
|
+ (lines || []).map(async (line) => {
|
|
|
+ if (!line.productId) return
|
|
|
+ try {
|
|
|
+ const res: any = await fetchProductDetail({ id: String(line.productId) })
|
|
|
+ const data = res?.data ?? res
|
|
|
+ if (!data) return
|
|
|
+ const meta = initLineBrandMetaFromProduct(data)
|
|
|
+ line.standardProductName = meta.standardProductName
|
|
|
+ line.nameOptions = buildProductNameOptions(meta.standardProductName, data.aliases)
|
|
|
+ if (line.productName && !line.nameOptions.includes(String(line.productName))) {
|
|
|
+ line.nameOptions = [...line.nameOptions, String(line.productName)]
|
|
|
+ }
|
|
|
+ line.brandOptions = meta.brandOptions
|
|
|
+ if (meta.brandOptions.length) {
|
|
|
+ if (!line.brandMode) line.brandMode = 'any'
|
|
|
+ } else {
|
|
|
+ line.brandMode = undefined
|
|
|
+ line.brandId = undefined
|
|
|
+ line.brandName = undefined
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('enrich contract brand meta failed', line.productId, e)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
/** 规范化税率展示 */
|
|
|
const normalizeTaxRateDisplay = (value: any, defaultValue = 0): number => {
|
|
|
if (value === null || value === undefined || value === '') {
|
|
|
@@ -1685,7 +1805,10 @@ const buildProductsPayload = () => {
|
|
|
packageSpec: product.packageSpec,
|
|
|
deliveryMethod: product.deliveryMethod,
|
|
|
sort: product.sort ?? idx + 1,
|
|
|
- remark: product.remark
|
|
|
+ remark: product.remark,
|
|
|
+ brandMode: product.brandMode || null,
|
|
|
+ brandId: product.brandMode === 'required' ? product.brandId || null : null,
|
|
|
+ brandName: product.brandMode === 'required' ? product.brandName || null : null
|
|
|
}
|
|
|
if (product.id) {
|
|
|
base.id = product.id
|
|
|
@@ -1839,6 +1962,10 @@ const handleSaveDraft = async () => {
|
|
|
$modal.msgWarning(`第${rowNum}行产品的产品名称不能为空`)
|
|
|
return
|
|
|
}
|
|
|
+ if ((product.brandOptions || []).length && product.brandMode === 'required' && !product.brandId) {
|
|
|
+ $modal.msgWarning(`第${rowNum}行产品请选择指定品牌`)
|
|
|
+ return
|
|
|
+ }
|
|
|
}
|
|
|
data.products = buildProductsPayload()
|
|
|
} else {
|
|
|
@@ -1890,6 +2017,10 @@ const handleSubmit = async () => {
|
|
|
$modal.msgWarning(`第${rowNum}行产品的产品名称不能为空`)
|
|
|
return false
|
|
|
}
|
|
|
+ if ((product.brandOptions || []).length && product.brandMode === 'required' && !product.brandId) {
|
|
|
+ $modal.msgWarning(`第${rowNum}行产品请选择指定品牌`)
|
|
|
+ return false
|
|
|
+ }
|
|
|
if (!product.quantity || Number(product.quantity) <= 0) {
|
|
|
$modal.msgWarning(`第${rowNum}行产品的数量不能为空且必须大于0`)
|
|
|
return false
|