feat(pl): 合同用证改为客户用证并修复列表筛选。
证件额度按客户展示;Web 客户列表改用 toQueryParams,修复 buildQuery is not a function。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
d5f3a79ea5
commit
ea46761f40
29 files changed
+1777
-295
No files matched your search
@@ -203,13 +203,14 @@ test('real template keeps plans quota proof order and separate lazy contract sco
|
||||
canViewQuotaCert: true,
|
||||
canViewProof: true,
|
||||
detailActiveTab: 'proof',
|
||||
detailContract: { id: 42 }
|
||||
detailContract: { id: 42, customerId: 'cust-42' }
|
||||
}
|
||||
const rows = panes(ctx)
|
||||
assert.deepEqual(
|
||||
rows.map((v: any) => v.props.name),
|
||||
['plans', 'quota', 'proof']
|
||||
)
|
||||
assert.equal(rows[1].props.label, '客户证件与额度')
|
||||
assert.equal(rows[2].props.label, '运输计划合法使用证明')
|
||||
assert.equal(rows[2].props.lazy, '')
|
||||
assert.equal(panel(rows[1]), undefined)
|
||||
@@ -219,14 +220,19 @@ test('real template keeps plans quota proof order and separate lazy contract sco
|
||||
assert.equal(proof.props.active, true)
|
||||
assert.equal(proof.props.embedded, '')
|
||||
ctx.detailContract.id = 43
|
||||
ctx.detailContract.customerId = 'cust-43'
|
||||
ctx.currentDetailId = 43
|
||||
assert.equal(panel(panes(ctx)[2]).key, 43)
|
||||
ctx.detailActiveTab = 'quota'
|
||||
const quota = panel(panes(ctx)[1])
|
||||
assert.equal(quota.type?.name || quota.type, 'CustomerCertQuotaPanel')
|
||||
assert.equal(quota.props['customer-id'] ?? quota.props.customerId, 'cust-43')
|
||||
assert.equal(quota.props.active, true)
|
||||
ctx.canViewQuotaCert = false
|
||||
assert.deepEqual(
|
||||
panes(ctx).map((v: any) => v.props.name),
|
||||
['plans', 'proof']
|
||||
)
|
||||
ctx.detailActiveTab = 'quota'
|
||||
assert.equal(panel(panes(ctx)[1]), undefined)
|
||||
})
|
||||
|
||||
@@ -265,7 +271,14 @@ test('mounted template unmounts old contract and never mounts quota for proof-on
|
||||
}
|
||||
}),
|
||||
StopDocuments: Child('proof'),
|
||||
ContractQuotaPanel: Child('quota'),
|
||||
CustomerCertQuotaPanel: vue.defineComponent({
|
||||
props: ['customerId', 'active'],
|
||||
setup(props) {
|
||||
lifecycle.push(`mount:quota:${props.customerId}`)
|
||||
vue.onBeforeUnmount(() => lifecycle.push(`unmount:quota:${props.customerId}`))
|
||||
return () => vue.h('span')
|
||||
}
|
||||
}),
|
||||
PlContractLinkedPlansPanel: vue.defineComponent({
|
||||
setup() {
|
||||
return () => vue.h('span')
|
||||
@@ -282,7 +295,7 @@ test('mounted template unmounts old contract and never mounts quota for proof-on
|
||||
canViewQuotaCert: false,
|
||||
canViewProof: true,
|
||||
detailActiveTab: 'proof',
|
||||
detailContract: { id: 'A' }
|
||||
detailContract: { id: 'A', customerId: 'cust-A' }
|
||||
})
|
||||
const renderer = vue.createRenderer<any, any>({
|
||||
createElement: () => ({ children: [] }),
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
test('web contract detail embeds customer cert panel and renamed tab', () => {
|
||||
const source = readFileSync(
|
||||
new URL('../src/views/crm/contract/components/ContractDetailDialog.vue', import.meta.url),
|
||||
'utf8'
|
||||
)
|
||||
assert.match(source, /label="客户证件与额度"/)
|
||||
assert.match(source, /CustomerCertQuotaPanel/)
|
||||
assert.doesNotMatch(source, /ContractQuotaPanel/)
|
||||
assert.match(source, /:customer-id="detailContract\.customerId"/)
|
||||
})
|
||||
|
||||
test('h5 contract detail includes customer cert tab', () => {
|
||||
const source = readFileSync(
|
||||
new URL('../src/views/m/crm/contract/detail.vue', import.meta.url),
|
||||
'utf8'
|
||||
)
|
||||
assert.match(source, /CustomerCertQuotaTab/)
|
||||
assert.match(source, /客户证件与额度/)
|
||||
assert.match(source, /canViewQuotaCert/)
|
||||
assert.match(source, /:customer-id="contract\.customerId"/)
|
||||
})
|
||||
|
||||
test('customer cert panel lists by customerId without contract bindings', () => {
|
||||
const source = readFileSync(
|
||||
new URL('../src/views/pl/contract-quota/CustomerCertQuotaPanel.vue', import.meta.url),
|
||||
'utf8'
|
||||
)
|
||||
assert.match(source, /quotaCertApi\.list\('purchase', \{ customerId \}\)/)
|
||||
assert.match(source, /quotaCertApi\.list\('transport', \{ customerId \}\)/)
|
||||
assert.match(source, /有效次数/)
|
||||
assert.match(source, /singleUseConsumed/)
|
||||
assert.doesNotMatch(source, /saveBindings|openCreate|bindings\(/)
|
||||
})
|
||||
|
||||
test('standalone contract-quota page is customer quota list', () => {
|
||||
const panel = readFileSync(
|
||||
new URL('../src/views/pl/contract-quota/CustomerQuotaPanel.vue', import.meta.url),
|
||||
'utf8'
|
||||
)
|
||||
const wrapper = readFileSync(
|
||||
new URL('../src/views/pl/contract-quota/ContractQuotaPanel.vue', import.meta.url),
|
||||
'utf8'
|
||||
)
|
||||
const router = readFileSync(new URL('../src/router/modules/pl.ts', import.meta.url), 'utf8')
|
||||
assert.match(panel, /客户用证/)
|
||||
assert.match(panel, /quotaStatisticsApi\.customers/)
|
||||
assert.match(wrapper, /CustomerQuotaPanel/)
|
||||
assert.match(router, /title: '客户用证'/)
|
||||
})
|
||||
|
||||
test('web customer quota panel uses toQueryParams from useServerColumnFilters', () => {
|
||||
const panel = readFileSync(
|
||||
new URL('../src/views/pl/contract-quota/CustomerQuotaPanel.vue', import.meta.url),
|
||||
'utf8'
|
||||
)
|
||||
assert.match(panel, /toQueryParams/)
|
||||
assert.match(panel, /\.\.\.toQueryParams\(\)/)
|
||||
assert.doesNotMatch(panel, /\bbuildQuery\b/)
|
||||
assert.doesNotMatch(panel, /\bresetFilters\b/)
|
||||
assert.match(panel, /clearAllFilters/)
|
||||
assert.match(panel, /watchVisibleColumns/)
|
||||
})
|
||||
|
||||
test('h5 customer quota tab does not call buildQuery', () => {
|
||||
const tab = readFileSync(
|
||||
new URL('../src/views/m/pl/plan-cert/CustomerTab.vue', import.meta.url),
|
||||
'utf8'
|
||||
)
|
||||
assert.doesNotMatch(tab, /\bbuildQuery\b/)
|
||||
assert.match(tab, /quotaStatisticsApi\.customers/)
|
||||
})
|
||||
@@ -7,7 +7,11 @@ const readyTab = readFileSync(
|
||||
'utf8'
|
||||
)
|
||||
const contractTab = readFileSync(
|
||||
new URL('../src/views/m/pl/plan-cert/ContractTab.vue', import.meta.url),
|
||||
new URL('../src/views/m/pl/plan-cert/CustomerTab.vue', import.meta.url),
|
||||
'utf8'
|
||||
)
|
||||
const planCertIndex = readFileSync(
|
||||
new URL('../src/views/m/pl/plan-cert/index.vue', import.meta.url),
|
||||
'utf8'
|
||||
)
|
||||
const archiveForm = readFileSync(
|
||||
@@ -30,9 +34,11 @@ test('H5 证书齐备对齐 2.0:待完善列表与完善并关闭,且文案
|
||||
assert.doesNotMatch(readyTab, /也不扣减额度/)
|
||||
})
|
||||
|
||||
test('H5 合同用证页对新规则给出只读提示', () => {
|
||||
assert.match(contractTab, /客户\+证用品类/)
|
||||
assert.match(contractTab, /仅供历史查看/)
|
||||
test('H5 客户用证按客户列表展示证件', () => {
|
||||
assert.match(contractTab, /quotaStatisticsApi\.customers/)
|
||||
assert.match(contractTab, /\/m\/pl\/plan-cert\/customer\//)
|
||||
assert.match(contractTab, /暂无客户用证数据/)
|
||||
assert.match(planCertIndex, /title: '客户用证'/)
|
||||
})
|
||||
|
||||
test('H5 建档页使用关联购买证且无证面引用残留', () => {
|
||||
|
||||
@@ -105,6 +105,9 @@ function harness(fixed?: string) {
|
||||
useServerColumnFilters,
|
||||
createStatisticsLoader,
|
||||
quotaStatisticsApi: api,
|
||||
certQuotaRuleApi: {
|
||||
get: async () => ({ configured: false })
|
||||
},
|
||||
onBeforeUnmount: (fn: Function) => unmount.push(fn)
|
||||
}
|
||||
const src = ts.createSourceFile(
|
||||
@@ -154,16 +157,21 @@ test('cert tabs keep their identities and legal proof is a sibling menu', () =>
|
||||
assert.equal(legal.meta!.title, '合法使用证明')
|
||||
assert.deepEqual(
|
||||
group.children!.filter((r) => !r.meta?.isHide).map((r) => r.path),
|
||||
['quota-cert', 'contract-quota', 'certificate-usage', 'plan-cert', 'cert-ready']
|
||||
['quota-cert', 'cert-category', 'contract-quota', 'certificate-usage', 'plan-cert', 'cert-ready']
|
||||
)
|
||||
assert.equal(group.children![0].id, 'PL_QUOTA_CERT')
|
||||
assert.equal(group.children![3].id, 'PL00000000000000000000000000015')
|
||||
assert.equal(group.children!.find((r) => r.path === 'contract-quota')?.meta?.title, '客户用证')
|
||||
assert.equal(
|
||||
group.children!.find((r) => r.path === 'plan-cert')?.id,
|
||||
'PL00000000000000000000000000015'
|
||||
)
|
||||
assert.deepEqual(
|
||||
group.children!.filter((r) => !r.meta?.isHide).map((r) => r.meta!.menuCode),
|
||||
[
|
||||
'pl:quota-cert:list',
|
||||
'pl:quota-cert:list',
|
||||
'pl:quota-cert:list',
|
||||
'pl:quota-cert:list',
|
||||
'pl:plan-cert:list',
|
||||
'pl:cert-ready:list'
|
||||
]
|
||||
@@ -648,71 +656,58 @@ function compiledSfc(path: string, dependencies: Record<string, any>, inlineTemp
|
||||
)
|
||||
return new Function(...Object.keys(env), `${code}; return component`)(...Object.values(env))
|
||||
}
|
||||
for (const wrapper of [
|
||||
'contract-quota/ContractQuotaPanel.vue',
|
||||
'certificate-usage/CertificateUsagePanel.vue'
|
||||
]) {
|
||||
for (const active of [undefined, false])
|
||||
test(`compiled ${wrapper} ${active === false ? 'inactive skips requests' : 'defaults to active and loads'}`, async () => {
|
||||
const calls: string[] = []
|
||||
const dependencies = {
|
||||
useUserStore: () => vue.reactive({ accessToken: 'token', getUserInfo: { tenantNo: 'a' } }),
|
||||
useRouter: () => ({ push() {}, replace() {} }),
|
||||
fetchGetMenuCodes: async () => {
|
||||
calls.push('permissions')
|
||||
return { all: true }
|
||||
},
|
||||
quotaStatisticsApi: {
|
||||
contracts: async () => {
|
||||
calls.push('contracts')
|
||||
return { list: [], total: 0 }
|
||||
},
|
||||
certificates: async () => {
|
||||
calls.push('certificates')
|
||||
return { list: [], total: 0 }
|
||||
}
|
||||
},
|
||||
usePersistedListColumns,
|
||||
useServerColumnFilters,
|
||||
createStatisticsLoader,
|
||||
useElementSize: () => ({ width: vue.ref(1120) }),
|
||||
useListTableViewportHeight: () => ({ tableHeight: vue.ref(400) }),
|
||||
statusLabel: statisticsLabels.statusLabel,
|
||||
contractTypeLabel: statisticsLabels.contractTypeLabel
|
||||
}
|
||||
// Compile the real inner setup too; only its visual child tree is omitted here.
|
||||
const StatisticsPanel = compiledSfc(
|
||||
'../src/views/pl/quota-statistics/StatisticsPanel.vue',
|
||||
dependencies,
|
||||
false
|
||||
)
|
||||
StatisticsPanel.render = () => null
|
||||
const Wrapper = compiledSfc(`../src/views/pl/${wrapper}`, { StatisticsPanel })
|
||||
const renderer = vue.createRenderer<any, any>({
|
||||
createElement: () => ({}),
|
||||
createText: () => ({}),
|
||||
createComment: () => ({}),
|
||||
setText: () => {},
|
||||
setElementText: () => {},
|
||||
parentNode: () => null,
|
||||
nextSibling: () => null,
|
||||
insert: () => {},
|
||||
remove: () => {},
|
||||
patchProp: () => {}
|
||||
})
|
||||
const app = renderer.createApp(Wrapper, active === undefined ? {} : { active })
|
||||
app.mount({})
|
||||
await flush()
|
||||
await vue.nextTick()
|
||||
assert.deepEqual(
|
||||
calls,
|
||||
active === false
|
||||
? []
|
||||
: ['permissions', wrapper.startsWith('contract') ? 'contracts' : 'certificates']
|
||||
)
|
||||
app.unmount()
|
||||
for (const active of [undefined, false])
|
||||
test(`compiled certificate-usage/CertificateUsagePanel.vue ${active === false ? 'inactive skips requests' : 'defaults to active and loads'}`, async () => {
|
||||
const calls: string[] = []
|
||||
const dependencies = {
|
||||
useUserStore: () => vue.reactive({ accessToken: 'token', getUserInfo: { tenantNo: 'a' } }),
|
||||
useRouter: () => ({ push() {}, replace() {} }),
|
||||
fetchGetMenuCodes: async () => {
|
||||
calls.push('permissions')
|
||||
return { all: true }
|
||||
},
|
||||
quotaStatisticsApi: {
|
||||
certificates: async () => {
|
||||
calls.push('certificates')
|
||||
return { list: [], total: 0 }
|
||||
}
|
||||
},
|
||||
usePersistedListColumns,
|
||||
useServerColumnFilters,
|
||||
createStatisticsLoader,
|
||||
useElementSize: () => ({ width: vue.ref(1120) }),
|
||||
useListTableViewportHeight: () => ({ tableHeight: vue.ref(400) }),
|
||||
statusLabel: statisticsLabels.statusLabel,
|
||||
contractTypeLabel: statisticsLabels.contractTypeLabel
|
||||
}
|
||||
const StatisticsPanel = compiledSfc(
|
||||
'../src/views/pl/quota-statistics/StatisticsPanel.vue',
|
||||
dependencies,
|
||||
false
|
||||
)
|
||||
StatisticsPanel.render = () => null
|
||||
const Wrapper = compiledSfc('../src/views/pl/certificate-usage/CertificateUsagePanel.vue', {
|
||||
StatisticsPanel
|
||||
})
|
||||
}
|
||||
const renderer = vue.createRenderer<any, any>({
|
||||
createElement: () => ({}),
|
||||
createText: () => ({}),
|
||||
createComment: () => ({}),
|
||||
setText: () => {},
|
||||
setElementText: () => {},
|
||||
parentNode: () => null,
|
||||
nextSibling: () => null,
|
||||
insert: () => {},
|
||||
remove: () => {},
|
||||
patchProp: () => {}
|
||||
})
|
||||
const app = renderer.createApp(Wrapper, active === undefined ? {} : { active })
|
||||
app.mount({})
|
||||
await flush()
|
||||
await vue.nextTick()
|
||||
assert.deepEqual(calls, active === false ? [] : ['permissions', 'certificates'])
|
||||
app.unmount()
|
||||
})
|
||||
|
||||
test('expanded content follows the table viewport width and keeps alignment local', () => {
|
||||
const h = harness()
|
||||
|
||||
Reference in new issue
Block a user