优化
This commit is contained in:
@@ -8,13 +8,17 @@ const view = readFileSync(new URL('./components/BusinessOrdersView.vue', import.
|
||||
|
||||
test('activates all three customer cross-border pages with frozen permissions', () => {
|
||||
for (const [path, permission] of [
|
||||
['cross-border/procurement', 'merchant:cross-border:procurement'],
|
||||
['cross-border/domestic-trucking', 'merchant:cross-border:domestic-trucking'],
|
||||
['cross-border/pallet-shipping', 'merchant:cross-border:pallet-shipping'],
|
||||
['cross-border/procurement', 'merchant:cross-border:procurement:list'],
|
||||
['cross-border/domestic-trucking', 'merchant:cross-border:domestic-trucking:list'],
|
||||
['cross-border/pallet-shipping', 'merchant:cross-border:pallet-shipping:list'],
|
||||
]) {
|
||||
assert.match(routes, new RegExp(`path: '${path}'[\\s\\S]*?release: 'active'[\\s\\S]*?permission: '${permission}'`))
|
||||
}
|
||||
assert.doesNotMatch(view, /PrototypeFeatureView|mock|customerId|tenantId/i)
|
||||
assert.match(view, /merchant:cross-border:procurement:create/)
|
||||
assert.match(view, /merchant:cross-border:domestic-trucking:create/)
|
||||
assert.match(view, /merchant:cross-border:pallet-shipping:create/)
|
||||
assert.match(view, /v-else-if="canCreate"/)
|
||||
})
|
||||
|
||||
test('uses only merchant overseas endpoints and keeps retry idempotency stable', () => {
|
||||
|
||||
@@ -17,6 +17,11 @@ import { CONTAINER_SPECIFICATIONS, DEFAULT_CONTAINER_TYPE } from '../containerSp
|
||||
|
||||
const props = defineProps<{ kind: CrossBorderBusinessKind }>()
|
||||
const auth = useAuthStore()
|
||||
const canCreate = computed(() => auth.hasPermission(({
|
||||
procurement: 'merchant:cross-border:procurement:create',
|
||||
trucking: 'merchant:cross-border:domestic-trucking:create',
|
||||
looseboard: 'merchant:cross-border:pallet-shipping:create',
|
||||
} as Record<CrossBorderBusinessKind, string>)[props.kind]))
|
||||
|
||||
type BusinessRow = ProcurementSummary | TransportSummary
|
||||
type BusinessDetail = ProcurementDetail | TransportDetail
|
||||
@@ -277,7 +282,7 @@ onMounted(() => void load())
|
||||
<p>{{ mode === 'list' ? configuration.description : mode === 'create' ? '请填写订单信息,带 * 项为必填项' : `订单号:${detail?.summary.orderNo || '—'}` }}</p>
|
||||
</div>
|
||||
<el-button v-if="mode !== 'list'" @click="mode = 'list'">返回列表</el-button>
|
||||
<el-button v-else type="primary" @click="resetForm(); mode = 'create'">新增下单</el-button>
|
||||
<el-button v-else-if="canCreate" type="primary" @click="resetForm(); mode = 'create'">新增下单</el-button>
|
||||
</header>
|
||||
|
||||
<template v-if="mode === 'list'">
|
||||
@@ -340,7 +345,7 @@ onMounted(() => void load())
|
||||
<el-form-item label="联系人" prop="contactName"><el-input v-model.trim="form.contactName" maxlength="128" placeholder="请输入联系人" /></el-form-item>
|
||||
<el-form-item label="联系方式" prop="contactInfo"><el-input v-model.trim="form.contactInfo" maxlength="128" placeholder="请输入电话、微信或邮箱" /></el-form-item>
|
||||
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="4" maxlength="1000" show-word-limit placeholder="请输入其他要求" /></el-form-item>
|
||||
<el-form-item><el-button @click="mode = 'list'">取消</el-button><el-button type="primary" :loading="submitting" @click="submit">提交订单</el-button></el-form-item>
|
||||
<el-form-item><el-button @click="mode = 'list'">取消</el-button><el-button v-if="canCreate" type="primary" :loading="submitting" @click="submit">提交订单</el-button></el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</section>
|
||||
|
||||
@@ -10,7 +10,7 @@ export const crossBorderModule = {
|
||||
name: 'cross-border-procurement',
|
||||
component: () => import('./views/ProcurementView.vue'),
|
||||
release: 'active',
|
||||
meta: { title: '海外采购', description: '管理海外采购订单', permission: 'merchant:cross-border:procurement' },
|
||||
meta: { title: '海外采购', description: '管理海外采购订单', permission: 'merchant:cross-border:procurement:list' },
|
||||
navigation: { key: 'cross-border-procurement', icon: Package },
|
||||
},
|
||||
{
|
||||
@@ -18,7 +18,7 @@ export const crossBorderModule = {
|
||||
name: 'cross-border-domestic-trucking',
|
||||
component: () => import('./views/DomesticTruckingView.vue'),
|
||||
release: 'active',
|
||||
meta: { title: '美国本土卡', description: '创建美国本土卡车运输订单', permission: 'merchant:cross-border:domestic-trucking' },
|
||||
meta: { title: '美国本土卡', description: '创建美国本土卡车运输订单', permission: 'merchant:cross-border:domestic-trucking:list' },
|
||||
navigation: { key: 'cross-border-domestic-trucking', icon: Truck },
|
||||
},
|
||||
{
|
||||
@@ -26,7 +26,7 @@ export const crossBorderModule = {
|
||||
name: 'cross-border-pallet-shipping',
|
||||
component: () => import('./views/PalletShippingView.vue'),
|
||||
release: 'active',
|
||||
meta: { title: '美本土散板', description: '创建美国本土散板运输订单', permission: 'merchant:cross-border:pallet-shipping' },
|
||||
meta: { title: '美本土散板', description: '创建美国本土散板运输订单', permission: 'merchant:cross-border:pallet-shipping:list' },
|
||||
navigation: { key: 'cross-border-pallet-shipping', icon: Ship },
|
||||
},
|
||||
],
|
||||
|
||||
@@ -68,7 +68,7 @@ const businessCards = [
|
||||
icon: ShoppingCart,
|
||||
tone: 'blue',
|
||||
route: '/cross-border/procurement',
|
||||
permission: 'merchant:cross-border:procurement',
|
||||
permission: 'merchant:cross-border:procurement:list',
|
||||
},
|
||||
{
|
||||
title: '美国本土卡',
|
||||
@@ -76,7 +76,7 @@ const businessCards = [
|
||||
icon: Truck,
|
||||
tone: 'green',
|
||||
route: '/cross-border/domestic-trucking',
|
||||
permission: 'merchant:cross-border:domestic-trucking',
|
||||
permission: 'merchant:cross-border:domestic-trucking:list',
|
||||
},
|
||||
{
|
||||
title: '美本土散板',
|
||||
@@ -84,7 +84,7 @@ const businessCards = [
|
||||
icon: PanelsTopLeft,
|
||||
tone: 'teal',
|
||||
route: '/cross-border/pallet-shipping',
|
||||
permission: 'merchant:cross-border:pallet-shipping',
|
||||
permission: 'merchant:cross-border:pallet-shipping:list',
|
||||
},
|
||||
] as const
|
||||
|
||||
|
||||
@@ -8,25 +8,25 @@ export const overseasWarehouseModule = {
|
||||
{
|
||||
path: 'overseas-warehouse/labeling', name: 'overseas-labeling',
|
||||
component: () => import('./views/WarehouseServiceView.vue'), props: { service: 'labeling' }, release: 'active',
|
||||
meta: { title: '贴标申请', description: '查询并管理海外仓贴标申请', permission: 'dashboard:view' },
|
||||
meta: { title: '贴标申请', description: '查询并管理海外仓贴标申请', permission: 'merchant:overseas-warehouse:relabel:list' },
|
||||
navigation: { key: 'overseas-labeling', icon: Tags },
|
||||
},
|
||||
{
|
||||
path: 'overseas-warehouse/storage-transfer', name: 'overseas-storage-transfer',
|
||||
component: () => import('./views/WarehouseServiceView.vue'), props: { service: 'transfer' }, release: 'active',
|
||||
meta: { title: '仓储转运', description: '查询并管理海外仓转运订单', permission: 'dashboard:view' },
|
||||
meta: { title: '仓储转运', description: '查询并管理海外仓转运订单', permission: 'merchant:overseas-warehouse:transfer:list' },
|
||||
navigation: { key: 'overseas-storage-transfer', icon: Truck },
|
||||
},
|
||||
{
|
||||
path: 'overseas-warehouse/cargo-disposal', name: 'overseas-cargo-disposal',
|
||||
component: () => import('./views/WarehouseServiceView.vue'), props: { service: 'disposal' }, release: 'active',
|
||||
meta: { title: '货物废弃', description: '查询并管理海外仓货物废弃申请', permission: 'dashboard:view' },
|
||||
meta: { title: '货物废弃', description: '查询并管理海外仓货物废弃申请', permission: 'merchant:overseas-warehouse:discard:list' },
|
||||
navigation: { key: 'overseas-cargo-disposal', icon: PackageX },
|
||||
},
|
||||
{
|
||||
path: 'overseas-warehouse/return-relabel', name: 'overseas-return-relabel',
|
||||
component: () => import('./views/ReturnOrdersView.vue'), release: 'active',
|
||||
meta: { title: '退货换标', description: '创建退货预报并查看仓库签收结果', permission: 'dashboard:view' },
|
||||
meta: { title: '退货换标', description: '创建退货预报并查看仓库签收结果', permission: 'merchant:overseas-warehouse:return-order:list' },
|
||||
navigation: { key: 'overseas-return-relabel', icon: RefreshCcw },
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Plus, Search } from '@lucide/vue'
|
||||
import { useAuthStore } from '@/modules/auth/public'
|
||||
import { getMerchantWarehouses, type MerchantWarehouse } from '@/modules/first-leg/public'
|
||||
import { createReturnOrder, getReturnOrder, listReturnOrders, type ReturnOrderDetail, type ReturnOrderSummary, type ReturnReceipt } from '../api/returnOrders'
|
||||
|
||||
const loading = ref(false)
|
||||
const auth = useAuthStore()
|
||||
const canCreate = computed(() => auth.hasPermission('merchant:overseas-warehouse:return-order:create'))
|
||||
const creating = ref(false)
|
||||
const showCreate = ref(false)
|
||||
const showDetail = ref(false)
|
||||
@@ -103,7 +106,7 @@ onMounted(() => { void Promise.all([load(), loadOperationWarehouses()]) })
|
||||
|
||||
<template>
|
||||
<section class="return-orders-page">
|
||||
<div class="page-heading"><div><h2>退货签收</h2><p>预报退货 SKU 和数量,仓库 PDA 实收后自动更新签收状态。</p></div><el-button type="primary" @click="openCreate"><Plus :size="16" />新建退货预报</el-button></div>
|
||||
<div class="page-heading"><div><h2>退货签收</h2><p>预报退货 SKU 和数量,仓库 PDA 实收后自动更新签收状态。</p></div><el-button v-if="canCreate" type="primary" @click="openCreate"><Plus :size="16" />新建退货预报</el-button></div>
|
||||
<div class="filter-bar">
|
||||
<el-input v-model="query.returnNo" clearable placeholder="退货单号" @keyup.enter="query.page=1; load()" />
|
||||
<el-input v-model="query.trackingNo" clearable placeholder="快递单号" @keyup.enter="query.page=1; load()" />
|
||||
@@ -142,7 +145,7 @@ onMounted(() => { void Promise.all([load(), loadOperationWarehouses()]) })
|
||||
<el-table-column label="中文品名" min-width="260"><template #default="{ row }"><el-input v-model="row.productNameZh" placeholder="填写中文品名(选填)" /></template></el-table-column>
|
||||
<el-table-column label="预报数量" width="160"><template #default="{ row }"><el-input-number v-model="row.expectedQuantity" :min="1" controls-position="right" placeholder="数量" /></template></el-table-column>
|
||||
</el-table>
|
||||
<template #footer><el-button @click="showCreate=false">取消</el-button><el-button type="primary" :loading="creating" @click="submit">提交预报</el-button></template>
|
||||
<template #footer><el-button @click="showCreate=false">取消</el-button><el-button v-if="canCreate" type="primary" :loading="creating" @click="submit">提交预报</el-button></template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="showDetail" title="退货签收详情" width="min(980px, calc(100vw - 32px))" top="8vh" class="return-detail-dialog" destroy-on-close>
|
||||
<template v-if="selected">
|
||||
|
||||
@@ -3,6 +3,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch }
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ArrowLeft, Download, Eye, Plus, Printer, Search, Trash2, Upload, X } from '@lucide/vue'
|
||||
import SingleLineText from '@/components/common/SingleLineText.vue'
|
||||
import { useAuthStore } from '@/modules/auth/public'
|
||||
import {
|
||||
createDiscard, createRelabel, createTransfer, getDiscard, getRelabel, getTransfer,
|
||||
getTransferCartonLabel, submitTransfer, updateTransfer,
|
||||
@@ -33,6 +34,17 @@ interface ServiceConfig { title: string; createTitle: string; numberLabel: strin
|
||||
interface TransferCarton { cartonMark: string; customerCartonMark?: string; cartonIndex: number; totalCartons: number; status: string }
|
||||
|
||||
const props = defineProps<{ service: ServiceType }>()
|
||||
const auth = useAuthStore()
|
||||
const canCreate = computed(() => auth.hasPermission(({
|
||||
labeling: 'merchant:overseas-warehouse:relabel:create',
|
||||
transfer: 'merchant:overseas-warehouse:transfer:create',
|
||||
disposal: 'merchant:overseas-warehouse:discard:create',
|
||||
relabel: 'merchant:overseas-warehouse:return-order:create',
|
||||
} as Record<ServiceType, string>)[props.service]))
|
||||
const canManageRelabelFiles = computed(() => auth.hasPermission('merchant:overseas-warehouse:relabel:files'))
|
||||
const canConfirmRelabel = computed(() => auth.hasPermission('merchant:overseas-warehouse:relabel:confirm'))
|
||||
const canConfirmTransfer = computed(() => auth.hasPermission('merchant:overseas-warehouse:transfer:confirm'))
|
||||
const canCancelDiscard = computed(() => auth.hasPermission('merchant:overseas-warehouse:discard:cancel'))
|
||||
const configs: Record<ServiceType, ServiceConfig> = {
|
||||
labeling: { title: '贴标申请', createTitle: '新增贴标申请', numberLabel: '贴标申请单号', emptyText: '暂无贴标申请', columns: ['操作', '贴标申请单号', '客户单号', '海外操作仓', '贴标要求', 'SKU 数', '总箱数', '状态', '创建时间'] },
|
||||
transfer: { title: '仓储转运单', createTitle: '新增仓储转运单', numberLabel: '转运单号', emptyText: '暂无仓储转运订单', columns: ['操作', '转运单号', '入库箱数', '海外仓', 'FBA 目的仓', '入仓方式', '是否换标', '状态', '创建时间'] },
|
||||
@@ -984,7 +996,7 @@ function selectRelabelInventory(inboundOrderId: string) {
|
||||
<section class="warehouse-service-page">
|
||||
<template v-if="mode === 'list'">
|
||||
<div class="warehouse-breadcrumb"><span>首页</span><b>/</b><strong>{{ config.title }}</strong></div>
|
||||
<div class="warehouse-page-heading"><h2>{{ config.title }}</h2><el-button type="primary" @click="openCreate"><Plus :size="16" />{{ props.service === 'relabel' ? '新建退货换标单' : props.service === 'transfer' ? '新增订单' : `新增${config.title}` }}</el-button></div>
|
||||
<div class="warehouse-page-heading"><h2>{{ config.title }}</h2><el-button v-if="canCreate" type="primary" @click="openCreate"><Plus :size="16" />{{ props.service === 'relabel' ? '新建退货换标单' : props.service === 'transfer' ? '新增订单' : `新增${config.title}` }}</el-button></div>
|
||||
<div class="warehouse-card warehouse-filter-card">
|
||||
<div class="warehouse-filter-grid">
|
||||
<label><span>{{ config.numberLabel }}</span><el-input v-model="filters.number" :placeholder="`请输入${config.numberLabel}`" clearable /></label>
|
||||
@@ -999,7 +1011,7 @@ function selectRelabelInventory(inboundOrderId: string) {
|
||||
</div>
|
||||
<div v-loading="loading" class="warehouse-card warehouse-table-card">
|
||||
<div v-if="props.service === 'transfer' && submittedRows.some((item) => item.__status === 'WAIT_CUSTOMER_CONFIRMATION')" class="warehouse-hint">部分入库完成订单已有 PDA 实测数据,请在对应行确认测量数据。</div>
|
||||
<div class="warehouse-table-scroll"><table><thead><tr><th v-for="column in config.columns" :key="column">{{ column }}</th></tr></thead><tbody><tr v-for="(row, rowIndex) in submittedRows" :key="rowIndex"><td><div class="warehouse-list-actions"><button class="warehouse-link" type="button" @click="openDetail(row)"><Eye :size="15" />查看</button><el-button v-if="props.service === 'transfer' && row.__status === 'DRAFT'" native-type="button" size="small" type="primary" @click="openEdit(row['0'])">编辑</el-button><el-button v-if="props.service === 'transfer' && row.__status === 'WAIT_CUSTOMER_CONFIRMATION'" size="small" type="primary" @click="confirmTransferFromList(row['0'])">确认测量数据</el-button><el-button v-if="props.service === 'labeling' && row.__status === 'CUSTOMER_CONFIRM_PENDING' && !row.__confirmedAt" size="small" type="primary" @click="confirmRelabelFromList(row['0'])">换标完成确认</el-button></div></td><td v-for="(_, index) in config.columns.slice(1)" :key="index"><SingleLineText :value="row[String(index)]" /></td></tr><tr v-if="submittedRows.length === 0"><td :colspan="config.columns.length"><div class="warehouse-empty">{{ config.emptyText }}</div></td></tr></tbody></table></div>
|
||||
<div class="warehouse-table-scroll"><table><thead><tr><th v-for="column in config.columns" :key="column">{{ column }}</th></tr></thead><tbody><tr v-for="(row, rowIndex) in submittedRows" :key="rowIndex"><td><div class="warehouse-list-actions"><button class="warehouse-link" type="button" @click="openDetail(row)"><Eye :size="15" />查看</button><el-button v-if="canCreate && props.service === 'transfer' && row.__status === 'DRAFT'" native-type="button" size="small" type="primary" @click="openEdit(row['0'])">编辑</el-button><el-button v-if="canConfirmTransfer && props.service === 'transfer' && row.__status === 'WAIT_CUSTOMER_CONFIRMATION'" size="small" type="primary" @click="confirmTransferFromList(row['0'])">确认测量数据</el-button><el-button v-if="canConfirmRelabel && props.service === 'labeling' && row.__status === 'CUSTOMER_CONFIRM_PENDING' && !row.__confirmedAt" size="small" type="primary" @click="confirmRelabelFromList(row['0'])">换标完成确认</el-button></div></td><td v-for="(_, index) in config.columns.slice(1)" :key="index"><SingleLineText :value="row[String(index)]" /></td></tr><tr v-if="submittedRows.length === 0"><td :colspan="config.columns.length"><div class="warehouse-empty">{{ config.emptyText }}</div></td></tr></tbody></table></div>
|
||||
<div class="warehouse-pagination">
|
||||
<span class="warehouse-pagination-total">共 {{ total }} 条</span>
|
||||
<el-select v-model="pageSize" class="warehouse-page-size-select" aria-label="每页条数" @change="changePageSize"><el-option v-for="size in [15, 30, 50, 100]" :key="size" :label="`${size}条/页`" :value="size" /></el-select>
|
||||
@@ -1040,7 +1052,7 @@ function selectRelabelInventory(inboundOrderId: string) {
|
||||
<label v-if="props.service === 'labeling'"><span><i>*</i>贴标截止时间</span><el-date-picker v-model="form.labelDeadline" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择截止时间" /></label>
|
||||
<label v-if="props.service === 'labeling' && labelingMode !== 'sku'"><span><i>*</i>换标后派送方式</span><el-select v-model="form.deliveryMethod" placeholder="请选择派送方式"><el-option label="卡车派送" value="TRUCK" /><el-option label="快递派送" value="EXPRESS" /><el-option label="卡车自提" value="TRUCK_PICKUP" /><el-option label="快递自提" value="EXPRESS_PICKUP" /></el-select><small class="warehouse-hint">卡车派送进入预排车;其余方式确认换标完成后进入换标出库。</small></label>
|
||||
<div v-if="props.service === 'labeling' && labelingMode !== 'sku' && (form.deliveryMethod === 'EXPRESS' || form.deliveryMethod === 'EXPRESS_PICKUP')" class="warehouse-checkbox-field"><span>是否打印面单</span><el-checkbox v-model="form.relabel">需要仓库打印面单</el-checkbox></div>
|
||||
<label v-if="props.service === 'labeling' && labelingMode !== 'sku' && (form.deliveryMethod === 'EXPRESS' || form.deliveryMethod === 'EXPRESS_PICKUP') && form.relabel"><span><i>*</i>上传面单文件</span><input class="warehouse-native-file" type="file" accept="application/pdf,image/*,.pdf" @change="selectShippingLabel"><small class="warehouse-hint">支持 PDF 或图片,最大 20 MB。</small></label>
|
||||
<label v-if="canManageRelabelFiles && props.service === 'labeling' && labelingMode !== 'sku' && (form.deliveryMethod === 'EXPRESS' || form.deliveryMethod === 'EXPRESS_PICKUP') && form.relabel"><span><i>*</i>上传面单文件</span><input class="warehouse-native-file" type="file" accept="application/pdf,image/*,.pdf" @change="selectShippingLabel"><small class="warehouse-hint">支持 PDF 或图片,最大 20 MB。</small></label>
|
||||
<label v-if="props.service === 'relabel'" class="warehouse-span-2"><span><i>*</i>详细派送地址</span><el-input v-model="form.address" placeholder="请输入派送地址" /></label>
|
||||
<label v-if="props.service === 'transfer' || props.service === 'relabel'"><span><i>*</i>预估总体积(CBM)</span><el-input-number v-model="form.volumeCbm" :min="0" :precision="4" :step="0.0001" controls-position="right" placeholder="请输入预估总体积" /></label>
|
||||
<label v-if="props.service === 'transfer' || props.service === 'relabel'"><span><i>*</i>{{ props.service === 'transfer' ? '预计入仓时间' : '预计到达时间' }}</span><el-date-picker v-model="form.estimatedArrivalAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" :placeholder="props.service === 'transfer' ? '请选择预计入仓时间' : '请选择预计到达时间'" /></label>
|
||||
@@ -1064,8 +1076,8 @@ function selectRelabelInventory(inboundOrderId: string) {
|
||||
<div class="warehouse-section-heading warehouse-labeling-heading">
|
||||
<h3>货物明细</h3>
|
||||
<div class="warehouse-template-actions">
|
||||
<el-button size="small" @click="chooseCreateTotalFile('CARTON_TOTAL')"><Upload :size="15" />上传箱唛总文件</el-button>
|
||||
<el-button size="small" @click="chooseCreateTotalFile('SKU_TOTAL')"><Upload :size="15" />上传 SKU 总文件</el-button>
|
||||
<el-button v-if="canManageRelabelFiles" size="small" @click="chooseCreateTotalFile('CARTON_TOTAL')"><Upload :size="15" />上传箱唛总文件</el-button>
|
||||
<el-button v-if="canManageRelabelFiles" size="small" @click="chooseCreateTotalFile('SKU_TOTAL')"><Upload :size="15" />上传 SKU 总文件</el-button>
|
||||
<el-button size="small" :disabled="!cargoRows.length" @click="downloadRelabelTemplate('sku')"><Download :size="15" />下载 SKU 关联模板</el-button>
|
||||
<el-button size="small" :disabled="!cargoRows.length" @click="skuTemplateInput?.click()"><Upload :size="15" />上传 SKU 关联</el-button>
|
||||
<el-button size="small" :disabled="!cargoRows.length" @click="downloadRelabelTemplate('carton')"><Download :size="15" />下载箱号关联模板</el-button>
|
||||
@@ -1079,7 +1091,7 @@ function selectRelabelInventory(inboundOrderId: string) {
|
||||
<input ref="skuTotalCreateInput" class="warehouse-hidden-input" type="file" accept="application/pdf,.pdf" @change="selectSkuTotalFile">
|
||||
</div>
|
||||
</div>
|
||||
<div class="warehouse-upload-summary">
|
||||
<div v-if="canManageRelabelFiles" class="warehouse-upload-summary">
|
||||
<span>箱唛总文件:{{ cartonTotalFile?.name || '未选择' }}<button v-if="cartonTotalFile" type="button" class="warehouse-file-clear" title="清除箱唛总文件" aria-label="清除箱唛总文件" :disabled="saving" @click="clearCreateTotalFile('CARTON_TOTAL')"><X :size="14" /></button></span>
|
||||
<span>SKU 总文件:{{ skuTotalFile?.name || '未选择' }}<button v-if="skuTotalFile" type="button" class="warehouse-file-clear" title="清除 SKU 总文件" aria-label="清除 SKU 总文件" :disabled="saving" @click="clearCreateTotalFile('SKU_TOTAL')"><X :size="14" /></button></span>
|
||||
</div>
|
||||
@@ -1125,7 +1137,7 @@ function selectRelabelInventory(inboundOrderId: string) {
|
||||
|
||||
<template v-else>
|
||||
<div class="warehouse-breadcrumb"><button type="button" @click="mode = 'list'">{{ config.title }}</button><b>/</b><strong>订单详情</strong></div>
|
||||
<div class="warehouse-page-heading"><h2>{{ config.title }}详情</h2><div><el-button v-if="props.service === 'disposal' && selectedDetail?.status === 'PENDING_REVIEW'" type="danger" @click="cancelSelectedDiscard">取消申请</el-button><el-button v-if="props.service === 'transfer' && selectedDetail?.status === 'DRAFT'" type="primary" @click="openEdit(String(selectedDetail?.transferNo || selectedDetail?.['0'] || ''))">编辑</el-button><el-button v-if="props.service === 'transfer' && selectedDetail?.status === 'WAIT_CUSTOMER_CONFIRMATION'" type="primary" @click="confirmTransferMeasurements">确认测量数据</el-button><el-button @click="mode = 'list'"><ArrowLeft :size="16" />返回列表</el-button></div></div>
|
||||
<div class="warehouse-page-heading"><h2>{{ config.title }}详情</h2><div><el-button v-if="canCancelDiscard && props.service === 'disposal' && selectedDetail?.status === 'PENDING_REVIEW'" type="danger" @click="cancelSelectedDiscard">取消申请</el-button><el-button v-if="canCreate && props.service === 'transfer' && selectedDetail?.status === 'DRAFT'" type="primary" @click="openEdit(String(selectedDetail?.transferNo || selectedDetail?.['0'] || ''))">编辑</el-button><el-button v-if="canConfirmTransfer && props.service === 'transfer' && selectedDetail?.status === 'WAIT_CUSTOMER_CONFIRMATION'" type="primary" @click="confirmTransferMeasurements">确认测量数据</el-button><el-button @click="mode = 'list'"><ArrowLeft :size="16" />返回列表</el-button></div></div>
|
||||
<div class="warehouse-card warehouse-form-section"><h3>订单概览</h3><div class="warehouse-detail-grid"><div v-for="(column, index) in config.columns.slice(1)" :key="column"><span>{{ column }}</span><strong>{{ selectedDetail?.[String(index)] || '—' }}</strong></div></div></div>
|
||||
<div v-if="props.service === 'transfer'" class="warehouse-card warehouse-form-section warehouse-transfer-cargo-card">
|
||||
<div class="warehouse-section-heading"><h3>货物明细</h3><el-button v-if="selectedDetail?.status !== 'DRAFT'" native-type="button" type="primary" @click="printTransferCartons(String(selectedDetail?.transferNo || selectedDetail?.['0'] || ''))"><Printer :size="16" />打印箱唛</el-button></div>
|
||||
@@ -1136,7 +1148,7 @@ function selectRelabelInventory(inboundOrderId: string) {
|
||||
<div class="warehouse-table-scroll"><table><thead><tr><th>换标类型</th><th>客户箱唛号</th><th>旧 SKU</th><th>新 SKU</th><th>新 FBA 箱唛</th><th>实际 FBA 目的仓</th><th>数量</th><th>备注</th></tr></thead><tbody><tr v-for="(binding, index) in selectedDetail?.relabelBindings || []" :key="`${binding.customerCartonMark}-${index}`"><td>{{ binding.operationType === 'CARTON_AND_SKU' ? 'SKU 和箱唛都换' : binding.operationType === 'SKU_LABEL' ? '只换 SKU' : '只换箱唛' }}</td><td>{{ binding.customerCartonMark || '—' }}</td><td>{{ binding.originalSku || '—' }}</td><td>{{ binding.newSku || '—' }}</td><td>{{ binding.fbaCartonMark || '—' }}</td><td>{{ binding.operationType === 'SKU_LABEL' ? '沿用原箱去向' : warehouseDisplay(binding.destinationWarehouseCode) }}</td><td>{{ binding.quantity ?? '—' }}</td><td>{{ binding.remark || '—' }}</td></tr><tr v-if="!selectedDetail?.relabelBindings?.length"><td colspan="8"><div class="warehouse-empty">暂无换标绑定明细</div></td></tr></tbody></table></div>
|
||||
</div>
|
||||
<div v-if="props.service === 'transfer'" class="warehouse-card warehouse-form-section warehouse-transfer-times"><h3>发货时间</h3><div class="warehouse-detail-grid"><div><span>预计发货时间</span><strong>{{ formatDateTime(selectedDetail?.estimatedShipAt) }}</strong></div><div><span>实际发货时间</span><strong>{{ formatDateTime(selectedDetail?.actualShipAt) }}</strong></div></div><p>实际发货时间由仓库出库或 PDA 完成后回填,客户不能手动修改。</p></div>
|
||||
<div v-if="props.service === 'labeling'" class="warehouse-card warehouse-form-section">
|
||||
<div v-if="props.service === 'labeling' && canManageRelabelFiles" class="warehouse-card warehouse-form-section">
|
||||
<div class="warehouse-section-heading"><h3>换标完成与出库</h3></div>
|
||||
<div class="warehouse-detail-grid warehouse-fulfillment-grid">
|
||||
<div><span>派送方式</span><strong>{{ deliveryMethodLabel(selectedRelabelDetail?.application.deliveryMethod || '') || '无需出库' }}</strong></div>
|
||||
|
||||
@@ -93,9 +93,19 @@ test('shares the merchant permission map and preserves resources not rendered by
|
||||
assert.match(contractsSource, /'company:member:create': \['merchant:member:create'\]/)
|
||||
for (const permission of [
|
||||
'merchant:warehouse:inbound:list',
|
||||
'merchant:cross-border:procurement',
|
||||
'merchant:cross-border:domestic-trucking',
|
||||
'merchant:cross-border:pallet-shipping',
|
||||
'merchant:cross-border:procurement:list',
|
||||
'merchant:cross-border:procurement:create',
|
||||
'merchant:cross-border:domestic-trucking:list',
|
||||
'merchant:cross-border:domestic-trucking:create',
|
||||
'merchant:cross-border:domestic-trucking:edit',
|
||||
'merchant:cross-border:pallet-shipping:list',
|
||||
'merchant:cross-border:pallet-shipping:create',
|
||||
'merchant:cross-border:pallet-shipping:edit',
|
||||
'merchant:overseas-warehouse:return-inventory:list',
|
||||
'merchant:overseas-warehouse:relabel:list',
|
||||
'merchant:overseas-warehouse:transfer:list',
|
||||
'merchant:overseas-warehouse:discard:list',
|
||||
'merchant:overseas-warehouse:return-order:list',
|
||||
]) {
|
||||
assert.match(contractsSource, new RegExp(`'${permission}': \\['${permission}'\\]`))
|
||||
}
|
||||
|
||||
@@ -58,6 +58,42 @@ export const TENANT_PERMISSION_GROUPS: readonly TenantPermissionGroup[] = [
|
||||
{ code: 'merchant:warehouse:delivery:create', label: '创建派送单', description: '从排车单创建海外仓派送单' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'overseas-warehouse',
|
||||
label: '海外仓业务',
|
||||
items: [
|
||||
{ code: 'merchant:warehouse:inbound:list', label: '查看海外入库单', description: '查看本企业海外仓入库订单列表和详情' },
|
||||
{ code: 'merchant:overseas-warehouse:last-mile-order:list', label: '查看海外尾程订单', description: '查看本企业尾程订单列表和详情' },
|
||||
{ code: 'merchant:overseas-warehouse:relabel:list', label: '查看贴标申请', description: '查看本企业贴标申请列表和详情' },
|
||||
{ code: 'merchant:overseas-warehouse:relabel:create', label: '创建贴标申请', description: '使用本企业可用库存创建贴标申请' },
|
||||
{ code: 'merchant:overseas-warehouse:relabel:files', label: '管理贴标文件', description: '上传本企业贴标申请的面单和明细文件' },
|
||||
{ code: 'merchant:overseas-warehouse:relabel:confirm', label: '确认贴标完成', description: '确认仓库完成的贴标结果并继续出库' },
|
||||
{ code: 'merchant:overseas-warehouse:transfer:list', label: '查看仓储转运', description: '查看本企业仓储转运单列表和详情' },
|
||||
{ code: 'merchant:overseas-warehouse:transfer:create', label: '创建仓储转运', description: '创建和提交本企业仓储转运单' },
|
||||
{ code: 'merchant:overseas-warehouse:transfer:confirm', label: '确认转运实测', description: '确认仓库回传的箱数、尺寸、重量和打托数据' },
|
||||
{ code: 'merchant:overseas-warehouse:discard:list', label: '查看货物废弃', description: '查看本企业货物废弃申请列表和详情' },
|
||||
{ code: 'merchant:overseas-warehouse:discard:create', label: '创建货物废弃', description: '使用本企业可用库存创建废弃申请' },
|
||||
{ code: 'merchant:overseas-warehouse:discard:cancel', label: '取消货物废弃', description: '取消本企业尚未审核的废弃申请' },
|
||||
{ code: 'merchant:overseas-warehouse:return-order:list', label: '查看退货预报', description: '查看本企业退货预报和仓库签收结果' },
|
||||
{ code: 'merchant:overseas-warehouse:return-order:create', label: '创建退货预报', description: '创建本企业退货 SKU 和数量预报' },
|
||||
{ code: 'merchant:overseas-warehouse:return-order:relabel', label: '退货转换标', description: '将已签收退货库存转换为换标申请' },
|
||||
{ code: 'merchant:overseas-warehouse:return-inventory:list', label: '查看退货库存', description: '查看本企业退货 SKU 库存和箱号明细' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'cross-border',
|
||||
label: '海外业务',
|
||||
items: [
|
||||
{ code: 'merchant:cross-border:procurement:list', label: '查看海外采购', description: '查看本企业海外采购订单列表和详情' },
|
||||
{ code: 'merchant:cross-border:procurement:create', label: '创建海外采购', description: '创建本企业海外采购订单' },
|
||||
{ code: 'merchant:cross-border:domestic-trucking:list', label: '查看美国本土卡', description: '查看本企业美国本土卡订单' },
|
||||
{ code: 'merchant:cross-border:domestic-trucking:create', label: '创建美国本土卡', description: '创建本企业美国本土卡订单' },
|
||||
{ code: 'merchant:cross-border:domestic-trucking:edit', label: '编辑美国本土卡', description: '编辑本企业允许修改的美国本土卡订单' },
|
||||
{ code: 'merchant:cross-border:pallet-shipping:list', label: '查看美本土散板', description: '查看本企业美本土散板订单' },
|
||||
{ code: 'merchant:cross-border:pallet-shipping:create', label: '创建美本土散板', description: '创建本企业美本土散板订单' },
|
||||
{ code: 'merchant:cross-border:pallet-shipping:edit', label: '编辑美本土散板', description: '编辑本企业允许修改的美本土散板订单' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'company',
|
||||
label: '企业资料',
|
||||
@@ -107,9 +143,28 @@ export const FRONTEND_TO_MERCHANT_PERMISSION_CODES: Readonly<Record<string, read
|
||||
'merchant:warehouse:delivery:create': ['merchant:warehouse:delivery:create'],
|
||||
'merchant:warehouse:inbound:list': ['merchant:warehouse:inbound:list'],
|
||||
'merchant:overseas-warehouse:return-inventory:list': ['merchant:overseas-warehouse:return-inventory:list'],
|
||||
'merchant:cross-border:procurement': ['merchant:cross-border:procurement'],
|
||||
'merchant:cross-border:domestic-trucking': ['merchant:cross-border:domestic-trucking'],
|
||||
'merchant:cross-border:pallet-shipping': ['merchant:cross-border:pallet-shipping'],
|
||||
'merchant:overseas-warehouse:last-mile-order:list': ['merchant:overseas-warehouse:last-mile-order:list'],
|
||||
'merchant:overseas-warehouse:relabel:list': ['merchant:overseas-warehouse:relabel:list'],
|
||||
'merchant:overseas-warehouse:relabel:create': ['merchant:overseas-warehouse:relabel:create'],
|
||||
'merchant:overseas-warehouse:relabel:files': ['merchant:overseas-warehouse:relabel:files'],
|
||||
'merchant:overseas-warehouse:relabel:confirm': ['merchant:overseas-warehouse:relabel:confirm'],
|
||||
'merchant:overseas-warehouse:transfer:list': ['merchant:overseas-warehouse:transfer:list'],
|
||||
'merchant:overseas-warehouse:transfer:create': ['merchant:overseas-warehouse:transfer:create'],
|
||||
'merchant:overseas-warehouse:transfer:confirm': ['merchant:overseas-warehouse:transfer:confirm'],
|
||||
'merchant:overseas-warehouse:discard:list': ['merchant:overseas-warehouse:discard:list'],
|
||||
'merchant:overseas-warehouse:discard:create': ['merchant:overseas-warehouse:discard:create'],
|
||||
'merchant:overseas-warehouse:discard:cancel': ['merchant:overseas-warehouse:discard:cancel'],
|
||||
'merchant:overseas-warehouse:return-order:list': ['merchant:overseas-warehouse:return-order:list'],
|
||||
'merchant:overseas-warehouse:return-order:create': ['merchant:overseas-warehouse:return-order:create'],
|
||||
'merchant:overseas-warehouse:return-order:relabel': ['merchant:overseas-warehouse:return-order:relabel'],
|
||||
'merchant:cross-border:procurement:list': ['merchant:cross-border:procurement:list'],
|
||||
'merchant:cross-border:procurement:create': ['merchant:cross-border:procurement:create'],
|
||||
'merchant:cross-border:domestic-trucking:list': ['merchant:cross-border:domestic-trucking:list'],
|
||||
'merchant:cross-border:domestic-trucking:create': ['merchant:cross-border:domestic-trucking:create'],
|
||||
'merchant:cross-border:domestic-trucking:edit': ['merchant:cross-border:domestic-trucking:edit'],
|
||||
'merchant:cross-border:pallet-shipping:list': ['merchant:cross-border:pallet-shipping:list'],
|
||||
'merchant:cross-border:pallet-shipping:create': ['merchant:cross-border:pallet-shipping:create'],
|
||||
'merchant:cross-border:pallet-shipping:edit': ['merchant:cross-border:pallet-shipping:edit'],
|
||||
'company:profile:view': ['merchant:profile:view'],
|
||||
'company:profile:edit': ['merchant:profile:edit'],
|
||||
'company:qualification:upload': ['merchant:qualification:upload'],
|
||||
|
||||
Reference in New Issue
Block a user