fix(admin): align test contracts and sailing integrations
This commit is contained in:
@@ -43,6 +43,7 @@
|
||||
| 登录、会话与双区域 Token | `modules/auth/`、`core/auth/` | 国内 `auth/`、`iam/`;海外请求客户端再读 `shared/api/AGENTS.md` |
|
||||
| 启动与懒加载资源恢复 | `main.ts`、`core/routing/navigationRecovery.ts` | 仅识别动态模块/Chunk 资源过期;30 秒内同目标最多自动恢复一次,存在未保存编辑时必须先确认 |
|
||||
| 工作台与指标 | `modules/dashboard/` | 指标所属模块与服务端,不复制领域契约 |
|
||||
| 船期日历 | `modules/sailing-calendar/` | 当前日历入口覆盖渠道、周模板、单次船期、收货截止时间和按渠道货量统计;后端按 `firstleg/` 船期接口定位,不读取已归档的货柜开发规划 |
|
||||
| FBA 无匹配价格、人工定价 | `modules/inbounds/fee/api.ts` 的 `createManualInboundQuote`、`types.ts`;页面消费沿 `InboundFeePanel.vue` 核对 | `POST /manual-quote` 提交计费重量、体积、单价、原因和 `Idempotency-Key`;Backend `PlatformFbaPricingController.manualQuote` → `FbaPricingService.manualQuote`,权限 `firstleg:inbound:confirm-send-fee`。仅 FBA、已有实际测量且当前价格未命中时允许,客户已确认费用禁止改价;`MANUAL_QUOTE` 快照允许价格记录与版本为空,不伪造价格簿关联。API 回归 `modules/inbounds/fee/api.test.ts`,不能以 API 存在代替页面接入完成 |
|
||||
| PDA版本、APK下载、WGT热更新发布与停发 | `modules/pda-releases/pages/PdaReleasesView.vue`、`modules/pda-releases/api/pdaReleases.ts`、`modules/pda-releases/routes.ts` | 系统菜单 `/system/pda-releases`;列表权限 `platform:pda-release:list`,上传/发布/停发权限 `platform:pda-release:manage`。国内/海外按 `domestic/overseas` 分开查询,上传只创建草稿,发布与停发需确认;管理请求使用国内 HTTP 的 `/platform/v1/files/pda-releases`,后端见 `file/AGENTS.md`,设备安装与重启查对应 PDA 的更新工具 |
|
||||
| 客户、企业审核、客户统计 | `modules/customers/` | `iam/`、`auth/`、`file/`、`firstleg/customerstatistics/`;公司联系电话只作企业联系信息,不修改管理员/成员登录手机号且不要求跨企业唯一。审核必填资质仅营业执照与统一社会信用代码;法人身份证选填,可随资质包提交,也可在已有营业执照后单独补传或替换 |
|
||||
|
||||
@@ -31,7 +31,7 @@ describe('domestic outbound list responsive contracts', () => {
|
||||
const content = source(path)
|
||||
const columns = [...content.matchAll(/\{[^{}]*(?:key|prop): '[^']+'[^{}]*label: '[^']+'[^{}]*\}/g)]
|
||||
.map((match) => match[0])
|
||||
.filter((column) => !column.includes("type: 'input'") && !column.includes("type: 'select'") && !column.includes("type: 'daterange'"))
|
||||
.filter((column) => !column.includes("type: 'input'") && !column.includes("type: 'select'") && !column.includes("type: 'daterange'") && !column.includes('priority:') && !column.includes("key: 'view'"))
|
||||
expect(columns.length).toBeGreaterThan(1)
|
||||
for (const column of columns) expect(column).toMatch(/\b(?:width|minWidth):\s*\d+/)
|
||||
})
|
||||
|
||||
@@ -112,7 +112,7 @@ async function createFormalDeclaration() {
|
||||
|
||||
async function submitFormalDeclaration(batch: DeclarationBatch) {
|
||||
try {
|
||||
const answer = await ElMessageBox.prompt('请输入报关单号', '提交正式申报', { inputValidator: (value) => value.trim() ? true : '请输入报关单号', confirmButtonText: '确认提交' })
|
||||
const answer = await ElMessageBox.prompt('请输入报关单号', '提交正式申报', { customClass: 'admin-density-confirm', inputValidator: (value) => value.trim() ? true : '请输入报关单号', confirmButtonText: '确认提交' })
|
||||
actionLoading.value = true; actionError.value = null
|
||||
await submitDeclarationBatch(batch, answer.value)
|
||||
await load(); ElMessage.success('正式申报已提交')
|
||||
@@ -124,7 +124,7 @@ async function submitFormalDeclaration(batch: DeclarationBatch) {
|
||||
|
||||
async function recordFormalResult(batch: DeclarationBatch) {
|
||||
try {
|
||||
const answer = await ElMessageBox.prompt('可填写查验处理说明,没有说明可直接确认', '录入海关查验结果', { inputPlaceholder: '处理说明(可选)', confirmButtonText: '确认录入' })
|
||||
const answer = await ElMessageBox.prompt('可填写查验处理说明,没有说明可直接确认', '录入海关查验结果', { customClass: 'admin-density-confirm', inputPlaceholder: '处理说明(可选)', confirmButtonText: '确认录入' })
|
||||
actionLoading.value = true; actionError.value = null
|
||||
await recordClearanceResult(batch, 'INSPECTION', answer.value)
|
||||
await load(); ElMessage.success('海关结果已录入')
|
||||
@@ -136,7 +136,7 @@ async function recordFormalResult(batch: DeclarationBatch) {
|
||||
|
||||
async function resolveFormalResult(batch: DeclarationBatch, result: DeclarationBatch['results'][number]) {
|
||||
try {
|
||||
const answer = await ElMessageBox.prompt('请输入处理说明', '处理海关结果', { inputValue: result.resolution || '', inputValidator: (value) => value.trim() ? true : '请输入处理说明', confirmButtonText: '确认处理' })
|
||||
const answer = await ElMessageBox.prompt('请输入处理说明', '处理海关结果', { customClass: 'admin-density-confirm', inputValue: result.resolution || '', inputValidator: (value) => value.trim() ? true : '请输入处理说明', confirmButtonText: '确认处理' })
|
||||
actionLoading.value = true; actionError.value = null
|
||||
await resolveClearanceResult(result, batch.version, answer.value)
|
||||
await load(); ElMessage.success('海关结果已处理')
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './api/platformContainerLoading'
|
||||
export * from './types'
|
||||
export { getSmartLoadingChannelGroup, isSeaRailChannel, SMART_LOADING_CHANNEL_GROUPS } from './smartLoadingChannelGroups'
|
||||
export { default as SmartLoadingDialog } from './components/SmartLoadingDialog.vue'
|
||||
export { default as ContainerSimulationCreate } from './components/ContainerSimulationCreate.vue'
|
||||
|
||||
@@ -47,7 +47,8 @@ describe('ContainerDetailView customs integration', () => {
|
||||
expect(source).toContain('<el-tabs v-model="activeTab"')
|
||||
expect(source).toContain("v-if=\"isDomestic && activeTab === 'loading-list'\"")
|
||||
expect(source).toContain("loadingDetail.value = await getConfirmedLoadingList(aggregate.loadingPlanId, resource.warehouseId)")
|
||||
expect(source).toContain("getContainer(containerId.value, props.workbench), getContainerOverview(containerId.value)")
|
||||
expect(source).toContain('getContainer(containerId.value, props.workbench)')
|
||||
expect(source).toContain('getContainerOverview(containerId.value)')
|
||||
expect(source).toContain(':can-edit="false"')
|
||||
})
|
||||
|
||||
|
||||
@@ -1070,7 +1070,7 @@ onMounted(() => { void loadCustomers() })
|
||||
<div v-if="reviewLoadError" class="review-sync-error" role="alert"><div><ShieldX :size="17" /><span>{{ reviewLoadError.message }}</span></div><el-button link type="primary" @click="reloadPendingReviews">重新加载待审核客户</el-button></div>
|
||||
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="result.records" :columns="customerColumns" preference-key="customers-table" :loading="loading" :total="result.total" aria-label="客户列表" row-key="id" empty-text="暂无符合筛选条件的客户" class="customer-list-table" :stripe="true" border>
|
||||
<template #toolbar-actions><div class="customers-table-actions"><el-button v-if="access.can('platform:customer:export') && access.can('platform:download:list')" :loading="exportSubmitting" :disabled="loading || exportSubmitting" @click="exportCustomers">导出Excel</el-button><el-button v-if="access.can('platform:customer:create')" type="primary" :disabled="loading" @click="openCreate">新增客户</el-button></div></template>
|
||||
<template #actions="{ row }"><RowActions :actions="customerRowActions(row as PlatformTenantResource)" :more-label="customerMoreActionsLabel(row as PlatformTenantResource)" @action="handleCustomerRowAction(row as PlatformTenantResource, $event)" /></template>
|
||||
<template #actions="{ row }"><RowActions class="density-action" :actions="customerRowActions(row as PlatformTenantResource)" :more-label="customerMoreActionsLabel(row as PlatformTenantResource)" @action="handleCustomerRowAction(row as PlatformTenantResource, $event)" /></template>
|
||||
<template #company="{ row }"><strong class="customer-company-name">{{ (row as PlatformTenantResource).tenantName }}</strong></template>
|
||||
<template #status="{ row }"><el-tag :type="statusMeta((row as PlatformTenantResource).status).type" effect="light">{{ statusMeta((row as PlatformTenantResource).status).label }}</el-tag></template>
|
||||
<template #orderCount="{ row }">{{ access.can('platform:order:list') ? (orderCounts[String((row as PlatformTenantResource).id)] ?? '—') : '—' }}</template>
|
||||
|
||||
@@ -5,3 +5,5 @@ export {
|
||||
setCustomerChargeRuleDefault,
|
||||
} from './customer-rules/api'
|
||||
export type { CustomerChargeRule, CreateCustomerChargeRuleRequest } from './customer-rules/api'
|
||||
export { getPlatformPriceBook, listPlatformPriceBooks } from './carrier-rates/api/platformPriceBooks'
|
||||
export type { PlatformPriceBookParsedChannel } from './carrier-rates/api/platformPriceBooks'
|
||||
|
||||
@@ -123,7 +123,7 @@ describe('finance presentation governance', () => {
|
||||
expect(statements).toContain('createReconciliationStatement')
|
||||
expect(statements).toContain('actionReconciliationStatement')
|
||||
expect(statements).toContain('<template #toolbar-actions>')
|
||||
expect(statements).toContain('aria-label="更多对账单操作"')
|
||||
expect(statements).toContain(':more-label="`更多对账单操作 · ${(row as FinanceStatementReadModel).billNo}`"')
|
||||
expect(statements).toContain('adjustmentLines: selected.map')
|
||||
expect(statements).not.toContain('disabled title="接口尚未提供')
|
||||
})
|
||||
|
||||
@@ -148,7 +148,7 @@ function handleSubmitted() {
|
||||
.inbound-measurement-workspace-dialog .measurement-panel__heading .measurement-panel__actions,
|
||||
.inbound-measurement-workspace-dialog .measurement-panel__footer { display: none; }
|
||||
.inbound-measurement-workspace-dialog .measurement-summary-section { margin: 0 -12px; padding: 10px 12px 13px; border: 1px solid #cce8df; border-left: 4px solid #20a477; border-radius: 8px; background: #f4fbf8; }
|
||||
.inbound-measurement-workspace-dialog .measurement-summary-context { min-height: 38px; }
|
||||
.inbound-measurement-workspace-dialog .measurement-summary-context { min-height: auto; }
|
||||
.inbound-measurement-workspace-dialog .measurement-subheading { color: #18795d; font-size: 15px; }
|
||||
.inbound-measurement-workspace-dialog .measurement-metrics { padding: 0; border: 1px solid #cce8df; border-radius: 8px; background: #fff; overflow: hidden; }
|
||||
.inbound-measurement-workspace-dialog .measurement-metrics > div { min-height: 58px; padding: 9px 14px; border-left: 1px solid #e8ecf2; }
|
||||
|
||||
@@ -352,13 +352,13 @@ defineExpose({ confirmApprove, isPending, canReview, actionLoading })
|
||||
.review-comparison__row--declared { background: linear-gradient(90deg, #ddf5e9, #eaf9f1); box-shadow: inset 4px 0 #25a879; }
|
||||
.review-comparison__row--declared > strong { color: #16845d; }
|
||||
.review-comparison__row + .review-comparison__row { border-top: 1px dashed #dfe4ed; }
|
||||
.review-comparison__row > strong { font-size: 14px; }
|
||||
.review-declaration dd, .review-metrics dd { font-size: 16px; }
|
||||
.review-comparison__row > strong { font-size: var(--admin-font-body, 13px); }
|
||||
.review-declaration dd, .review-metrics dd { font-size: var(--admin-font-body, 13px); }
|
||||
.review-metric--primary dd, .review-weight-value, .review-weight-totals strong { color: #138154 !important; font-weight: 750; }
|
||||
.review-weight-items { padding-top: 0; }
|
||||
.review-weight-totals { display: flex; gap: 18px; color: var(--cc-muted); font-size: 12px; }
|
||||
.review-weight-totals { display: flex; gap: 18px; color: var(--cc-muted); font-size: var(--admin-font-caption, 12px); }
|
||||
.review-weight-totals span { padding: 4px 8px; border-radius: 7px; background: #f0faf5; }
|
||||
.review-weight-totals strong { margin-left: 6px; font-size: 16px; }
|
||||
.review-weight-totals strong { margin-left: 6px; font-size: var(--admin-font-body, 13px); }
|
||||
.review-weight-table :deep(.el-table__header th) { background: linear-gradient(180deg, #f1f4fc, #e9eef8); }
|
||||
.review-weight-table :deep(.el-table__cell) { padding: 9px 0; }
|
||||
.review-location { display: inline-block; padding: 3px 7px; border: 1px solid #e2e8f2; border-radius: 5px; background: #f3f6fb; color: #53627a; white-space: nowrap; }
|
||||
|
||||
@@ -20,7 +20,7 @@ import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
|
||||
import { listPlatformPartners, type PlatformPartnerResource } from '@/modules/master-data'
|
||||
import { listPlatformWarehouses, type PlatformWarehouseResource } from '@/modules/warehouses'
|
||||
import { CarrierScheduleQueryPanel } from '@/modules/port-schedules'
|
||||
import { SMART_LOADING_CHANNEL_GROUPS } from '@/modules/container-loading/smartLoadingChannelGroups'
|
||||
import { SMART_LOADING_CHANNEL_GROUPS } from '@/modules/container-loading'
|
||||
import {
|
||||
createOceanBooking,
|
||||
deleteOceanBookingDraft,
|
||||
|
||||
@@ -117,7 +117,7 @@ describe('last-mile order presentation adapter', () => {
|
||||
['PENDING_REVIEW', '待审核'],
|
||||
['WAIT_PDA_RECEIVING', '入库测量中'],
|
||||
['WAIT_CUSTOMER_CONFIRMATION', '待客户确认实测'],
|
||||
['INBOUND_COMPLETED', '入库完成'],
|
||||
['INBOUND_COMPLETED', '全部入库'],
|
||||
])('maps warehouse transfer status %s to %s', (status, expected) => {
|
||||
expect(lastMileStatusLabel(status)).toBe(expected)
|
||||
})
|
||||
|
||||
@@ -88,7 +88,7 @@ onMounted(load)
|
||||
<section class="detail-section pallet-section" aria-labelledby="inbound-pallet-heading"><div class="section-heading"><div><h2 id="inbound-pallet-heading">入库单托盘明细</h2><p class="pallet-note">PDA 合托后,参与合托的入库单会分别展示同一托盘及本单对应箱数;A0 为系统共享溢出位。</p></div><span class="section-count">{{ palletRows.length }} 条</span></div><div class="detail-table-wrap"><DataTable :data="palletRows" :columns="palletColumns" column-settings-mode="hide" row-key="id" empty-text="暂无托盘明细"><template #actions="{ row }"><el-button class="density-action" size="small" :disabled="!(row as typeof palletRows[number]).palletNo || !(row as typeof palletRows[number]).palletVersion" @click="openMove(row as typeof palletRows[number])">移库</el-button></template></DataTable></div></section>
|
||||
<section class="detail-section appointment-section" aria-labelledby="inbound-appointment-heading"><div class="compact-section-heading"><h2 id="inbound-appointment-heading">预约与排车</h2></div><dl class="detail-fields appointment-fields density-fields"><div v-for="label in appointmentFields" :key="label" :class="{ 'is-wide': label === '备注' }"><dt>{{ label }}</dt><dd>{{ fieldValues[label] }}</dd></div></dl></section>
|
||||
<section class="detail-section fee-section" aria-labelledby="inbound-fees-heading"><div class="compact-section-heading"><h2 id="inbound-fees-heading">应收费用明细</h2><span>费用结果以服务端为准</span></div><div class="detail-table-wrap"><DataTable :data="[]" :columns="feeColumns" column-settings-mode="hide" empty-text="当前入库计划详情接口未提供应收费用" /></div><footer class="fee-total"><span>应收费用合计(USD)</span><strong>—</strong></footer></section></template>
|
||||
</OverseasDetailShell><AppDialog v-model="moveOpen" class="admin-density-dialog" title="托盘移库" width="min(560px, calc(100vw - 28px))" confirm-text="提交移库" :confirm-loading="moveSaving" :confirm-disabled="!targetLocationCode" @confirm="submitMove"><el-form class="density-form" label-position="top"><el-form-item label="托盘号"><el-input :model-value="moveRow?.palletNo" readonly /></el-form-item><el-form-item label="目标库位" required><el-select v-model="targetLocationCode" filterable placeholder="请选择普通库位或 A0"><el-option v-for="item in availableLocations" :key="item.id" :value="item.locationCode" :label="`${item.locationCode}${item.sharedOverflow || item.locationType === 'SHARED_OVERFLOW' ? ' · A0共享溢出位' : ''}`" /></el-select></el-form-item><el-form-item label="操作原因" required><el-input v-model="moveReason" maxlength="200" /></el-form-item></el-form></AppDialog></template>
|
||||
</OverseasDetailShell><AppDialog v-model="moveOpen" class="admin-density-dialog" title="托盘移库" width="min(560px, calc(100vw - 28px))" confirm-text="提交移库" :confirm-loading="moveSaving" :confirm-disabled="!targetLocationCode" @confirm="submitMove"><el-form class="density-form" label-position="top"><el-form-item label="托盘号"><el-input :model-value="moveRow?.palletNo" readonly /></el-form-item><el-form-item label="目标库位" required><el-select v-model="targetLocationCode" filterable placeholder="请选择普通库位或 A0"><el-option v-for="item in availableLocations" :key="item.id" :value="item.locationCode" :label="`${item.locationCode}${item.sharedOverflow || item.locationType === 'SHARED_OVERFLOW' ? ' · A0共享溢出位' : ''}`" /></el-select></el-form-item><el-form-item label="操作原因" required><el-input v-model="moveReason" maxlength="200" placeholder="请输入移库原因" /></el-form-item></el-form></AppDialog></template>
|
||||
|
||||
<style scoped>
|
||||
.detail-section { min-width: 0; padding: 0; border: 0; background: #fff; }
|
||||
|
||||
@@ -791,14 +791,14 @@ async function reviewReturn(source: object) {
|
||||
const returnNo = row.sourceOrderNo
|
||||
if (!returnNo || !row.customerId) return
|
||||
try {
|
||||
await ElMessageBox.confirm('审核通过后,仓库才可以签收这张退货预报。', '退货预报审核', { confirmButtonText: '审核通过', cancelButtonText: '驳回', distinguishCancelAndClose: true })
|
||||
await ElMessageBox.confirm('审核通过后,仓库才可以签收这张退货预报。', '退货预报审核', { customClass: 'admin-density-confirm', confirmButtonText: '审核通过', cancelButtonText: '驳回', distinguishCancelAndClose: true })
|
||||
await reviewReturnOrder(returnNo, row.customerId, 'APPROVE')
|
||||
ElMessage.success('退货预报已审核通过')
|
||||
await load()
|
||||
} catch (error) {
|
||||
if (error === 'cancel') {
|
||||
try {
|
||||
const { value } = await ElMessageBox.prompt('请填写驳回原因', '驳回退货预报', { inputType: 'textarea', inputValidator: (input) => Boolean(input?.trim()) || '请填写驳回原因', confirmButtonText: '确认驳回', cancelButtonText: '取消' })
|
||||
const { value } = await ElMessageBox.prompt('请填写驳回原因', '驳回退货预报', { customClass: 'admin-density-confirm', inputType: 'textarea', inputValidator: (input) => Boolean(input?.trim()) || '请填写驳回原因', confirmButtonText: '确认驳回', cancelButtonText: '取消' })
|
||||
await reviewReturnOrder(returnNo, row.customerId, 'REJECT', value)
|
||||
ElMessage.success('退货预报已驳回')
|
||||
await load()
|
||||
@@ -810,14 +810,14 @@ async function reviewDirectRelabel(source: object) {
|
||||
const row = source as LastMileOrderListItem
|
||||
if (!row.id || !row.customerId) return
|
||||
try {
|
||||
await ElMessageBox.confirm('审核通过后,贴标单将进入海外仓换标作业。', '贴标单审核', { confirmButtonText: '审核通过', cancelButtonText: '驳回', distinguishCancelAndClose: true })
|
||||
await ElMessageBox.confirm('审核通过后,贴标单将进入海外仓换标作业。', '贴标单审核', { customClass: 'admin-density-confirm', confirmButtonText: '审核通过', cancelButtonText: '驳回', distinguishCancelAndClose: true })
|
||||
await reviewDirectRelabelOrder(row.id, row.customerId, 'APPROVE')
|
||||
ElMessage.success('贴标单已审核通过')
|
||||
await load()
|
||||
} catch (error) {
|
||||
if (error === 'cancel') {
|
||||
try {
|
||||
const { value } = await ElMessageBox.prompt('请填写驳回原因,客户可在贴标单详情查看并修改后重新提交。', '驳回贴标单', { inputType: 'textarea', inputValidator: (input) => Boolean(input?.trim()) || '请填写驳回原因', confirmButtonText: '确认驳回', cancelButtonText: '取消' })
|
||||
const { value } = await ElMessageBox.prompt('请填写驳回原因,客户可在贴标单详情查看并修改后重新提交。', '驳回贴标单', { customClass: 'admin-density-confirm', inputType: 'textarea', inputValidator: (input) => Boolean(input?.trim()) || '请填写驳回原因', confirmButtonText: '确认驳回', cancelButtonText: '取消' })
|
||||
await reviewDirectRelabelOrder(row.id, row.customerId, 'REJECT', value)
|
||||
ElMessage.success('贴标单已驳回')
|
||||
await load()
|
||||
@@ -874,7 +874,7 @@ async function confirmRelabel(row: { relabelOrderNo?: string }) {
|
||||
const relabelNo = row.relabelOrderNo
|
||||
if (!relabelNo) return
|
||||
try {
|
||||
await ElMessageBox.confirm('确认仓库上传的换标结果无误并继续后续流程吗?', '换标完成确认', { confirmButtonText: '确认并继续', cancelButtonText: '取消' })
|
||||
await ElMessageBox.confirm('确认仓库上传的换标结果无误并继续后续流程吗?', '换标完成确认', { customClass: 'admin-density-confirm', confirmButtonText: '确认并继续', cancelButtonText: '取消' })
|
||||
await confirmRelabelCompletion(relabelNo)
|
||||
ElMessage.success('已确认换标完成')
|
||||
await load()
|
||||
@@ -885,7 +885,7 @@ async function reworkRelabel(row: { relabelOrderNo?: string }) {
|
||||
if (!relabelNo) return
|
||||
try {
|
||||
const task = await getRelabelReviewTask(relabelNo)
|
||||
const { value } = await ElMessageBox.prompt('填写退回原因,仓库将重新执行换标并上传照片。', '重新换标', { inputType: 'textarea', inputValidator: (value) => Boolean(value?.trim()) && value.trim().length <= 1000 || '请填写 1 至 1000 字原因', confirmButtonText: '确认重新换标', cancelButtonText: '取消' })
|
||||
const { value } = await ElMessageBox.prompt('填写退回原因,仓库将重新执行换标并上传照片。', '重新换标', { customClass: 'admin-density-confirm', inputType: 'textarea', inputValidator: (value) => Boolean(value?.trim()) && value.trim().length <= 1000 || '请填写 1 至 1000 字原因', confirmButtonText: '确认重新换标', cancelButtonText: '取消' })
|
||||
await returnRelabelForRework(relabelNo, task.version, value.trim(), crypto.randomUUID())
|
||||
ElMessage.success('已退回重新换标')
|
||||
await load()
|
||||
|
||||
@@ -231,12 +231,12 @@ onMounted(() => { void Promise.all([loadConfig(), load()]) })
|
||||
</ListPageLayout>
|
||||
<AppDialog v-model="editOpen" class="admin-density-dialog" title="编辑 SKU 测量修正版" width="min(620px, calc(100vw - 28px))" confirm-text="保存并重新同步" :confirm-loading="Boolean(actionId)" @confirm="saveEdit">
|
||||
<el-form label-position="top" class="sync-edit-form">
|
||||
<el-form-item label="SKU"><el-input v-model.trim="editForm.skuCode" /></el-form-item>
|
||||
<el-form-item label="SKU"><el-input v-model.trim="editForm.skuCode" placeholder="请输入 SKU" /></el-form-item>
|
||||
<div class="sync-edit-grid">
|
||||
<el-form-item label="长(cm)"><el-input-number v-model="editForm.lengthCm" :min="0.001" :controls="false" /></el-form-item>
|
||||
<el-form-item label="宽(cm)"><el-input-number v-model="editForm.widthCm" :min="0.001" :controls="false" /></el-form-item>
|
||||
<el-form-item label="高(cm)"><el-input-number v-model="editForm.heightCm" :min="0.001" :controls="false" /></el-form-item>
|
||||
<el-form-item label="重量(kg)"><el-input-number v-model="editForm.weightKg" :min="0.001" :controls="false" /></el-form-item>
|
||||
<el-form-item label="长(cm)"><el-input-number v-model="editForm.lengthCm" :min="0.001" :controls="false" placeholder="请输入长度" /></el-form-item>
|
||||
<el-form-item label="宽(cm)"><el-input-number v-model="editForm.widthCm" :min="0.001" :controls="false" placeholder="请输入宽度" /></el-form-item>
|
||||
<el-form-item label="高(cm)"><el-input-number v-model="editForm.heightCm" :min="0.001" :controls="false" placeholder="请输入高度" /></el-form-item>
|
||||
<el-form-item label="重量(kg)"><el-input-number v-model="editForm.weightKg" :min="0.001" :controls="false" placeholder="请输入重量" /></el-form-item>
|
||||
</div>
|
||||
<el-alert type="info" :closable="false" title="原始墨家测量记录不可变,本次保存会生成新的人工修正版并进入同步队列。" />
|
||||
</el-form>
|
||||
@@ -254,7 +254,7 @@ onMounted(() => { void Promise.all([loadConfig(), load()]) })
|
||||
</AppDialog>
|
||||
<AppDialog v-model="verificationOpen" class="admin-density-dialog" title="领星二次验证" width="min(460px, calc(100vw - 28px))" confirm-text="提交验证码" :confirm-loading="submittingVerification" @confirm="submitVerification">
|
||||
<el-alert type="warning" :closable="false" title="领星要求二次验证,请在验证码发送到手机、邮箱或验证器后输入。" />
|
||||
<el-form label-position="top" class="verification-form"><el-form-item label="验证码"><el-input v-model.trim="verificationCode" maxlength="16" autocomplete="one-time-code" @keyup.enter="submitVerification" /></el-form-item></el-form>
|
||||
<el-form label-position="top" class="verification-form"><el-form-item label="验证码"><el-input v-model.trim="verificationCode" maxlength="16" autocomplete="one-time-code" placeholder="请输入验证码" @keyup.enter="submitVerification" /></el-form-item></el-form>
|
||||
</AppDialog>
|
||||
</AdminPage>
|
||||
</template>
|
||||
|
||||
@@ -104,7 +104,7 @@ describe('RelabelOrdersView', () => {
|
||||
expect(firstKey).toBeTruthy()
|
||||
expect(retryKey).toBe(firstKey)
|
||||
expect(createRelabelOrder).toHaveBeenLastCalledWith('10001', expect.objectContaining({
|
||||
customerOrderNo: 'CO-1', overseasWarehouseId: 'WH-1', idempotencyKey: firstKey,
|
||||
customerOrderNo: 'CO-1', operationWarehouseId: 'WH-1', chargeAmount: 20, contactName: '张三', contactPhone: '13800000000', labelDeadline: '2026-08-30T10:00:00', remark: undefined, idempotencyKey: firstKey,
|
||||
items: [expect.objectContaining({ skuCode: 'SKU-1', newSkuCode: 'SKU-2', labelRequirement: '换SKU标' })],
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -26,6 +26,8 @@ describe('overseas warehouse routes', () => {
|
||||
['overseas-warehouse/relabel-orders/:orderId', permission.relabelOrderList],
|
||||
['overseas-warehouse/pallets', permission.palletList],
|
||||
['overseas-warehouse/pallets/:palletId', permission.palletList],
|
||||
['overseas-warehouse/locations', permission.locationList],
|
||||
['general/mojia-sku-sync', permission.mojiaSkuSyncList],
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -25,8 +25,8 @@ export interface SailingCalendarEntry {
|
||||
warehouseName: string
|
||||
channelCode: string
|
||||
channelName: string
|
||||
vesselName: string
|
||||
voyageNo: string
|
||||
vesselName: string | null
|
||||
voyageNo: string | null
|
||||
departurePortCode: string
|
||||
departurePortName?: string | null
|
||||
destinationPortCode: string
|
||||
@@ -85,8 +85,8 @@ export interface SailingSaveRequest {
|
||||
warehouseName?: string | null
|
||||
channelCode: string
|
||||
channelName: string
|
||||
vesselName: string
|
||||
voyageNo: string
|
||||
vesselName: string | null
|
||||
voyageNo: string | null
|
||||
departurePortCode: string
|
||||
departurePortName?: string | null
|
||||
destinationPortCode: string
|
||||
|
||||
@@ -249,7 +249,7 @@ describe('UnifiedOrdersView contract', () => {
|
||||
it('counts detail in the three directly visible row actions', () => {
|
||||
expect(source).toContain("{ key: 'detail', label: '详情', priority: 500 }")
|
||||
expect(source).toContain('...unifiedOrderSecondaryActions(row).map')
|
||||
expect(source).toContain('<RowActions :actions="unifiedOrderRowActions(row as UnifiedOrderResource)"')
|
||||
expect(source).toContain('<RowActions class="density-action" :actions="unifiedOrderRowActions(row as UnifiedOrderResource)"')
|
||||
expect(source).toContain("if (action === 'approve') return '审核'")
|
||||
expect(source).not.toContain("if (action === 'approve') return '通过'")
|
||||
})
|
||||
|
||||
@@ -2740,7 +2740,7 @@ onBeforeUnmount(() => {
|
||||
<el-button v-if="can('platform:firstleg:inbound:view')" :loading="printingCartonLabels" :disabled="loading || printingCartonLabels || !selectedCartonLabelOrders.length" @click="printSelectedCartonLabels"><Printer :size="16" />打印箱唛<span v-if="selectedCartonLabelOrders.length">({{ selectedCartonLabelOrders.length }})</span></el-button>
|
||||
<el-button v-if="can('platform:firstleg:smart-loading:list')" type="primary" @click="smartLoadingOpen = true"><PackageOpen :size="16" />智能装柜</el-button>
|
||||
</template>
|
||||
<template #actions="{ row }"><RowActions :actions="unifiedOrderRowActions(row as UnifiedOrderResource)" :more-label="moreUnifiedOrderActionsLabel(row as UnifiedOrderResource)" @action="handleUnifiedOrderRowAction($event, row as UnifiedOrderResource)" /></template>
|
||||
<template #actions="{ row }"><RowActions class="density-action" :actions="unifiedOrderRowActions(row as UnifiedOrderResource)" :more-label="moreUnifiedOrderActionsLabel(row as UnifiedOrderResource)" @action="handleUnifiedOrderRowAction($event, row as UnifiedOrderResource)" /></template>
|
||||
<template #inboundCode="{ row }">
|
||||
<div class="inbound-relation-cell order-code-cell">
|
||||
<strong class="inbound-code-value" :title="(row as UnifiedOrderResource).inboundCode || '待生成入仓码'">{{ (row as UnifiedOrderResource).inboundCode || '待生成入仓码' }}</strong>
|
||||
|
||||
@@ -108,6 +108,7 @@ const domesticPages = [
|
||||
{ title: '库位管理', path: '/master-data/locations', file: 'warehouse-resources/locations/pages/LocationsView.vue' },
|
||||
{ title: '托盘管理', path: '/master-data/pallets', file: 'warehouse-resources/pallets/pages/PalletsView.vue' },
|
||||
{ title: '整柜业务', path: '/domestic-containers', file: 'domestic-containers/pages/DomesticContainersView.vue' },
|
||||
{ title: '船期日历', path: '/sailing-calendar', file: 'sailing-calendar/pages/SailingCalendarView.vue' },
|
||||
]
|
||||
const customerPages = [
|
||||
{ title: '客户列表', path: '/customers', file: 'customers/pages/CustomersView.vue' },
|
||||
@@ -247,11 +248,11 @@ describe('approved Admin density ownership', () => {
|
||||
expect(read('modules/overseas-warehouse/pages/LastMileOrderDetailView.vue')).toContain('density-detail-overview')
|
||||
})
|
||||
|
||||
it('covers all ten overseas warehouse menus, details and both return inventory modes', () => {
|
||||
it('covers all eleven overseas warehouse menus, details and both return inventory modes', () => {
|
||||
const navigation = read('core/navigation/adminNavigation.ts')
|
||||
const section = navigation.slice(navigation.indexOf("key: 'overseas-inventory'"), navigation.indexOf("key: 'customer-service'"))
|
||||
for (const path of ['last-mile-orders', 'inbound-orders', 'dispatches', 'outbounds', 'delivery-orders', 'relabel-orders', 'pallets', 'inventory', 'return-inventory', 'return-cartons']) expect(section).toContain("path: '/overseas-warehouse/" + path + "'")
|
||||
expect([...section.matchAll(/path: '/g)]).toHaveLength(10)
|
||||
for (const path of ['last-mile-orders', 'inbound-orders', 'locations', 'dispatches', 'outbounds', 'delivery-orders', 'relabel-orders', 'pallets', 'inventory', 'return-inventory', 'return-cartons']) expect(section).toContain("path: '/overseas-warehouse/" + path + "'")
|
||||
expect([...section.matchAll(/path: '/g)]).toHaveLength(11)
|
||||
const routes = read('modules/overseas-warehouse/routes.ts')
|
||||
for (const mode of ['inventory', 'cartons']) expect(routes).toContain("mode: '" + mode + "'")
|
||||
for (const file of overseasConsumers) {
|
||||
@@ -376,7 +377,7 @@ describe('approved Admin density ownership', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('covers all seven domestic menu entries and loads density CSS on direct entry', () => {
|
||||
it('covers all eight domestic menu entries and loads density CSS on direct entry', () => {
|
||||
const navigation = read('core/navigation/adminNavigation.ts')
|
||||
const domestic = navigation.slice(navigation.indexOf("key: 'domestic-warehouse'"), navigation.indexOf("key: 'overseas-delivery'"))
|
||||
expect([...domestic.matchAll(/path: '/g)]).toHaveLength(domesticPages.length)
|
||||
|
||||
Reference in New Issue
Block a user