Files
qipai/scripts/check-m09-c-settlement-integrity.mjs
T

79 lines
3.2 KiB
JavaScript

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.');