mirror of
https://github.com/documenso/documenso.git
synced 2026-06-22 04:12:06 +10:00
4f346d3c2d
Adds a CANCELLED envelope status that privileged members (owner or team admin/manager) can move a pending document into. Sending recipient notifications via a background job while retaining the document in the dashboard as proof of distribution. Includes a dedicated Cancelled tab, single and bulk cancel actions, the ENVELOPE_CANCELLED mutability guard, and e2e coverage for permissions and visibility.
96 lines
2.6 KiB
TypeScript
96 lines
2.6 KiB
TypeScript
import { cancelDocument } from '@documenso/lib/server-only/document/cancel-document';
|
|
import { getMultipleEnvelopeWhereInput } from '@documenso/lib/server-only/envelope/get-envelopes-by-ids';
|
|
import { prisma } from '@documenso/prisma';
|
|
import { EnvelopeType } from '@prisma/client';
|
|
import pMap from 'p-map';
|
|
|
|
import { authenticatedProcedure } from '../trpc';
|
|
import { ZBulkCancelEnvelopesRequestSchema, ZBulkCancelEnvelopesResponseSchema } from './bulk-cancel-envelopes.types';
|
|
|
|
export const bulkCancelEnvelopesRoute = authenticatedProcedure
|
|
.input(ZBulkCancelEnvelopesRequestSchema)
|
|
.output(ZBulkCancelEnvelopesResponseSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
const { teamId, user } = ctx;
|
|
const { envelopeIds, reason } = input;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
envelopeIds,
|
|
},
|
|
});
|
|
|
|
// Cancelling only applies to documents. Templates are filtered out here so
|
|
// selecting a template never counts towards a cancellation.
|
|
const { envelopeWhereInput } = await getMultipleEnvelopeWhereInput({
|
|
ids: {
|
|
type: 'envelopeId',
|
|
ids: envelopeIds,
|
|
},
|
|
userId: user.id,
|
|
teamId,
|
|
type: EnvelopeType.DOCUMENT,
|
|
});
|
|
|
|
const envelopes = await prisma.envelope.findMany({
|
|
where: envelopeWhereInput,
|
|
select: {
|
|
id: true,
|
|
},
|
|
});
|
|
|
|
const results = await pMap(
|
|
envelopes,
|
|
async (envelope) => {
|
|
const { id: envelopeId } = envelope;
|
|
|
|
try {
|
|
await cancelDocument({
|
|
id: {
|
|
type: 'envelopeId',
|
|
id: envelopeId,
|
|
},
|
|
userId: user.id,
|
|
teamId,
|
|
reason,
|
|
requestMetadata: ctx.metadata,
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
envelopeId,
|
|
};
|
|
} catch (err) {
|
|
ctx.logger.warn(
|
|
{
|
|
envelopeId,
|
|
error: err,
|
|
},
|
|
'Failed to cancel envelope during bulk cancel',
|
|
);
|
|
|
|
return {
|
|
success: false,
|
|
envelopeId,
|
|
};
|
|
}
|
|
},
|
|
{
|
|
concurrency: 10,
|
|
stopOnError: false,
|
|
},
|
|
);
|
|
|
|
const cancelledCount = results.filter((r) => r.success).length;
|
|
const failedIds = results.filter((r) => !r.success).map((r) => r.envelopeId);
|
|
|
|
// Include envelope IDs that were not attempted (unauthorized/not found/not a document).
|
|
const attemptedIds = new Set(envelopes.map((e) => e.id));
|
|
const unattemptedIds = envelopeIds.filter((id) => !attemptedIds.has(id));
|
|
|
|
return {
|
|
cancelledCount,
|
|
failedIds: [...failedIds, ...unattemptedIds],
|
|
};
|
|
});
|