feat(M08-B): 补保洁统计趋势

This commit is contained in:
Codex
2026-06-30 10:52:38 +08:00
parent 326c534ba7
commit d35c39e33f
13 changed files with 200 additions and 21 deletions
@@ -137,6 +137,15 @@ interface ManagerMemberStatsRow extends RowDataPacket {
pendingSettlementCents: number | null;
settledRewardCents: number | null;
}
interface ManagerDailyTrendRow extends RowDataPacket {
bucketDate: string | Date;
taskTotal: number;
pendingReview: number;
completed: number;
rejected: number;
rewardCents: number | null;
paidSettlementCents: number | null;
}
export class CleaningTaskRepository {
constructor(private readonly pool: MySqlPool) {}
@@ -827,10 +836,34 @@ export class CleaningTaskRepository {
WHERE ${taskWhereSql}
GROUP BY m.user_id, u.nickname
ORDER BY pendingSettlementCents DESC, settledRewardCents DESC, taskCount DESC
LIMIT 20`,
LIMIT 20`,
taskParams
);
return publicManagerStatistics(statusRows, storeRows, settlementRows, memberRows);
const [trendRows] = await this.pool.execute<ManagerDailyTrendRow[]>(
`SELECT DATE(t.created_at) AS bucketDate,
COUNT(*) AS taskTotal,
SUM(CASE WHEN t.status = 'SUBMITTED' THEN 1 ELSE 0 END) AS pendingReview,
SUM(CASE WHEN t.status IN ('COMPLETED', 'SETTLED') THEN 1 ELSE 0 END) AS completed,
SUM(CASE WHEN t.status = 'REJECTED' THEN 1 ELSE 0 END) AS rejected,
COALESCE(SUM(t.reward_cents), 0) AS rewardCents,
COALESCE((
SELECT SUM(s.total_reward_cents)
FROM qipai_cleaning_settlements s
WHERE s.tenant_id = t.tenant_id
AND s.deleted_at IS NULL
AND s.status = 'PAID'
AND DATE(s.paid_at) = DATE(t.created_at)
AND ${storeScopeSql(input.access, 'COALESCE(s.store_id, 0)')}
${input.storeId ? 'AND s.store_id = ?' : ''}
), 0) AS paidSettlementCents
FROM qipai_cleaning_tasks t
WHERE ${taskWhereSql}
GROUP BY DATE(t.created_at), t.tenant_id
ORDER BY bucketDate ASC
LIMIT 62`,
input.storeId ? [input.storeId, ...taskParams] : taskParams
);
return publicManagerStatistics(statusRows, storeRows, settlementRows, memberRows, trendRows);
}
private async listByWhere(
@@ -1259,7 +1292,8 @@ function publicManagerStatistics(
statusRows: ManagerStatusStatsRow[],
storeRows: ManagerStoreStatsRow[],
settlementRows: ManagerSettlementStatsRow[],
memberRows: ManagerMemberStatsRow[]
memberRows: ManagerMemberStatsRow[],
trendRows: ManagerDailyTrendRow[]
) {
const byStatus = statusRows.map((row) => ({
status: row.status,
@@ -1304,6 +1338,15 @@ function publicManagerStatistics(
taskCount: Number(row.taskCount),
pendingSettlementCents: Number(row.pendingSettlementCents ?? 0),
settledRewardCents: Number(row.settledRewardCents ?? 0)
})),
trend: trendRows.map((row) => ({
date: formatDateOnly(row.bucketDate),
taskTotal: Number(row.taskTotal),
pendingReview: Number(row.pendingReview),
completed: Number(row.completed),
rejected: Number(row.rejected),
rewardCents: Number(row.rewardCents ?? 0),
paidSettlementCents: Number(row.paidSettlementCents ?? 0)
}))
};
}
@@ -1315,6 +1358,11 @@ function countStatus(
return rows.find((row) => row.status === status)?.total ?? 0;
}
function formatDateOnly(value: string | Date) {
if (value instanceof Date) return value.toISOString().slice(0, 10);
return String(value).slice(0, 10);
}
function minDate(values: Array<Date | null>) {
const dates = values.filter((value): value is Date => value instanceof Date);
if (dates.length === 0) return null;