diff --git a/src/modules/ocean-bookings/containerRequirements.test.ts b/src/modules/ocean-bookings/containerRequirements.test.ts new file mode 100644 index 00000000..5cf0884d --- /dev/null +++ b/src/modules/ocean-bookings/containerRequirements.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest' +import { containerRequirementError, createBlankContainerRequirement } from './containerRequirements' + +describe('ocean booking container requirements', () => { + it('does not guess capacity or weight for a new container type', () => { + expect(createBlankContainerRequirement()).toEqual({ + containerType: '40HC', containerKind: 'FCL', capacityCbm: null, maxWeightKg: null, quantity: 1, + }) + }) + + it('rejects missing or non-positive capacity, weight and quantity', () => { + expect(containerRequirementError([createBlankContainerRequirement()])).toContain('容量、限重和数量必须大于 0') + expect(containerRequirementError([{ containerType: 'UNKNOWN', containerKind: 'FCL', capacityCbm: 1, maxWeightKg: 1, quantity: 0 }])).toContain('UNKNOWN') + expect(containerRequirementError([{ containerType: 'UNKNOWN', containerKind: 'FCL', capacityCbm: 1, maxWeightKg: 1, quantity: 1 }])).toBeNull() + }) +}) diff --git a/src/modules/ocean-bookings/containerRequirements.ts b/src/modules/ocean-bookings/containerRequirements.ts new file mode 100644 index 00000000..b6987887 --- /dev/null +++ b/src/modules/ocean-bookings/containerRequirements.ts @@ -0,0 +1,10 @@ +import type { OceanBookingContainer } from './types' + +export function createBlankContainerRequirement(): OceanBookingContainer { + return { containerType: '40HC', containerKind: 'FCL', capacityCbm: null, maxWeightKg: null, quantity: 1 } +} + +export function containerRequirementError(requirements: OceanBookingContainer[]) { + const invalid = requirements.find((item) => !(Number(item.capacityCbm) > 0) || !(Number(item.maxWeightKg) > 0) || !(Number(item.quantity) > 0)) + return invalid ? `箱型 ${invalid.containerType || '未命名'} 的容量、限重和数量必须大于 0,请补充后再提交` : null +} diff --git a/src/modules/ocean-bookings/pages/OceanBookingDetailView.test.ts b/src/modules/ocean-bookings/pages/OceanBookingDetailView.test.ts index 15a97d10..0c5e63a2 100644 --- a/src/modules/ocean-bookings/pages/OceanBookingDetailView.test.ts +++ b/src/modules/ocean-bookings/pages/OceanBookingDetailView.test.ts @@ -48,10 +48,9 @@ describe('OceanBookingDetailView interaction contract', () => { expect(source).toContain('v-model="container.capacityCbm"') expect(source).toContain('v-model="container.maxWeightKg"') expect(source).toContain('releaseOceanBooking(id.value, releaseRequest)') - expect(source).toContain('capacityCbm: null') - expect(source).toContain('maxWeightKg: null') + expect(source).toContain('createBlankContainerRequirement()') + expect(source).toContain('containerRequirementError(form.data.containerRequirements)') expect(source).toContain('function validateContainerRequirements()') - expect(source).toContain('容量、限重和数量必须大于 0') expect(source).not.toContain('capacityCbm: 68') expect(source).not.toContain('maxWeightKg: 28000') }) diff --git a/src/modules/ocean-bookings/pages/OceanBookingDetailView.vue b/src/modules/ocean-bookings/pages/OceanBookingDetailView.vue index 707630ba..e8d21baa 100644 --- a/src/modules/ocean-bookings/pages/OceanBookingDetailView.vue +++ b/src/modules/ocean-bookings/pages/OceanBookingDetailView.vue @@ -32,6 +32,7 @@ import type { OceanBookingStatus, } from '../types' import { oceanBookingStatusLabel } from '../status' +import { containerRequirementError, createBlankContainerRequirement } from '../containerRequirements' const partyTypes = [ { value: 'SHIPPER', label: '发货人' }, @@ -85,7 +86,7 @@ function blankParties(): OceanBookingParty[] { function blankData(): OceanBookingDraftData { return { customerId: '', customerName: '', channelCode: '', channelName: '', warehouseId: '', warehouseName: '', parties: blankParties(), - containerRequirements: [{ containerType: '40HC', containerKind: 'FCL', capacityCbm: null, maxWeightKg: null, quantity: 1 }], + containerRequirements: [createBlankContainerRequirement()], } } const form = reactive({ expectedVersion: 0, data: blankData() }) @@ -151,7 +152,7 @@ function applyDetail(value: OceanBookingDetail) { estimatedSailingAt: value.estimatedSailingAt, estimatedArrivalAt: value.estimatedArrivalAt, remark: value.remark, parties: value.parties.length ? value.parties.map(({ partyType, partyName, contactName, contactPhone, address }) => ({ partyType, partyName, contactName, contactPhone, address })) : blankParties(), - containerRequirements: value.containerRequirements.length ? value.containerRequirements.map(({ containerType, containerKind, capacityCbm, maxWeightKg, quantity }) => ({ containerType, containerKind, capacityCbm: capacityCbm ?? null, maxWeightKg: maxWeightKg ?? null, quantity: quantity ?? null })) : [{ containerType: '40HC', containerKind: 'FCL', capacityCbm: null, maxWeightKg: null, quantity: 1 }], + containerRequirements: value.containerRequirements.length ? value.containerRequirements.map(({ containerType, containerKind, capacityCbm, maxWeightKg, quantity }) => ({ containerType, containerKind, capacityCbm: capacityCbm ?? null, maxWeightKg: maxWeightKg ?? null, quantity: quantity ?? null })) : [createBlankContainerRequirement()], } } async function load() { @@ -230,14 +231,14 @@ async function runAction(action: OceanBookingAction) { } finally { actionSaving.value = false } } function validateContainerRequirements() { - const invalid = form.data.containerRequirements.find((container) => !(Number(container.capacityCbm) > 0) || !(Number(container.maxWeightKg) > 0) || !(Number(container.quantity) > 0)) - if (invalid) { - actionError.value = { message: `箱型 ${invalid.containerType || '未命名'} 的容量、限重和数量必须大于 0,请补充后再提交` } + const message = containerRequirementError(form.data.containerRequirements) + if (message) { + actionError.value = { message } return false } return true } -function addContainer() { form.data.containerRequirements.push({ containerType: '40HC', containerKind: 'FCL', capacityCbm: null, maxWeightKg: null, quantity: 1 }) } +function addContainer() { form.data.containerRequirements.push(createBlankContainerRequirement()) } function removeContainer(index: number) { if (form.data.containerRequirements.length > 1) form.data.containerRequirements.splice(index, 1) } onMounted(() => { void load(); void loadOptions() })