fix(admin): clarify customs results and apply dialog density
This commit is contained in:
@@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs'
|
|||||||
import { resolve } from 'node:path'
|
import { resolve } from 'node:path'
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
const config = readFileSync(resolve(process.cwd(), 'nginx/default.conf.template'), 'utf8')
|
const config = readFileSync(resolve(process.cwd(), 'nginx/default.conf.template'), 'utf8').replace(/\r\n/g, '\n')
|
||||||
const testCompose = readFileSync(resolve(process.cwd(), 'docker-compose.test.yml'), 'utf8')
|
const testCompose = readFileSync(resolve(process.cwd(), 'docker-compose.test.yml'), 'utf8')
|
||||||
const developmentCompose = readFileSync(resolve(process.cwd(), 'docker-compose.development.yml'), 'utf8')
|
const developmentCompose = readFileSync(resolve(process.cwd(), 'docker-compose.development.yml'), 'utf8')
|
||||||
const productionCompose = readFileSync(resolve(process.cwd(), 'docker-compose.production.yml'), 'utf8')
|
const productionCompose = readFileSync(resolve(process.cwd(), 'docker-compose.production.yml'), 'utf8')
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|||||||
import { flushPromises, mount } from '@vue/test-utils'
|
import { flushPromises, mount } from '@vue/test-utils'
|
||||||
import ElementPlus from 'element-plus'
|
import ElementPlus from 'element-plus'
|
||||||
import { ApiResponseError } from '@/shared/api/http'
|
import { ApiResponseError } from '@/shared/api/http'
|
||||||
import type { ContainerCustomsStatus, ContainerCustomsView } from '../api/platformContainerCustoms'
|
import type { ContainerCustomsStatus, ContainerCustomsView, DeclarationBatch, ClearanceResultType } from '../api/platformContainerCustoms'
|
||||||
|
|
||||||
const api = vi.hoisted(() => ({
|
const api = vi.hoisted(() => ({
|
||||||
get: vi.fn(),
|
get: vi.fn(),
|
||||||
@@ -77,6 +77,42 @@ describe('ContainerCustomsPanel', () => {
|
|||||||
vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => undefined)
|
vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('renders all customs result reasons in Chinese with the recorded remarks', async () => {
|
||||||
|
const reasons: Array<[ClearanceResultType, string]> = [
|
||||||
|
['INSPECTION', '查验'], ['SUPPLEMENT_REQUIRED', '补资料'],
|
||||||
|
['DETAINED', '扣货'], ['RELEASED', '放行'], ['OTHER', '其他'],
|
||||||
|
]
|
||||||
|
const batch: DeclarationBatch = {
|
||||||
|
id: '101', customsContainerId: '102', loadingPlanId: '2092438843630391402',
|
||||||
|
batchNo: 'DEC-TEST-001', status: 'RESULT_PENDING', sourceVersion: 1,
|
||||||
|
customsEntryNo: 'ENTRY-TEST-001', version: 1, submittedAt: null,
|
||||||
|
submittedByName: null, acknowledgedAt: null, acknowledgedByName: null, remark: null,
|
||||||
|
results: reasons.map(([resultType], index) => ({
|
||||||
|
id: String(200 + index), declarationBatchId: '101', customsContainerId: '102',
|
||||||
|
resultType, resultStatus: 'OPEN', customsEntryNo: 'ENTRY-TEST-001',
|
||||||
|
occurredAt: '2026-09-29T10:00:00', resolution: `人工备注${index}`,
|
||||||
|
evidenceFileId: null, recordedByName: '操作员', recordedAt: '2026-09-29T10:00:00',
|
||||||
|
previousResultId: null,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
api.getDeclarationBatches.mockResolvedValue([batch])
|
||||||
|
api.get.mockResolvedValue({ ...customs('COMPLETED', true), brokerSubmittedAt: '2026-09-29T09:00:00' })
|
||||||
|
const wrapper = mount(ContainerCustomsPanel, {
|
||||||
|
props: { loadingPlanId: batch.loadingPlanId, canList: true, canManage: false, departureState: 'WAITING_OTHER_GATES' },
|
||||||
|
global: { plugins: [ElementPlus] },
|
||||||
|
})
|
||||||
|
await flushPromises()
|
||||||
|
const rows = wrapper.findAll('.declaration-result-row')
|
||||||
|
expect(rows).toHaveLength(reasons.length)
|
||||||
|
for (const [index, [code, label]] of reasons.entries()) {
|
||||||
|
expect(rows[index]!.text()).toContain(label)
|
||||||
|
expect(rows[index]!.text()).toContain(`人工备注${index}`)
|
||||||
|
expect(rows[index]!.text()).not.toContain(code)
|
||||||
|
expect(rows[index]!.find('button').exists()).toBe(false)
|
||||||
|
}
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
it('keeps READY_TO_START read-only while exposing source file name and download', async () => {
|
it('keeps READY_TO_START read-only while exposing source file name and download', async () => {
|
||||||
const wrapper = await render('READY_TO_START')
|
const wrapper = await render('READY_TO_START')
|
||||||
expect(wrapper.text()).toContain('开始报关')
|
expect(wrapper.text()).toContain('开始报关')
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ const clearanceResultOptions: Array<{ value: ClearanceResultType; label: string
|
|||||||
{ value: 'OTHER', label: '其他' },
|
{ value: 'OTHER', label: '其他' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
function clearanceResultLabel(value: ClearanceResultType) {
|
||||||
|
return clearanceResultOptions.find((option) => option.value === value)?.label || '未知结果'
|
||||||
|
}
|
||||||
|
|
||||||
const missingInbounds = computed(() => customs.value?.inbounds.filter((item) => item.taxMode === 'TAX_REFUND' && !item.covered) || [])
|
const missingInbounds = computed(() => customs.value?.inbounds.filter((item) => item.taxMode === 'TAX_REFUND' && !item.covered) || [])
|
||||||
const missingCoverageCount = computed(() => Math.max(0, (customs.value?.taxRefundCount || 0) - (customs.value?.coveredTaxRefundCount || 0)))
|
const missingCoverageCount = computed(() => Math.max(0, (customs.value?.taxRefundCount || 0) - (customs.value?.coveredTaxRefundCount || 0)))
|
||||||
const allBuy = computed(() => customs.value?.hasActualCargo === true && customs.value.taxRefundCount === 0)
|
const allBuy = computed(() => customs.value?.hasActualCargo === true && customs.value.taxRefundCount === 0)
|
||||||
@@ -152,8 +156,8 @@ async function recordFormalResult() {
|
|||||||
try {
|
try {
|
||||||
await recordClearanceResult(batch, clearanceResultType.value, clearanceRemark.value.trim() || undefined)
|
await recordClearanceResult(batch, clearanceResultType.value, clearanceRemark.value.trim() || undefined)
|
||||||
clearanceDialogVisible.value = false
|
clearanceDialogVisible.value = false
|
||||||
await load(); ElMessage.success('海关异常节点已标记')
|
await load(); ElMessage.success('海关结果已记录')
|
||||||
} catch (error) { actionError.value = getApiErrorDetails(error, '海关异常节点标记失败') }
|
} catch (error) { actionError.value = getApiErrorDetails(error, '海关结果记录失败') }
|
||||||
finally { actionLoading.value = false }
|
finally { actionLoading.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +333,7 @@ onMounted(load)
|
|||||||
<div class="declaration-batch-main"><strong>{{ batch.batchNo }}</strong><span>报关单号:{{ batch.customsEntryNo || '—' }} · 版本 {{ batch.version }}</span></div>
|
<div class="declaration-batch-main"><strong>{{ batch.batchNo }}</strong><span>报关单号:{{ batch.customsEntryNo || '—' }} · 版本 {{ batch.version }}</span></div>
|
||||||
<el-tag size="small" :type="batch.status === 'CLOSED' ? 'success' : batch.status === 'CANCELLED' ? 'info' : 'warning'">{{ declarationStatusLabel(batch.status) }}</el-tag>
|
<el-tag size="small" :type="batch.status === 'CLOSED' ? 'success' : batch.status === 'CANCELLED' ? 'info' : 'warning'">{{ declarationStatusLabel(batch.status) }}</el-tag>
|
||||||
<div class="declaration-batch-actions" v-if="canManage"><el-button v-if="batch.status === 'READY'" size="small" type="primary" :loading="actionLoading" @click="submitFormalDeclaration(batch)">提交正式申报</el-button><el-button v-if="['SUBMITTED','ACKNOWLEDGED','RESULT_PENDING'].includes(batch.status)" size="small" plain :loading="actionLoading" @click="openFormalResultDialog(batch)">标记海关异常/结果</el-button></div>
|
<div class="declaration-batch-actions" v-if="canManage"><el-button v-if="batch.status === 'READY'" size="small" type="primary" :loading="actionLoading" @click="submitFormalDeclaration(batch)">提交正式申报</el-button><el-button v-if="['SUBMITTED','ACKNOWLEDGED','RESULT_PENDING'].includes(batch.status)" size="small" plain :loading="actionLoading" @click="openFormalResultDialog(batch)">标记海关异常/结果</el-button></div>
|
||||||
<div v-if="batch.results.length" class="declaration-result-list"><div v-for="result in batch.results" :key="result.id" class="declaration-result-row"><span>{{ result.resultType === 'INSPECTION' ? '查验' : result.resultType === 'SUPPLEMENT_REQUIRED' ? '补充资料' : result.resultType === 'RELEASED' ? '放行' : result.resultType }}</span><el-tag size="small" :type="result.resultStatus === 'RESOLVED' ? 'success' : 'warning'">{{ result.resultStatus === 'RESOLVED' ? '已处理' : result.resultStatus === 'OPEN' ? '待处理' : '已取消' }}</el-tag><small>{{ result.resolution || '—' }}</small><el-button v-if="canManage && result.resultStatus === 'OPEN'" link type="primary" size="small" :loading="actionLoading" @click="resolveFormalResult(batch, result)">处理</el-button></div></div>
|
<div v-if="batch.results.length" class="declaration-result-list"><div v-for="result in batch.results" :key="result.id" class="declaration-result-row"><span>{{ clearanceResultLabel(result.resultType) }}</span><el-tag size="small" :type="result.resultStatus === 'RESOLVED' ? 'success' : 'warning'">{{ result.resultStatus === 'RESOLVED' ? '已处理' : result.resultStatus === 'OPEN' ? '待处理' : '已取消' }}</el-tag><small>{{ result.resolution || '—' }}</small><el-button v-if="canManage && result.resultStatus === 'OPEN'" link type="primary" size="small" :loading="actionLoading" @click="resolveFormalResult(batch, result)">处理</el-button></div></div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -337,7 +341,7 @@ onMounted(load)
|
|||||||
<div v-if="customs.status === 'COMPLETED'" class="departure-state"><strong>国内离仓状态:</strong><el-tag :type="departureState === 'DEPARTED' ? 'success' : 'warning'">{{ departureState === 'DEPARTED' ? '已自动离仓' : departureState === 'UNKNOWN' ? '离仓状态尚未读取' : '报关已完成,待其他离仓条件' }}</el-tag></div>
|
<div v-if="customs.status === 'COMPLETED'" class="departure-state"><strong>国内离仓状态:</strong><el-tag :type="departureState === 'DEPARTED' ? 'success' : 'warning'">{{ departureState === 'DEPARTED' ? '已自动离仓' : departureState === 'UNKNOWN' ? '离仓状态尚未读取' : '报关已完成,待其他离仓条件' }}</el-tag></div>
|
||||||
</template>
|
</template>
|
||||||
<el-empty v-else-if="canList" description="当前装柜计划尚未完成,完成装柜后可处理报关" :image-size="56" />
|
<el-empty v-else-if="canList" description="当前装柜计划尚未完成,完成装柜后可处理报关" :image-size="56" />
|
||||||
<el-dialog v-model="clearanceDialogVisible" title="标记海关异常/结果" width="460px" :close-on-click-modal="false">
|
<el-dialog v-model="clearanceDialogVisible" class="admin-density-dialog" title="标记海关异常/结果" width="460px" :close-on-click-modal="false">
|
||||||
<el-form label-position="top" class="clearance-result-form">
|
<el-form label-position="top" class="clearance-result-form">
|
||||||
<el-form-item label="选择理由" required>
|
<el-form-item label="选择理由" required>
|
||||||
<el-select v-model="clearanceResultType" class="clearance-result-select" placeholder="请选择理由">
|
<el-select v-model="clearanceResultType" class="clearance-result-select" placeholder="请选择理由">
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useAuthStore } from '@/core/auth/auth'
|
|||||||
import { listPlatformFeeItems } from '@/modules/fee-items/api/platformFeeItems'
|
import { listPlatformFeeItems } from '@/modules/fee-items/api/platformFeeItems'
|
||||||
import FeeItemsView from './FeeItemsView.vue'
|
import FeeItemsView from './FeeItemsView.vue'
|
||||||
|
|
||||||
const source = readFileSync(join(process.cwd(), 'src/modules/fee-items/pages/FeeItemsView.vue'), 'utf8')
|
const source = readFileSync(join(process.cwd(), 'src/modules/fee-items/pages/FeeItemsView.vue'), 'utf8').replace(/\r\n/g, '\n')
|
||||||
|
|
||||||
vi.mock('@/modules/fee-items/api/platformFeeItems', () => ({
|
vi.mock('@/modules/fee-items/api/platformFeeItems', () => ({
|
||||||
listPlatformFeeItems: vi.fn(),
|
listPlatformFeeItems: vi.fn(),
|
||||||
|
|||||||
Reference in New Issue
Block a user