feat: support customer and supplier role selection for partners

This commit is contained in:
文睿 张 2026-10-10 10:47:12 +08:00
parent 1c316d0105
commit 8dd0427adb
2 changed files with 114 additions and 15 deletions

View File

@ -73,6 +73,90 @@ async function clickSave(wrapper) {
await flushPromises() await flushPromises()
} }
function businessRoleCheckboxes(wrapper) {
const item = wrapper.findAll('.el-form-item').find(
node => node.find('.el-form-item__label').text() === '业务分类'
)
expect(item.text()).toContain('客户')
expect(item.text()).toContain('供应商')
return item.findAll('input[type="checkbox"]')
}
describe('伙伴客户和供应商身份多选', () => {
beforeEach(() => {
partnersApi.list.mockReset().mockResolvedValue({ data: { items: [], total: 0 } })
partnersApi.create.mockReset().mockResolvedValue({ data: {} })
partnersApi.update.mockReset().mockResolvedValue({ data: {} })
})
afterEach(() => { vi.restoreAllMocks() })
test.each([['supplier', '供应商', false, true], ['customer', '客户', true, false]])(
'%s 新增默认当前身份,可双选并提交两个身份', async (role, title, customer, supplier) => {
const wrapper = mountPartner(role, title)
await flushPromises()
await openCreateDialog(wrapper, title)
const checkboxes = businessRoleCheckboxes(wrapper)
expect(checkboxes[0].element.checked).toBe(customer)
expect(checkboxes[1].element.checked).toBe(supplier)
for (const checkbox of checkboxes) await checkbox.setValue(true)
await inputByLabel(wrapper, '伙伴编码').setValue('BOTH-001')
await inputByLabel(wrapper, '企业名称').setValue('双身份公司')
await clickSave(wrapper)
expect(partnersApi.create).toHaveBeenCalledWith(expect.objectContaining({ is_customer: true, is_supplier: true }))
wrapper.unmount()
}
)
test.each([['supplier', '供应商', true, false], ['customer', '客户', false, true]])(
'%s 编辑回显双身份并允许取消当前页面身份', async (role, title, customer, supplier) => {
const row = { id: 7, partner_code: 'BOTH-001', legal_name: '双身份公司', is_customer: true, is_supplier: true,
customer_status: 'suspended', supplier_status: 'active' }
partnersApi.list.mockResolvedValue({ data: { items: [row], total: 1 } })
const wrapper = mountPartner(role, title)
await flushPromises()
await findButton(wrapper, '编辑').trigger('click')
await flushPromises()
const checkboxes = businessRoleCheckboxes(wrapper)
expect(checkboxes.every(checkbox => checkbox.element.checked)).toBe(true)
await checkboxes[0].setValue(customer)
await checkboxes[1].setValue(supplier)
expect(row.is_customer).toBe(true)
expect(row.is_supplier).toBe(true)
await clickSave(wrapper)
expect(partnersApi.update).toHaveBeenCalledWith(7, expect.objectContaining({ is_customer: customer, is_supplier: supplier,
customer_status: 'suspended', supplier_status: 'active' }))
await openCreateDialog(wrapper, title)
const reset = businessRoleCheckboxes(wrapper)
expect(reset[0].element.checked).toBe(role === 'customer')
expect(reset[1].element.checked).toBe(role === 'supplier')
wrapper.unmount()
}
)
test.each(['create', 'edit'])('%s 不允许取消所有业务分类', async mode => {
const warnSpy = vi.spyOn(ElMessage, 'warning').mockImplementation(() => {})
partnersApi.list.mockResolvedValue({ data: { items: [{ id: 7, partner_code: 'SUP-001', legal_name: '示例公司',
is_supplier: true, is_customer: false }], total: 1 } })
const wrapper = mountPartner()
await flushPromises()
if (mode === 'create') {
await openCreateDialog(wrapper, '供应商')
await inputByLabel(wrapper, '伙伴编码').setValue('SUP-002')
await inputByLabel(wrapper, '企业名称').setValue('示例公司')
} else {
await findButton(wrapper, '编辑').trigger('click')
await flushPromises()
}
for (const checkbox of businessRoleCheckboxes(wrapper)) await checkbox.setValue(false)
await clickSave(wrapper)
expect(warnSpy).toHaveBeenCalledWith('请至少选择一项业务分类')
expect(partnersApi.create).not.toHaveBeenCalled()
expect(partnersApi.update).not.toHaveBeenCalled()
expect(findButton(wrapper, '保存')).toBeDefined()
wrapper.unmount()
})
})
describe('伙伴表单必填校验', () => { describe('伙伴表单必填校验', () => {
let warnSpy let warnSpy
@ -264,7 +348,7 @@ describe('伙伴列表分页', () => {
}) })
}) })
describe('伙伴业务分类多选', () => { describe('伙伴合作范围多选', () => {
beforeEach(() => { beforeEach(() => {
partnersApi.list.mockReset().mockResolvedValue({ data: { items: [], total: 0 } }) partnersApi.list.mockReset().mockResolvedValue({ data: { items: [], total: 0 } })
partnersApi.create.mockReset().mockResolvedValue({ data: {} }) partnersApi.create.mockReset().mockResolvedValue({ data: {} })
@ -279,8 +363,8 @@ describe('伙伴业务分类多选', () => {
await openCreateDialog(wrapper, title) await openCreateDialog(wrapper, title)
await inputByLabel(wrapper, '伙伴编码').setValue('PARTNER-001') await inputByLabel(wrapper, '伙伴编码').setValue('PARTNER-001')
await inputByLabel(wrapper, '企业名称').setValue('示例公司') await inputByLabel(wrapper, '企业名称').setValue('示例公司')
const item = wrapper.findAll('.el-form-item').find(node => node.text().includes('业务分类')) const item = wrapper.findAll('.el-form-item').find(node => node.text().includes('合作范围'))
expect(item, '表单提供业务分类多选').toBeDefined() expect(item, '表单提供合作范围多选').toBeDefined()
for (const checkbox of item.findAll('input[type="checkbox"]')) await checkbox.setValue(true) for (const checkbox of item.findAll('input[type="checkbox"]')) await checkbox.setValue(true)
await clickSave(wrapper) await clickSave(wrapper)
expect(partnersApi.create).toHaveBeenCalledWith(expect.objectContaining({ expect(partnersApi.create).toHaveBeenCalledWith(expect.objectContaining({
@ -311,8 +395,8 @@ describe('伙伴业务分类多选', () => {
await flushPromises() await flushPromises()
await findButton(wrapper, '编辑').trigger('click') await findButton(wrapper, '编辑').trigger('click')
await flushPromises() await flushPromises()
const item = wrapper.findAll('.el-form-item').find(node => node.text().includes('业务分类')) const item = wrapper.findAll('.el-form-item').find(node => node.text().includes('合作范围'))
expect(item, '表单提供业务分类多选').toBeDefined() expect(item, '表单提供合作范围多选').toBeDefined()
const checkboxes = item.findAll('input[type="checkbox"]') const checkboxes = item.findAll('input[type="checkbox"]')
await checkboxes[0].setValue(false) await checkboxes[0].setValue(false)
await checkboxes[1].setValue(true) await checkboxes[1].setValue(true)
@ -323,7 +407,7 @@ describe('伙伴业务分类多选', () => {
expect(wrapper.find('.el-table__body tbody tr').text()).not.toContain('成品级(经营买卖)') expect(wrapper.find('.el-table__body tbody tr').text()).not.toContain('成品级(经营买卖)')
await findButton(wrapper, '编辑').trigger('click') await findButton(wrapper, '编辑').trigger('click')
await flushPromises() await flushPromises()
const reopened = wrapper.findAll('.el-form-item').find(node => node.text().includes('业务分类')).findAll('input[type="checkbox"]') const reopened = wrapper.findAll('.el-form-item').find(node => node.text().includes('合作范围')).findAll('input[type="checkbox"]')
expect(reopened[0].element.checked).toBe(true) expect(reopened[0].element.checked).toBe(true)
expect(reopened[1].element.checked).toBe(false) expect(reopened[1].element.checked).toBe(false)
await reopened[0].setValue(false) await reopened[0].setValue(false)
@ -331,7 +415,8 @@ describe('伙伴业务分类多选', () => {
await clickSave(wrapper) await clickSave(wrapper)
expect(partnersApi.update).toHaveBeenCalledWith(1, expect.objectContaining({ supplier_categories: ['finished_product'], customer_categories: ['finished_product'] })) expect(partnersApi.update).toHaveBeenCalledWith(1, expect.objectContaining({ supplier_categories: ['finished_product'], customer_categories: ['finished_product'] }))
await openCreateDialog(wrapper, '供应商') await openCreateDialog(wrapper, '供应商')
expect(wrapper.findAll('input[type="checkbox"]').every(input => !input.element.checked)).toBe(true) const ranges = wrapper.findAll('.el-form-item').find(node => node.text().includes('合作范围'))
expect(ranges.findAll('input[type="checkbox"]').every(input => !input.element.checked)).toBe(true)
wrapper.unmount() wrapper.unmount()
}) })
@ -341,8 +426,8 @@ describe('伙伴业务分类多选', () => {
await flushPromises() await flushPromises()
await wrapper.find('.btn-next').trigger('click') await wrapper.find('.btn-next').trigger('click')
await flushPromises() await flushPromises()
const filter = wrapper.findAllComponents(SelectStub).find(select => select.props('placeholder') === '全部业务分类') const filter = wrapper.findAllComponents(SelectStub).find(select => select.props('placeholder') === '全部合作范围')
expect(filter, '列表提供业务分类筛选').toBeDefined() expect(filter, '列表提供合作范围筛选').toBeDefined()
filter.vm.$emit('update:modelValue', 'material') filter.vm.$emit('update:modelValue', 'material')
filter.vm.$emit('change', 'material') filter.vm.$emit('change', 'material')
await flushPromises() await flushPromises()

View File

@ -5,7 +5,7 @@
<el-button type="primary" @click="openCreate">新增{{ title }}</el-button> <el-button type="primary" @click="openCreate">新增{{ title }}</el-button>
</div> </div>
<div class="filters"> <div class="filters">
<el-select v-model="categoryFilter" placeholder="全部业务分类" clearable @change="load(1)"> <el-select v-model="categoryFilter" placeholder="全部合作范围" clearable @change="load(1)">
<el-option v-for="(label, value) in CATEGORY_LABELS" :key="value" :label="label" :value="value" /> <el-option v-for="(label, value) in CATEGORY_LABELS" :key="value" :label="label" :value="value" />
<el-option label="未分类" value="unclassified" /> <el-option label="未分类" value="unclassified" />
</el-select> </el-select>
@ -21,7 +21,7 @@
> >
<el-table-column prop="partner_code" label="伙伴编码" width="140" /> <el-table-column prop="partner_code" label="伙伴编码" width="140" />
<el-table-column prop="legal_name" label="企业名称" min-width="180" /> <el-table-column prop="legal_name" label="企业名称" min-width="180" />
<el-table-column label="业务分类" min-width="220"> <el-table-column label="合作范围" min-width="220">
<template #default="{ row }"> <template #default="{ row }">
<template v-if="row[categoriesField]?.length"> <template v-if="row[categoriesField]?.length">
<el-tag v-for="category in row[categoriesField]" :key="category" class="category-tag">{{ CATEGORY_LABELS[category] || category }}</el-tag> <el-tag v-for="category in row[categoriesField]" :key="category" class="category-tag">{{ CATEGORY_LABELS[category] || category }}</el-tag>
@ -40,7 +40,13 @@
<el-form :model="form" label-width="130px"> <el-form :model="form" label-width="130px">
<el-form-item label="伙伴编码" required><el-input v-model="form.partner_code" :disabled="Boolean(editingId)" /></el-form-item> <el-form-item label="伙伴编码" required><el-input v-model="form.partner_code" :disabled="Boolean(editingId)" /></el-form-item>
<el-form-item label="企业名称" required><el-input v-model="form.legal_name" /></el-form-item> <el-form-item label="企业名称" required><el-input v-model="form.legal_name" /></el-form-item>
<el-form-item label="业务分类"> <el-form-item label="业务分类" required>
<el-checkbox-group v-model="businessRoles">
<el-checkbox value="customer">客户</el-checkbox>
<el-checkbox value="supplier">供应商</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="合作范围">
<el-checkbox-group v-model="form[categoriesField]"> <el-checkbox-group v-model="form[categoriesField]">
<el-checkbox v-for="(label, value) in CATEGORY_LABELS" :key="value" :value="value">{{ label }}</el-checkbox> <el-checkbox v-for="(label, value) in CATEGORY_LABELS" :key="value" :value="value">{{ label }}</el-checkbox>
</el-checkbox-group> </el-checkbox-group>
@ -75,6 +81,7 @@ const loading = ref(false)
const saving = ref(false) const saving = ref(false)
const dialogVisible = ref(false) const dialogVisible = ref(false)
const editingId = ref(null) const editingId = ref(null)
const businessRoles = ref([])
// 伙伴新增/编辑弹窗开着时切换公司会丢弃未保存内容,先确认。 // 伙伴新增/编辑弹窗开着时切换公司会丢弃未保存内容,先确认。
useOrganizationSwitchGuard(() => dialogVisible.value) useOrganizationSwitchGuard(() => dialogVisible.value)
@ -111,9 +118,15 @@ async function load(currentPage = pagination.currentPage) {
watch(() => props.role, () => { categoryFilter.value = ''; load(1) }) watch(() => props.role, () => { categoryFilter.value = ''; load(1) })
// 用 replaceAllKeys 而不是 Object.assign:后者删不掉上次编辑残留的 id, // 用 replaceAllKeys 而不是 Object.assign:后者删不掉上次编辑残留的 id,
// 新增时会把别人的 id 一起提交上去。 // 新增时会把别人的 id 一起提交上去。
function openCreate() { editingId.value = null; replaceAllKeys(form, emptyForm()); dialogVisible.value = true } function openCreate() {
editingId.value = null
replaceAllKeys(form, emptyForm())
businessRoles.value = [props.role]
dialogVisible.value = true
}
function openEdit(row) { function openEdit(row) {
editingId.value = row.id editingId.value = row.id
businessRoles.value = ['customer', 'supplier'].filter(role => row[`is_${role}`])
replaceAllKeys(form, { ...emptyForm(), ...row, status: row[statusField.value] || 'active', replaceAllKeys(form, { ...emptyForm(), ...row, status: row[statusField.value] || 'active',
supplier_categories: [...(row.supplier_categories || [])], customer_categories: [...(row.customer_categories || [])] }) supplier_categories: [...(row.supplier_categories || [])], customer_categories: [...(row.customer_categories || [])] })
dialogVisible.value = true dialogVisible.value = true
@ -121,14 +134,15 @@ function openEdit(row) {
function payload() { function payload() {
return { return {
...form, ...form,
is_supplier: form.is_supplier || props.role === 'supplier', is_supplier: businessRoles.value.includes('supplier'),
is_customer: form.is_customer || props.role === 'customer', is_customer: businessRoles.value.includes('customer'),
[`${props.role}_status`]: form.status [`${props.role}_status`]: form.status
} }
} }
async function save() { async function save() {
if (!form.partner_code.trim()) { ElMessage.warning('请填写伙伴编码'); return } if (!form.partner_code.trim()) { ElMessage.warning('请填写伙伴编码'); return }
if (!form.legal_name.trim()) { ElMessage.warning('请填写企业名称'); return } if (!form.legal_name.trim()) { ElMessage.warning('请填写企业名称'); return }
if (!businessRoles.value.length) { ElMessage.warning('请至少选择一项业务分类'); return }
saving.value = true saving.value = true
// 保存后停在第几页由 load 的默认参数决定:留在当前页,不把用户甩回第一页 // 保存后停在第几页由 load 的默认参数决定:留在当前页,不把用户甩回第一页
try { try {