This commit is contained in:
wujinliang
2026-08-26 18:13:12 +08:00
parent a67f6300f8
commit 9eec68f7c5
7 changed files with 49 additions and 15 deletions
@@ -57,6 +57,7 @@ describe('FinanceCustomerAccountsView delivery', () => {
expect(page).toContain('待收金额')
expect(page).toContain('待开账金额')
expect(page).toContain('class="ledger-query"')
expect(page).toContain('@search="ledgerPage = 1; void loadLedger($event)"')
expect(page).toContain('class="ledger-table"')
expect(page).toContain('<template #toolbar-actions>')
expect(page).toContain('<template #pagination-left>共 {{ ledgerTotal }} 条</template>')
@@ -280,7 +280,7 @@ watch(() => [route.name, String(route.params.customerId || '')], ([name, custome
<template v-else-if="selected">
<FormSection class="customer-profile-section" title="客户信息" :bordered="false"><el-descriptions :column="3"><el-descriptions-item label="客户编号">{{ customerField(selected.customerCode) }}</el-descriptions-item><el-descriptions-item label="联系人">{{ customerField(selected.contactName) }}</el-descriptions-item><el-descriptions-item label="联系电话">{{ customerField(selected.contactPhone) }}</el-descriptions-item></el-descriptions></FormSection>
<FormSection v-if="!editing" class="ledger-section" title="账户流水" :bordered="false">
<QueryToolbar v-model="ledgerFilters" class="ledger-query" :fields="ledgerQueryFields" ariaLabel="账户流水筛选" preference-key="finance-account-ledger-query" @search="ledgerPage = 1; loadLedger" />
<QueryToolbar v-model="ledgerFilters" class="ledger-query" :fields="ledgerQueryFields" ariaLabel="账户流水筛选" preference-key="finance-account-ledger-query" @search="ledgerPage = 1; void loadLedger($event)" />
<div class="detail-table-scroll"><DataTable v-model:page="ledgerPage" v-model:page-size="ledgerPageSize" class="ledger-table" :data="ledger" :columns="ledgerColumns" :loading="ledgerLoading" :total="ledgerTotal" row-key="ledgerId" empty-text="暂无账户流水" @update:page="loadLedger()" @update:page-size="ledgerPage = 1; loadLedger()"><template #toolbar-actions><el-button v-if="canExport" :loading="ledgerExportBusy" :disabled="ledgerLoading || ledgerExportBusy" @click="exportLedger"><Download :size="16" />导出流水</el-button></template><template #pagination-left>共 {{ ledgerTotal }} 条</template><template #ledgerId="{ row }"><span class="ledger-identifier" :title="displayLedgerId(row.ledgerId)">{{ displayLedgerId(row.ledgerId) }}</span></template><template #occurredAt="{ row }">{{ formatDateTime(row.occurredAt) }}</template><template #businessType="{ row }">{{ ledgerBusinessType(row.businessType) }}</template><template #documentNo="{ row }"><span class="ledger-document" :title="displayDocumentNo(row.documentNo)">{{ displayDocumentNo(row.documentNo) }}</span></template><template #operatorName="{ row }">{{ displayOperator(row.operatorName) }}</template></DataTable></div>
</FormSection>
<FormSection v-else class="term-section" title="账期设置" :bordered="false"><div class="term-editor"><span class="term-label">账期天数</span><div class="term-control"><el-input-number v-model="creditDays" :min="0" :max="3650" :step="1" /><span class="term-unit">天</span></div></div></FormSection>
@@ -129,6 +129,20 @@ describe('FinanceCustomerArapView G2 operations', () => {
expect(adjustFinanceFeeSource).not.toHaveBeenCalled()
})
it('uses sourceSystem to suppress Firstleg editing when the source type is not in the legacy fallback list', async () => {
const firstlegOtherType = { ...feeSummary, sourceType: 'ORDER_FEE', reviewStatus: 'DRAFT' as const }
vi.mocked(listFinanceFeeSources).mockResolvedValue({ records: [firstlegOtherType], total: 1, pages: 1 })
const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:finance:fee-source:edit'])
await loadRows(wrapper)
const buttonLabels = wrapper.findAll('button').map((button) => button.text())
expect(buttonLabels).not.toContain('缂栬緫')
const vm = wrapper.vm as unknown as { openMutation(row: Record<string, unknown>, mode: 'EDIT'): Promise<void> }
await vm.openMutation(firstlegOtherType as unknown as Record<string, unknown>, 'EDIT')
expect(getFinanceFeeSource).not.toHaveBeenCalled()
expect(editFinanceFeeSource).not.toHaveBeenCalled()
})
it('loads all customers by default and keeps explicit customer filtering', async () => {
const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:customer:list'])
expect(listFinanceFeeSources).toHaveBeenCalledWith(expect.objectContaining({ page: 1, size: 20, customerId: undefined, direction: undefined }))
@@ -25,6 +25,7 @@ import type { FinanceFeeSourceDetail } from '../types'
interface ArapRow {
id: string
feeSourceId: string | null
sourceSystem: string
customerId: string
customerName: string
direction: 'RECEIVABLE' | 'PAYABLE'
@@ -217,7 +218,7 @@ async function load(model: QueryModel = filters.value) {
statementGenerated: String(filters.value.statementGenerated || '') ? String(filters.value.statementGenerated) === 'true' : undefined,
businessDateFrom: range[0], businessDateTo: range[1], keyword: String(filters.value.keyword || '').trim() || undefined,
})
allRows.value = result.records.map((item): ArapRow => ({ id: item.poolItemId, feeSourceId: item.feeSourceId, customerId: item.customerId, customerName: customerName(item.customerId, item.customerName), direction: item.direction, documentNo: feeDocumentLabel(item.direction, item.feeItemName), sourceBusinessId: item.sourceBusinessId, sourceType: item.sourceType, sourceId: sourceSummary(item.sourceType, item.orderId, item.inboundId, item.containerId), orderId: item.orderId || '', inboundId: item.inboundId || '', containerId: item.containerId || '', inboundCode: item.inboundCode, containerNo: item.containerNo, supplierName: item.supplierName, feeName: displayFeeName(item.feeItemName), amount: item.amount, status: item.sourceStatus, occurredAt: item.businessDate, businessDate: item.businessDate, reviewStatus: item.reviewStatus, statementGenerated: item.statementGenerated }))
allRows.value = result.records.map((item): ArapRow => ({ id: item.poolItemId, feeSourceId: item.feeSourceId, sourceSystem: item.sourceSystem, customerId: item.customerId, customerName: customerName(item.customerId, item.customerName), direction: item.direction, documentNo: feeDocumentLabel(item.direction, item.feeItemName), sourceBusinessId: item.sourceBusinessId, sourceType: item.sourceType, sourceId: sourceSummary(item.sourceType, item.orderId, item.inboundId, item.containerId), orderId: item.orderId || '', inboundId: item.inboundId || '', containerId: item.containerId || '', inboundCode: item.inboundCode, containerNo: item.containerNo, supplierName: item.supplierName, feeName: displayFeeName(item.feeItemName), amount: item.amount, status: item.sourceStatus, occurredAt: item.businessDate, businessDate: item.businessDate, reviewStatus: item.reviewStatus, statementGenerated: item.statementGenerated }))
total.value = result.total
} catch (error) { loadError.value = getApiErrorDetails(error, '客户应收应付加载失败,请重试') }
finally { loading.value = false }
@@ -336,7 +337,7 @@ async function restoreFeeSourceDetail(feeSourceId: string) {
detailOpen.value = true; detailLoading.value = true; detailError.value = null; detail.value = null
try {
detail.value = await getFinanceFeeSource(feeSourceId)
selected.value = { id: detail.value.poolItemId, feeSourceId, customerId: detail.value.customerId, customerName: customerName(detail.value.customerId, detail.value.customerName), direction: detail.value.direction, documentNo: feeDocumentLabel(detail.value.direction, detail.value.feeItemName), sourceBusinessId: detail.value.sourceBusinessId, sourceType: detail.value.sourceType, sourceId: detail.value.sourceId, orderId: detail.value.orderId || '', inboundId: detail.value.inboundId || '', containerId: detail.value.containerId || '', inboundCode: detail.value.inboundCode, containerNo: detail.value.containerNo, supplierName: detail.value.supplierName, feeName: displayFeeName(detail.value.feeItemName), amount: detail.value.amount, status: detail.value.status, occurredAt: detail.value.createdAt, businessDate: detail.value.businessDate, reviewStatus: detail.value.reviewStatus, statementGenerated: detail.value.statementGenerated }
selected.value = { id: detail.value.poolItemId, feeSourceId, sourceSystem: detail.value.sourceSystem, customerId: detail.value.customerId, customerName: customerName(detail.value.customerId, detail.value.customerName), direction: detail.value.direction, documentNo: feeDocumentLabel(detail.value.direction, detail.value.feeItemName), sourceBusinessId: detail.value.sourceBusinessId, sourceType: detail.value.sourceType, sourceId: detail.value.sourceId, orderId: detail.value.orderId || '', inboundId: detail.value.inboundId || '', containerId: detail.value.containerId || '', inboundCode: detail.value.inboundCode, containerNo: detail.value.containerNo, supplierName: detail.value.supplierName, feeName: displayFeeName(detail.value.feeItemName), amount: detail.value.amount, status: detail.value.status, occurredAt: detail.value.createdAt, businessDate: detail.value.businessDate, reviewStatus: detail.value.reviewStatus, statementGenerated: detail.value.statementGenerated }
} catch (error) { detailError.value = getApiErrorDetails(error, '费用来源详情加载失败,请重试') }
finally { detailLoading.value = false }
}
@@ -65,6 +65,15 @@ describe('InboundTaskList measurement scope', () => {
expect(queryFieldsSource).not.toContain('客户编码')
})
it('clears date conditions safely and uses explicit select placeholders', () => {
const source = readFileSync(resolve('src/modules/inbounds/components/InboundTaskList.vue'), 'utf8')
const queryFieldsSource = source.slice(source.indexOf('const queryFields'), source.indexOf('const columns'))
expect(source).toContain('Array.isArray(filters.value.expectedArrivalRange)')
expect(queryFieldsSource).toContain("placeholder: customerLoadError.value ? '客户列表加载失败' : '请选择客户'")
expect(queryFieldsSource).toContain("placeholder: '请选择仓库'")
})
it('requests every active-customer page and merges name-only options', async () => {
const calls: Array<{ page: number; size: number; status: 'ACTIVE' }> = []
const records = await loadAllActiveCustomers(async (query) => {
@@ -65,14 +65,14 @@ const queryFields = computed<QueryField[]>(() => [
type: 'select',
label: '客户',
width: 260,
placeholder: customerLoadError.value ? '客户列表加载失败' : '全部客户',
placeholder: customerLoadError.value ? '客户列表加载失败' : '请选择客户',
options: customers.value.map((customer) => ({
value: String(customer.id),
label: customer.legalName?.trim() || customer.tenantName,
})),
props: { filterable: true, disabled: Boolean(customerLoadError.value) },
},
{ key: 'warehouseId', type: 'select', label: '定位仓库', width: 220, placeholder: '全部仓库', options: warehouseOptions.value, props: { filterable: true } },
{ key: 'warehouseId', type: 'select', label: '定位仓库', width: 220, placeholder: '请选择仓库', options: warehouseOptions.value, props: { filterable: true } },
{ key: 'inboundCode', type: 'input', label: '入仓码', width: 220, placeholder: '请输入入仓码' },
{ key: 'expectedArrivalRange', type: 'daterange', label: '预计到达时间', width: 320, placeholder: '请选择日期范围' },
])
@@ -164,7 +164,10 @@ async function loadTasks() {
}
function applyFilters() {
appliedFilters.value = { ...filters.value, expectedArrivalRange: [...filters.value.expectedArrivalRange] }
appliedFilters.value = {
...filters.value,
expectedArrivalRange: Array.isArray(filters.value.expectedArrivalRange) ? [...filters.value.expectedArrivalRange] : [],
}
if (page.value === 1) void loadTasks()
else page.value = 1
}
@@ -187,6 +187,7 @@ type WarehouseForm = Omit<Warehouse, 'updatedAt'> & {
countryCode: string
timezone: string
postalCode: string
receivingCutoffTime: string
}
function generatedCode(type: WarehouseType) {
@@ -204,10 +205,11 @@ const emptyForm = (type: WarehouseType = 'FBA'): WarehouseForm => ({
postalCode: '',
contactName: '',
contactPhone: '',
businessHours: '',
warehouseType: type,
countryCode: type === 'DOMESTIC' ? 'CN' : 'US',
timezone: type === 'DOMESTIC' ? 'UTC+8' : '',
businessHours: type === 'FBA' ? '' : '周一至周六 08:30-18:00',
receivingCutoffTime: type === 'FBA' ? '' : '18:00:00',
status: 'ACTIVE',
remark: '',
})
@@ -309,7 +311,7 @@ function toRow(warehouse: PlatformWarehouseSummaryResource): WarehouseRow {
address: addressText,
postalCode: address?.postalCode || '',
contactName: warehouse.contactName || '',
contactPhone: warehouse.contactPhone || '',
contactPhone: digitsOnly(warehouse.contactPhone),
businessHours: '',
status: warehouse.status,
remark: warehouse.remark || '',
@@ -464,8 +466,8 @@ async function openEdit(warehouse: WarehouseRow) {
address: address?.addressLine1 || '',
postalCode: address?.postalCode || '',
contactName: detail.warehouse.contactName || '',
contactPhone: detail.warehouse.contactPhone || '',
businessHours: receiving?.description || '',
contactPhone: digitsOnly(detail.warehouse.contactPhone),
receivingCutoffTime: receiving?.cutoffTime || '',
status: detail.warehouse.status,
remark: detail.warehouse.remark || '',
})
@@ -481,6 +483,10 @@ function nullable(value: string) {
return value.trim() || null
}
function digitsOnly(value: unknown) {
return String(value || '').replace(/\D/g, '').slice(0, 32)
}
function buildSaveRequest(): SavePlatformWarehouseRequest {
const existing = form.id ? rows.value.find((item) => item.id === form.id)?.detail : null
const warehouse = existing?.warehouse
@@ -491,13 +497,13 @@ function buildSaveRequest(): SavePlatformWarehouseRequest {
capabilityCode: capability.capabilityCode,
capabilityName: capability.capabilityName,
enabled: capability.enabled ?? true,
cutoffTime: capability.cutoffTime,
description: capability.capabilityCode === 'RECEIVING' ? nullable(form.businessHours) : capability.description,
cutoffTime: capability.capabilityCode === 'RECEIVING' ? nullable(form.receivingCutoffTime) : capability.cutoffTime,
description: capability.description,
}))
if (form.warehouseType !== 'FBA' && !capabilities.some((item) => item.capabilityCode === 'RECEIVING')) {
capabilities.push({
capabilityCode: 'RECEIVING', capabilityName: '收货服务', enabled: true, cutoffTime: null,
description: nullable(form.businessHours),
description: null,
})
}
return {
@@ -790,8 +796,8 @@ onMounted(() => void loadWarehouses())
<FormSection v-if="!isFbaCreate" title="联系信息">
<div class="form-grid two-cols compact-grid">
<el-form-item label="联系人"><el-input v-model="form.contactName" maxlength="64" placeholder="选填" /></el-form-item>
<el-form-item label="联系电话"><el-input v-model="form.contactPhone" maxlength="32" placeholder="选填" /></el-form-item>
<el-form-item label="收货时间"><el-input v-model="form.businessHours" maxlength="500" placeholder="例如 周一至周六 08:30-18:00" /></el-form-item>
<el-form-item label="联系电话"><el-input v-model="form.contactPhone" maxlength="32" inputmode="numeric" placeholder="选填" @input="form.contactPhone = digitsOnly($event)" /></el-form-item>
<el-form-item label="收货时间"><el-time-picker v-model="form.receivingCutoffTime" value-format="HH:mm:ss" format="HH:mm" placeholder="请选择收货截止时间" /></el-form-item>
</div>
<el-form-item label="备注"><el-input v-model="form.remark" maxlength="500" type="textarea" :rows="3" show-word-limit placeholder="选填" /></el-form-item>
</FormSection>