fix(pl): 证件解析后回填系统客户与证用品类。
解析结果不再只写旧合同绑定器;按证面名称精确匹配并填入客户与品类下拉。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
e8cda33a3c
commit
c460386583
4 files changed
+118
-3
No files matched your search
@@ -610,6 +610,7 @@
|
||||
} from '@/views/pl/quota-cert/model'
|
||||
import {
|
||||
bindingsFromPurchaseCerts,
|
||||
exactCategoryMatches,
|
||||
exactCustomerMatches,
|
||||
matchPurchaseCert,
|
||||
scopeProductsFromLines
|
||||
@@ -1064,6 +1065,24 @@
|
||||
if (current === epoch) ElMessage.warning('客户匹配失败,请手工选择')
|
||||
}
|
||||
}
|
||||
|
||||
async function matchCertCategory(productName: string, current: number) {
|
||||
const name = productName.trim()
|
||||
if (!name || form.value.categoryId || edited.has('categoryId')) return
|
||||
if (!categoryOptions.value.length) await loadCategories()
|
||||
if (current !== epoch || form.value.categoryId || edited.has('categoryId')) return
|
||||
const matches = exactCategoryMatches(categoryOptions.value, name)
|
||||
if (matches.length === 1) {
|
||||
form.value.categoryId = matches[0].id
|
||||
touch('categoryId')
|
||||
return
|
||||
}
|
||||
ElMessage.warning(
|
||||
matches.length
|
||||
? '有多个品类与证面品名同名,请手工选择证用品类'
|
||||
: '未找到与证面品名完全一致的证用品类,请手工选择'
|
||||
)
|
||||
}
|
||||
function toContractOption(row: ContractVO): ContractOption {
|
||||
return {
|
||||
id: String(row.id),
|
||||
@@ -1524,7 +1543,9 @@
|
||||
if (current !== epoch || kind.value !== targetKind || recognitionImage.value !== image) return
|
||||
if (targetKind === 'purchase') {
|
||||
const buyer = String(form.value.buyerName || '').trim()
|
||||
const productName = String(form.value.productNameOnCert || '').trim()
|
||||
if (buyer) await matchBuyerCustomer(buyer, current)
|
||||
if (productName) await matchCertCategory(productName, current)
|
||||
}
|
||||
if (targetKind === 'transport') await linkRecognizedPurchaseCerts(current, targetKind, image)
|
||||
if (current !== epoch || kind.value !== targetKind || recognitionImage.value !== image) return
|
||||
|
||||
@@ -3,6 +3,7 @@ import { test } from 'node:test'
|
||||
import {
|
||||
bindingsFromPurchaseCerts,
|
||||
contractsForCustomer,
|
||||
exactCategoryMatches,
|
||||
exactCustomerMatches,
|
||||
matchPurchaseCert,
|
||||
scopeProductsFromLines
|
||||
@@ -22,6 +23,19 @@ test('购买单位只命中名称完全一致的客户', () => {
|
||||
assert.equal(exactCustomerMatches(customers, '腾龙源').length, 0)
|
||||
})
|
||||
|
||||
test('证面品名只命中名称完全一致的证用品类', () => {
|
||||
const categories = [
|
||||
{ id: 'acid98', categoryName: '98% 工业硫酸' },
|
||||
{ id: 'acid', categoryName: ' 硫酸 ' },
|
||||
{ id: 'other', categoryName: '盐酸' }
|
||||
]
|
||||
assert.deepEqual(
|
||||
exactCategoryMatches(categories, '硫酸').map((item) => item.id),
|
||||
['acid']
|
||||
)
|
||||
assert.deepEqual(exactCategoryMatches(categories, ' '), [])
|
||||
assert.equal(exactCategoryMatches(categories, '工业硫酸').length, 0)
|
||||
})
|
||||
test('选定客户后只保留该客户的合同', () => {
|
||||
const contracts = [
|
||||
{ id: 'a', customerId: 'c1', customerName: '安庆市腾龙源科技有限公司' },
|
||||
|
||||
@@ -22,6 +22,19 @@ export function exactCustomerMatches<T extends NamedCustomer>(
|
||||
return customers.filter((item) => (item.customerName || '').trim() === name)
|
||||
}
|
||||
|
||||
export interface NamedCategory {
|
||||
categoryName?: string | null
|
||||
}
|
||||
|
||||
/** 证用品类名与证面品名去首尾空白后完全一致才算命中。 */
|
||||
export function exactCategoryMatches<T extends NamedCategory>(
|
||||
categories: T[],
|
||||
productNameOnCert: string
|
||||
): T[] {
|
||||
const name = productNameOnCert.trim()
|
||||
if (!name) return []
|
||||
return categories.filter((item) => (item.categoryName || '').trim() === name)
|
||||
}
|
||||
export interface NamedContract {
|
||||
customerId?: string | null
|
||||
customerName?: string | null
|
||||
|
||||
@@ -669,7 +669,12 @@
|
||||
import { fetchCustomerOptions } from '@/api/crm/customer'
|
||||
import AttachmentUploadField from '@/components/business/attachment-upload-field/index.vue'
|
||||
import CertContractBinder from './CertContractBinder.vue'
|
||||
import { bindingsFromPurchaseCerts, matchPurchaseCert } from './contract-binder-rules'
|
||||
import {
|
||||
bindingsFromPurchaseCerts,
|
||||
exactCategoryMatches,
|
||||
exactCustomerMatches,
|
||||
matchPurchaseCert
|
||||
} from './contract-binder-rules'
|
||||
import {
|
||||
buyPartyName,
|
||||
certKindLabel,
|
||||
@@ -788,6 +793,63 @@
|
||||
}
|
||||
}
|
||||
|
||||
function extractCustomerRows(result: unknown): Array<{ id: string; customerName: string }> {
|
||||
const list = Array.isArray(result)
|
||||
? result
|
||||
: (result as { list?: unknown[]; data?: unknown[] })?.list ||
|
||||
(result as { data?: unknown[] })?.data ||
|
||||
[]
|
||||
return (list || [])
|
||||
.map((row: any) => ({
|
||||
id: String(row.id || row.customerId || ''),
|
||||
customerName: String(row.customerName || row.name || '')
|
||||
}))
|
||||
.filter((row: { id: string }) => row.id)
|
||||
}
|
||||
|
||||
/** 解析后回填【客户与品类】系统客户:名称完全一致且唯一才选中。 */
|
||||
async function matchSystemCustomer(name: string, protectedFields: Set<string>) {
|
||||
const buyer = name.trim()
|
||||
if (!buyer || form.value.customerId || protectedFields.has('customerId')) return
|
||||
try {
|
||||
const result = await fetchCustomerOptions({ keyword: buyer, limit: 200 })
|
||||
if (form.value.customerId || protectedFields.has('customerId')) return
|
||||
const matches = exactCustomerMatches(extractCustomerRows(result), buyer)
|
||||
if (matches.length === 1) {
|
||||
await ensureCustomerOption(matches[0].id, matches[0].customerName || buyer)
|
||||
form.value.customerId = matches[0].id
|
||||
edited.add('customerId')
|
||||
return
|
||||
}
|
||||
ElMessage.warning(
|
||||
matches.length
|
||||
? '有多个客户与购买单位同名,请手工选择系统客户'
|
||||
: '未找到与购买单位完全一致的客户,请手工选择系统客户'
|
||||
)
|
||||
} catch {
|
||||
ElMessage.warning('系统客户匹配失败,请手工选择')
|
||||
}
|
||||
}
|
||||
|
||||
/** 解析后回填证用品类:证面品名与品类名完全一致且唯一才选中。 */
|
||||
async function matchCertCategory(productName: string, protectedFields: Set<string>) {
|
||||
const name = productName.trim()
|
||||
if (!name || form.value.categoryId || protectedFields.has('categoryId')) return
|
||||
if (!categoryOptions.value.length) await loadCategories()
|
||||
if (form.value.categoryId || protectedFields.has('categoryId')) return
|
||||
const matches = exactCategoryMatches(categoryOptions.value, name)
|
||||
if (matches.length === 1) {
|
||||
form.value.categoryId = matches[0].id
|
||||
edited.add('categoryId')
|
||||
return
|
||||
}
|
||||
ElMessage.warning(
|
||||
matches.length
|
||||
? '有多个品类与证面品名同名,请手工选择证用品类'
|
||||
: '未找到与证面品名完全一致的证用品类,请手工选择'
|
||||
)
|
||||
}
|
||||
|
||||
function onCustomerChange() {
|
||||
edited.add('customerId')
|
||||
}
|
||||
@@ -1561,9 +1623,14 @@
|
||||
form.value.referencesEditable === false ? new Set([...edited, 'purchaseRefs']) : edited
|
||||
mergeCandidates(form.value as any, result.candidates, protectedFields)
|
||||
if (!protectedFields.has('totalQty')) form.value.totalQty = inputQty(form.value.totalQty)
|
||||
if (targetKind === 'purchase' && form.value.bindingsEditable !== false) {
|
||||
if (targetKind === 'purchase') {
|
||||
const buyer = String(form.value.buyerName || '').trim()
|
||||
if (buyer) await purchaseBinder.value?.matchBuyerCustomer(buyer)
|
||||
const productName = String(form.value.productNameOnCert || '').trim()
|
||||
if (buyer) await matchSystemCustomer(buyer, protectedFields)
|
||||
if (productName) await matchCertCategory(productName, protectedFields)
|
||||
if (form.value.bindingsEditable !== false && buyer) {
|
||||
await purchaseBinder.value?.matchBuyerCustomer(buyer)
|
||||
}
|
||||
}
|
||||
if (targetKind === 'transport')
|
||||
await linkRecognizedPurchaseCerts(session, targetKind, targetImage)
|
||||
|
||||
Reference in new issue
Block a user