feat(M08-B): 补保洁结算发放记录

This commit is contained in:
Codex
2026-06-27 15:44:32 +08:00
parent 5d224693ae
commit 05907a6bea
15 changed files with 281 additions and 19 deletions
+56 -1
View File
@@ -122,6 +122,26 @@ const app = await buildApp({
calls.push(['confirmSettlement', input]);
return settlementResponse('CONFIRMED');
},
async markSettlementPaid(input) {
calls.push(['markSettlementPaid', input]);
return {
...settlementResponse('PAID'),
paidBy: input.userId,
paidAt: new Date(),
payoutChannel: input.payoutChannel,
payoutReference: input.payoutReference,
payoutError: ''
};
},
async recordSettlementPayoutFailure(input) {
calls.push(['recordSettlementPayoutFailure', input]);
return {
...settlementResponse('CONFIRMED'),
payoutChannel: input.payoutChannel || '',
payoutReference: input.payoutReference || '',
payoutError: input.error
};
},
async reclaimTimeouts(input) {
calls.push(['reclaimTimeouts', input]);
return { reclaimed: 2, taskIds: ['101', '102'] };
@@ -322,6 +342,37 @@ assert.equal(confirmedSettlement.statusCode, 200);
assert.equal(calls.at(-1)[0], 'confirmSettlement');
assert.equal(calls.at(-1)[1].settlementId, '501');
const failedPayout = await app.inject({
method: 'POST',
url: '/admin-api/cleaning/settlements/501/payout-failure',
headers: { authorization: `Bearer ${token}` },
payload: {
payoutChannel: 'WECHAT_TRANSFER',
payoutReference: 'wx-failed-001',
error: 'insufficient merchant balance',
note: 'retry later'
}
});
assert.equal(failedPayout.statusCode, 200);
assert.equal(calls.at(-1)[0], 'recordSettlementPayoutFailure');
assert.equal(calls.at(-1)[1].settlementId, '501');
assert.equal(calls.at(-1)[1].error, 'insufficient merchant balance');
const paidSettlement = await app.inject({
method: 'POST',
url: '/admin-api/cleaning/settlements/501/paid',
headers: { authorization: `Bearer ${token}` },
payload: {
payoutChannel: 'WECHAT_TRANSFER',
payoutReference: 'wx-paid-001',
note: 'paid'
}
});
assert.equal(paidSettlement.statusCode, 200);
assert.equal(paidSettlement.json().data.status, 'PAID');
assert.equal(paidSettlement.json().data.payoutReference, 'wx-paid-001');
assert.equal(calls.at(-1)[0], 'markSettlementPaid');
const reclaimed = await app.inject({
method: 'POST',
url: '/admin-api/cleaning/reclaim-timeouts',
@@ -383,8 +434,12 @@ function settlementResponse(status) {
totalRewardCents: 1200,
periodStart: new Date(),
periodEnd: new Date(),
paidBy: status === 'PAID' ? '31' : null,
confirmedAt: status === 'CONFIRMED' ? new Date() : null,
paidAt: null,
paidAt: status === 'PAID' ? new Date() : null,
payoutChannel: '',
payoutReference: '',
payoutError: '',
note: ''
};
}
+14
View File
@@ -90,6 +90,9 @@ const cleaningSettlementVerifySql = read('database/migrations/2026062626_m08b_cl
const cleaningCollaborationUpSql = read('database/migrations/2026062627_m08b_cleaning_collaboration.up.sql');
const cleaningCollaborationDownSql = read('database/migrations/2026062627_m08b_cleaning_collaboration.down.sql');
const cleaningCollaborationVerifySql = read('database/migrations/2026062627_m08b_cleaning_collaboration.verify.sql');
const cleaningPayoutUpSql = read('database/migrations/2026062728_m08b_cleaning_payouts.up.sql');
const cleaningPayoutDownSql = read('database/migrations/2026062728_m08b_cleaning_payouts.down.sql');
const cleaningPayoutVerifySql = read('database/migrations/2026062728_m08b_cleaning_payouts.verify.sql');
const coreTables = [
'qipai_schema_migrations',
@@ -426,4 +429,15 @@ assert.match(cleaningCollaborationDownSql, /ADD UNIQUE KEY uq_qipai_cleaning_set
assert.match(cleaningCollaborationVerifySql, /'qipai_cleaning_task_members'/);
assert.match(cleaningCollaborationVerifySql, /'cleaner_user_id'/);
assert.match(cleaningPayoutUpSql, /ADD COLUMN paid_by BIGINT UNSIGNED NULL/);
assert.match(cleaningPayoutUpSql, /ADD COLUMN payout_channel VARCHAR\(32\) NOT NULL DEFAULT ''/);
assert.match(cleaningPayoutUpSql, /ADD COLUMN payout_reference VARCHAR\(128\) NOT NULL DEFAULT ''/);
assert.match(cleaningPayoutUpSql, /ADD COLUMN payout_error VARCHAR\(512\) NOT NULL DEFAULT ''/);
assert.match(cleaningPayoutUpSql, /fk_qipai_cleaning_settlements_paid_by/);
assert.match(cleaningPayoutUpSql, /idx_qipai_cleaning_settlement_payout/);
assert.match(cleaningPayoutDownSql, /DROP FOREIGN KEY fk_qipai_cleaning_settlements_paid_by/);
assert.match(cleaningPayoutDownSql, /DROP COLUMN payout_error/);
assert.match(cleaningPayoutVerifySql, /'payout_reference'/);
assert.match(cleaningPayoutVerifySql, /'2026062728'/);
console.log('PASS: M01-B through M08-B migration contracts are present.');
+2 -1
View File
@@ -37,7 +37,8 @@ assert.match(plan.file, /2026062422_m07b_recharge_plans\.up\.sql/);
assert.match(plan.file, /2026062524_m08a_recharge_wechat\.up\.sql/);
assert.match(plan.file, /2026062525_m08b_cleaner_tasks\.up\.sql/);
assert.match(plan.file, /2026062626_m08b_cleaning_settlements\.up\.sql/);
assert.match(plan.file, /2026062627_m08b_cleaning_collaboration\.up\.sql$/);
assert.match(plan.file, /2026062627_m08b_cleaning_collaboration\.up\.sql/);
assert.match(plan.file, /2026062728_m08b_cleaning_payouts\.up\.sql$/);
assert.match(plan.checksum, /^[a-f0-9]{64}$/);
assert.ok(plan.statements.length >= 11);