feat(M08-B): 增加保洁结算单和超时回收
This commit is contained in:
@@ -41,7 +41,10 @@ const app = await buildApp({
|
||||
return userId === '31'
|
||||
? {
|
||||
roles: ['CLEANER'],
|
||||
capabilities: ['cleaning.task.read', 'cleaning.task.write', 'cleaning.statistics.read'],
|
||||
capabilities: [
|
||||
'cleaning.task.read', 'cleaning.task.write', 'cleaning.statistics.read',
|
||||
'cleaning.settlement.read', 'cleaning.settlement.write'
|
||||
],
|
||||
storeIds: ['11']
|
||||
}
|
||||
: { roles: ['CUSTOMER'], capabilities: ['profile.read'], storeIds: [] };
|
||||
@@ -95,6 +98,22 @@ const app = await buildApp({
|
||||
calls.push(['settlementCandidates', input]);
|
||||
return { items: [task('COMPLETED')], total: 1, page: input.page, pageSize: input.pageSize };
|
||||
},
|
||||
async listSettlements(input) {
|
||||
calls.push(['listSettlements', input]);
|
||||
return { items: [settlementResponse(input.status || 'DRAFT')], total: 1, page: input.page, pageSize: input.pageSize };
|
||||
},
|
||||
async generateSettlement(input) {
|
||||
calls.push(['generateSettlement', input]);
|
||||
return settlementResponse('DRAFT');
|
||||
},
|
||||
async confirmSettlement(input) {
|
||||
calls.push(['confirmSettlement', input]);
|
||||
return settlementResponse('CONFIRMED');
|
||||
},
|
||||
async reclaimTimeouts(input) {
|
||||
calls.push(['reclaimTimeouts', input]);
|
||||
return { reclaimed: 2, taskIds: ['101', '102'] };
|
||||
},
|
||||
async stats(input) {
|
||||
calls.push(['stats', input]);
|
||||
return { byStatus: { SUBMITTED: 2 }, pendingSettlementCents: 1200 };
|
||||
@@ -232,6 +251,45 @@ const settlement = await app.inject({
|
||||
assert.equal(settlement.statusCode, 200);
|
||||
assert.equal(calls.at(-1)[0], 'settlementCandidates');
|
||||
|
||||
const settlementList = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/admin-api/cleaning/settlements?status=DRAFT',
|
||||
headers: { authorization: `Bearer ${token}` }
|
||||
});
|
||||
assert.equal(settlementList.statusCode, 200);
|
||||
assert.equal(calls.at(-1)[0], 'listSettlements');
|
||||
assert.equal(calls.at(-1)[1].status, 'DRAFT');
|
||||
|
||||
const generatedSettlement = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/admin-api/cleaning/settlements',
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { cleanerUserId: '31', storeId: '11', note: 'weekly settlement' }
|
||||
});
|
||||
assert.equal(generatedSettlement.statusCode, 201);
|
||||
assert.equal(calls.at(-1)[0], 'generateSettlement');
|
||||
assert.equal(calls.at(-1)[1].cleanerUserId, '31');
|
||||
|
||||
const confirmedSettlement = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/admin-api/cleaning/settlements/501/confirm',
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { note: 'confirmed' }
|
||||
});
|
||||
assert.equal(confirmedSettlement.statusCode, 200);
|
||||
assert.equal(calls.at(-1)[0], 'confirmSettlement');
|
||||
assert.equal(calls.at(-1)[1].settlementId, '501');
|
||||
|
||||
const reclaimed = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/admin-api/cleaning/reclaim-timeouts',
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { olderThanMinutes: 30, limit: 10 }
|
||||
});
|
||||
assert.equal(reclaimed.statusCode, 200);
|
||||
assert.equal(calls.at(-1)[0], 'reclaimTimeouts');
|
||||
assert.equal(calls.at(-1)[1].olderThanMinutes, 30);
|
||||
|
||||
const stats = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/app-api/cleaning/stats',
|
||||
@@ -269,3 +327,22 @@ function task(status) {
|
||||
photoUrls: []
|
||||
};
|
||||
}
|
||||
|
||||
function settlementResponse(status) {
|
||||
return {
|
||||
id: '501',
|
||||
settlementNo: 'CLS-20260626-0001',
|
||||
cleanerUserId: '31',
|
||||
cleanerName: 'Cleaner',
|
||||
storeId: '11',
|
||||
storeName: 'Test Store',
|
||||
status,
|
||||
taskCount: 2,
|
||||
totalRewardCents: 1200,
|
||||
periodStart: new Date(),
|
||||
periodEnd: new Date(),
|
||||
confirmedAt: status === 'CONFIRMED' ? new Date() : null,
|
||||
paidAt: null,
|
||||
note: ''
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,6 +84,9 @@ const rechargeWechatVerifySql = read('database/migrations/2026062524_m08a_rechar
|
||||
const cleaningUpSql = read('database/migrations/2026062525_m08b_cleaner_tasks.up.sql');
|
||||
const cleaningDownSql = read('database/migrations/2026062525_m08b_cleaner_tasks.down.sql');
|
||||
const cleaningVerifySql = read('database/migrations/2026062525_m08b_cleaner_tasks.verify.sql');
|
||||
const cleaningSettlementUpSql = read('database/migrations/2026062626_m08b_cleaning_settlements.up.sql');
|
||||
const cleaningSettlementDownSql = read('database/migrations/2026062626_m08b_cleaning_settlements.down.sql');
|
||||
const cleaningSettlementVerifySql = read('database/migrations/2026062626_m08b_cleaning_settlements.verify.sql');
|
||||
|
||||
const coreTables = [
|
||||
'qipai_schema_migrations',
|
||||
@@ -398,4 +401,15 @@ assert.match(cleaningUpSql, /uq_qipai_cleaning_task_order/);
|
||||
assert.match(cleaningUpSql, /cleaning\.task\.write/);
|
||||
assert.match(cleaningUpSql, /cleaning\.statistics\.read/);
|
||||
|
||||
for (const table of ['qipai_cleaning_settlements', 'qipai_cleaning_settlement_items']) {
|
||||
assert.match(cleaningSettlementUpSql, new RegExp(`CREATE TABLE IF NOT EXISTS ${table}`));
|
||||
assert.match(cleaningSettlementDownSql, new RegExp(`DROP TABLE IF EXISTS ${table}`));
|
||||
assert.match(cleaningSettlementVerifySql, new RegExp(`'${table}'`));
|
||||
}
|
||||
assert.match(cleaningSettlementUpSql, /settlement_no VARCHAR\(64\) NOT NULL/);
|
||||
assert.match(cleaningSettlementUpSql, /total_reward_cents INT UNSIGNED NOT NULL/);
|
||||
assert.match(cleaningSettlementUpSql, /uq_qipai_cleaning_settlement_item_task/);
|
||||
assert.match(cleaningSettlementUpSql, /cleaning\.settlement\.read/);
|
||||
assert.match(cleaningSettlementUpSql, /cleaning\.settlement\.write/);
|
||||
|
||||
console.log('PASS: M01-B through M08-B migration contracts are present.');
|
||||
|
||||
@@ -35,7 +35,8 @@ assert.match(plan.file, /2026062220_m06c_iot_messages\.up\.sql/);
|
||||
assert.match(plan.file, /2026062421_m07a_wallet_ledger\.up\.sql/);
|
||||
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, /2026062525_m08b_cleaner_tasks\.up\.sql/);
|
||||
assert.match(plan.file, /2026062626_m08b_cleaning_settlements\.up\.sql$/);
|
||||
assert.match(plan.checksum, /^[a-f0-9]{64}$/);
|
||||
assert.ok(plan.statements.length >= 11);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user