发布当前测试版本至临时灰度环境 #12

Merged
admin merged 66 commits from release/test into main 2026-10-07 11:11:53 +08:00
4 changed files with 47 additions and 7 deletions
Showing only changes of commit 6bada2aca9 - Show all commits
+1 -1
View File
@@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
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 developmentCompose = readFileSync(resolve(process.cwd(), 'docker-compose.development.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 ElementPlus from 'element-plus'
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(() => ({
get: vi.fn(),
@@ -77,6 +77,42 @@ describe('ContainerCustomsPanel', () => {
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 () => {
const wrapper = await render('READY_TO_START')
expect(wrapper.text()).toContain('开始报关')
@@ -59,6 +59,10 @@ const clearanceResultOptions: Array<{ value: ClearanceResultType; label: string
{ 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 missingCoverageCount = computed(() => Math.max(0, (customs.value?.taxRefundCount || 0) - (customs.value?.coveredTaxRefundCount || 0)))
const allBuy = computed(() => customs.value?.hasActualCargo === true && customs.value.taxRefundCount === 0)
@@ -152,8 +156,8 @@ async function recordFormalResult() {
try {
await recordClearanceResult(batch, clearanceResultType.value, clearanceRemark.value.trim() || undefined)
clearanceDialogVisible.value = false
await load(); ElMessage.success('海关异常节点已标记')
} catch (error) { actionError.value = getApiErrorDetails(error, '海关异常节点标记失败') }
await load(); ElMessage.success('海关结果已记录')
} catch (error) { actionError.value = getApiErrorDetails(error, '海关结果记录失败') }
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>
<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 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>
</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>
</template>
<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-item label="选择理由" required>
<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 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', () => ({
listPlatformFeeItems: vi.fn(),