优化
This commit is contained in:
@@ -19,11 +19,11 @@ const detail = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/co
|
||||
|
||||
class ResizeObserverStub { observe() {}; unobserve() {}; disconnect() {} }
|
||||
function memoryStorage(): Storage { const values = new Map<string, string>(); return { get length() { return values.size }, clear: () => values.clear(), getItem: (key) => values.get(key) ?? null, key: (index) => [...values.keys()][index] ?? null, removeItem: (key) => values.delete(key), setItem: (key, value) => values.set(key, value) } }
|
||||
async function mountPanel(permissions: string[], customers = [{ accountId: '11', customerName: '客户A' }]) {
|
||||
async function mountPanel(permissions: string[], customers = [{ accountId: '11', customerName: '客户A' }], containerBookingId = '99') {
|
||||
const pinia = createPinia(); setActivePinia(pinia)
|
||||
const auth = useAuthStore(); auth.token = 'token'; auth.permissions = permissions
|
||||
useAccessPreviewStore().initializeAfterLogin()
|
||||
const wrapper = mount(ContainerFeesPanel, { attachTo: document.body, props: { planId: '9', customers }, global: { plugins: [pinia, ElementPlus] } })
|
||||
const wrapper = mount(ContainerFeesPanel, { attachTo: document.body, props: { planId: '9', containerBookingId, customers }, global: { plugins: [pinia, ElementPlus] } })
|
||||
await flushPromises(); return wrapper
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ beforeEach(() => {
|
||||
|
||||
describe('ContainerFeesPanel contract', () => {
|
||||
it('is exposed from loading-list detail and derives customer context from real items', () => {
|
||||
expect(detail).toContain('<ContainerFeesPanel :plan-id="summary.planId" :customers="feeCustomers" />')
|
||||
expect(detail).toContain(':container-booking-id="summary.containerBookingId"')
|
||||
expect(detail).toContain('item.accountId')
|
||||
expect(panel).toContain('该柜次包含多个客户,请先选择费用归属客户')
|
||||
expect(panel).toContain('装柜明细缺少客户归属,无法安全维护费用')
|
||||
@@ -79,6 +79,29 @@ describe('ContainerFeesPanel contract', () => {
|
||||
expect(listContainerCostDetails).not.toHaveBeenCalledWith('11', expect.anything())
|
||||
})
|
||||
|
||||
it('uses the summary container ID when fee rows are empty', async () => {
|
||||
vi.mocked(listContainerFees).mockResolvedValue([])
|
||||
await mountPanel(['firstleg:inbound:fee-detail:view'])
|
||||
expect(listContainerCostDetails).toHaveBeenCalledWith('11', '99')
|
||||
})
|
||||
|
||||
it('uses the summary container ID when fee rows agree', async () => {
|
||||
vi.mocked(listContainerFees).mockResolvedValue([
|
||||
{ id: '1', loadingPlanId: '9', containerBookingId: '99', containerNo: null, feeItemId: '1', feeItemName: '费用A', direction: 'RECEIVABLE', partnerId: null, partnerName: null, businessDate: '2026-08-12', amount: 10, currency: 'CNY', settlementIncluded: true, reviewStatus: 'PENDING', sourceStatus: 'ACTIVE', sourceVersion: 1, remark: null, reviewerName: null, reviewedAt: null, reviewReason: null },
|
||||
])
|
||||
await mountPanel(['firstleg:inbound:fee-detail:view'])
|
||||
expect(listContainerCostDetails).toHaveBeenCalledWith('11', '99')
|
||||
})
|
||||
|
||||
it('fails closed when fee rows disagree with the summary container', async () => {
|
||||
vi.mocked(listContainerFees).mockResolvedValue([
|
||||
{ id: '2', loadingPlanId: '9', containerBookingId: '100', containerNo: null, feeItemId: '2', feeItemName: '费用B', direction: 'PAYABLE', partnerId: '41', partnerName: '承运商A', businessDate: '2026-08-12', amount: 20, currency: 'CNY', settlementIncluded: true, reviewStatus: 'PENDING', sourceStatus: 'ACTIVE', sourceVersion: 1, remark: null, reviewerName: null, reviewedAt: null, reviewReason: null },
|
||||
])
|
||||
const wrapper = await mountPanel(['firstleg:inbound:fee-detail:view', 'firstleg:fee-item:view'])
|
||||
expect(wrapper.text()).toContain('柜次费用加载失败')
|
||||
expect(listContainerCostDetails).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('blocks write entry points without a selected customer or edit permission', async () => {
|
||||
const wrapper = await mountPanel(['firstleg:inbound:fee-detail:view'], [{ accountId: '11', customerName: '客户A' }, { accountId: '22', customerName: '客户B' }])
|
||||
const state = (wrapper.vm.$ as unknown as { setupState: { openCreate(): void; save(): Promise<void>; editorOpen: boolean } }).setupState
|
||||
|
||||
@@ -11,7 +11,7 @@ import StatusBadge from '@/shared/components/StatusBadge.vue'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import { adjustContainerFee, approveContainerFee, createContainerFee, listApplicableFeeItems, listContainerCostDetails, listContainerFeePartnerOptions, listContainerFees, rejectContainerFee, voidContainerFee, type ApplicableFeeItem, type ContainerCostDetail, type ContainerFeePartnerOption, type ContainerFeeResource, type ContainerFeeSaveRequest } from '../api/containerFees'
|
||||
|
||||
const props = defineProps<{ planId: string; customers: Array<{ accountId: string; customerName: string | null }> }>()
|
||||
const props = defineProps<{ planId: string; containerBookingId: string; customers: Array<{ accountId: string; customerName: string | null }> }>()
|
||||
const access = useAccessPreviewStore()
|
||||
const accountId = ref('')
|
||||
const fees = ref<ContainerFeeResource[]>([])
|
||||
@@ -46,7 +46,6 @@ const customerOptions = computed(() => {
|
||||
const selectedCustomer = computed(() => customerOptions.value.find((item) => item.value === accountId.value))
|
||||
const applicableItems = computed(() => feeItems.value.filter((item) => form.direction === 'RECEIVABLE'
|
||||
? item.warehouseReceivable || item.pickupReceivable : item.warehousePayable || item.pickupPayable))
|
||||
const containerId = computed(() => fees.value.find((item) => item.containerBookingId)?.containerBookingId || null)
|
||||
const columns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', fixed: 'left', width: 300, slotName: 'actions', hideable: false, movable: false },
|
||||
{ key: 'feeItemName', prop: 'feeItemName', label: '费用项目', minWidth: 150 },
|
||||
@@ -102,7 +101,9 @@ async function load() {
|
||||
if (sequence !== loadSequence || requestedAccountId !== accountId.value) return
|
||||
fees.value = feeRows
|
||||
costs.value = []
|
||||
const currentContainerId = feeRows.find((item) => item.containerBookingId)?.containerBookingId
|
||||
const containerIds = [...new Set(feeRows.map((item) => item.containerBookingId).filter((value): value is string => Boolean(value)))]
|
||||
if (containerIds.some((value) => value !== props.containerBookingId)) throw new Error('柜次费用与装柜摘要的柜次标识不一致,已停止查询财务分摊')
|
||||
const currentContainerId = props.containerBookingId
|
||||
if (currentContainerId) {
|
||||
const result = await listContainerCostDetails(requestedAccountId, currentContainerId)
|
||||
if (sequence !== loadSequence || requestedAccountId !== accountId.value) return
|
||||
|
||||
@@ -119,7 +119,7 @@ function formatNumber(value?: number | null, digits = 3) {
|
||||
</DataTable>
|
||||
</section>
|
||||
|
||||
<ContainerFeesPanel :plan-id="summary.planId" :customers="feeCustomers" />
|
||||
<ContainerFeesPanel :plan-id="summary.planId" :container-booking-id="summary.containerBookingId" :customers="feeCustomers" />
|
||||
|
||||
<section class="history-section surface-panel" aria-label="操作历史">
|
||||
<header><h2>操作历史</h2><span>{{ detail.histories.length }} 条</span></header>
|
||||
|
||||
Reference in New Issue
Block a user