fix(pl): 证件解析后回填系统客户与证用品类。

解析结果不再只写旧合同绑定器;按证面名称精确匹配并填入客户与品类下拉。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-11-15 07:38:55 +08:00
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
+70 -3
View File
@@ -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)