优化构架
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
export type FirstLegPartyRole = 'SHIPPER' | 'CONSIGNEE'
|
||||
|
||||
export interface FirstLegParty {
|
||||
id: string
|
||||
code: string
|
||||
name: string
|
||||
roles: FirstLegPartyRole[]
|
||||
contactName: string
|
||||
phone: string
|
||||
email: string
|
||||
countryRegion: string
|
||||
province: string
|
||||
city: string
|
||||
postalCode: string
|
||||
address: string
|
||||
status: 'ACTIVE' | 'INACTIVE'
|
||||
}
|
||||
|
||||
export interface FirstLegPartySnapshot extends Omit<FirstLegParty, 'roles' | 'status'> {
|
||||
role: FirstLegPartyRole
|
||||
}
|
||||
|
||||
export function createFirstLegPartySnapshot(party: FirstLegParty, role: FirstLegPartyRole): FirstLegPartySnapshot {
|
||||
return {
|
||||
id: party.id,
|
||||
code: party.code,
|
||||
name: party.name,
|
||||
role,
|
||||
contactName: party.contactName,
|
||||
phone: party.phone,
|
||||
email: party.email,
|
||||
countryRegion: party.countryRegion,
|
||||
province: party.province,
|
||||
city: party.city,
|
||||
postalCode: party.postalCode,
|
||||
address: party.address,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
|
||||
const source = readFileSync(new URL('./firstLegPartySource.ts', import.meta.url), 'utf8')
|
||||
const viewSource = readFileSync(new URL('../views/FirstLegOrdersView.vue', import.meta.url), 'utf8')
|
||||
const apiSource = readFileSync(new URL('../api/partners.ts', import.meta.url), 'utf8')
|
||||
|
||||
test('keeps counterparty availability behind a replaceable read-only source', () => {
|
||||
assert.match(source, /export interface FirstLegPartySource/)
|
||||
assert.match(source, /listAvailable\(role/)
|
||||
assert.match(source, /findAvailable\(id, role/)
|
||||
assert.match(source, /async load\(\)/)
|
||||
assert.match(source, /party\.status === 'ACTIVE' && party\.roles\.includes\(role\)/)
|
||||
assert.match(source, /getMerchantPartnerCandidates/)
|
||||
assert.match(source, /partner\.types\?\.length/)
|
||||
assert.match(source, /partner\.types\?\.includes\(role\)/)
|
||||
assert.match(source, /: \['SHIPPER', 'CONSIGNEE'\]/)
|
||||
assert.match(source, /await getMerchantPartnerCandidates\(\)/)
|
||||
assert.doesNotMatch(source, /getMerchantPartnerCandidates\('SENDER'\)|getMerchantPartnerCandidates\('RECEIVER'\)/)
|
||||
assert.match(viewSource, /firstLegPartySource\.listAvailable\('SHIPPER'\)/)
|
||||
assert.match(viewSource, /firstLegPartySource\.listAvailable\('CONSIGNEE'\)/)
|
||||
assert.match(viewSource, /firstLegPartySource\.findAvailable\(id, role\)/)
|
||||
assert.match(viewSource, /firstLegPartySource\.load\(\)/)
|
||||
assert.doesNotMatch(viewSource, /DEMO_FIRST_LEG_PARTIES/)
|
||||
assert.match(apiSource, /url: '\/merchant\/v1\/partners\/candidates'/)
|
||||
assert.match(apiSource, /'CARRIER' \| 'CUSTOMS_BROKER' \| 'SHIPPER' \| 'CONSIGNEE'/)
|
||||
assert.match(apiSource, /types\?: MerchantPartnerType\[\]/)
|
||||
assert.match(apiSource, /url: `\/merchant\/v1\/partners\/\$\{id\}`/)
|
||||
})
|
||||
@@ -0,0 +1,49 @@
|
||||
import { getMerchantPartnerCandidates, type MerchantPartner } from '@/api/partners'
|
||||
import type { FirstLegParty, FirstLegPartyRole } from '@/data/firstLegParties'
|
||||
|
||||
export interface FirstLegPartySource {
|
||||
listAvailable(role: FirstLegPartyRole): readonly FirstLegParty[]
|
||||
findAvailable(id: string, role: FirstLegPartyRole): FirstLegParty | undefined
|
||||
load(): Promise<void>
|
||||
}
|
||||
|
||||
function isAvailableForRole(party: FirstLegParty, role: FirstLegPartyRole) {
|
||||
return party.status === 'ACTIVE' && party.roles.includes(role)
|
||||
}
|
||||
|
||||
const parties: FirstLegParty[] = []
|
||||
|
||||
function mapPartner(partner: MerchantPartner): FirstLegParty {
|
||||
const roles: FirstLegPartyRole[] = partner.types?.length
|
||||
? (['SHIPPER', 'CONSIGNEE'] as FirstLegPartyRole[]).filter((role) => partner.types?.includes(role))
|
||||
: ['SHIPPER', 'CONSIGNEE']
|
||||
return {
|
||||
id: String(partner.id),
|
||||
code: partner.partnerCode,
|
||||
name: partner.partnerName,
|
||||
// 新契约按稳定类型限制订单角色;旧响应无 types 时保持原有兼容行为。
|
||||
roles,
|
||||
contactName: partner.contactName ?? '',
|
||||
phone: partner.contactPhone ?? '',
|
||||
email: partner.contactEmail ?? '',
|
||||
countryRegion: partner.countryCode ?? '',
|
||||
province: partner.province ?? '',
|
||||
city: partner.city ?? '',
|
||||
postalCode: partner.postalCode ?? '',
|
||||
address: [partner.district, partner.addressLine].filter(Boolean).join(''),
|
||||
status: partner.status === 'ACTIVE' ? 'ACTIVE' : 'INACTIVE',
|
||||
}
|
||||
}
|
||||
|
||||
export const firstLegPartySource: FirstLegPartySource = {
|
||||
listAvailable(role) {
|
||||
return parties.filter((party) => isAvailableForRole(party, role))
|
||||
},
|
||||
findAvailable(id, role) {
|
||||
return parties.find((party) => party.id === id && isAvailableForRole(party, role))
|
||||
},
|
||||
async load() {
|
||||
parties.splice(0, parties.length)
|
||||
parties.push(...(await getMerchantPartnerCandidates()).map(mapPartner))
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user