feat: rejected and expired recipient filters (#2889)

This commit is contained in:
Ephraim Duncan
2026-07-30 10:41:45 +10:00
committed by GitHub
parent a457e1ef7d
commit 6ec67d1c4d
21 changed files with 867 additions and 10 deletions
+18 -1
View File
@@ -8,6 +8,7 @@ import { DateTime } from 'luxon';
import { STATS_COUNT_CAP } from '../../constants/document';
import { TEAM_DOCUMENT_VISIBILITY_MAP } from '../../constants/teams';
import { hasExpiredRecipient } from '../envelope/query-helpers';
import { getTeamById } from '../team/get-team';
// Kysely query builder type for Envelope queries.
@@ -253,6 +254,19 @@ export const getStats = async ({ userId, teamId, period, search = '', folderId,
return eb.and([teamDeletedFilter(eb), visibilityFilter(eb), eb.or(accessBranches)]);
});
// EXPIRED: docs visible to the team/user with at least one expired, unsigned recipient.
// Access control mirrors the EXPIRED branch in findDocuments so the count matches the listing.
const expiredQuery = buildBaseQuery().where((eb) => {
const accessBranches = [eb('Envelope.teamId', '=', team.id)];
if (teamEmail) {
accessBranches.push(senderEmailIs(eb, teamEmail));
accessBranches.push(recipientExists(eb, teamEmail));
}
return eb.and([teamDeletedFilter(eb), visibilityFilter(eb), hasExpiredRecipient(eb), eb.or(accessBranches)]);
});
// INBOX: non-draft docs where team email is a NOT_SIGNED, non-CC recipient
// Returns 0 if the team has no team email.
const inboxQuery = teamEmail
@@ -274,15 +288,17 @@ export const getStats = async ({ userId, teamId, period, search = '', folderId,
// ─── Execute all counts in parallel ──────────────────────────────────
const [draft, pending, completed, rejected, cancelled, inbox] = await Promise.all([
const [draft, pending, completed, rejected, cancelled, expired, inbox] = await Promise.all([
cappedCount(draftQuery),
cappedCount(pendingQuery),
cappedCount(completedQuery),
cappedCount(rejectedQuery),
cappedCount(cancelledQuery),
cappedCount(expiredQuery),
inboxQuery ? cappedCount(inboxQuery) : Promise.resolve(0),
]);
// `expired` is intentionally excluded from `all` — it overlaps PENDING.
const all = Math.min(draft + pending + completed + rejected + cancelled + inbox, STATS_COUNT_CAP);
const stats: Record<ExtendedDocumentStatus, number> = {
@@ -291,6 +307,7 @@ export const getStats = async ({ userId, teamId, period, search = '', folderId,
[ExtendedDocumentStatus.COMPLETED]: completed,
[ExtendedDocumentStatus.REJECTED]: rejected,
[ExtendedDocumentStatus.CANCELLED]: cancelled,
[ExtendedDocumentStatus.EXPIRED]: expired,
[ExtendedDocumentStatus.INBOX]: inbox,
[ExtendedDocumentStatus.ALL]: all,
};