merge: 合入证件建档按客户查询合同修复(#24)。

This commit is contained in:
wuzilong committed 2026-11-12 17:48:18 +08:00
commit 9f65cd447f
3 files changed
+115 -7

No files matched your search

@@ -512,6 +512,8 @@
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { fetchCustomerOptions } from '@/api/crm/customer'
import { listContract } from '@/api/crm/contract'
import type { ContractVO } from '@/api/crm/contract/types'
import AttachmentUploadField from '@/components/business/attachment-upload-field/index.vue'
import MpWebviewShell from '@/layouts/MpWebviewShell.vue'
import MpBusinessHeader from '@/components/mp/MpBusinessHeader.vue'
@@ -540,11 +542,14 @@
} from '@/views/pl/quota-cert/model'
import {
bindingsFromPurchaseCerts,
contractsForCustomer,
exactCustomerMatches,
matchPurchaseCert,
scopeProductsFromLines
} from '@/views/pl/quota-cert/contract-binder-rules'
import {
certArchiveContractListQuery,
filterAvailableBindingContracts
} from '@/views/pl/quota-cert/cert-archive-contract-query'
import { getProductUnitLabel } from '@/utils/crm-product-display'
import { useDictStore } from '@/store/modules/dict'
import { showCrmConfirmDialog } from '@/utils/mp-crm-confirm'
@@ -928,19 +933,39 @@
if (current === epoch) ElMessage.warning('客户匹配失败,请手工选择')
}
}
function toContractOption(row: ContractVO): ContractOption {
return {
id: String(row.id),
contractNo: row.contractNo || '',
contractName: row.contractName || '',
contractType: row.contractType,
customerId: row.customerId != null ? String(row.customerId) : '',
customerName: row.customerName,
status: row.status,
signDate: row.signDate || null
}
}
async function searchContracts(keyword = '') {
const current = epoch
const query = certArchiveContractListQuery(bindingCustomer.value.id, keyword)
if (!query) {
contractOptions.value = []
return
}
try {
const rows = await quotaCertApi.contracts(keyword)
const result = await listContract(query)
if (current !== epoch) return
const taken = new Set(
(form.value.contractBindings || []).map((row: CertificateContractBinding) => row.contractId)
const taken = (form.value.contractBindings || []).map(
(row: CertificateContractBinding) => row.contractId
)
contractOptions.value = contractsForCustomer(rows, bindingCustomer.value).filter(
(row) => !taken.has(row.id)
contractOptions.value = filterAvailableBindingContracts(result.rows || [], taken).map(
toContractOption
)
} catch {
if (current === epoch) contractOptions.value = []
if (current === epoch) {
contractOptions.value = []
ElMessage.warning('合同加载失败,请重试')
}
}
}
function openContracts() {
@@ -0,0 +1,34 @@
import assert from 'node:assert/strict'
import { test } from 'node:test'
import {
certArchiveContractListQuery,
filterAvailableBindingContracts
} from './cert-archive-contract-query'
test('证件建档合同查询必须带客户 ID,不能只靠前端再过滤最近合同', () => {
assert.deepEqual(certArchiveContractListQuery('cust-1', ' HT-01 '), {
pageNum: 1,
pageSize: 50,
keyword: 'HT-01',
customerId: 'cust-1',
excludeStatus: 'invalid',
includeProducts: false
})
assert.equal(certArchiveContractListQuery(''), null)
assert.equal(certArchiveContractListQuery(' '), null)
assert.deepEqual(certArchiveContractListQuery('cust-2', ' ').keyword, undefined)
})
test('已关联合同与不可选状态不进入添加列表', () => {
const rows = [
{ id: 'a', status: 'approved' },
{ id: 'b', status: 'executing' },
{ id: 'c', status: 'draft' },
{ id: 'd', status: 'approved' },
{ id: '', status: 'approved' }
]
assert.deepEqual(
filterAvailableBindingContracts(rows, ['d', '']).map((item) => item.id),
['a', 'b']
)
})
@@ -0,0 +1,49 @@
/** 证件建档添加合同时的 CRM 列表查询与本地过滤。 */
export interface CertArchiveContractListQuery {
pageNum: number
pageSize: number
keyword?: string
customerId: string
excludeStatus: 'invalid'
includeProducts: false
}
/** 已选客户后按客户 ID 分页查询,避免只取最近合同再前端过滤。 */
export function certArchiveContractListQuery(
customerId: string,
keyword = ''
): CertArchiveContractListQuery | null {
const id = String(customerId || '').trim()
if (!id) return null
const text = keyword.trim()
return {
pageNum: 1,
pageSize: 50,
keyword: text || undefined,
customerId: id,
excludeStatus: 'invalid',
includeProducts: false
}
}
const DEFAULT_STATUSES = ['approved', 'executing']
/** 去掉已关联合同和不在可选状态内的行。 */
export function filterAvailableBindingContracts<T extends { id?: string | number; status?: string }>(
rows: T[],
takenIds: Iterable<string>,
selectableStatuses: string[] = DEFAULT_STATUSES
): T[] {
const taken = new Set(
[...takenIds].map((id) => String(id || '').trim()).filter(Boolean)
)
return rows.filter((row) => {
const id = String(row.id || '').trim()
if (!id || taken.has(id)) return false
if (selectableStatuses.length && !selectableStatuses.includes(String(row.status || ''))) {
return false
}
return true
})
}