From 7e596b8d770e0921d1ec019b99c9fbde7bedd16c Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 7 Sep 2026 15:07:49 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 2 +- src/api/overseasHttp.ts | 13 +++++++++++-- .../order-management/api/inboundPlans.ts | 6 ++++-- .../order-management/inboundOrders.test.mjs | 19 +++++++++++++++++++ .../views/InboundOrdersView.vue | 7 ++++++- 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e454460..ac782aa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,7 +51,7 @@ | 装柜完成后的退税报关资料 | `src/modules/first-leg/views/FirstLegOrdersView.vue` 的“报关资料”、`components/CustomsDocumentsDialog.vue`、`api/customs.ts` | 只显示登录客户在同柜的退税入仓码;报关开始前可上传、替换、调整同一资料覆盖的一个或多个入仓码及删除误传,全部写入携带幂等键和版本;开始后只读冻结资料,整柜完成后才能下载平台回传的一对一完成文件。Backend `firstleg/customs/container/`、`customs-v1.yaml`,二进制归 `file/` | | 统一订单展示 | `src/modules/order-management/` | `src/modules/first-leg/` 与对应后端订单契约 | | 客户财务账单、入仓码联查、收款、未收余额与客户确认 | `src/modules/finance/`;确认与明细入口 `views/FinanceBillsView.vue`、`api/finance.ts`,入仓码通过 `src/modules/first-leg/public.ts` 复用 `InboundCodeAssociationDialog` | Backend `finance/`;账单行只显示服务端 `inboundCode` 并按登录客户查询业务关系,缺失显示“—”,不得回退内部 ID。收款方式由服务端枚举映射中文。出账或已出账调整后为 `PENDING_CONFIRMATION`,客户确认提交登录成员、`expectedVersion` 和幂等键 | -| 客户资金账户与充值申请 | `src/modules/finance/views/FinanceAccountView.vue`、`api/account.ts` | 显示当前企业 CNY 实际、冻结、可用余额和不可变流水;充值只展示服务端启用的微信/支付宝静态收款码,客户上传 JPG/PNG 付款截图后进入待审核。客户范围只取登录态,无收款码时禁止提交,不模拟支付成功 | +| 客户资金账户、授信与充值申请 | `src/modules/finance/views/FinanceAccountView.vue`、`api/account.ts` | 显示当前企业 CNY 实际余额、冻结金额、授信额度、已用授信、冻结授信、总可用余额和不可变流水;数据加载前显示“—”,不得先伪装为零。充值只展示服务端启用的微信/支付宝静态收款码,客户上传 JPG/PNG 付款截图后进入待审核。客户范围只取登录态,无收款码时禁止提交,不模拟支付成功 | | 海外采购、美国本土卡与美本土散板 | `src/modules/cross-border/routes.ts`、`components/BusinessOrdersView.vue`、`api/businessOrders.ts` | 三个路由均为 `active`,客户范围只取 Token;Backend `lastleg-service/` 的 `procurement/`、`trucking/`、`looseboard/` 客户接口 | | 海外仓入库订单 | `src/modules/order-management/routes.ts`、`views/InboundOrdersView.vue`、`api/inboundPlans.ts` | `orders/inbound` 为只读列表/详情,权限 `merchant:warehouse:inbound:list`;不展示或提交内部 ID | | 海外仓贴标、仓储转运、货物废弃、退货换标 | `src/modules/overseas-warehouse/routes.ts`、`views/WarehouseServiceView.vue`、`api/warehouseServices.ts` | 四个路由均为 `active`;Backend `lastleg-service/` 的 `relabel/`、`transfer/`、`discard/`,请求统一经 `src/api/overseasHttp.ts` 与 `gateway-overseas/` | diff --git a/src/api/overseasHttp.ts b/src/api/overseasHttp.ts index cfcaadc..72e4a1c 100644 --- a/src/api/overseasHttp.ts +++ b/src/api/overseasHttp.ts @@ -39,14 +39,23 @@ export async function overseasApiRequest(config: AxiosRequestConfig) { try { const response = await transport.request>(config) if (!isApiEnvelope(response.data) || response.data.code !== '200') { - throw new ApiError(isApiEnvelope(response.data) ? response.data.message : '海外接口响应格式错误', isApiEnvelope(response.data) ? response.data.code : 'INVALID_RESPONSE') + throw new ApiError( + isApiEnvelope(response.data) ? response.data.message : '海外接口响应格式错误', + isApiEnvelope(response.data) ? response.data.code : 'INVALID_RESPONSE', + undefined, + undefined, + isApiEnvelope(response.data) ? response.data.traceId : null, + isApiEnvelope(response.data) ? response.data.requestId : null, + ) } return response.data.data } catch (error) { if (error instanceof ApiError) throw error if (axios.isAxiosError(error)) { const body = error.response?.data - if (isApiEnvelope(body)) throw new ApiError(body.message, body.code, error.response?.status) + if (isApiEnvelope(body)) { + throw new ApiError(body.message, body.code, error.response?.status, undefined, body.traceId, body.requestId) + } throw new ApiError(error.code === 'ERR_NETWORK' ? '无法连接海外服务' : error.message, error.code || 'OVERSEAS_HTTP_ERROR', error.response?.status) } throw new ApiError(error instanceof Error ? error.message : '海外接口请求失败') diff --git a/src/modules/order-management/api/inboundPlans.ts b/src/modules/order-management/api/inboundPlans.ts index d62dfae..d49d921 100644 --- a/src/modules/order-management/api/inboundPlans.ts +++ b/src/modules/order-management/api/inboundPlans.ts @@ -17,8 +17,8 @@ export interface InboundPlanSummary { planNo: string inboundCode: string orderType: string | null - warehouseCode: string | null - warehouseName: string | null + overseasWarehouseCode: string | null + overseasWarehouseName: string | null fbaWarehouseCode: string | null packageCount: number pickedPackageCount: number @@ -52,6 +52,8 @@ export interface InboundPlanPallet { export interface InboundPlanDetail { planNo: string containerNo: string | null + overseasWarehouseCode: string | null + overseasWarehouseName: string | null status: string lineCount: number palletCount: number diff --git a/src/modules/order-management/inboundOrders.test.mjs b/src/modules/order-management/inboundOrders.test.mjs index a9036aa..45d5392 100644 --- a/src/modules/order-management/inboundOrders.test.mjs +++ b/src/modules/order-management/inboundOrders.test.mjs @@ -5,6 +5,7 @@ import test from 'node:test' const api = readFileSync(new URL('./api/inboundPlans.ts', import.meta.url), 'utf8') const routes = readFileSync(new URL('./routes.ts', import.meta.url), 'utf8') const view = readFileSync(new URL('./views/InboundOrdersView.vue', import.meta.url), 'utf8') +const overseasHttp = readFileSync(new URL('../../api/overseasHttp.ts', import.meta.url), 'utf8') test('activates the customer-scoped overseas inbound list and detail', () => { assert.match(routes, /path: 'orders\/inbound'[\s\S]*?release: 'active'[\s\S]*?permission: 'merchant:warehouse:inbound:list'/) @@ -23,3 +24,21 @@ test('keeps actions first and exposes filters loading errors empty state paginat assert.match(view, /detail\.planNo/) assert.doesNotMatch(view, /planId|orderId|warehouseId|customerId/) }) + +test('shows only the server-provided overseas receiving warehouse code and name', () => { + assert.match(api, /overseasWarehouseCode: string \| null/) + assert.match(api, /overseasWarehouseName: string \| null/) + assert.doesNotMatch(api, /overseasWarehouseId/) + assert.match(view, /label="海外接柜仓"/) + assert.match(view, /overseasWarehouseLabel\(scope\.row\.overseasWarehouseCode, scope\.row\.overseasWarehouseName\)/) + assert.match(view, /overseasWarehouseLabel\(detail\.overseasWarehouseCode, detail\.overseasWarehouseName\)/) + assert.match(view, /if \(typeof code !== 'string' \|\| !code\.trim\(\) \|\| typeof name !== 'string' \|\| !name\.trim\(\)\) return '—'/) + assert.doesNotMatch(view, /warehouseName \|\| warehouseCode|destinationWarehouse(?:Id|Code|Name).*overseasWarehouse/i) +}) + +test('preserves overseas API diagnostic metadata without rendering it as a business identifier', () => { + assert.match(overseasHttp, /response\.data\.traceId/) + assert.match(overseasHttp, /response\.data\.requestId/) + assert.match(overseasHttp, /body\.traceId, body\.requestId/) + assert.doesNotMatch(view, /requestId|traceId/) +}) diff --git a/src/modules/order-management/views/InboundOrdersView.vue b/src/modules/order-management/views/InboundOrdersView.vue index cb0d25d..35627ec 100644 --- a/src/modules/order-management/views/InboundOrdersView.vue +++ b/src/modules/order-management/views/InboundOrdersView.vue @@ -14,6 +14,10 @@ const query = reactive({ }) function text(value: unknown) { return value === null || value === undefined || value === '' ? '—' : String(value) } +function overseasWarehouseLabel(code: unknown, name: unknown) { + if (typeof code !== 'string' || !code.trim() || typeof name !== 'string' || !name.trim()) return '—' + return `${code.trim()} · ${name.trim()}` +} function dateTime(value: unknown) { return value ? new Date(String(value)).toLocaleString('zh-CN', { hour12: false }) : '—' } function statusLabel(value: unknown) { return ({ PENDING: '待处理', PROCESSING: '处理中', COMPLETED: '已完成', CANCELLED: '已取消', UNAPPOINTED: '未预约', SUCCESS: '预约成功', FAILED: '预约失败', SCHEDULED: '已排车', UNSCHEDULED: '未排车' } as Record)[String(value)] || text(value) @@ -82,7 +86,7 @@ onMounted(() => void load()) - + @@ -100,6 +104,7 @@ onMounted(() => void load())
计划号
{{ detail.planNo }}
集装箱号
{{ text(detail.containerNo) }}
+
海外接柜仓
{{ overseasWarehouseLabel(detail.overseasWarehouseCode, detail.overseasWarehouseName) }}
状态
{{ statusLabel(detail.status) }}
入仓码数
{{ detail.lineCount }}
托盘数
{{ detail.palletCount }}
总体积(m³)
{{ detail.totalVolumeCbm }}
计划箱数
{{ detail.expectedPackageCount }}
已拣箱数
{{ detail.pickedPackageCount }}
From cf79b8cd08ed198b0513b076854a22e643d9a319 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 7 Sep 2026 19:04:00 +0800 Subject: [PATCH 2/5] fix: normalize client table presentation --- src/components/common/DataTable.vue | 34 +++++++++++++- .../views/WarehouseServiceView.vue | 45 ++++++++++++------- 2 files changed, 63 insertions(+), 16 deletions(-) diff --git a/src/components/common/DataTable.vue b/src/components/common/DataTable.vue index 6965929..6a6016f 100644 --- a/src/components/common/DataTable.vue +++ b/src/components/common/DataTable.vue @@ -7,6 +7,9 @@ export interface DataTableColumn { columnKey?: string slotName?: string headerSlotName?: string + singleLine?: boolean + showOverflowTooltip?: boolean + className?: string [attribute: string]: unknown } @@ -88,10 +91,34 @@ watch([page, pageCount], ([value, count]) => { if (value !== nextValue) page.value = nextValue }, { immediate: true }) -function columnAttrs({ key, slotName, headerSlotName, ...attrs }: DataTableColumn) { +function columnAttrs({ key, slotName, headerSlotName, singleLine, ...attrs }: DataTableColumn) { return attrs } +function systemColumn(column: DataTableColumn) { + return column.type === 'selection' + || column.type === 'index' + || column.type === 'expand' + || column.slotName === 'actions' + || column.label === '操作' +} + +function singleLineColumn(column: DataTableColumn) { + return column.singleLine !== false && !systemColumn(column) +} + +function columnOverflowTooltip(column: DataTableColumn) { + return singleLineColumn(column) ? column.showOverflowTooltip ?? true : column.showOverflowTooltip +} + +function columnClassName(column: DataTableColumn) { + return [ + column.className, + systemColumn(column) ? 'data-table__system-cell' : '', + singleLineColumn(column) ? 'data-table__single-line-cell' : '', + ].filter(Boolean).join(' ') +} + function columnRenderKey(column: DataTableColumn, index: number) { return String(column.key ?? column.columnKey ?? column.prop ?? column.property ?? index) } @@ -119,6 +146,8 @@ function setPage(value: number) { v-for="(column, index) in columns" :key="columnRenderKey(column, index)" v-bind="columnAttrs(column)" + :class-name="columnClassName(column)" + :show-overflow-tooltip="columnOverflowTooltip(column)" > From 1fc730d845b9f3a3014883090821bb53e7b234ff Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 7 Sep 2026 20:27:35 +0800 Subject: [PATCH 4/5] fix: polish client inbound plan display --- .../order-management/inboundOrders.test.mjs | 20 +++++++++ .../views/InboundOrdersView.vue | 42 ++++++++++++++----- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/modules/order-management/inboundOrders.test.mjs b/src/modules/order-management/inboundOrders.test.mjs index 45d5392..d4ed904 100644 --- a/src/modules/order-management/inboundOrders.test.mjs +++ b/src/modules/order-management/inboundOrders.test.mjs @@ -42,3 +42,23 @@ test('preserves overseas API diagnostic metadata without rendering it as a busin assert.match(overseasHttp, /body\.traceId, body\.requestId/) assert.doesNotMatch(view, /requestId|traceId/) }) + +test('renders inbound workflow statuses in Chinese and keeps desktop values on one line', () => { + for (const [status, label] of [ + ['PENDING_ASSIGNMENT', '待分配'], ['PDA_PENDING', '待收货'], ['ASSIGNED', '已分配'], + ['SIGNED', '已签收'], ['PICKING', '拣货中'], ['PARTIAL_INBOUND', '部分入库'], + ]) assert.match(view, new RegExp(`${status}: '${label}'`)) + assert.match(view, /label="状态" width="120" show-overflow-tooltip/) + assert.match(view, /\.detail-grid div \{ min-width: 0;/) + assert.match(view, /\.detail-grid dd \{[^}]*overflow: hidden;[^}]*text-overflow: ellipsis;[^}]*white-space: nowrap;/) +}) + +test('uses the formal inbound status selector and presents destination fields without mixing receiving warehouses', () => { + assert.match(view, /const inboundStatusOptions = \[/) + assert.match(view, /label="作业状态">([]) const detail = ref(null) const total = ref(0) +const inboundStatusOptions = [ + { label: '待分配', value: 'PENDING_ASSIGNMENT' }, + { label: '已分配', value: 'ASSIGNED' }, + { label: '待收货', value: 'PDA_PENDING' }, + { label: '已签收', value: 'SIGNED' }, + { label: '拣货中', value: 'PICKING' }, + { label: '部分入库', value: 'PARTIAL_INBOUND' }, + { label: '已完成', value: 'COMPLETED' }, +] const query = reactive({ page: 1, size: 20, planNo: '', inboundCode: '', destinationWarehouseCode: '', status: '', appointmentStatus: '', dispatchStatus: '', createdDates: [] as string[], @@ -18,9 +27,20 @@ function overseasWarehouseLabel(code: unknown, name: unknown) { if (typeof code !== 'string' || !code.trim() || typeof name !== 'string' || !name.trim()) return '—' return `${code.trim()} · ${name.trim()}` } +function destinationWarehouseLabel(...values: unknown[]) { + const parts = values + .filter((value): value is string => typeof value === 'string' && Boolean(value.trim())) + .map((value) => value.trim()) + return [...new Set(parts)].join(' · ') || '—' +} function dateTime(value: unknown) { return value ? new Date(String(value)).toLocaleString('zh-CN', { hour12: false }) : '—' } function statusLabel(value: unknown) { - return ({ PENDING: '待处理', PROCESSING: '处理中', COMPLETED: '已完成', CANCELLED: '已取消', UNAPPOINTED: '未预约', SUCCESS: '预约成功', FAILED: '预约失败', SCHEDULED: '已排车', UNSCHEDULED: '未排车' } as Record)[String(value)] || text(value) + return ({ + PENDING: '待处理', PROCESSING: '处理中', PENDING_ASSIGNMENT: '待分配', PDA_PENDING: '待收货', + ASSIGNED: '已分配', SIGNED: '已签收', PICKING: '拣货中', PARTIAL_INBOUND: '部分入库', + COMPLETED: '已完成', CANCELLED: '已取消', UNAPPOINTED: '未预约', SUCCESS: '预约成功', + FAILED: '预约失败', SCHEDULED: '已排车', UNSCHEDULED: '未排车', + } as Record)[String(value)] || text(value) } function errorMessage(cause: unknown) { return cause instanceof Error ? cause.message : '请求失败,请稍后重试' } @@ -74,7 +94,7 @@ onMounted(() => void load()) - + @@ -91,10 +111,10 @@ onMounted(() => void load()) - - - - + + + + @@ -112,14 +132,14 @@ onMounted(() => void load()) - - + + - + @@ -136,7 +156,7 @@ onMounted(() => void load()) .filter-card :deep(.el-input), .filter-card :deep(.el-select) { width: 190px; } .pagination { justify-content: flex-end; margin-top: 18px; } .detail-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 0; } -.detail-grid div { padding: 18px 20px; border-right: 1px solid #ebeef5; border-bottom: 1px solid #ebeef5; } +.detail-grid div { min-width: 0; padding: 18px 20px; border-right: 1px solid #ebeef5; border-bottom: 1px solid #ebeef5; } .detail-grid dt { margin-bottom: 7px; color: #8492a6; font-size: 13px; } -.detail-grid dd { margin: 0; overflow-wrap: anywhere; color: #1f2937; } +.detail-grid dd { margin: 0; overflow: hidden; color: #1f2937; text-overflow: ellipsis; white-space: nowrap; } From 9b9a2b103cf6ec5fb22aedf24a041cbb997512c8 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 8 Sep 2026 16:11:29 +0800 Subject: [PATCH 5/5] test: freeze client receiving warehouse contract --- src/modules/order-management/api/inboundPlans.ts | 8 ++++---- .../order-management/inboundOrders.test.mjs | 16 +++++++++++++--- .../order-management/inboundPlanDisplay.ts | 4 ++++ .../order-management/views/InboundOrdersView.vue | 5 +---- 4 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 src/modules/order-management/inboundPlanDisplay.ts diff --git a/src/modules/order-management/api/inboundPlans.ts b/src/modules/order-management/api/inboundPlans.ts index d49d921..8512ef2 100644 --- a/src/modules/order-management/api/inboundPlans.ts +++ b/src/modules/order-management/api/inboundPlans.ts @@ -17,8 +17,8 @@ export interface InboundPlanSummary { planNo: string inboundCode: string orderType: string | null - overseasWarehouseCode: string | null - overseasWarehouseName: string | null + overseasWarehouseCode?: string | null + overseasWarehouseName?: string | null fbaWarehouseCode: string | null packageCount: number pickedPackageCount: number @@ -52,8 +52,8 @@ export interface InboundPlanPallet { export interface InboundPlanDetail { planNo: string containerNo: string | null - overseasWarehouseCode: string | null - overseasWarehouseName: string | null + overseasWarehouseCode?: string | null + overseasWarehouseName?: string | null status: string lineCount: number palletCount: number diff --git a/src/modules/order-management/inboundOrders.test.mjs b/src/modules/order-management/inboundOrders.test.mjs index d4ed904..9bba715 100644 --- a/src/modules/order-management/inboundOrders.test.mjs +++ b/src/modules/order-management/inboundOrders.test.mjs @@ -1,6 +1,7 @@ import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' +import { overseasWarehouseLabel } from './inboundPlanDisplay.ts' const api = readFileSync(new URL('./api/inboundPlans.ts', import.meta.url), 'utf8') const routes = readFileSync(new URL('./routes.ts', import.meta.url), 'utf8') @@ -12,6 +13,7 @@ test('activates the customer-scoped overseas inbound list and detail', () => { assert.match(api, /\/merchant\/v1\/last-leg\/inbound-plans/) assert.match(api, /encodeURIComponent\(planNo\)/) assert.doesNotMatch(api, /\/platform\/|\/internal\/|customerId|tenantId/) + assert.doesNotMatch(view, /customerId|tenantId/) }) test('keeps actions first and exposes filters loading errors empty state pagination and business identifiers', () => { @@ -26,16 +28,24 @@ test('keeps actions first and exposes filters loading errors empty state paginat }) test('shows only the server-provided overseas receiving warehouse code and name', () => { - assert.match(api, /overseasWarehouseCode: string \| null/) - assert.match(api, /overseasWarehouseName: string \| null/) + assert.equal((api.match(/overseasWarehouseCode\?: string \| null/g) || []).length, 2) + assert.equal((api.match(/overseasWarehouseName\?: string \| null/g) || []).length, 2) assert.doesNotMatch(api, /overseasWarehouseId/) assert.match(view, /label="海外接柜仓"/) assert.match(view, /overseasWarehouseLabel\(scope\.row\.overseasWarehouseCode, scope\.row\.overseasWarehouseName\)/) assert.match(view, /overseasWarehouseLabel\(detail\.overseasWarehouseCode, detail\.overseasWarehouseName\)/) - assert.match(view, /if \(typeof code !== 'string' \|\| !code\.trim\(\) \|\| typeof name !== 'string' \|\| !name\.trim\(\)\) return '—'/) assert.doesNotMatch(view, /warehouseName \|\| warehouseCode|destinationWarehouse(?:Id|Code|Name).*overseasWarehouse/i) }) +test('renders a dash when either formal receiving warehouse field is absent', () => { + assert.equal(overseasWarehouseLabel('US-LAX-01', '洛杉矶仓'), 'US-LAX-01 · 洛杉矶仓') + assert.equal(overseasWarehouseLabel(' US-LAX-01 ', ' 洛杉矶仓 '), 'US-LAX-01 · 洛杉矶仓') + for (const pair of [ + [undefined, undefined], [null, null], ['', '洛杉矶仓'], ['US-LAX-01', ''], + [undefined, '洛杉矶仓'], ['US-LAX-01', undefined], [' ', '洛杉矶仓'], ['US-LAX-01', ' '], + ]) assert.equal(overseasWarehouseLabel(pair[0], pair[1]), '—') +}) + test('preserves overseas API diagnostic metadata without rendering it as a business identifier', () => { assert.match(overseasHttp, /response\.data\.traceId/) assert.match(overseasHttp, /response\.data\.requestId/) diff --git a/src/modules/order-management/inboundPlanDisplay.ts b/src/modules/order-management/inboundPlanDisplay.ts new file mode 100644 index 0000000..30e94ca --- /dev/null +++ b/src/modules/order-management/inboundPlanDisplay.ts @@ -0,0 +1,4 @@ +export function overseasWarehouseLabel(code: unknown, name: unknown) { + if (typeof code !== 'string' || !code.trim() || typeof name !== 'string' || !name.trim()) return '—' + return `${code.trim()} · ${name.trim()}` +} diff --git a/src/modules/order-management/views/InboundOrdersView.vue b/src/modules/order-management/views/InboundOrdersView.vue index 2bfbed7..7627e05 100644 --- a/src/modules/order-management/views/InboundOrdersView.vue +++ b/src/modules/order-management/views/InboundOrdersView.vue @@ -2,6 +2,7 @@ import { onMounted, reactive, ref } from 'vue' import { ElMessage } from 'element-plus' import { getInboundPlan, listInboundPlans, type InboundPlanDetail, type InboundPlanSummary } from '../api/inboundPlans' +import { overseasWarehouseLabel } from '../inboundPlanDisplay' const loading = ref(false) const error = ref('') @@ -23,10 +24,6 @@ const query = reactive({ }) function text(value: unknown) { return value === null || value === undefined || value === '' ? '—' : String(value) } -function overseasWarehouseLabel(code: unknown, name: unknown) { - if (typeof code !== 'string' || !code.trim() || typeof name !== 'string' || !name.trim()) return '—' - return `${code.trim()} · ${name.trim()}` -} function destinationWarehouseLabel(...values: unknown[]) { const parts = values .filter((value): value is string => typeof value === 'string' && Boolean(value.trim()))