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
@@ -84,8 +84,12 @@ interface SettlementRow extends RowDataPacket {
totalRewardCents: number;
periodStart: Date | null;
periodEnd: Date | null;
paidBy: string | null;
confirmedAt: Date | null;
paidAt: Date | null;
payoutChannel: string;
payoutReference: string;
payoutError: string;
note: string;
createdAt: Date;
}
@@ -342,7 +346,9 @@ export class CleaningTaskRepository {
s.store_id AS storeId, st.name AS storeName, s.status,
s.task_count AS taskCount, s.total_reward_cents AS totalRewardCents,
s.period_start AS periodStart, s.period_end AS periodEnd,
s.confirmed_at AS confirmedAt, s.paid_at AS paidAt,
s.paid_by AS paidBy, s.confirmed_at AS confirmedAt, s.paid_at AS paidAt,
s.payout_channel AS payoutChannel, s.payout_reference AS payoutReference,
s.payout_error AS payoutError,
s.note, s.created_at AS createdAt
FROM qipai_cleaning_settlements s
INNER JOIN qipai_users u ON u.tenant_id = s.tenant_id AND u.id = s.cleaner_user_id
@@ -464,6 +470,53 @@ export class CleaningTaskRepository {
});
}
async markSettlementPaid(input: CleaningActor & {
settlementId: string; payoutChannel: string; payoutReference: string; note?: string;
}) {
this.assertSettlement(input.access, 'write');
return this.transaction(async (connection) => {
const [result] = await connection.execute<ResultSetHeader>(
`UPDATE qipai_cleaning_settlements s
SET s.status = 'PAID', s.paid_by = ?, s.paid_at = UTC_TIMESTAMP(3),
s.payout_channel = ?, s.payout_reference = ?, s.payout_error = '',
s.note = CASE WHEN ? = '' THEN s.note ELSE ? END
WHERE s.tenant_id = ? AND s.id = ? AND s.status = 'CONFIRMED' AND s.deleted_at IS NULL
AND ${storeScopeSql(input.access, 'COALESCE(s.store_id, 0)')}`,
[
input.userId, input.payoutChannel, input.payoutReference,
input.note ?? '', input.note ?? '', input.tenantId, input.settlementId
]
);
if (result.affectedRows !== 1) throw new CleaningTaskError('CLEANING_SETTLEMENT_STATUS_CONFLICT');
return this.getSettlement(connection, input.tenantId, input.settlementId);
});
}
async recordSettlementPayoutFailure(input: CleaningActor & {
settlementId: string; payoutChannel?: string; payoutReference?: string; error: string; note?: string;
}) {
this.assertSettlement(input.access, 'write');
return this.transaction(async (connection) => {
const [result] = await connection.execute<ResultSetHeader>(
`UPDATE qipai_cleaning_settlements s
SET s.payout_channel = CASE WHEN ? = '' THEN s.payout_channel ELSE ? END,
s.payout_reference = CASE WHEN ? = '' THEN s.payout_reference ELSE ? END,
s.payout_error = ?,
s.note = CASE WHEN ? = '' THEN s.note ELSE ? END
WHERE s.tenant_id = ? AND s.id = ? AND s.status = 'CONFIRMED' AND s.deleted_at IS NULL
AND ${storeScopeSql(input.access, 'COALESCE(s.store_id, 0)')}`,
[
input.payoutChannel ?? '', input.payoutChannel ?? '',
input.payoutReference ?? '', input.payoutReference ?? '',
input.error.slice(0, 512), input.note ?? '', input.note ?? '',
input.tenantId, input.settlementId
]
);
if (result.affectedRows !== 1) throw new CleaningTaskError('CLEANING_SETTLEMENT_STATUS_CONFLICT');
return this.getSettlement(connection, input.tenantId, input.settlementId);
});
}
async reclaimTimeouts(input: CleaningActor & { olderThanMinutes: number; limit: number }) {
this.assertCleaner(input.access, 'write');
const [rows] = await this.pool.execute<RowDataPacket[]>(
@@ -866,7 +919,9 @@ export class CleaningTaskRepository {
s.store_id AS storeId, st.name AS storeName, s.status,
s.task_count AS taskCount, s.total_reward_cents AS totalRewardCents,
s.period_start AS periodStart, s.period_end AS periodEnd,
s.confirmed_at AS confirmedAt, s.paid_at AS paidAt,
s.paid_by AS paidBy, s.confirmed_at AS confirmedAt, s.paid_at AS paidAt,
s.payout_channel AS payoutChannel, s.payout_reference AS payoutReference,
s.payout_error AS payoutError,
s.note, s.created_at AS createdAt
FROM qipai_cleaning_settlements s
INNER JOIN qipai_users u ON u.tenant_id = s.tenant_id AND u.id = s.cleaner_user_id
@@ -957,8 +1012,12 @@ function publicSettlement(row: SettlementRow) {
totalRewardCents: Number(row.totalRewardCents),
periodStart: row.periodStart,
periodEnd: row.periodEnd,
paidBy: row.paidBy === null ? null : String(row.paidBy),
confirmedAt: row.confirmedAt,
paidAt: row.paidAt,
payoutChannel: row.payoutChannel,
payoutReference: row.payoutReference,
payoutError: row.payoutError,
note: row.note,
createdAt: row.createdAt
};