import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import { parse } from '@vue/compiler-sfc' import ts from 'typescript' import * as vue from 'vue' import * as model from '../src/views/pl/quota-cert/model' import { useClientColumnFilters } from '../src/composables/useClientColumnFilters' import { usePersistedListColumns } from '../src/composables/usePersistedListColumns' import { getProductUnitLabel } from '../src/utils/crm-product-display' import { buyPartyName, expiryWarning, salePartyName } from '../src/views/pl/quota-cert/cert-list-face' import { bindingsFromPurchaseCerts, matchPurchaseCert } from '../src/views/pl/quota-cert/contract-binder-rules' function deferred() { let resolve!: (value: any) => void let reject!: (reason: any) => void const promise = new Promise((r, j) => { resolve = r reject = j }) return { promise, resolve, reject } } function harness() { const reads: string[] = [], writes: any[] = [], parses: any[] = [], messages: string[] = [] let certData: any = {} let saveError = false let pendingSave: ReturnType | undefined let pendingGet: ReturnType | undefined const user = vue.reactive({ accessToken: 'synthetic', getUserInfo: {} }) const contractRequests: any[] = [] const productRequests: any[] = [] let confirmSwitch = true let pendingOptions: ReturnType | undefined const env = { ...Object.fromEntries( Object.entries(vue).filter(([key]) => /^[a-zA-Z_$][\w$]*$/.test(key) && key !== 'default') ), ...model, useClientColumnFilters, usePersistedListColumns, useListTableViewportHeight: () => ({ tableHeight: vue.ref(400) }), useRoute: () => ({ query: {} }), useRouter: () => ({ push() {}, replace() {} }), useUserStore: () => user, useDictStore: () => ({ getLabel: (_type: string, value: string) => (value === 'dun' ? '吨' : value || '—'), getOptions: () => [{ value: 'dun', label: '吨', tagType: '' }] }), getProductUnitLabel, buyPartyName, expiryWarning, salePartyName, bindingsFromPurchaseCerts, matchPurchaseCert, onMounted: () => {}, onBeforeUnmount: () => {}, fetchGetMenuCodes: async () => ({ all: true }), ElMessage: { warning: (s: string) => messages.push(s), success: (s: string) => messages.push(s) }, ElMessageBox: { confirm: async () => { if (!confirmSwitch) throw new Error('cancel') } }, quotaCertApi: { contracts: (keyword: string) => { const pending = deferred() contractRequests.push({ keyword, ...pending }) return pending.promise }, products: (id: string) => { const pending = deferred() productRequests.push({ id, ...pending }) return pending.promise }, certificateContracts: async () => [ { id: 'old', contractNo: '历史合同', contractName: '真实标签' } ], list: async (kind: string) => { reads.push(kind) return pendingOptions ? pendingOptions.promise : [] }, get: async (kind: string, id: string) => pendingGet ? pendingGet.promise : { ...model.newCert(kind as any), id, certNo: 'EXISTING', attachmentUrls: 'a.png', ...certData }, save: async (kind: string, body: any, id?: string) => { writes.push({ kind, body, id }) if (saveError) throw new Error('保存失败') if (pendingSave) await pendingSave.promise }, recognize: (kind: string, url: string) => { const pending = deferred() parses.push({ kind, url, ...pending }) return pending.promise } } } const script = parse( readFileSync(new URL('../src/views/pl/quota-cert/index.vue', import.meta.url), 'utf8') ).descriptor.scriptSetup!.content const source = ts.createSourceFile('page.ts', script, ts.ScriptTarget.Latest, true) const statements = source.statements.filter((s) => !ts.isImportDeclaration(s)) const names = statements.flatMap((s) => ts.isVariableStatement(s) ? s.declarationList.declarations .filter((d) => ts.isIdentifier(d.name)) .map((d) => d.name.getText(source)) : ts.isFunctionDeclaration(s) && s.name ? [s.name.text] : [] ) const code = ts.transpile(statements.map((s) => s.getText(source)).join('\n'), { target: ts.ScriptTarget.ESNext }) const scope = vue.effectScope() const state = scope.run(() => new Function(...Object.keys(env), `${code}; return {${names.join(',')}}`)(...Object.values(env)) ) state.formRef.value = { validate: async () => true, clearValidate: () => {} } return { state, user, deferSave: () => (pendingSave = deferred()), deferGet: () => (pendingGet = deferred()), contractRequests, productRequests, setCert: (value: any) => { certData = value }, failSave: () => { saveError = true }, reads, writes, parses, messages, deferOptions: () => { pendingOptions = deferred() return pendingOptions }, cancelSwitch: () => { confirmSwitch = false }, stop: () => scope.stop() } } test('each contract link keeps its own tab, including repeated contracts', () => { const h = harness() try { const tabs = h.state.contractTabItems( [ { id: 'link-1', contractId: 'c1', products: [] }, { id: 'link-2', contractId: 'c1', products: [] }, { id: 'link-3', contractId: 'c2', products: [] } ], [ { id: 'c1', contractNo: 'HT-1', contractName: '重复合同' }, { id: 'c2', contractNo: 'HT-2', contractName: '另一合同' } ] ) assert.deepEqual( tabs.map((tab: { key: string; label: string }) => [tab.key, tab.label]), [ ['link-1', 'HT-1(1)'], ['link-2', 'HT-1(2)'], ['link-3', 'HT-2'] ] ) } finally { h.stop() } }) test('replacing certificate products keeps the other certificates on the contract', () => { const result = model.replaceCertProducts( [ { certType: 'transport', certId: 't', products: [{ contractProductId: 'keep' }] }, { certType: 'purchase', certId: 'p', id: 'link', products: [{ id: 'old', contractProductId: 'a' }] } ], 'purchase', 'p', [{ contractProductId: 'b' }] ) assert.equal(result[0].certId, 't') assert.deepEqual(result[0].products, [{ contractProductId: 'keep' }]) assert.equal(result[1].id, 'link') assert.deepEqual(result[1].products, [{ contractProductId: 'b' }]) }) test('certificate quantities display three decimal places', () => { assert.equal(model.formatCertQty('1700.000000'), '1700.000') assert.equal(model.formatCertQty('0.000000'), '0.000') assert.equal(model.formatCertQty('1.23456'), '1.235') assert.equal(model.formatCertQty('9.9995'), '10.000') assert.equal(model.formatCertQty('-1.2345'), '-1.235') assert.equal(model.formatCertQty('-0.0004'), '0.000') assert.equal(model.formatCertQty(''), '—') assert.equal(model.formatCertQty(null), '—') }) test('new certificate defaults to review after image parsing', () => { assert.equal(model.newCert('purchase').entryMode, 'image_parse') assert.equal(model.newCert('transport').entryMode, 'image_parse') }) test('type changes immediately while options load and stale options cannot enter a later form', async () => { const h = harness(), s = h.state try { await s.edit() s.form.value.attachmentUrls = 'one.png' const options = h.deferOptions() const switching = s.changeFormKind('transport') assert.equal(s.formKind.value, 'transport') await s.changeFormKind('purchase') options.resolve([{ id: 'old-option' }]) await switching assert.equal(s.formKind.value, 'purchase') assert.deepEqual(s.purchases.value, []) } finally { h.stop() } }) test('new form type changes without closing dialog or changing the list, and drives save', async () => { const h = harness(), s = h.state try { await s.edit() s.form.value.attachmentUrls = 'one.png' assert.equal(s.formKind?.value, 'purchase') await s.changeFormKind('transport') assert.equal(s.formKind.value, 'transport') assert.equal(s.kind.value, 'purchase') assert.equal(s.formVisible.value, true) assert.ok(s.formFields.value.some(([key]: string[]) => key === 'shipperName')) assert.ok(!s.formFields.value.some(([key]: string[]) => key === 'buyerName')) s.form.value.shipperName = '合成托运单位' await s.save() assert.equal(h.writes[0].kind, 'transport') assert.equal(h.writes[0].body.shipperName, '合成托运单位') assert.equal(h.writes[0].body.buyerName, undefined) assert.equal(s.kind.value, 'transport') } finally { h.stop() } }) test('switch clears old certificate data after confirmation, preserving attachments and mode', async () => { const h = harness(), s = h.state try { await s.edit() s.form.value.attachmentUrls = 'one.png' s.form.value.certNo = 'PURCHASE-1' s.form.value.entryMode = 'image_parse' s.form.value.attachmentUrls = 'one.png' s.form.value.remark = '保留备注' await s.changeFormKind('transport') assert.equal(s.form.value.certNo, '') assert.equal(s.form.value.attachmentUrls, 'one.png') assert.equal(s.form.value.entryMode, 'image_parse') assert.equal(s.form.value.remark, '保留备注') assert.equal(s.form.value.buyerName, undefined) } finally { h.stop() } }) test('cancel type switch and existing certificate edit preserve data and original type', async () => { const h = harness(), s = h.state try { await s.edit() s.form.value.attachmentUrls = 'one.png' s.form.value.certNo = 'KEEP' h.cancelSwitch() s.selectedFormKind.value = 'transport' await s.changeFormKind('transport') assert.equal(s.formKind.value, 'purchase') assert.equal(s.selectedFormKind.value, 'purchase') assert.equal(s.form.value.certNo, 'KEEP') await s.edit({ id: 'existing' }) await s.changeFormKind('transport') assert.equal(s.formKind.value, 'purchase') assert.equal(s.form.value.attachmentUrls, 'a.png') } finally { h.stop() } }) test('single attachment is automatically selected, parsing uses form type and preserves manual edits', async () => { const h = harness(), s = h.state try { await s.edit() s.form.value.attachmentUrls = 'one.png' await s.changeFormKind('transport') s.form.value.attachmentUrls = 'one.png' await vue.nextTick() assert.equal(s.recognitionImage.value, 'one.png') s.form.value.certNo = 'MANUAL' s.edited.add('certNo') const parsing = s.recognize() assert.equal(h.parses.length, 1) assert.equal(h.parses[0].kind, 'transport') assert.equal(h.parses[0].url, 'one.png') h.parses[0].resolve({ candidates: { certNo: 'OCR', shipperName: '合成托运单位' }, invalidFields: {} }) await parsing assert.equal(s.form.value.certNo, 'MANUAL') assert.equal(s.form.value.shipperName, '合成托运单位') } finally { h.stop() } }) test('unconfigured image parsing warns and keeps the manual entry', async () => { const h = harness(), s = h.state try { await s.edit() s.form.value.certNo = '手工证号' s.form.value.attachmentUrls = 'one.png' await vue.nextTick() const parsing = s.recognize() h.parses[0].reject(new Error('图片解析尚未配置,可继续手工录入')) await parsing assert.equal(s.form.value.certNo, '手工证号') assert.equal(s.recognizing.value, false) assert.ok(h.messages.includes('图片解析尚未配置,可继续手工录入')) } finally { h.stop() } }) test('missing, removed, multiple new attachments and uploads in progress never start parsing', async () => { const h = harness(), s = h.state try { await s.edit() s.form.value.attachmentUrls = '' const empty = s.recognize() assert.equal(h.parses.length, 0) await empty s.form.value.attachmentUrls = 'one.png' await vue.nextTick() s.form.value.attachmentUrls = '' await vue.nextTick() assert.equal(s.recognitionImage.value, '') await s.recognize() s.form.value.attachmentUrls = 'one.png,two.png' s.recognitionImage.value = 'one.png' await s.recognize() s.form.value.attachmentUrls = 'one.png' s.certAttachments.value = { hasUploading: () => true } await s.recognize() assert.equal(h.parses.length, 0) await s.save() assert.equal(h.writes.length, 0) } finally { h.stop() } }) test('closed or switched forms reject delayed recognition results', async () => { for (const action of ['close', 'switch']) { const h = harness(), s = h.state try { await s.edit() s.form.value.attachmentUrls = 'one.png' await vue.nextTick() const parsing = s.recognize() assert.equal(h.parses.length, 1) if (action === 'close') { s.formVisible.value = false await s.edit() s.form.value.attachmentUrls = 'one.png' } else await s.changeFormKind('transport') h.parses[0].resolve({ candidates: { certNo: 'OLD' }, invalidFields: {} }) await parsing assert.equal(s.form.value.certNo, '') assert.equal(s.recognizing.value, false) } finally { h.stop() } } }) test('多合同一次保存且失败保留输入;受限可见关系只展示并省略提交', async () => { const h = harness(), s = h.state const bindings = [ { id: 'b', contractId: 'old', linkStatus: 'inactive', remark: '保留', orderNum: 4, products: [ { id: 'scope', contractProductId: 'p', scopeStatus: 'inactive', remark: '范围', orderNum: 2 } ] } ] try { h.setCert({ contractBindings: bindings, bindingsEditable: true }) await s.edit({ id: 'existing' }) assert.deepEqual(s.form.value.contractBindings, bindings) assert.equal(s.bindingContracts.value[0].contractNo, '历史合同') s.addContractBinding('new') assert.equal(s.form.value.contractBindings.length, 2) h.failSave() await assert.rejects(s.save(), /保存失败/) assert.equal(h.writes.length, 1) assert.equal(h.writes[0].body.contractBindings[0].products[0].id, 'scope') assert.equal(s.formVisible.value, true) assert.equal(s.form.value.contractBindings.length, 2) s.formVisible.value = false h.setCert({ contractBindings: bindings, bindingsEditable: false }) await s.edit({ id: 'limited' }) s.addContractBinding('forbidden') assert.equal(s.form.value.contractBindings.length, 1) await assert.rejects(s.save()) assert.equal('contractBindings' in h.writes[1].body, false) } finally { h.stop() } }) test('确定产品时保留原范围身份,已用证不能增删产品', async () => { const h = harness(), s = h.state try { await s.edit() s.form.value.attachmentUrls = 'one.png' s.addContractBinding('c') s.form.value.contractBindings[0].products = [ { id: 'scope', contractProductId: 'p', scopeStatus: 'inactive' } ] s.acceptFormBinding({ contractId: 'c', contractNo: 'HT', contractName: '合同', products: [ { contractProductId: 'p', scopeStatus: 'inactive' }, { contractProductId: 'p2', scopeStatus: 'active' } ] }) assert.equal(s.form.value.contractBindings[0].products[0].id, 'scope') assert.equal(s.form.value.contractBindings[0].products[0].scopeStatus, 'inactive') assert.equal(s.form.value.contractBindings[0].products[1].contractProductId, 'p2') s.form.value.contractBindings[0].editLocked = true s.acceptFormBinding({ contractId: 'c', contractNo: 'HT', contractName: '合同', products: [{ contractProductId: 'p2', scopeStatus: 'active' }] }) assert.equal(s.form.value.contractBindings[0].products.length, 2) assert.ok(h.messages.some((message: string) => message.includes('不能增删产品'))) s.acceptFormBinding({ contractId: 'c', contractNo: 'HT', contractName: '合同', products: [ { contractProductId: 'p', scopeStatus: 'active' }, { contractProductId: 'p2', scopeStatus: 'inactive' } ] }) assert.equal(s.form.value.contractBindings[0].products[0].id, 'scope') assert.equal(s.form.value.contractBindings[0].products[0].scopeStatus, 'active') } finally { h.stop() } }) test('移除后重选同一合同保留主关联和产品范围身份', async () => { const h = harness(), s = h.state try { h.setCert({ contractBindings: [ { id: 'binding', contractId: 'old', linkStatus: 'inactive', products: [{ id: 'scope', contractProductId: 'p', scopeStatus: 'inactive' }] } ] }) await s.edit({ id: 'cert' }) s.removeContractBinding(0) s.addContractBinding('old') assert.equal(s.form.value.contractBindings[0].id, 'binding') assert.equal(s.form.value.contractBindings[0].products[0].id, 'scope') } finally { h.stop() } }) test('租户切换使保存和读取旧结果失效', async () => { const h = harness(), s = h.state try { await s.edit() s.form.value.attachmentUrls = 'one.png' const pending = h.deferSave() const saving = s.save() await vue.nextTick() h.user.accessToken = '' pending.resolve({}) await saving assert.equal(h.messages.includes('证件已保存'), false) assert.equal(s.formVisible.value, false) const get = h.deferGet(), editing = s.edit({ id: 'old' }) h.user.getUserInfo = { tenantNo: 'other' } get.resolve({ ...model.newCert('purchase'), contractBindings: [] }) await editing assert.equal(s.formVisible.value, false) } finally { h.stop() } }) test('新增多合同连同证面成功提交一次,移除全部提交明确空集合', async () => { const h = harness(), s = h.state try { await s.edit() s.form.value.attachmentUrls = 'one.png' s.acceptFormBinding({ contractId: 'one', contractNo: 'ONE', contractName: '合同一', products: [{ contractProductId: 'p1', scopeStatus: 'active' }] }) s.acceptFormBinding({ contractId: 'two', contractNo: 'TWO', contractName: '合同二', products: [{ contractProductId: 'p2', scopeStatus: 'active' }] }) s.form.value.certNo = 'MULTI' await s.save() assert.equal(h.writes.length, 1) assert.equal(h.writes[0].body.certNo, 'MULTI') assert.deepEqual( h.writes[0].body.contractBindings.map((b: any) => b.contractId), ['one', 'two'] ) assert.equal(s.formVisible.value, true) h.setCert({ contractBindings: [{ id: 'b', contractId: 'one', products: [] }] }) await s.edit({ id: 'existing' }) s.removeContractBinding(0) await s.save() assert.deepEqual(h.writes[1].body.contractBindings, []) } finally { h.stop() } }) test('未填写合同不能保存,确定另一份合同不会改掉已有合同', async () => { const h = harness(), s = h.state try { await s.edit() s.form.value.attachmentUrls = 'one.png' s.addContractBinding() assert.equal(s.form.value.contractBindings.length, 1) assert.equal(s.form.value.contractBindings[0].contractId, '') await s.save() assert.equal(h.writes.length, 0) s.acceptFormBinding({ contractId: 'c2', contractNo: 'HT-2', contractName: '另一合同', products: [{ contractProductId: 'p2', scopeStatus: 'active' }] }) assert.equal(s.form.value.contractBindings[1].contractId, 'c2') assert.equal(s.form.value.contractBindings[1].products[0].contractProductId, 'p2') assert.equal(s.form.value.contractBindings[0].contractId, '') } finally { h.stop() } }) test('referenced bindings and references cannot be edited or removed', async () => { const h = harness(), s = h.state try { h.setCert({ referencesEditable: false, contractBindings: [ { id: 'b', contractId: 'c', editLocked: true, products: [{ id: 'p', contractProductId: 'p1' }] } ], purchaseRefs: [{ id: 'r', referenceType: 'unknown', referenceNo: 'ref' }] }) s.kind.value = 'transport' await s.edit({ id: 'cert' }) s.removeContractBinding(0) assert.equal(s.form.value.contractBindings.length, 1) s.acceptFormBinding({ contractId: 'c', contractNo: 'HT', contractName: '合同', products: [{ contractProductId: 'other', scopeStatus: 'active' }] }) assert.equal(s.form.value.contractBindings[0].products[0].contractProductId, 'p1') s.removeReference(0) s.addReference() assert.equal(s.form.value.purchaseRefs.length, 1) assert.ok(h.messages.some((m) => m.includes('不能新增或移除证面引用'))) assert.equal(s.referenceRowLocked(s.form.value.purchaseRefs[0]), false) } finally { h.stop() } }) test('linked purchase archive stays locked while an empty archive can be completed', async () => { const h = harness(), s = h.state try { h.setCert({ referencesEditable: false, purchaseRefs: [ { id: 'open', referenceType: 'unknown', referenceNo: 'G35', purchaseCertId: null }, { id: 'linked', referenceType: 'purchase_permit', referenceNo: 'G1', purchaseCertId: 'p1' } ] }) s.kind.value = 'transport' await s.edit({ id: 'cert' }) assert.equal(s.referenceRowLocked(s.form.value.purchaseRefs[0]), false) assert.equal(s.referenceRowLocked(s.form.value.purchaseRefs[1]), true) s.purchases.value = [{ id: 'buy', certNo: 'G34260485131004', certType: 'purchase' }] s.form.value.purchaseRefs[0].purchaseCertId = 'buy' s.onPurchaseArchiveChange() assert.equal(s.form.value.purchaseRefs[0].referenceNo, 'G35') assert.equal(s.referenceRowLocked(s.form.value.purchaseRefs[0]), false) } finally { h.stop() } }) test('recognition cannot populate locked empty references while other empty fields remain fillable', async () => { const h = harness(), s = h.state try { h.setCert({ referencesEditable: false, purchaseRefs: [], attachmentUrls: 'one.png' }) s.kind.value = 'transport' await s.edit({ id: 'cert' }) const parsing = s.recognize() h.parses[0].resolve({ candidates: { purchaseRefs: [{ referenceType: 'unknown', referenceNo: 'OCR-NEW' }], shipperName: '合成托运单位' }, invalidFields: {} }) await parsing assert.deepEqual(s.form.value.purchaseRefs, []) assert.equal(s.form.value.shipperName, '合成托运单位') } finally { h.stop() } }) test('successful edit clears attachment and remark and becomes a fresh open form', async () => { const h = harness(), s = h.state try { await s.edit({ id: 'existing' }) s.form.value.attachmentUrls = 'one.png' s.form.value.remark = '本张备注' await s.save() assert.equal(h.writes[0].body.remark, '本张备注') assert.equal(s.formVisible.value, true) assert.equal(s.editingId.value, '') assert.equal(s.form.value.attachmentUrls, '') assert.equal(s.form.value.remark, '') s.form.value.attachmentUrls = 'next.png' await s.save() assert.equal(h.writes[1].id, undefined) } finally { h.stop() } }) test('save and recognition reject missing or historical multiple attachments', async () => { const h = harness(), s = h.state try { await s.edit({ id: 'existing' }) for (const urls of ['', 'a.png,b.png']) { s.form.value.attachmentUrls = urls await vue.nextTick() s.recognitionImage.value = urls.split(',')[0] await s.save() const parsing = s.recognize() for (const p of h.parses) p.resolve({ candidates: {}, invalidFields: {} }) await parsing } assert.equal(h.writes.length, 0) assert.equal(h.parses.length, 0) } finally { h.stop() } }) test('new transport save keeps its type open while failures and recognition preserve the current remark', async () => { const h = harness(), s = h.state try { await s.edit() await s.changeFormKind('transport') s.form.value.attachmentUrls = 'one.png' s.form.value.remark = '不得丢失' await s.save() assert.equal(s.formVisible.value, true) assert.equal(s.kind.value, 'transport') assert.equal(s.formKind.value, 'transport') assert.equal(s.form.value.remark, '') assert.deepEqual(s.form.value.purchaseRefs, []) assert.deepEqual(s.form.value.contractBindings, []) s.form.value.attachmentUrls = 'two.png' s.form.value.remark = '失败保留' s.recognizing.value = true await s.save() assert.equal(h.writes.length, 1) assert.equal(s.form.value.remark, '失败保留') s.recognizing.value = false h.failSave() await assert.rejects(s.save(), /保存失败/) assert.equal(s.form.value.remark, '失败保留') assert.equal(s.form.value.attachmentUrls, 'two.png') assert.equal(s.formVisible.value, true) } finally { h.stop() } }) test('saving while transport purchase candidates load refreshes candidates for the next session', async () => { const h = harness(), s = h.state try { await s.edit() const old = h.deferOptions() const switching = s.changeFormKind('transport') const fresh = h.deferOptions() s.form.value.attachmentUrls = 'one.png' await s.save() old.resolve([{ id: 'stale' }]) await switching assert.deepEqual(s.purchases.value, []) fresh.resolve([{ id: 'fresh' }]) await Promise.resolve() await vue.nextTick() assert.deepEqual(s.purchases.value, [{ id: 'fresh' }]) assert.equal(s.formVisible.value, true) } finally { h.stop() } }) test('transport recognition copies contracts and products from the matched purchase cert', async () => { const h = harness(), s = h.state try { await s.edit() await s.changeFormKind('transport') s.form.value.attachmentUrls = 'cert.pdf' await vue.nextTick() h.setCert({ contractBindings: [ { id: 'purchase-link', contractId: 'c1', linkStatus: 'active', products: [{ id: 'purchase-scope', contractProductId: 'p1', scopeStatus: 'active' }] } ] }) const options = h.deferOptions() const parsing = s.recognize() h.parses[0].resolve({ candidates: { purchaseRefs: [{ referenceType: 'unknown', referenceNo: 'G34260671919592' }] }, invalidFields: {} }) options.resolve([{ id: 'buy-1', certNo: 'G34260671919592' }]) await parsing assert.equal(s.form.value.purchaseRefs[0].purchaseCertId, 'buy-1') assert.equal(s.form.value.contractBindings.length, 1) assert.equal(s.form.value.contractBindings[0].contractId, 'c1') assert.equal(s.form.value.contractBindings[0].products[0].contractProductId, 'p1') assert.equal(s.form.value.contractBindings[0].products[0].id, undefined) assert.equal( s.bindingContracts.value.some((contract: { id: string }) => contract.id === 'old'), true ) } finally { h.stop() } }) test('deleting the certificate attachment clears face, bindings and references', () => { const h = harness(), s = h.state try { s.formKind.value = 'transport' s.form.value = { ...model.newCert('transport'), entryMode: 'manual', attachmentUrls: 'one.png', remark: '保留备注', orderNum: 3, status: 'active', certNo: 'Y1', productNameOnCert: '硫酸', totalQty: '10', vehicleNumbersJson: ['皖A1'], purchaseRefs: [{ referenceNo: 'G1' }], contractBindings: [{ contractId: 'c1', linkStatus: 'active', products: [{ id: 'p1' }] }], bindingsEditable: true, referencesEditable: false } s.edited.add('certNo') s.bindingContracts.value = [{ id: 'c1', contractNo: 'HT-1' }] s.lockedPlates.value = ['皖A1'] s.linkedRefIds.value = ['ref-1'] s.onCertificateAttachmentChange('one.png') assert.equal(s.form.value.certNo, 'Y1') assert.equal(s.bindingEpoch.value, 0) s.onCertificateAttachmentChange('') assert.equal(s.form.value.certNo, '') assert.equal(s.form.value.productNameOnCert, '') assert.equal(s.form.value.totalQty, '') assert.deepEqual(s.form.value.vehicleNumbersJson, []) assert.deepEqual(s.form.value.purchaseRefs, []) assert.deepEqual(s.form.value.contractBindings, []) assert.equal(s.form.value.remark, '保留备注') assert.equal(s.form.value.entryMode, 'manual') assert.equal(s.form.value.orderNum, 3) assert.equal(s.form.value.status, 'active') assert.equal(s.form.value.attachmentUrls, '') assert.equal(s.form.value.referencesEditable, false) assert.equal(s.edited.has('certNo'), false) assert.deepEqual(s.bindingContracts.value, []) assert.deepEqual(s.lockedPlates.value, []) assert.deepEqual(s.linkedRefIds.value, []) assert.equal(s.bindingEpoch.value, 1) const archive = readFileSync( new URL('../src/views/m/pl/plan-cert/archive-form.vue', import.meta.url), 'utf8' ) assert.match(archive, /onCertificateAttachmentChange/) assert.match(archive, /clearCertAfterAttachmentRemoved/) } finally { h.stop() } })