Merge branch 'release/test' of http://git.buggoglobal.com/html/admin-web into dev
This commit is contained in:
@@ -113,6 +113,9 @@ describe('LoadingListsView', () => {
|
|||||||
expect(source).toContain("router.replace({ name: 'loading-list-detail'")
|
expect(source).toContain("router.replace({ name: 'loading-list-detail'")
|
||||||
expect(confirmedDetailSource).toContain("result.summary.status !== 'COMPLETED'")
|
expect(confirmedDetailSource).toContain("result.summary.status !== 'COMPLETED'")
|
||||||
expect(confirmedDetailSource).toContain('MODE_STATUS_MISMATCH')
|
expect(confirmedDetailSource).toContain('MODE_STATUS_MISMATCH')
|
||||||
|
expect(confirmedDetailSource).toContain('watch([planId, warehouseId], () => void loadDetail())')
|
||||||
|
expect(confirmedDetailSource).toContain('if (sequence !== loadSequence) return')
|
||||||
|
expect(preloadDetailSource).toContain('watch([planId, warehouseId], () => void loadDetail())')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('uses backend readiness for whole-plan confirmation and translates blocking reasons', () => {
|
it('uses backend readiness for whole-plan confirmation and translates blocking reasons', () => {
|
||||||
@@ -139,8 +142,8 @@ describe('LoadingListsView', () => {
|
|||||||
expect(source).toContain('flex-wrap: wrap')
|
expect(source).toContain('flex-wrap: wrap')
|
||||||
expect(source).toContain('@media (max-width: 720px)')
|
expect(source).toContain('@media (max-width: 720px)')
|
||||||
expect(source).toContain('.row-actions { gap: 4px; }')
|
expect(source).toContain('.row-actions { gap: 4px; }')
|
||||||
expect(source).toContain(':loading="loading"\n stripe')
|
expect(source).toMatch(/:loading="loading"\s+stripe/)
|
||||||
expect(detailSource).toContain(':columns="itemColumns"\n stripe')
|
expect(detailSource).toMatch(/:columns="itemColumns"\s+stripe/)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows every prototype detail field that is backed by the loading item contract', () => {
|
it('shows every prototype detail field that is backed by the loading item contract', () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { FileUp } from '@lucide/vue'
|
import { FileUp } from '@lucide/vue'
|
||||||
import { ElMessage, type UploadFile } from 'element-plus'
|
import { ElMessage, type UploadFile } from 'element-plus'
|
||||||
@@ -28,13 +28,16 @@ const customsDialogOpen = ref(false)
|
|||||||
const customsFile = ref<File | null>(null)
|
const customsFile = ref<File | null>(null)
|
||||||
const customsSaving = ref(false)
|
const customsSaving = ref(false)
|
||||||
const customsError = ref<ApiErrorDetails | null>(null)
|
const customsError = ref<ApiErrorDetails | null>(null)
|
||||||
|
let loadSequence = 0
|
||||||
const planId = computed(() => String(route.params.planId || ''))
|
const planId = computed(() => String(route.params.planId || ''))
|
||||||
const warehouseId = computed(() => typeof route.query.warehouseId === 'string' ? route.query.warehouseId : '')
|
const warehouseId = computed(() => typeof route.query.warehouseId === 'string' ? route.query.warehouseId : '')
|
||||||
const canManageCustoms = computed(() => access.can('platform:firstleg:customs:manage'))
|
const canManageCustoms = computed(() => access.can('platform:firstleg:customs:manage'))
|
||||||
|
|
||||||
async function loadDetail() {
|
async function loadDetail() {
|
||||||
|
const sequence = ++loadSequence
|
||||||
|
detail.value = null
|
||||||
if (!planId.value || !warehouseId.value) {
|
if (!planId.value || !warehouseId.value) {
|
||||||
detail.value = null
|
loading.value = false
|
||||||
error.value = { code: 'MISSING_WAREHOUSE_CONTEXT', message: '该清单缺少所属仓库信息,无法安全查询详情,请返回列表重新进入。' }
|
error.value = { code: 'MISSING_WAREHOUSE_CONTEXT', message: '该清单缺少所属仓库信息,无法安全查询详情,请返回列表重新进入。' }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -49,17 +52,18 @@ async function loadDetail() {
|
|||||||
throw cause
|
throw cause
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
if (sequence !== loadSequence) return
|
||||||
if (result.summary.status !== 'COMPLETED') {
|
if (result.summary.status !== 'COMPLETED') {
|
||||||
detail.value = null
|
|
||||||
error.value = { code: 'MODE_STATUS_MISMATCH', message: '该计划尚未形成正式装柜清单,请从预装清单入口查看。' }
|
error.value = { code: 'MODE_STATUS_MISMATCH', message: '该计划尚未形成正式装柜清单,请从预装清单入口查看。' }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
detail.value = { ...result, customsDocument }
|
detail.value = { ...result, customsDocument }
|
||||||
} catch (cause) {
|
} catch (cause) {
|
||||||
|
if (sequence !== loadSequence) return
|
||||||
detail.value = null
|
detail.value = null
|
||||||
error.value = getApiErrorDetails(cause, '装柜清单详情加载失败,请稍后重试')
|
error.value = getApiErrorDetails(cause, '装柜清单详情加载失败,请稍后重试')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
if (sequence === loadSequence) loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +133,7 @@ async function completeCustomsDocument() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => void loadDetail())
|
onMounted(() => void loadDetail())
|
||||||
|
watch([planId, warehouseId], () => void loadDetail())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, reactive, ref } from 'vue'
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||||
@@ -33,6 +33,7 @@ const remarkError = ref<ApiErrorDetails | null>(null)
|
|||||||
const remarkTarget = ref<LoadingItem | null>(null)
|
const remarkTarget = ref<LoadingItem | null>(null)
|
||||||
const warehouseRemark = ref('')
|
const warehouseRemark = ref('')
|
||||||
const remarkOperationId = ref('')
|
const remarkOperationId = ref('')
|
||||||
|
let loadSequence = 0
|
||||||
const planId = computed(() => String(route.params.planId || ''))
|
const planId = computed(() => String(route.params.planId || ''))
|
||||||
const warehouseId = computed(() => typeof route.query.warehouseId === 'string' ? route.query.warehouseId : '')
|
const warehouseId = computed(() => typeof route.query.warehouseId === 'string' ? route.query.warehouseId : '')
|
||||||
const canEdit = computed(() => access.can('platform:firstleg:preload:edit'))
|
const canEdit = computed(() => access.can('platform:firstleg:preload:edit'))
|
||||||
@@ -47,7 +48,10 @@ const editRules: FormRules = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadDetail() {
|
async function loadDetail() {
|
||||||
|
const sequence = ++loadSequence
|
||||||
|
detail.value = null
|
||||||
if (!planId.value || !warehouseId.value) {
|
if (!planId.value || !warehouseId.value) {
|
||||||
|
loading.value = false
|
||||||
error.value = { code: 'MISSING_WAREHOUSE_CONTEXT', message: '该清单缺少所属仓库信息,无法安全查询详情,请返回列表重新进入。' }
|
error.value = { code: 'MISSING_WAREHOUSE_CONTEXT', message: '该清单缺少所属仓库信息,无法安全查询详情,请返回列表重新进入。' }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -55,6 +59,7 @@ async function loadDetail() {
|
|||||||
error.value = null
|
error.value = null
|
||||||
try {
|
try {
|
||||||
const result = await getPreloadPlan(planId.value, warehouseId.value)
|
const result = await getPreloadPlan(planId.value, warehouseId.value)
|
||||||
|
if (sequence !== loadSequence) return
|
||||||
if (!['EDITING', 'LOADING'].includes(result.summary.status)) {
|
if (!['EDITING', 'LOADING'].includes(result.summary.status)) {
|
||||||
detail.value = null
|
detail.value = null
|
||||||
error.value = { code: 'MODE_STATUS_MISMATCH', message: '该计划不属于预装清单状态,请从对应清单入口查看。' }
|
error.value = { code: 'MODE_STATUS_MISMATCH', message: '该计划不属于预装清单状态,请从对应清单入口查看。' }
|
||||||
@@ -62,9 +67,10 @@ async function loadDetail() {
|
|||||||
}
|
}
|
||||||
detail.value = result
|
detail.value = result
|
||||||
} catch (cause) {
|
} catch (cause) {
|
||||||
|
if (sequence !== loadSequence) return
|
||||||
error.value = getApiErrorDetails(cause, '预装清单详情加载失败,请稍后重试')
|
error.value = getApiErrorDetails(cause, '预装清单详情加载失败,请稍后重试')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
if (sequence === loadSequence) loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,6 +189,7 @@ async function submitConfirmPreload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => void loadDetail())
|
onMounted(() => void loadDetail())
|
||||||
|
watch([planId, warehouseId], () => void loadDetail())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ describe('InboundFeeDetailManager', () => {
|
|||||||
const second = wrapper.findAll('.el-table__row')[1]!.text()
|
const second = wrapper.findAll('.el-table__row')[1]!.text()
|
||||||
const secondButtons = wrapper.findAll('.el-table__row')[1]!.findAll('button').map((button) => button.text())
|
const secondButtons = wrapper.findAll('.el-table__row')[1]!.findAll('button').map((button) => button.text())
|
||||||
expect(first).toContain('编辑'); expect(first).toContain('审核'); expect(first).toContain('驳回')
|
expect(first).toContain('编辑'); expect(first).toContain('审核'); expect(first).toContain('驳回')
|
||||||
|
expect(first).toContain('草稿')
|
||||||
|
expect(second).toContain('已发布')
|
||||||
expect(second).toContain('调整'); expect(second).toContain('作废'); expect(secondButtons).not.toContain('审核')
|
expect(second).toContain('调整'); expect(second).toContain('作废'); expect(secondButtons).not.toContain('审核')
|
||||||
expect(wrapper.find('th').text()).toContain('操作')
|
expect(wrapper.find('th').text()).toContain('操作')
|
||||||
expect(source).not.toMatch(/<el-button[^>]+link[^>]+(?:编辑|审核|驳回|调整|作废)/)
|
expect(source).not.toMatch(/<el-button[^>]+link[^>]+(?:编辑|审核|驳回|调整|作废)/)
|
||||||
@@ -244,7 +246,11 @@ describe('InboundFeeDetailManager', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('refreshes fee rows and the order detail after every successful write', () => {
|
it('refreshes fee rows and the order detail after every successful write', () => {
|
||||||
|
<<<<<<< HEAD
|
||||||
expect(source).toMatch(/async function finishAction\(message: string\) \{\r?\n await load\(\); emit\('changed'\)/)
|
expect(source).toMatch(/async function finishAction\(message: string\) \{\r?\n await load\(\); emit\('changed'\)/)
|
||||||
|
=======
|
||||||
|
expect(source).toMatch(/async function finishAction\(message: string\)\s*\{\s*await load\(\); emit\('changed'\)/)
|
||||||
|
>>>>>>> a67f6300f8a7a5225657bc9c9be8ee716f97ccf6
|
||||||
expect(source).toMatch(/createInboundFeeDetail[\s\S]*updateInboundFeeDetail[\s\S]*adjustInboundFeeDetail[\s\S]*await finishAction\(/)
|
expect(source).toMatch(/createInboundFeeDetail[\s\S]*updateInboundFeeDetail[\s\S]*adjustInboundFeeDetail[\s\S]*await finishAction\(/)
|
||||||
expect(source).toMatch(/deleteInboundFeeDetail[\s\S]*await finishAction\('费用明细已删除'\)/)
|
expect(source).toMatch(/deleteInboundFeeDetail[\s\S]*await finishAction\('费用明细已删除'\)/)
|
||||||
expect(source).toMatch(/reviewInboundFeeDetail[\s\S]*await finishAction\('费用审核通过'\)/)
|
expect(source).toMatch(/reviewInboundFeeDetail[\s\S]*await finishAction\('费用审核通过'\)/)
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ function canReview(row: InboundFeeDetail) { return isManual(row) && access.can('
|
|||||||
function canAdjust(row: InboundFeeDetail) { return isManual(row) && access.can('firstleg:inbound:fee-detail:edit') && row.reviewStatus === 'APPROVED' && row.publicationStatus !== 'VOIDED' }
|
function canAdjust(row: InboundFeeDetail) { return isManual(row) && access.can('firstleg:inbound:fee-detail:edit') && row.reviewStatus === 'APPROVED' && row.publicationStatus !== 'VOIDED' }
|
||||||
function canVoid(row: InboundFeeDetail) { return isManual(row) && access.can('firstleg:inbound:fee-detail:review') && row.reviewStatus === 'APPROVED' && row.publicationStatus !== 'VOIDED' }
|
function canVoid(row: InboundFeeDetail) { return isManual(row) && access.can('firstleg:inbound:fee-detail:review') && row.reviewStatus === 'APPROVED' && row.publicationStatus !== 'VOIDED' }
|
||||||
function reviewLabel(value: string) { return ({ PENDING: '待审核', APPROVED: '已审核', REJECTED: '已驳回' } as Record<string, string>)[value] || value }
|
function reviewLabel(value: string) { return ({ PENDING: '待审核', APPROVED: '已审核', REJECTED: '已驳回' } as Record<string, string>)[value] || value }
|
||||||
|
function publicationLabel(value: string) {
|
||||||
|
return ({ DRAFT: '草稿', PUBLISHED: '已发布', SUPERSEDED: '已被替代', VOIDED: '已作废' } as Record<string, string>)[value] || value || '未知状态'
|
||||||
|
}
|
||||||
function directionLabel(value: string) { return value === 'RECEIVABLE' ? '应收' : '应付' }
|
function directionLabel(value: string) { return value === 'RECEIVABLE' ? '应收' : '应付' }
|
||||||
function settlementLabel(row: InboundFeeDetail) { return row.direction !== 'PAYABLE' ? '—' : row.settlementMode === 'DIRECT_PAID' ? '已付成本' : row.settlementMode === 'ON_ACCOUNT' ? '账期应付' : '未设置' }
|
function settlementLabel(row: InboundFeeDetail) { return row.direction !== 'PAYABLE' ? '—' : row.settlementMode === 'DIRECT_PAID' ? '已付成本' : row.settlementMode === 'ON_ACCOUNT' ? '账期应付' : '未设置' }
|
||||||
function paymentMethodLabel(value: string | null | undefined) { return ({ ONLINE: '线上支付', BANK_TRANSFER: '银行转账', CASH: '现金', OTHER: '其他' } as Record<string, string>)[value || ''] || '—' }
|
function paymentMethodLabel(value: string | null | undefined) { return ({ ONLINE: '线上支付', BANK_TRANSFER: '银行转账', CASH: '现金', OTHER: '其他' } as Record<string, string>)[value || ''] || '—' }
|
||||||
@@ -302,7 +305,7 @@ onMounted(() => void load())
|
|||||||
<el-table-column label="金额" min-width="120"><template #default="{ row }">{{ Number(row.amount).toFixed(2) }}</template></el-table-column>
|
<el-table-column label="金额" min-width="120"><template #default="{ row }">{{ Number(row.amount).toFixed(2) }}</template></el-table-column>
|
||||||
<el-table-column label="单价" min-width="110"><template #default="{ row }">{{ Number(row.unitPrice).toFixed(4) }}</template></el-table-column>
|
<el-table-column label="单价" min-width="110"><template #default="{ row }">{{ Number(row.unitPrice).toFixed(4) }}</template></el-table-column>
|
||||||
<el-table-column prop="sourceFeeVersion" label="版本" width="85" />
|
<el-table-column prop="sourceFeeVersion" label="版本" width="85" />
|
||||||
<el-table-column prop="publicationStatus" label="发布状态" min-width="110" />
|
<el-table-column label="发布状态" min-width="110"><template #default="{ row }"><StatusBadge :status="publicationLabel(row.publicationStatus)" /></template></el-table-column>
|
||||||
<el-table-column prop="reviewReason" label="驳回原因" min-width="180" show-overflow-tooltip />
|
<el-table-column prop="reviewReason" label="驳回原因" min-width="180" show-overflow-tooltip />
|
||||||
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
|
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|||||||
Reference in New Issue
Block a user