Release/test #6
Vendored
+2
@@ -22,6 +22,8 @@ declare module 'vue' {
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { overseasApiRequest } from '@/api/overseasHttp'
|
||||
import { overseasApiRequest, overseasBlobRequest } from '@/api/overseasHttp'
|
||||
import type { PageResult } from './warehouseServices'
|
||||
|
||||
export interface ReturnOrderSummary {
|
||||
returnNo: string; overseasWarehouseId: string; warehouseContactName?: string; warehouseContactPhone?: string; customerReferenceNo?: string; status: string
|
||||
skuTypes: number; expectedQuantity: number; receivedQuantity: number; needRelabel: boolean; createdAt: string
|
||||
skuTypes: number; expectedQuantity: number; receivedQuantity: number; needRelabel: boolean; relabelNo?: string; relabelStatus?: string; createdAt: string
|
||||
}
|
||||
export interface ReturnOrderItem {
|
||||
skuCode: string; productNameZh?: string; expectedQuantity: number; receivedQuantity: number
|
||||
}
|
||||
export interface ReturnReceipt {
|
||||
receiptNo: string; receiptBatchNo: string; cartonMark?: string; trackingNo?: string; startedBy: 'RETURN_NO' | 'SKU'
|
||||
receiptNo: string; receiptBatchNo: string; palletCode: string; trackingNo?: string; startedBy: 'RETURN_NO' | 'SKU'
|
||||
receivedQuantity: number; operatorName?: string; signedAt: string
|
||||
}
|
||||
export interface ReturnOrderDetail { order: ReturnOrderSummary; remark?: string; items: ReturnOrderItem[]; receipts: ReturnReceipt[] }
|
||||
export interface ReturnPallet { palletCode: string; skuTypes: number; totalQuantity: number; items: Array<{ skuCode: string; productNameZh?: string; quantity: number }> }
|
||||
export interface ReturnPhoto { fileId: number; fileName: string; contentType: string; sizeBytes: number }
|
||||
export interface ReturnOrderDetail { order: ReturnOrderSummary; remark?: string; items: ReturnOrderItem[]; receipts: ReturnReceipt[]; pallets: ReturnPallet[]; photos: ReturnPhoto[] }
|
||||
export interface ReturnOrderCreate {
|
||||
overseasWarehouseId: string; warehouseContactName: string; warehouseContactPhone: string; customerReferenceNo?: string; remark?: string; needRelabel: boolean; idempotencyKey: string
|
||||
items: Array<{ skuCode: string; productNameZh?: string; expectedQuantity: number }>
|
||||
@@ -27,3 +29,21 @@ export function getReturnOrder(returnNo: string) {
|
||||
export function createReturnOrder(payload: ReturnOrderCreate) {
|
||||
return overseasApiRequest<ReturnOrderDetail>({ method: 'POST', url: '/merchant/v1/last-leg/return-orders', data: payload })
|
||||
}
|
||||
export function updateReturnOrder(returnNo: string, payload: ReturnOrderCreate) {
|
||||
return overseasApiRequest<ReturnOrderDetail>({ method: 'PUT', url: `/merchant/v1/last-leg/return-orders/${encodeURIComponent(returnNo)}`, data: payload })
|
||||
}
|
||||
export interface ReturnRelabelCreate {
|
||||
fileScope: 'CARTON_TOTAL' | 'SKU_TOTAL'; destinationFbaWarehouseId: string; destinationFbaWarehouseCode: string; cartonRemark?: string; idempotencyKey: string
|
||||
items: Array<{ skuCode: string; quantity: number }>
|
||||
}
|
||||
export interface ReturnRelabelCreated { relabelNo: string; relabelApplicationId: string; returnNo: string; skuCodes: string[]; status: string }
|
||||
export function createReturnRelabel(returnNo: string, payload: ReturnRelabelCreate) {
|
||||
return overseasApiRequest<ReturnRelabelCreated>({ method: 'POST', url: `/merchant/v1/last-leg/return-orders/${encodeURIComponent(returnNo)}/relabel`, data: payload })
|
||||
}
|
||||
export function uploadReturnRelabelFile(relabelNo: string, scope: 'CARTON_TOTAL' | 'SKU_TOTAL', file: File) {
|
||||
const data = new FormData(); data.append('file', file)
|
||||
return overseasApiRequest<unknown>({ method: 'POST', url: `/merchant/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}/files`, params: { scope }, data, headers: { 'Content-Type': 'multipart/form-data' } })
|
||||
}
|
||||
export function downloadReturnPhoto(returnNo: string, fileId: string | number) {
|
||||
return overseasBlobRequest({ method: 'GET', url: `/merchant/v1/last-leg/return-orders/${encodeURIComponent(returnNo)}/photos/${encodeURIComponent(String(fileId))}/download` })
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface RelabelInventoryCandidate {
|
||||
inventoryBalanceId: string; inboundOrderId: string; inboundOrderNo?: string; inboundCode?: string
|
||||
overseasWarehouseId: string; overseasWarehouseCode?: string; overseasWarehouseName?: string
|
||||
destinationFbaWarehouseCode?: string; destinationFbaWarehouseName?: string; skuCode?: string; productNameZh?: string
|
||||
availablePackages: number; version: number
|
||||
availablePackages: number | string; version: number
|
||||
positions: Array<{ positionId: string; palletNo?: string; batchNo?: string; warehouseLocationCode?: string; availablePackages: number }>
|
||||
sourceType?: 'INBOUND' | 'RETURN_CARTON'; returnCartonId?: string; cartonMark?: string
|
||||
skuItems?: Array<{ skuCode: string; newSkuCode?: string; quantity: number; remark?: string }>
|
||||
|
||||
@@ -17,11 +17,32 @@ test('总文件可以独立清除并重选同名文件,提交时禁止清除',
|
||||
assert.match(view, /aria-label="清除 SKU 总文件" :disabled="saving"/)
|
||||
})
|
||||
|
||||
test('贴标申请从当前客户仍有库存的历史入仓单选择来源', () => {
|
||||
test('贴标文件按总文件和关联模板分组展示', () => {
|
||||
assert.match(view, /warehouse-file-groups/)
|
||||
assert.match(view, /<span>1<\/span>贴标总文件/)
|
||||
assert.match(view, /必传 · 二选一/)
|
||||
assert.match(view, /必传 · 两份文件/)
|
||||
assert.match(view, /<span>2<\/span>明细关联模板/)
|
||||
assert.match(view, /SKU 关联/)
|
||||
assert.match(view, /箱号关联/)
|
||||
assert.match(view, /SKU 混箱/)
|
||||
assert.match(view, /warehouse-file-groups--single/)
|
||||
})
|
||||
|
||||
test('贴标申请移除操作项下方的解释文字,保留必要状态', () => {
|
||||
assert.doesNotMatch(view, /选择后仅查询当前客户在该中转仓的可用库存/)
|
||||
assert.doesNotMatch(view, /托盘仅作为库内承载位置/)
|
||||
assert.doesNotMatch(view, /整张贴标申请只能选择一个目的仓/)
|
||||
assert.doesNotMatch(view, /SKU 与箱号关联通过仓库唛头合并/)
|
||||
assert.match(view, /必传 · 二选一/)
|
||||
assert.match(view, /必传 · 两份文件/)
|
||||
})
|
||||
|
||||
test('贴标申请可从当前客户仍有库存的入仓单或退货单选择来源', () => {
|
||||
assert.match(view, /海外操作仓(中转仓)/)
|
||||
assert.match(view, /选择有库存的历史入仓单/)
|
||||
assert.match(view, /曾经下单且当前仍有库存的入仓单/)
|
||||
assert.match(view, /当前中转仓暂无已入库可用库存/)
|
||||
assert.match(view, /选择有库存的入仓单或退货单/)
|
||||
assert.match(view, /quantityUnit: candidate\.sourceType === 'RETURN_CARTON' \? '件' : '箱'/)
|
||||
assert.match(view, /当前中转仓暂无可用库存/)
|
||||
assert.match(view, /relabelInboundOptions/)
|
||||
assert.match(view, /listRelabelInventoryCandidates\(form\.warehouse\)/)
|
||||
assert.match(api, /relabel-applications\/inventory-candidates/)
|
||||
@@ -38,12 +59,11 @@ test('客户贴标表单不展示客户单号、托盘和内部库存来源列',
|
||||
|
||||
test('贴标申请保留关联模板下载和上传入口', () => {
|
||||
assert.match(view, /downloadRelabelTemplate/)
|
||||
assert.match(view, /下载 SKU 关联模板/)
|
||||
assert.match(view, /上传 SKU 关联/)
|
||||
assert.match(view, /下载箱号关联模板/)
|
||||
assert.match(view, /上传箱号关联/)
|
||||
assert.match(view, /下载 SKU 混箱模板/)
|
||||
assert.match(view, /上传 SKU 混箱/)
|
||||
assert.match(view, /<strong>SKU 关联<\/strong>/)
|
||||
assert.match(view, /<strong>箱号关联<\/strong>/)
|
||||
assert.match(view, /<strong>SKU 混箱<\/strong>/)
|
||||
assert.match(view, />下载模板<\/el-button>/)
|
||||
assert.match(view, />上传文件<\/el-button>/)
|
||||
assert.match(view, /skuTemplateInput\?\.click\(\)/)
|
||||
assert.match(view, /cartonTemplateInput\?\.click\(\)/)
|
||||
assert.match(view, /importRelabelTemplate/)
|
||||
@@ -51,7 +71,7 @@ test('贴标申请保留关联模板下载和上传入口', () => {
|
||||
assert.match(view, /'明细行标识', '旧箱唛', '旧SKU标签', '新SKU标签'/)
|
||||
assert.match(view, /const includeDestination = false/)
|
||||
assert.doesNotMatch(view, /const includeDestination = props\.service !== 'transfer'/)
|
||||
assert.match(view, /SKU 与箱号关联通过仓库唛头合并/)
|
||||
assert.match(view, /<strong>绑定明细<\/strong>/)
|
||||
assert.match(view, /<th>仓库唛头 \*<\/th>/)
|
||||
assert.doesNotMatch(view, /库存位置ID/)
|
||||
})
|
||||
@@ -76,7 +96,7 @@ test('上传关系支持部分或全量选择后导入本次换标明细', () =>
|
||||
})
|
||||
|
||||
test('箱唛换标一行固定代表一箱', () => {
|
||||
const cartonTable = view.match(/<div v-if="labelingMode === 'carton'" class="warehouse-table-scroll">.+?(?=<div v-else class="warehouse-table-scroll">)/s)?.[0]
|
||||
const cartonTable = view.match(/<div v-if="!isReturnRelabelSource && labelingMode === 'carton'" class="warehouse-table-scroll">.+?(?=<div v-else-if="!isReturnRelabelSource" class="warehouse-table-scroll">)/s)?.[0]
|
||||
assert.ok(cartonTable)
|
||||
assert.match(cartonTable, /<th>箱数<\/th>/)
|
||||
assert.match(cartonTable, /<td><strong>1 箱<\/strong><\/td>/)
|
||||
@@ -115,22 +135,47 @@ test('箱唛相关换标必须选择四选一派送方式', () => {
|
||||
assert.match(view, /labelingMode\.value !== 'sku' && !form\.deliveryMethod/)
|
||||
})
|
||||
|
||||
test('一次申请单选库存来源且保留普通入仓或退货合箱归属', () => {
|
||||
test('一次申请单选库存来源且退货库存按退货单汇总', () => {
|
||||
assert.doesNotMatch(view, /multiple filterable collapse-tags/)
|
||||
assert.match(view, /RETURN_CARTON:\$\{item\.returnCartonId\}/)
|
||||
assert.match(view, /RETURN_ORDER:\$\{returnOrderNo\}/)
|
||||
assert.match(view, /current\.availablePackages \+= inventoryQuantity\(candidate\.availablePackages\)/)
|
||||
assert.match(view, /availablePackages: inventoryQuantity\(candidate\.availablePackages\)/)
|
||||
assert.match(view, /const quantity = Number\(value\)/)
|
||||
assert.match(view, /interface ReturnRelabelSkuRow/)
|
||||
assert.match(view, /current\.availableQuantity \+= quantity/)
|
||||
assert.match(view, /库存总数/)
|
||||
assert.match(view, /本次贴标数量/)
|
||||
assert.match(view, /returnRelabelSkuRows\.value = \[\.\.\.grouped\.values\(\)\]/)
|
||||
assert.match(view, /createReturnRelabel\(selectedReturnOrderNo\.value/)
|
||||
assert.match(view, /uploadReturnRelabelFile\(created\.relabelNo, fileScope, selectedFile\)/)
|
||||
assert.doesNotMatch(view, /candidate\.sourceType === 'RETURN_CARTON'\) rows\.push\(\.\.\.Array\.from/)
|
||||
assert.match(view, /sourceInventoryBalanceId: String\(candidate\.inventoryBalanceId\)/)
|
||||
assert.match(view, /sourceInventoryPositionId: String\(position\.positionId\)/)
|
||||
assert.match(view, /sourceInboundOrderId: String\(candidate\.inboundOrderId\)/)
|
||||
assert.match(view, /returnCartonId: String\(candidate\.returnCartonId\)/)
|
||||
assert.match(api, /sourceInventoryBalanceId\?: string; sourceInventoryPositionId\?: string; sourceInboundOrderId\?: string; returnCartonId\?: string/)
|
||||
})
|
||||
|
||||
test('退货贴标总文件二选一且提交使用退货专用链路', () => {
|
||||
assert.match(view, /cartonTotalFile\.value && !requiresBothTotalFiles\.value\) clearCreateTotalFile\('SKU_TOTAL'\)/)
|
||||
assert.match(view, /skuTotalFile\.value && !requiresBothTotalFiles\.value\) clearCreateTotalFile\('CARTON_TOTAL'\)/)
|
||||
assert.match(view, /fileScope = cartonTotalFile\.value \? 'CARTON_TOTAL' as const : 'SKU_TOTAL' as const/)
|
||||
assert.match(view, /destinationFbaWarehouseId: String\(destination\.id\)/)
|
||||
})
|
||||
|
||||
test('SKU 和箱唛都换时两份总文件必须同时上传', () => {
|
||||
assert.match(view, /labelingMode\.value === 'carton_and_sku'/)
|
||||
assert.match(view, /requiresBothTotalFiles\.value && \(!cartonTotalFile\.value \|\| !skuTotalFile\.value\)/)
|
||||
assert.match(view, /必须同时上传箱唛总文件和 SKU 总文件/)
|
||||
assert.match(view, /requiresBothTotalFiles \? '和' : '或'/)
|
||||
assert.match(view, /必传 · 两份文件/)
|
||||
})
|
||||
|
||||
test('海外操作仓和FBA目的仓使用独立字段', () => {
|
||||
assert.match(api, /operationWarehouseId: string/)
|
||||
assert.match(api, /destinationFbaWarehouseCode\?: string/)
|
||||
assert.match(view, /operationWarehouseId: form\.warehouse/)
|
||||
assert.match(view, /destinationFbaWarehouseCode: form\.destination/)
|
||||
assert.match(view, /整张贴标申请只能选择一个目的仓/)
|
||||
assert.match(view, /<el-select v-model="form\.destination"[^\n]+FBA 目的仓/)
|
||||
assert.doesNotMatch(view, /v-model="row\.destinationWarehouseId"[^\n]+请选择 FBA 目的仓/)
|
||||
})
|
||||
|
||||
@@ -179,5 +224,6 @@ test('客户详情进入页面后直接回显完工照片并支持大图预览',
|
||||
})
|
||||
|
||||
test('进入复核后客户订单回显换标完成状态', () => {
|
||||
assert.match(view, /\['CUSTOMER_CONFIRM_PENDING', 'READY_FOR_OUTBOUND', 'COMPLETED'\]\.includes\(value\) \? '换标完成'/)
|
||||
assert.match(view, /value === 'CUSTOMER_CONFIRM_PENDING'\) return '待客户确认'/)
|
||||
assert.match(view, /\['READY_FOR_OUTBOUND', 'COMPLETED'\]\.includes\(value\) \? '换标完成'/)
|
||||
})
|
||||
|
||||
@@ -14,14 +14,14 @@ test('仓储转运先选择入仓方式并按方式切换地址信息', () => {
|
||||
})
|
||||
|
||||
test('仓储转运支持无目的地、FBA 仓和私人仓并显式选择是否换标', () => {
|
||||
assert.match(view, /是否有目的去向/)
|
||||
assert.match(view, />目的去向</)
|
||||
assert.match(view, /value="NONE">暂未确定/)
|
||||
assert.match(view, /value="FBA">FBA 仓/)
|
||||
assert.match(view, /value="PRIVATE">私人仓/)
|
||||
assert.match(view, /transferDestinationType === 'PRIVATE'.*私人仓地址.*邮编.*私人仓联系人.*联系电话/s)
|
||||
assert.match(view, /是否需要换标/)
|
||||
assert.match(view, /:value="false">不需要换标/)
|
||||
assert.match(view, /:value="true">需要换标/)
|
||||
assert.match(view, />换标服务</)
|
||||
assert.match(view, /:value="false">不需要/)
|
||||
assert.match(view, /:value="true">需要/)
|
||||
assert.match(api, /fbaWarehouseCode\?: string/)
|
||||
assert.match(api, /privateDestinationAddress\?: string; privateDestinationPostalCode\?: string/)
|
||||
assert.match(api, /fbaShipmentNo\?: string/)
|
||||
@@ -30,13 +30,11 @@ test('仓储转运支持无目的地、FBA 仓和私人仓并显式选择是否
|
||||
})
|
||||
|
||||
test('仓储转运换标整票只允许一个目的仓', () => {
|
||||
assert.match(view, /props\.service === 'transfer' && transferDestinationType === 'FBA'[^\n]+未换标货物目的 FBA 仓/)
|
||||
assert.match(view, /此处只表示不换标箱子的去向/)
|
||||
assert.match(view, /props\.service === 'transfer' && transferDestinationType === 'FBA'[^\n]+FBA 目的仓/)
|
||||
assert.match(view, /FBA 货件编号<\/span><el-input v-model="form\.fbaShipmentNo" placeholder="选填,可后续补充"/)
|
||||
assert.match(view, /transferDestinationType\.value === 'FBA' && !form\.destination/)
|
||||
assert.match(view, /transferDestinationType\.value === 'PRIVATE'.*!form\.privateDestinationPostalCode/)
|
||||
assert.match(view, /整票换标货物目的 FBA 仓/)
|
||||
assert.match(view, /暂不支持一票多个目的仓/)
|
||||
assert.match(view, /if \(!transferRelabelDefaultDestination\.value\) \{ ElMessage\.warning\('请选择整票换标货物的 FBA 目的仓'\)/)
|
||||
assert.match(view, /destinationWarehouseCode: transferRelabelDefaultDestination\.value \|\| undefined/)
|
||||
const transferRelabelSection = view.split('warehouse-transfer-relabel">')[1]?.split('warehouse-transfer-submit-bar')[0] || ''
|
||||
@@ -56,15 +54,14 @@ test('仓储转运不要求客户填写内部入仓号和我方箱唛,但换
|
||||
assert.doesNotMatch(transferConfig, /进仓编号/)
|
||||
assert.doesNotMatch(transferSubmit, /inboundCode|sourceLastlegOrderIds/)
|
||||
assert.match(view, /货物明细/)
|
||||
assert.match(view, /客户只填写自己的箱唛,我方箱唛由系统生成/)
|
||||
assert.match(view, /客户箱唛号 \*/)
|
||||
assert.match(view, /downloadTransferCartonMarkTemplate/)
|
||||
assert.match(view, /importTransferCartonMarks/)
|
||||
assert.match(view, /下载 SKU 关联模板/)
|
||||
assert.match(view, /上传 SKU 关联/)
|
||||
assert.match(view, /下载箱号关联模板/)
|
||||
assert.match(view, /上传箱号关联/)
|
||||
assert.match(view, /下载 SKU 混箱模板/)
|
||||
assert.match(view, /上传 SKU 混箱/)
|
||||
assert.match(view, /<strong>SKU 关联<\/strong>/)
|
||||
assert.match(view, /<strong>箱号关联<\/strong>/)
|
||||
assert.match(view, /<strong>SKU 混箱<\/strong>/)
|
||||
assert.match(view, />下载模板<\/el-button>/)
|
||||
assert.match(view, />上传文件<\/el-button>/)
|
||||
assert.match(view, /换标类型/)
|
||||
assert.match(view, /v-if="labelingMode === 'carton'"/)
|
||||
assert.match(view, /v-else-if="labelingMode === 'sku'"/)
|
||||
@@ -80,9 +77,29 @@ test('仓储转运不要求客户填写内部入仓号和我方箱唛,但换
|
||||
assert.doesNotMatch(view, /<th v-if="props\.service === 'transfer'">FNSKU \/ 箱唛<\/th>/)
|
||||
})
|
||||
|
||||
test('仓储转运货物明细可按 SKU 和箱数打印客户预贴箱唛', () => {
|
||||
assert.match(view, /printTransferDraftCartonMarks/)
|
||||
assert.match(view, /请先填写商品 SKU 和对应的整数箱数/)
|
||||
assert.match(view, /Array\.from\(\{ length: Number\(row\.quantity\) \}/)
|
||||
assert.match(view, /row\.customerCartonMarks = marks\.join\('\\n'\)/)
|
||||
assert.match(view, /仓储转运客户箱唛/)
|
||||
assert.match(view, /@click="printTransferDraftCartonMarks"/)
|
||||
assert.match(view, /打印唛头/)
|
||||
assert.match(view, /单次最多打印 500 张箱唛/)
|
||||
})
|
||||
|
||||
test('仓储转运换标资料按步骤分组展示', () => {
|
||||
assert.match(view, /<span v-if="props\.service === 'transfer'" class="warehouse-step-number">1<\/span>货物明细/)
|
||||
assert.match(view, /<span class="warehouse-step-number">2<\/span>换标申请/)
|
||||
assert.match(view, /<span>3<\/span>明细关联模板/)
|
||||
assert.match(view, /<span class="warehouse-step-number">4<\/span>绑定明细/)
|
||||
assert.match(view, /warehouse-transfer-template-group/)
|
||||
assert.match(view, /warehouse-transfer-relabel/)
|
||||
})
|
||||
|
||||
test('上门提货不提交物流号,需要换标时随转运单登记申请', () => {
|
||||
assert.match(view, /trackingNo:\s*form\.deliveryMethod === 'CUSTOMER_DELIVERY'\s*\?[^:]+:\s*undefined/)
|
||||
assert.match(view, /本次提交将同时登记换标申请/)
|
||||
assert.match(view, /relabelItems: form\.relabel/)
|
||||
assert.match(view, /form\.deliveryMethod === 'CUSTOMER_DELIVERY'.*物流跟踪号/s)
|
||||
assert.match(view, /提货联系人/)
|
||||
assert.match(view, /联系人电话/)
|
||||
@@ -109,7 +126,7 @@ test('仓储转运列表操作仓使用海外仓接口数据', () => {
|
||||
test('仓储转运预计发货时间可选并只读展示实际发货时间', () => {
|
||||
assert.match(view, /预计发货时间/)
|
||||
assert.match(view, /v-model="form\.estimatedShipAt"/)
|
||||
assert.match(view, /选填,可线下确认后补充/)
|
||||
assert.match(view, /v-model="form\.estimatedShipAt"[^>]+placeholder="选填"/)
|
||||
assert.doesNotMatch(view, /!form\.estimatedShipAt && '预计发货时间'/)
|
||||
assert.match(view, /estimatedShipAt: form\.estimatedShipAt/)
|
||||
assert.match(view, /实际发货时间由仓库出库或 PDA 完成后回填/)
|
||||
|
||||
Reference in New Issue
Block a user