feat(cleaning): close settlement integrity and reconciliation
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const root = process.cwd();
|
||||
const read = (path) => readFileSync(join(root, path), 'utf8');
|
||||
|
||||
for (const suffix of ['up', 'down', 'verify']) {
|
||||
assert.ok(
|
||||
existsSync(join(root, 'database/migrations/2026081006_m09c_cleaning_settlement_integrity.' + suffix + '.sql')),
|
||||
'M09-C ' + suffix + ' migration is missing'
|
||||
);
|
||||
}
|
||||
|
||||
const migration = read('database/migrations/2026081006_m09c_cleaning_settlement_integrity.up.sql');
|
||||
for (const pattern of [
|
||||
'qipai_cleaning_settlement_reversals',
|
||||
'qipai_cleaning_settlement_events',
|
||||
'amount_delta_cents BIGINT',
|
||||
'active_share_key',
|
||||
'uq_qipai_cleaning_settlement_active_share',
|
||||
'payout_request_no',
|
||||
'uq_qipai_cleaning_settlement_payout_request',
|
||||
"actor_type IN ('USER', 'SYSTEM')"
|
||||
]) assert.ok(migration.includes(pattern), 'migration is missing ' + pattern);
|
||||
|
||||
const repository = read('backend/src/cleaning/cleaning-task-repository.ts');
|
||||
for (const pattern of [
|
||||
'CLEANING_SETTLEMENT_CONCURRENTLY_CLAIMED',
|
||||
'assertTaskSettlementMutable',
|
||||
'beginSettlementPayout',
|
||||
'cancelSettlement',
|
||||
'recordSettlementEventWithConnection',
|
||||
"status = 'SETTLED'",
|
||||
'exportSettlements',
|
||||
'querySettlementRows'
|
||||
]) assert.ok(repository.includes(pattern), 'repository is missing ' + pattern);
|
||||
assert.match(repository, /payout_state IN \('PROCESSING', 'WAIT_USER_CONFIRM'\)/);
|
||||
assert.match(repository, /reversed_at IS NULL/);
|
||||
|
||||
const payout = read('backend/src/cleaning/cleaning-payout-service.ts');
|
||||
assert.match(payout, /await this\.repository\.beginSettlementPayout/);
|
||||
assert.match(payout, /actorType: 'SYSTEM'/);
|
||||
assert.match(payout, /payout_request_no AS payoutRequestNo/);
|
||||
assert.ok(payout.includes('CLP$' + '{settlementId}'), 'stable payout request id is missing');
|
||||
|
||||
const routes = read('backend/src/routes/cleaning.ts');
|
||||
assert.match(routes, /settlements\/export/);
|
||||
assert.match(routes, /settlements\/:settlementId\/cancel/);
|
||||
assert.match(routes, /manualOverride: true/);
|
||||
|
||||
const admin = read('admin/src/components/CleaningSettlementsPanel.vue');
|
||||
for (const pattern of [
|
||||
'exportCleaningSettlements',
|
||||
'canCancelSettlement',
|
||||
'openManualPaid',
|
||||
'detailDialog.detail.reversals',
|
||||
'detailDialog.detail.events',
|
||||
'payoutState: props.filters.payoutState',
|
||||
"['', 'FAIL'].includes(row.payoutState)",
|
||||
'金额(分)'
|
||||
]) assert.ok(admin.includes(pattern), 'admin settlement UI is missing ' + pattern);
|
||||
|
||||
for (const [file, pattern] of [
|
||||
['admin/src/components/CleaningStatisticsPanel.vue', '待结算(分)'],
|
||||
['admin/src/components/CleaningTasksPanel.vue', '金额(分)'],
|
||||
['admin/src/components/CleaningWorkspace.vue', '金额(分)']
|
||||
]) assert.ok(read(file).includes(pattern), file + ' must export monetary values as integer cents');
|
||||
|
||||
const mysqlTest = read('backend/tests/mysql-migration-roundtrip.test.mjs');
|
||||
for (const pattern of [
|
||||
'm09c-generate-one',
|
||||
'CLEANING_MEMBER_SETTLEMENT_LOCKED',
|
||||
'm09c-cancel-processing',
|
||||
'collaborative task settles only after every member share is paid'
|
||||
]) assert.ok(mysqlTest.includes(pattern), 'live MySQL test is missing ' + pattern);
|
||||
|
||||
console.log('PASS: M09-C settlement integrity, reversal, payout and shared-query gates are present.');
|
||||
Reference in New Issue
Block a user