fix(client): clear protected views on logout and show customer customs state
This commit is contained in:
@@ -14,6 +14,9 @@ function evaluate(source, dependencies = {}) {
|
||||
return exports
|
||||
}
|
||||
const eligibility = evaluate(readFileSync(new URL('../utils/customsEligibility.ts', import.meta.url), 'utf8'))
|
||||
const customsApi = evaluate(readFileSync(new URL('../api/customs.ts', import.meta.url), 'utf8'), {
|
||||
'@/api/http': {}, '@/modules/first-leg/api/firstLeg': {},
|
||||
})
|
||||
const order = (id, status = 'LOADED', taxMode = 'TAX_REFUND') => ({ id, status, taxMode, orderNo: `ORDER-${id}`, businessSummary: { inboundCode: `IN-${id}` } })
|
||||
const detail = id => ({ status: 'COMPLETED', merchantStatus: 'CUSTOMS_COMPLETED', inbounds: [{ orderId: id, taxMode: 'TAX_REFUND' }] })
|
||||
function harness(t, orders, getDetail = async id => detail(id)) {
|
||||
@@ -38,9 +41,10 @@ function harness(t, orders, getDetail = async id => detail(id)) {
|
||||
})
|
||||
const source = readFileSync(new URL('./CustomsDocumentsView.vue', import.meta.url), 'utf8').match(/<script setup lang="ts">([\s\S]*?)<\/script>/)[1]
|
||||
const scope = vue.effectScope()
|
||||
const view = scope.run(() => evaluate(source + '\nexport { load, records, loading, errorMessage, openDetail, selectedOrderId, selectedDetail, dialogOpen, search, queryModel, filteredRecords }', {
|
||||
const view = scope.run(() => evaluate(source + '\nexport { load, records, loading, errorMessage, openDetail, selectedOrderId, selectedDetail, dialogOpen, search, queryModel, filteredRecords, queryFields, statusLabel, statusType, updateDetail }', {
|
||||
vue: { ...vue, onMounted() {} },
|
||||
'../utils/customsDocumentsReader': reader,
|
||||
'../api/customs': customsApi,
|
||||
}))
|
||||
t.after(() => scope.stop())
|
||||
return { view, store, calls, pageCalls }
|
||||
@@ -128,7 +132,7 @@ test('tax-refund documents remain accessible through delivered and completed ord
|
||||
assert.equal(view.selectedOrderId.value, 'COMPLETED')
|
||||
assert.equal(view.selectedDetail.value.status, 'COMPLETED')
|
||||
assert.equal(view.dialogOpen.value, true)
|
||||
view.queryModel.value.status = 'COMPLETED'; view.search()
|
||||
view.queryModel.value.status = 'CUSTOMS_COMPLETED'; view.search()
|
||||
assert.equal(view.filteredRecords.value.length, 3)
|
||||
assert.equal(calls.length, 3, 'local filtering does not refetch documents')
|
||||
})
|
||||
@@ -175,3 +179,46 @@ test('repeated refresh during an active load does not duplicate requests', async
|
||||
resolve(detail('one')); await first
|
||||
assert.equal(view.loading.value, false)
|
||||
})
|
||||
|
||||
test('mixed-container labels and filters use only the current customer status', async t => {
|
||||
const states = [
|
||||
['uploaded', 'WAITING_CUSTOMER_DOCUMENTS', 'DOCUMENT_UPLOADED', 1, '资料已上传', 'success'],
|
||||
['waiting', 'WAITING_CUSTOMER_DOCUMENTS', 'WAITING_UPLOAD', 0, '待上传资料', 'warning'],
|
||||
['declaring', 'DECLARING', 'DECLARING', 1, '报关中', 'primary'],
|
||||
['completed', 'COMPLETED', 'CUSTOMS_COMPLETED', 1, '报关完成', 'success'],
|
||||
]
|
||||
const { view, calls } = harness(t, states.map(([id]) => order(id)), async id => {
|
||||
const [, status, merchantStatus, coveredTaxRefundCount] = states.find(row => row[0] === id)
|
||||
return { ...detail(id), status, merchantStatus, coveredTaxRefundCount, taxRefundCount: 1 }
|
||||
})
|
||||
await view.load()
|
||||
for (const [id, , merchantStatus, , label, tone] of states) {
|
||||
const row = view.records.value.find(item => item.order.id === id)
|
||||
assert.equal(view.statusLabel(row.detail), label)
|
||||
assert.equal(view.statusType(row.detail), tone)
|
||||
view.queryModel.value.status = merchantStatus; view.search()
|
||||
assert.deepEqual(view.filteredRecords.value.map(item => item.order.id), [id])
|
||||
}
|
||||
assert.deepEqual(view.queryFields.find(field => field.key === 'status').options,
|
||||
states.map(([, , value, , label]) => ({ value, label })).sort((a, b) =>
|
||||
['WAITING_UPLOAD', 'DOCUMENT_UPLOADED', 'DECLARING', 'CUSTOMS_COMPLETED'].indexOf(a.value)
|
||||
- ['WAITING_UPLOAD', 'DOCUMENT_UPLOADED', 'DECLARING', 'CUSTOMS_COMPLETED'].indexOf(b.value)))
|
||||
view.queryModel.value.status = 'WAITING_CUSTOMER_DOCUMENTS'; view.search()
|
||||
assert.equal(view.filteredRecords.value.length, 0, 'container statuses are not customer filter values')
|
||||
view.queryModel.value.status = ''; view.search()
|
||||
assert.equal(view.filteredRecords.value.length, 4)
|
||||
assert.equal(calls.length, 4, 'filtering does not requery or infer status from coverage')
|
||||
})
|
||||
|
||||
test('dialog update refreshes customer status and active filter without inferring from container status', async t => {
|
||||
const { view } = harness(t, [order('one')], async id => ({ ...detail(id), status: 'WAITING_CUSTOMER_DOCUMENTS', merchantStatus: 'WAITING_UPLOAD' }))
|
||||
await view.load(); view.openDetail(view.records.value[0])
|
||||
view.queryModel.value.status = 'WAITING_UPLOAD'; view.search()
|
||||
view.updateDetail({ ...detail('one'), status: 'WAITING_CUSTOMER_DOCUMENTS', merchantStatus: 'DOCUMENT_UPLOADED', coveredTaxRefundCount: 1, taxRefundCount: 1 })
|
||||
assert.equal(view.filteredRecords.value.length, 0)
|
||||
view.queryModel.value.status = 'DOCUMENT_UPLOADED'; view.search()
|
||||
assert.equal(view.filteredRecords.value.length, 1)
|
||||
assert.equal(view.statusLabel(view.filteredRecords.value[0].detail), '资料已上传')
|
||||
assert.equal(view.statusLabel({ status: 'COMPLETED', coveredTaxRefundCount: 1, taxRefundCount: 1 }), '—')
|
||||
assert.equal(view.statusType({ status: 'COMPLETED' }), 'info')
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ import { RefreshCcw } from '@lucide/vue'
|
||||
import DataTable, { type DataTableColumn } from '@/components/common/DataTable.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/components/common/QueryToolbar.vue'
|
||||
import type { TableAction } from '@/components/common/tableActions'
|
||||
import type { MerchantCustomsContainer } from '../api/customs'
|
||||
import { MERCHANT_CUSTOMS_STATUS_LABELS, type MerchantCustomsContainer } from '../api/customs'
|
||||
import { readCustomsDocuments, type CustomsDocumentRow } from '../utils/customsDocumentsReader'
|
||||
import CustomsDocumentsDialog from '../components/CustomsDocumentsDialog.vue'
|
||||
|
||||
@@ -18,12 +18,7 @@ const queryFields: QueryField[] = [
|
||||
{ key: 'orderNo', label: '订单号', type: 'input', placeholder: '请输入订单号' },
|
||||
{ key: 'inboundCode', label: '入仓码', type: 'input', placeholder: '请输入入仓码' },
|
||||
{ key: 'containerNo', label: '柜号', type: 'input', placeholder: '请输入柜号' },
|
||||
{ key: 'status', label: '状态', type: 'select', placeholder: '全部', options: [
|
||||
{ label: '待上传资料', value: 'WAITING_CUSTOMER_DOCUMENTS' },
|
||||
{ label: '待报关', value: 'READY_TO_START' },
|
||||
{ label: '报关中', value: 'DECLARING' },
|
||||
{ label: '报关完成', value: 'COMPLETED' },
|
||||
] },
|
||||
{ key: 'status', label: '状态', type: 'select', placeholder: '全部', options: Object.entries(MERCHANT_CUSTOMS_STATUS_LABELS).map(([value, label]) => ({ label, value })) },
|
||||
]
|
||||
const columns: DataTableColumn[] = [
|
||||
{ label: '操作', slotName: 'actions', width: 120, fixed: 'left' },
|
||||
@@ -46,7 +41,7 @@ const filteredRecords = computed(() => {
|
||||
matches(order.orderNo, 'orderNo')
|
||||
&& matches(order.businessSummary.inboundCode, 'inboundCode')
|
||||
&& (matches(detail.containerNo, 'containerNo') || matches(detail.cabinetNo, 'containerNo'))
|
||||
&& (!query.status || detail.status === query.status),
|
||||
&& (!query.status || detail.merchantStatus === query.status),
|
||||
)
|
||||
})
|
||||
const pagedRecords = computed(() => filteredRecords.value.slice((page.value - 1) * pageSize.value, page.value * pageSize.value))
|
||||
@@ -65,10 +60,11 @@ function rowActions(row: object): TableAction[] {
|
||||
}
|
||||
|
||||
function statusLabel(detail: MerchantCustomsContainer) {
|
||||
return ({ WAITING_CUSTOMER_DOCUMENTS: '待上传资料', READY_TO_START: '待报关', DECLARING: '报关中', COMPLETED: '报关完成' } as Record<string, string>)[detail.status] ?? detail.status
|
||||
return detail.merchantStatus ? MERCHANT_CUSTOMS_STATUS_LABELS[detail.merchantStatus] ?? '—' : '—'
|
||||
}
|
||||
function statusType(detail: MerchantCustomsContainer) {
|
||||
return detail.status === 'COMPLETED' ? 'success' : detail.status === 'DECLARING' ? 'primary' : 'warning'
|
||||
return detail.merchantStatus === 'CUSTOMS_COMPLETED' || detail.merchantStatus === 'DOCUMENT_UPLOADED'
|
||||
? 'success' : detail.merchantStatus === 'DECLARING' ? 'primary' : detail.merchantStatus === 'WAITING_UPLOAD' ? 'warning' : 'info'
|
||||
}
|
||||
async function load() {
|
||||
if (loading.value) return
|
||||
|
||||
Reference in New Issue
Block a user