优化
This commit is contained in:
@@ -2,16 +2,20 @@ const MODULE = 'finance-reconciliation-full'
|
||||
const SCENARIO = 'finance-full-reconciliation'
|
||||
const BASE = '/api/platform/finance/v1'
|
||||
const OPTIONS = { scenario: SCENARIO, deploymentBlocker: true }
|
||||
const STATEMENT_LIST_PERMISSION = 'platform:finance:customer-statement:list'
|
||||
const LEGACY_STATEMENT_VIEW_PERMISSION = 'platform:finance:bill:view'
|
||||
const REQUIRED_PERMISSIONS = [
|
||||
'platform:finance:customer-account:list',
|
||||
STATEMENT_LIST_PERMISSION,
|
||||
'platform:finance:reconciliation:create',
|
||||
'platform:finance:reconciliation:review',
|
||||
'platform:finance:reconciliation:issue',
|
||||
'platform:finance:reconciliation:adjust',
|
||||
'platform:finance:reconciliation:void',
|
||||
'platform:finance:settlement-ar:create',
|
||||
'platform:finance:settlement-ar:view',
|
||||
'platform:finance:settlement-ar:list',
|
||||
'platform:finance:settlement-ap:create',
|
||||
'platform:finance:settlement-ap:view',
|
||||
'platform:finance:settlement-ap:list',
|
||||
]
|
||||
|
||||
function id(value) { return value == null ? '' : String(value) }
|
||||
@@ -46,6 +50,41 @@ function sameIds(actual, expected) {
|
||||
return left.length === right.length && left.every((value, index) => value === right[index])
|
||||
}
|
||||
|
||||
function present(value) { return value !== undefined && value !== null && String(value).trim() !== '' }
|
||||
|
||||
async function verifyIssuedStatementReadback(context, fixture, statement, label) {
|
||||
const customerId = id(fixture.customerId)
|
||||
const targetId = statementId(statement)
|
||||
if (!customerId || !targetId) {
|
||||
context.addCoverageGap(MODULE, SCENARIO, `${label} 对账单列表/详情查回`,
|
||||
`必须使用本轮 fixture.customerId 和刚创建且已出账的 statementId;customerId=${customerId || 'missing'}, statementId=${targetId || 'missing'}`,
|
||||
'missing-issued-statement-readback-fixture', { marker: context.marker, customerId, statementId: targetId })
|
||||
return
|
||||
}
|
||||
const listResponse = await context.request('GET',
|
||||
`${BASE}/statements?customerId=${encodeURIComponent(customerId)}`)
|
||||
const summaries = context.apiSuccess(MODULE, `${label} 从正式对账单列表查回`, listResponse,
|
||||
`customerId=${customerId}, statementId=${targetId}, permission=${STATEMENT_LIST_PERMISSION}`, OPTIONS)
|
||||
const statementRows = rows(summaries)
|
||||
const found = statementRows.find((item) => statementId(item) === targetId)
|
||||
context.assert(MODULE, `${label} 正式对账单列表仅返回本轮客户且包含同一 statementId`, listResponse,
|
||||
statementRows.length > 0 && statementRows.every((item) => id(item?.customerId) === customerId) && Boolean(found),
|
||||
`customerId=${customerId}, statementId=${targetId}, rows=${statementRows.length}, found=${statementId(found) || 'missing'}`, OPTIONS)
|
||||
|
||||
const detailResponse = await context.request('GET',
|
||||
`${BASE}/reconciliation-statements/${encodeURIComponent(targetId)}`)
|
||||
const detail = context.apiSuccess(MODULE, `${label} 从正式对账单详情查回`, detailResponse,
|
||||
`customerId=${customerId}, statementId=${targetId}, permission=${STATEMENT_LIST_PERMISSION}`, OPTIONS)
|
||||
const auditShape = ['createdBy', 'createdAt', 'updatedBy', 'updatedAt']
|
||||
.every((field) => Object.hasOwn(detail ?? {}, field) && present(detail?.[field]))
|
||||
context.assert(MODULE, `${label} 对账单详情归属、明细、revision 与审计结构完整`, detailResponse,
|
||||
id(detail?.customerId) === customerId && statementId(detail) === targetId
|
||||
&& Array.isArray(detail?.lines) && detail.lines.length > 0
|
||||
&& Array.isArray(detail?.revisions) && detail.revisions.length > 0
|
||||
&& auditShape,
|
||||
`customerId=${id(detail?.customerId) || 'missing'}, statementId=${statementId(detail) || 'missing'}, lines=${list(detail?.lines).length}, revisions=${list(detail?.revisions).length}, auditShape=${auditShape}`, OPTIONS)
|
||||
}
|
||||
|
||||
async function findSettlementInOfficialList(context, direction, customerId, targetId, label) {
|
||||
const segment = direction === 'RECEIVABLE' ? 'receivable-settlements' : 'payable-settlements'
|
||||
const response = await context.request('GET',
|
||||
@@ -82,6 +121,42 @@ function diagnostic(response) {
|
||||
code: response?.body?.code == null ? null : String(response.body.code),
|
||||
requestId: response?.requestId ?? null, traceId: response?.traceId ?? null }
|
||||
}
|
||||
function accountRows(value) {
|
||||
const data = value?.body?.data
|
||||
return rows(data).concat(rows(data?.items))
|
||||
}
|
||||
function accountCustomerId(value) { return id(value?.customerId ?? value?.tenantId ?? value?.accountId ?? value?.id) }
|
||||
function hasWalletField(value) {
|
||||
return value && typeof value === 'object' && ['balance', 'recharge', 'prepayment'].some((field) => Object.hasOwn(value, field))
|
||||
}
|
||||
async function verifyCustomerAccountAndLedger(context, fixture) {
|
||||
const customerId = id(fixture.customerId)
|
||||
const accountResponse = await context.request('GET', `${BASE}/accounts?page=1&pageSize=20`)
|
||||
const accounts = accountRows(accountResponse)
|
||||
const account = accounts.find((item) => accountCustomerId(item) === customerId)
|
||||
context.assert(MODULE, '客户账户列表包含本轮 marker 客户且不暴露钱包字段', accountResponse,
|
||||
Boolean(account) && !accounts.some(hasWalletField),
|
||||
`customerId=${customerId}, accountCount=${accounts.length}, requestId=${accountResponse.requestId ?? 'null'}, traceId=${accountResponse.traceId ?? 'null'}`, OPTIONS)
|
||||
const ledgerResponse = await context.request('GET', `${BASE}/accounts/${encodeURIComponent(customerId)}/ledger?page=1&pageSize=200`)
|
||||
const ledger = accountRows(ledgerResponse)
|
||||
const rowsForCustomer = ledger.filter((item) => {
|
||||
const itemCustomer = accountCustomerId(item)
|
||||
return !itemCustomer || itemCustomer === customerId
|
||||
})
|
||||
const expectedIds = new Set([
|
||||
...(fixture.statements || []).map((item) => statementId(item)),
|
||||
...(fixture.receivableSettlements || []).map((item) => settlementId(item)),
|
||||
...(fixture.payableSettlements || []).map((item) => settlementId(item)),
|
||||
].filter(Boolean))
|
||||
const containsRoundTrip = rowsForCustomer.some((item) => expectedIds.has(statementId(item) || settlementId(item)
|
||||
|| id(item?.documentId ?? item?.businessId ?? item?.sourceId ?? item?.referenceId))
|
||||
|| String(item?.remark ?? '').includes(context.marker))
|
||||
context.assert(MODULE, '客户账户流水仅属于本轮客户并包含账单/收款或结算流水', ledgerResponse,
|
||||
rowsForCustomer.length === ledger.length && containsRoundTrip,
|
||||
`customerId=${customerId}, ledgerCount=${ledger.length}, requestId=${ledgerResponse.requestId ?? 'null'}, traceId=${ledgerResponse.traceId ?? 'null'}`, OPTIONS)
|
||||
fixture.root.accountReadback = { account: account || null, accountDiagnostic: diagnostic(accountResponse),
|
||||
ledgerCount: ledger.length, ledgerDiagnostic: diagnostic(ledgerResponse) }
|
||||
}
|
||||
function blocker(context, name, evidence, category, response = null) {
|
||||
context.addStep(MODULE, name, response, false, evidence, OPTIONS)
|
||||
context.addCoverageGap(MODULE, SCENARIO, name, evidence, category,
|
||||
@@ -251,6 +326,13 @@ export async function run(context) {
|
||||
const permissionResponse = await context.request('GET', '/api/platform/v1/iam/authorization/permissions')
|
||||
const permissions = context.apiSuccess(MODULE, '查询当前账号 Finance 有效权限', permissionResponse, '', OPTIONS)
|
||||
const granted = new Set(list(permissions).map(String))
|
||||
if (granted.has(LEGACY_STATEMENT_VIEW_PERMISSION)) {
|
||||
blocker(context, '客户对账单旧 view 权限兼容阻断',
|
||||
`检测到旧权限=${LEGACY_STATEMENT_VIEW_PERMISSION};列表和详情只能以正式权限=${STATEMENT_LIST_PERMISSION} 验收,旧 view 不得作为通过依据`,
|
||||
'legacy-customer-statement-view-permission', permissionResponse)
|
||||
finish(context, start)
|
||||
return
|
||||
}
|
||||
const missingPermissions = REQUIRED_PERMISSIONS.filter((permission) => !granted.has(permission))
|
||||
if (missingPermissions.length > 0) {
|
||||
blocker(context, 'Finance 写权限门禁',
|
||||
@@ -374,6 +456,7 @@ export async function run(context) {
|
||||
finish(context, start)
|
||||
return
|
||||
}
|
||||
await verifyIssuedStatementReadback(context, fixture, issued, `第 ${index + 1} 张已出账对账单`)
|
||||
issuedStatements.push(issued)
|
||||
if (index === 0) {
|
||||
context.assert(MODULE, 'SELECTED 日期范围只包含指定真实日期来源', created.response,
|
||||
@@ -455,5 +538,6 @@ export async function run(context) {
|
||||
feeSourceIds: payables.map((source) => source.feeSourceId),
|
||||
}] : []
|
||||
fixture.root.sourceMappings = fixture.sourceMappings
|
||||
await verifyCustomerAccountAndLedger(context, fixture)
|
||||
finish(context, start)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user