fix(admin): align supplier carrier master data

This commit is contained in:
2026-08-27 10:12:20 +08:00
parent 29bb641524
commit 3e3c7e237a
14 changed files with 168 additions and 44 deletions
@@ -21,6 +21,7 @@ const payload: SavePlatformPartnerRequest = {
partnerName: '测试往来单位',
partnerType: 'SHIPPER',
types: ['SHIPPER'],
carrierCode: null,
contactName: '测试联系人',
contactPhone: '13800000000',
contactEmail: null,
@@ -66,7 +67,7 @@ describe('platform partner API', () => {
vi.mocked(http.get)
.mockResolvedValueOnce({ code: '200', message: 'success', data: {
id: '19001', tenantId: 0, dataScopeId: 0, partnerCode: 'CUS-001', partnerName: '测试单位',
partnerType: 'CONSIGNEE', contactName: '张三', contactPhone: '13800000000', contactEmail: null,
partnerType: 'CONSIGNEE', carrierCode: null, contactName: '张三', contactPhone: '13800000000', contactEmail: null,
countryCode: 'CN', province: null, city: '深圳市', district: null, addressLine: '测试地址',
postalCode: null, status: 'ACTIVE',
} })
@@ -2,6 +2,7 @@ import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
export type PlatformPartnerType = 'CARRIER' | 'SHIPPER' | 'CONSIGNEE'
export type PlatformPartnerStatus = 'ACTIVE' | 'INACTIVE'
export type PlatformPartnerCarrierCode = 'MATSON' | 'SML' | 'ZIM' | 'COSCO' | 'EMC' | 'HEDE' | 'OTHER'
export interface PlatformPartnerResource {
id: number | string
@@ -11,6 +12,7 @@ export interface PlatformPartnerResource {
partnerName: string
partnerType: PlatformPartnerType
types: PlatformPartnerType[]
carrierCode: PlatformPartnerCarrierCode | null
contactName: string
contactPhone: string
contactEmail: string | null
@@ -54,6 +56,7 @@ export interface SavePlatformPartnerRequest {
partnerName: string
partnerType: PlatformPartnerType
types: PlatformPartnerType[]
carrierCode: PlatformPartnerCarrierCode | null
contactName: string
contactPhone: string
contactEmail: string | null
@@ -89,6 +92,7 @@ function normalizePartner(resource: PlatformPartnerResource): PlatformPartnerRes
...resource,
dataScopeId: resource.dataScopeId ?? resource.tenantId,
types: resource.types?.length ? resource.types : [resource.partnerType],
carrierCode: resource.carrierCode ?? null,
remark: resource.remark ?? null,
}
}
@@ -52,6 +52,7 @@ const partner: PlatformPartnerResource = {
partnerName: '深圳示例贸易有限公司',
partnerType: 'SHIPPER',
types: ['SHIPPER'],
carrierCode: null,
contactName: '张三',
contactPhone: '13800138000',
contactEmail: 'contact@example.com',
@@ -159,6 +160,25 @@ describe('PartnersView real API integration', () => {
expect(document.body.textContent).not.toContain('供应商')
})
it('uses the fixed carrier-code selector when the unit is a shipping company', async () => {
const wrapper = await mountView()
await wrapper.findAll('button').find((button) => button.text().trim() === '新增往来单位')?.trigger('click')
await flushPromises()
const typeSelect = wrapper.findAllComponents({ name: 'ElSelect' }).find((select) => select.props('multiple'))
typeSelect!.vm.$emit('update:modelValue', ['CARRIER'])
await flushPromises()
expect(document.body.textContent).toContain('船司标准编码')
const carrierValues = new Set(['MATSON', 'SML', 'ZIM', 'COSCO', 'EMC', 'HEDE', 'OTHER'])
const carrierOptions = wrapper.findAllComponents({ name: 'ElOption' }).filter((option) => carrierValues.has(String(option.props('value'))))
expect(carrierOptions.map((option) => option.props('value'))).toEqual(['MATSON', 'SML', 'ZIM', 'COSCO', 'EMC', 'HEDE', 'OTHER'])
expect(carrierOptions.map((option) => option.props('label'))).toContain('美森 (MATSON)')
expect(source).toContain('placeholder="请选择船司标准编码"')
expect(source).toContain("MATSON: '美森'")
expect(source).toContain("OTHER: '其他'")
expect(source).toContain("carrierCode: types.includes('CARRIER') ? form.carrierCode : null")
})
it('renders a padded detail layout without an empty data-scope cell', async () => {
const wrapper = await mountView()
const detailButton = wrapper.findAll('button').find((button) => button.text().trim() === '详情')
@@ -23,6 +23,7 @@ import {
listPlatformPartners,
updatePlatformPartner,
type PlatformPartnerResource,
type PlatformPartnerCarrierCode,
type PlatformPartnerStatus,
type PlatformPartnerType,
type SavePlatformPartnerRequest,
@@ -94,6 +95,7 @@ const emptyForm = (): PartnerForm => ({
partnerName: '',
partnerType: 'SHIPPER',
types: ['SHIPPER'],
carrierCode: null,
contactName: '',
contactPhone: '',
contactEmail: null,
@@ -121,6 +123,7 @@ const rules: FormRules<PartnerForm> = {
partnerCode: [{ required: true, whitespace: true, message: '请输入往来单位编码', trigger: 'blur' }],
partnerName: [{ required: true, whitespace: true, message: '请输入往来单位名称', trigger: 'blur' }],
types: [{ required: true, type: 'array', min: 1, message: '请至少选择一个单位类型', trigger: 'change' }],
carrierCode: [{ validator: (_rule, value, callback) => form.types.includes('CARRIER') && !value ? callback(new Error('请选择船司标准编码')) : callback(), trigger: 'change' }],
contactName: [{ required: true, whitespace: true, message: '请输入联系人', trigger: 'blur' }],
contactPhone: [{ required: true, whitespace: true, message: '请输入联系电话', trigger: 'blur' }],
contactEmail: [{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' }],
@@ -139,6 +142,11 @@ const typeLabels: Record<PlatformPartnerType, string> = {
CONSIGNEE: '收货单位',
}
const typeOptions = Object.entries(typeLabels).map(([value, label]) => ({ value: value as PlatformPartnerType, label }))
const carrierCodeLabels: Record<PlatformPartnerCarrierCode, string> = {
MATSON: '美森', SML: 'SML', ZIM: '以星', COSCO: '中远', EMC: '长荣', HEDE: '合德', OTHER: '其他',
}
const carrierCodeOptions = (Object.entries(carrierCodeLabels) as [PlatformPartnerCarrierCode, string][]).map(([value, label]) => ({ value, label: `${label} (${value})` }))
function formatCarrierCode(value?: PlatformPartnerCarrierCode | null) { return value ? `${carrierCodeLabels[value]} (${value})` : '—' }
function partnerTypeLabel(type: PlatformPartnerType) {
return typeLabels[type]
}
@@ -225,6 +233,7 @@ function openEdit(row: PlatformPartnerResource) {
partnerName: row.partnerName,
partnerType: row.types[0],
types: [...row.types],
carrierCode: row.carrierCode,
contactName: row.contactName,
contactPhone: row.contactPhone,
contactEmail: row.contactEmail,
@@ -249,6 +258,7 @@ function savePayload(): SavePlatformPartnerRequest {
partnerName: form.partnerName.trim(),
partnerType: types[0],
types,
carrierCode: types.includes('CARRIER') ? form.carrierCode : null,
contactName: form.contactName.trim(),
contactPhone: form.contactPhone.trim(),
contactEmail: nullable(form.contactEmail),
@@ -342,6 +352,11 @@ function formatUpdatedAt(value?: string | null) {
}
watch([page, pageSize], () => { void loadPartners() })
watch(() => form.types.includes('CARRIER'), (carrier) => {
if (carrier) return
form.carrierCode = null
formRef.value?.clearValidate('carrierCode')
})
watch(detailOpen, (open) => {
if (open) return
detailSequence += 1
@@ -415,6 +430,7 @@ onMounted(() => {
<el-form-item label="单位名称" prop="partnerName" required><el-input v-model="form.partnerName" placeholder="请输入工商登记或常用单位名称" maxlength="128" /></el-form-item>
<el-form-item label="单位类型" prop="types" required><el-select v-model="form.types" multiple collapse-tags collapse-tags-tooltip placeholder="请选择一个或多个单位类型"><el-option v-for="option in typeOptions" :key="option.value" :label="option.label" :value="option.value" /></el-select></el-form-item>
<el-form-item label="状态" prop="status" required><el-radio-group v-model="form.status"><el-radio-button value="ACTIVE">启用</el-radio-button><el-radio-button value="INACTIVE">停用</el-radio-button></el-radio-group></el-form-item>
<el-form-item v-if="form.types.includes('CARRIER')" label="船司标准编码" prop="carrierCode" required><el-select v-model="form.carrierCode" placeholder="请选择船司标准编码" style="width:100%"><el-option v-for="option in carrierCodeOptions" :key="option.value" :label="option.label" :value="option.value" /></el-select></el-form-item>
</div></FormSection>
<FormSection title="联系信息"><div class="form-grid two-cols">
<el-form-item label="联系人" prop="contactName" required><el-input v-model="form.contactName" placeholder="请输入主要联系人姓名" maxlength="64" /></el-form-item>
@@ -446,6 +462,7 @@ onMounted(() => {
<div><dt>单位名称</dt><dd>{{ detail.partnerName }}</dd></div>
<div><dt>状态</dt><dd><el-tag :type="detail.status === 'ACTIVE' ? 'success' : 'info'">{{ detail.status === 'ACTIVE' ? '启用' : '停用' }}</el-tag></dd></div>
<div><dt>单位类型</dt><dd class="partner-types"><el-tag v-for="type in detail.types" :key="type" effect="plain">{{ partnerTypeLabel(type) }}</el-tag></dd></div>
<div v-if="detail.types.includes('CARRIER')"><dt>船司标准编码</dt><dd>{{ formatCarrierCode(detail.carrierCode) }}</dd></div>
</dl></FormSection>
<FormSection title="联系与地址"><dl class="detail-grid">
<div><dt>联系人</dt><dd>{{ detail.contactName }}</dd></div><div><dt>联系电话</dt><dd>{{ detail.contactPhone }}</dd></div>