feat(first-leg): reorder pickup choices and collect optional courier tracking
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# 头程下单交货方式调整
|
||||
|
||||
依据:当前对话 2026-10-19 用户确认“卡车自送排第一,我司提货排第二,快递自提排第三;快递自提增加非必填快递单号”。该确认优先于历史原型对应字段。
|
||||
|
||||
| 页面 | 操作 | 承载方式 | 布局 | 当前实现 | 结论 |
|
||||
|---|---|---|---|---|---|
|
||||
| 头程下单 | 新增 | 现有独立页面 | 保持现有国内交货区布局 | 快递自送、我司提货、卡车自送;无快递单号输入 | 按确认顺序排列;第三项改为快递自提,下方条件显示选填快递单号 |
|
||||
| 头程订单 | 编辑草稿 | 现有居中弹框 | 复用新增表单 | 无快递单号输入 | 同步新增规则,回显已保存单号 |
|
||||
| 头程订单 | 详情 | 现有独立页面 | 保持现有信息区 | 已有快递单号只读展示 | 复用当前展示;说明客户可在快递自提草稿填写单号 |
|
||||
|
||||
自动检查与人工视觉验收分别记录;1920×1080、100% 缩放的页面效果待人工截图确认。
|
||||
@@ -139,6 +139,7 @@ export interface FirstLegOrderSaveRequest {
|
||||
privateDestinationAddressId?: number | string
|
||||
cargoes: FirstLegOrderCargoPayload[]
|
||||
pickupMethod: ApiPickupMethod
|
||||
expressTrackingNo?: string
|
||||
receivingWarehouseId: number | string
|
||||
receivingWarehouseName: string
|
||||
receivingWarehouseAddress?: string | null
|
||||
|
||||
@@ -8,6 +8,21 @@ const storeSource = readFileSync(new URL('./firstLegOrders.ts', import.meta.url)
|
||||
const apiSource = readFileSync(new URL('../api/firstLeg.ts', import.meta.url), 'utf8')
|
||||
const unifiedApiSource = readFileSync(new URL('../api/unifiedOrders.ts', import.meta.url), 'utf8')
|
||||
|
||||
test('saves optional express tracking, trims it, and omits it for other pickup methods', () => {
|
||||
const functions = storeSource.slice(storeSource.indexOf('function apiTransport('), storeSource.indexOf('function prepareDraftForSave('))
|
||||
const javascript = ts.transpileModule(functions, { compilerOptions: { target: ts.ScriptTarget.ES2022 } }).outputText
|
||||
const save = runInNewContext(`${javascript}; savePayload`)
|
||||
const draft = { pickupMethod: 'SELF_EXPRESS', trackingNumber: ' SF123456789 ', destinationCountryCode: 'US', destinationType: 'OVERSEAS', items: [] }
|
||||
const warehouse = { id: '1', name: '收货仓' }
|
||||
assert.equal(save(draft, warehouse, '客户').expressTrackingNo, 'SF123456789')
|
||||
for (const trackingNumber of ['', ' ']) {
|
||||
assert.equal(save({ ...draft, trackingNumber }, warehouse, '客户').expressTrackingNo, undefined)
|
||||
}
|
||||
for (const pickupMethod of ['SELF_TRUCK', 'PICKUP']) {
|
||||
assert.equal(save({ ...draft, pickupMethod }, warehouse, '客户').expressTrackingNo, undefined)
|
||||
}
|
||||
})
|
||||
|
||||
test('detail fetches authoritative progress on direct entry and refresh, and clears stale progress on failure', async () => {
|
||||
const functionSource = storeSource.slice(storeSource.indexOf(' async function loadDetail('), storeSource.indexOf(' async function loadWarehouseDetail('))
|
||||
const progressSource = readFileSync(new URL('../utils/orderProgress.ts', import.meta.url), 'utf8')
|
||||
|
||||
@@ -120,6 +120,7 @@ function savePayload(draft: FirstLegOrderDraft, warehouse: ClientWarehouse, cust
|
||||
productImageFileId: item.productImageFileId || undefined,
|
||||
})),
|
||||
pickupMethod: draft.pickupMethod,
|
||||
expressTrackingNo: draft.pickupMethod === 'SELF_EXPRESS' ? draft.trackingNumber.trim() || undefined : undefined,
|
||||
receivingWarehouseId: warehouse.id,
|
||||
receivingWarehouseName: warehouse.name,
|
||||
channelCode: draft.channelCode,
|
||||
|
||||
@@ -183,6 +183,7 @@ export type FirstLegOrderDraft = Omit<Pick<
|
||||
| 'destinationWarehouseId'
|
||||
| 'destinationWarehouseName'
|
||||
| 'pickupMethod'
|
||||
| 'trackingNumber'
|
||||
| 'channelCode'
|
||||
| 'channelName'
|
||||
| 'pricingTaxMode'
|
||||
|
||||
@@ -549,9 +549,19 @@ test('shows fulfillment courier information as read-only client detail', () => {
|
||||
assert.match(source, /履约快递信息/)
|
||||
assert.match(source, /detailOrder\.expressCompanyName/)
|
||||
assert.match(source, /detailOrder\.trackingNumber/)
|
||||
assert.match(source, /客户端仅只读展示/)
|
||||
assert.match(source, /快递公司由平台维护/)
|
||||
assert.doesNotMatch(source, /v-model="form\.expressCompanyName"/)
|
||||
assert.doesNotMatch(source, /v-model="form\.trackingNumber"/)
|
||||
})
|
||||
|
||||
test('orders pickup choices and offers an optional tracking number only for express pickup', () => {
|
||||
const group = source.match(/<el-radio-group v-model="form\.pickupMethod"[\s\S]*?<\/el-radio-group>/)[0]
|
||||
assert.deepEqual([...group.matchAll(/value="([A-Z_]+)"/g)].map(match => match[1]), ['SELF_TRUCK', 'PICKUP', 'SELF_EXPRESS'])
|
||||
assert.match(group, /value="SELF_EXPRESS">快递自提/)
|
||||
const field = source.match(/<el-form-item v-if="form\.pickupMethod === 'SELF_EXPRESS'"[^>]*label="快递单号"[\s\S]*?<\/el-form-item>/)[0]
|
||||
assert.match(field, /v-model="form\.trackingNumber" maxlength="128"/)
|
||||
assert.doesNotMatch(field, /\brequired\b|:rules=/)
|
||||
assert.match(source, /trackingNumber: order\.pickupMethod === 'SELF_EXPRESS' \? order\.trackingNumber : ''/)
|
||||
assert.match(source, /if \(form\.pickupMethod !== 'SELF_EXPRESS'\) form\.trackingNumber = ''/)
|
||||
})
|
||||
|
||||
test('shows the full customer-safe order detail without platform-only controls', () => {
|
||||
|
||||
@@ -372,9 +372,9 @@ function formatExportDateTime(value: string | null | undefined) {
|
||||
}
|
||||
|
||||
const pickupMethodLabels: Record<FirstLegOrder['pickupMethod'], string> = {
|
||||
SELF_EXPRESS: '自行快递到仓',
|
||||
PICKUP: '平台上门提货',
|
||||
SELF_TRUCK: '自行送货到仓',
|
||||
SELF_TRUCK: '卡车自送',
|
||||
PICKUP: '我司提货',
|
||||
SELF_EXPRESS: '快递自提',
|
||||
}
|
||||
const destinationTypeLabels: Record<FirstLegOrder['destinationType'], string> = {
|
||||
FBA: 'FBA 仓',
|
||||
@@ -424,6 +424,7 @@ function emptyDraft(): FirstLegOrderDraft {
|
||||
destinationWarehouseId: '',
|
||||
destinationWarehouseName: '',
|
||||
pickupMethod: 'SELF_EXPRESS',
|
||||
trackingNumber: '',
|
||||
channelCode: '',
|
||||
channelName: '',
|
||||
pricingTaxMode: 'TAX_INCLUDED',
|
||||
@@ -920,6 +921,7 @@ function draftFromOrder(order: FirstLegOrder): FirstLegOrderDraft {
|
||||
destinationWarehouseId: order.destinationWarehouseId,
|
||||
destinationWarehouseName: order.destinationWarehouseName,
|
||||
pickupMethod: order.pickupMethod,
|
||||
trackingNumber: order.pickupMethod === 'SELF_EXPRESS' ? order.trackingNumber : '',
|
||||
channelCode: order.channelCode,
|
||||
channelName: order.channelName,
|
||||
pricingTaxMode: order.pricingTaxMode,
|
||||
@@ -942,6 +944,7 @@ function copyDraftFromOrder(order: FirstLegOrder): FirstLegOrderDraft {
|
||||
return {
|
||||
...draft,
|
||||
inboundCode: '',
|
||||
trackingNumber: '',
|
||||
items: draft.items.map((item) => ({
|
||||
...item,
|
||||
id: `copied-cargo-${++cargoSequence}`,
|
||||
@@ -1134,6 +1137,7 @@ async function changeDestinationType() {
|
||||
}
|
||||
|
||||
function changePickupMethod() {
|
||||
if (form.pickupMethod !== 'SELF_EXPRESS') form.trackingNumber = ''
|
||||
if (form.pickupMethod === 'PICKUP') {
|
||||
form.shipperPartySnapshot ??= createPickupPartySnapshot()
|
||||
} else {
|
||||
@@ -2241,7 +2245,8 @@ async function searchOrders(model: QueryModel) {
|
||||
<div class="prototype-form-flow shipping-form-flow">
|
||||
<el-form-item label="履约类型" prop="fulfillmentType" required class="prototype-form-row prototype-form-row--tabs"><el-radio-group v-model="form.fulfillmentType" class="segmented-options" @change="changeFulfillmentType"><el-radio-button value="STANDARD">普通头程</el-radio-button><el-radio-button value="ONE_CLICK_DROPSHIP">一件代发</el-radio-button></el-radio-group><small v-if="form.fulfillmentType === 'ONE_CLICK_DROPSHIP'" class="field-guidance">测量完成后不办理国内库存入库,将直接推送 PDA 合托。</small></el-form-item>
|
||||
<el-form-item v-if="form.fulfillmentType === 'ONE_CLICK_DROPSHIP'" label="LX入库单号" prop="lxCode" class="prototype-form-row prototype-form-row--wide"><el-input v-model="form.lxCode" maxlength="128" placeholder="请输入LX入库单号" /><small class="field-guidance">用于绑定领星入库单,提交后可在尾程订单、入库单和出库单中查询。</small></el-form-item>
|
||||
<el-form-item label="取货方式" prop="pickupMethod" required class="prototype-form-row prototype-form-row--tabs"><el-radio-group v-model="form.pickupMethod" class="segmented-options" @change="changePickupMethod"><el-radio-button value="SELF_EXPRESS">快递自送</el-radio-button><el-radio-button value="PICKUP">我司提货</el-radio-button><el-radio-button value="SELF_TRUCK">卡车自送</el-radio-button></el-radio-group></el-form-item>
|
||||
<el-form-item label="取货方式" prop="pickupMethod" required class="prototype-form-row prototype-form-row--tabs"><el-radio-group v-model="form.pickupMethod" class="segmented-options" @change="changePickupMethod"><el-radio-button value="SELF_TRUCK">卡车自送</el-radio-button><el-radio-button value="PICKUP">我司提货</el-radio-button><el-radio-button value="SELF_EXPRESS">快递自提</el-radio-button></el-radio-group></el-form-item>
|
||||
<el-form-item v-if="form.pickupMethod === 'SELF_EXPRESS'" label="快递单号" prop="trackingNumber" class="prototype-form-row prototype-form-row--wide"><el-input v-model="form.trackingNumber" maxlength="128" placeholder="请输入快递单号(选填)" /></el-form-item>
|
||||
<template v-if="form.pickupMethod === 'PICKUP' && form.shipperPartySnapshot">
|
||||
<el-form-item label="企业提货地址簿" prop="senderAddressId" required :rules="requiredSelectRules('请选择企业提货地址')" class="prototype-form-row prototype-form-row--wide address-book-row">
|
||||
<div class="address-book-control"><el-select v-model="form.senderAddressId" clearable filterable :loading="orderAddressesLoading" placeholder="选择企业保存的提货地址" @change="selectPickupAddress"><el-option v-for="address in pickupAddresses" :key="String(address.id)" :label="addressLabel(address)" :value="String(address.id)" /></el-select><el-button @click="savePickupAddress">保存当前地址</el-button><el-button v-if="form.senderAddressId" link type="danger" @click="removeOrderAddress('PICKUP')">停用</el-button></div>
|
||||
@@ -2474,7 +2479,7 @@ async function searchOrders(model: QueryModel) {
|
||||
<section v-if="detailOrder.status !== 'CANCELLED' || detailOrder.expressCompanyName || detailOrder.trackingNumber" class="detail-section">
|
||||
<h3>履约快递信息</h3>
|
||||
<dl v-if="detailOrder.expressCompanyName || detailOrder.trackingNumber" class="express-information"><div><dt>快递公司</dt><dd>{{ detailOrder.expressCompanyName || (detailOrder.status === 'CANCELLED' ? '—' : '待平台补充') }}</dd></div><div><dt>快递单号</dt><dd class="table-code">{{ detailOrder.trackingNumber || (detailOrder.status === 'CANCELLED' ? '—' : '待平台补充') }}</dd></div></dl>
|
||||
<div v-else class="express-empty"><PackageCheck :size="20" /><div><strong>平台尚未维护快递信息</strong><span>快递公司和快递单号由平台后台维护,客户端仅只读展示。</span></div></div>
|
||||
<div v-else class="express-empty"><PackageCheck :size="20" /><div><strong>暂无快递信息</strong><span>快递自提可在下单或编辑草稿时选填快递单号,快递公司由平台维护。</span></div></div>
|
||||
</section>
|
||||
<section class="detail-section">
|
||||
<h3>货物明细</h3>
|
||||
|
||||
@@ -202,6 +202,7 @@ function buildOrderDraft(row: QuickOrderRow, channel: QuickChannelOption): First
|
||||
destinationWarehouseId: row.destinationWarehouseId,
|
||||
destinationWarehouseName: row.destinationWarehouseName,
|
||||
pickupMethod: 'SELF_EXPRESS',
|
||||
trackingNumber: '',
|
||||
channelCode: channel.channelCode,
|
||||
channelName: channel.channelName,
|
||||
pricingTaxMode,
|
||||
|
||||
Reference in New Issue
Block a user