mirror of
https://github.com/documenso/documenso.git
synced 2026-07-26 18:04:55 +10:00
feat: initial tags
This commit is contained in:
@@ -36,6 +36,8 @@ export type FindDocumentsOptions = {
|
||||
senderIds?: number[];
|
||||
query?: string;
|
||||
folderId?: string;
|
||||
includeAllFolders?: boolean;
|
||||
tagIds?: string[];
|
||||
/**
|
||||
* When true (default), use a windowed count that caps early for faster pagination.
|
||||
* When false, use a full COUNT(*) for exact totals — preferred for external API consumers.
|
||||
@@ -102,6 +104,22 @@ const senderEmailIs = (eb: EnvelopeExpressionBuilder, email: string) =>
|
||||
.select(sql.lit(1).as('one')),
|
||||
);
|
||||
|
||||
export const applyEnvelopeTagFilter = (qb: EnvelopeQueryBuilder, tagIds?: string[]) => {
|
||||
if (!tagIds || tagIds.length === 0) {
|
||||
return qb;
|
||||
}
|
||||
|
||||
return qb.where((eb) =>
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom('EnvelopeTag')
|
||||
.whereRef('EnvelopeTag.envelopeId', '=', 'Envelope.id')
|
||||
.where('EnvelopeTag.tagId', 'in', tagIds)
|
||||
.select(sql.lit(1).as('one')),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export const findDocuments = async ({
|
||||
userId,
|
||||
teamId,
|
||||
@@ -115,6 +133,8 @@ export const findDocuments = async ({
|
||||
senderIds,
|
||||
query = '',
|
||||
folderId,
|
||||
includeAllFolders = false,
|
||||
tagIds,
|
||||
useWindowedCount = true,
|
||||
}: FindDocumentsOptions) => {
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
@@ -147,8 +167,12 @@ export const findDocuments = async ({
|
||||
qb = qb.where('Envelope.type', '=', sql.lit(EnvelopeType.DOCUMENT));
|
||||
|
||||
// Folder filter
|
||||
qb =
|
||||
folderId !== undefined ? qb.where('Envelope.folderId', '=', folderId) : qb.where('Envelope.folderId', 'is', null);
|
||||
if (!includeAllFolders) {
|
||||
qb =
|
||||
folderId !== undefined
|
||||
? qb.where('Envelope.folderId', '=', folderId)
|
||||
: qb.where('Envelope.folderId', 'is', null);
|
||||
}
|
||||
|
||||
// Period filter
|
||||
if (period) {
|
||||
@@ -199,6 +223,9 @@ export const findDocuments = async ({
|
||||
);
|
||||
}
|
||||
|
||||
// Tag filter (OR semantics — envelope must have at least one of the selected tags)
|
||||
qb = applyEnvelopeTagFilter(qb, tagIds);
|
||||
|
||||
return qb;
|
||||
};
|
||||
|
||||
@@ -520,6 +547,7 @@ export const findDocuments = async ({
|
||||
envelopeItems: {
|
||||
select: { id: true, envelopeId: true, title: true, order: true },
|
||||
},
|
||||
tags: { include: { tag: true } },
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents';
|
||||
import { applyEnvelopeTagFilter, type PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents';
|
||||
import { kyselyPrisma, prisma, sql } from '@documenso/prisma';
|
||||
import type { DB } from '@documenso/prisma/generated/types';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
@@ -57,7 +57,9 @@ export type GetStatsInput = {
|
||||
period?: PeriodSelectorValue;
|
||||
search?: string;
|
||||
folderId?: string;
|
||||
includeAllFolders?: boolean;
|
||||
senderIds?: number[];
|
||||
tagIds?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -80,7 +82,16 @@ const cappedCount = async (qb: EnvelopeQueryBuilder): Promise<number> => {
|
||||
return Math.min(Number(result.total ?? 0), STATS_COUNT_CAP);
|
||||
};
|
||||
|
||||
export const getStats = async ({ userId, teamId, period, search = '', folderId, senderIds }: GetStatsInput) => {
|
||||
export const getStats = async ({
|
||||
userId,
|
||||
teamId,
|
||||
period,
|
||||
search = '',
|
||||
folderId,
|
||||
includeAllFolders = false,
|
||||
senderIds,
|
||||
tagIds,
|
||||
}: GetStatsInput) => {
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
where: { id: userId },
|
||||
select: { id: true, email: true },
|
||||
@@ -105,8 +116,12 @@ export const getStats = async ({ userId, teamId, period, search = '', folderId,
|
||||
qb = qb.where('Envelope.type', '=', sql.lit(EnvelopeType.DOCUMENT));
|
||||
|
||||
// Folder filter
|
||||
qb =
|
||||
folderId !== undefined ? qb.where('Envelope.folderId', '=', folderId) : qb.where('Envelope.folderId', 'is', null);
|
||||
if (!includeAllFolders) {
|
||||
qb =
|
||||
folderId !== undefined
|
||||
? qb.where('Envelope.folderId', '=', folderId)
|
||||
: qb.where('Envelope.folderId', 'is', null);
|
||||
}
|
||||
|
||||
// Period filter
|
||||
if (period) {
|
||||
@@ -121,6 +136,9 @@ export const getStats = async ({ userId, teamId, period, search = '', folderId,
|
||||
qb = qb.where('Envelope.userId', 'in', senderIds);
|
||||
}
|
||||
|
||||
// Tag filter (OR semantics — envelope must have at least one of the selected tags)
|
||||
qb = applyEnvelopeTagFilter(qb, tagIds);
|
||||
|
||||
// Search filter
|
||||
if (hasSearch) {
|
||||
qb = qb.where(({ or, eb }) =>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import type { TTagType } from '../../types/tag-type';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
import { getTeamSettings } from '../team/get-team-settings';
|
||||
|
||||
export type CreateTagOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
name: string;
|
||||
type: TTagType;
|
||||
};
|
||||
|
||||
export const createTag = async ({ userId, teamId, name, type }: CreateTagOptions) => {
|
||||
// This indirectly verifies whether the user has access to the team.
|
||||
await getTeamSettings({ userId, teamId });
|
||||
|
||||
const normalizedName = name.trim().replace(/\s+/g, ' ');
|
||||
const normalizedNameKey = normalizedName.toLowerCase();
|
||||
|
||||
if (!normalizedName) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Tag name cannot be empty',
|
||||
});
|
||||
}
|
||||
|
||||
const existing = await prisma.tag.findFirst({
|
||||
where: {
|
||||
teamId,
|
||||
normalizedName: normalizedNameKey,
|
||||
type,
|
||||
team: buildTeamWhereQuery({ teamId, userId }),
|
||||
},
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
throw new AppError(AppErrorCode.ALREADY_EXISTS, {
|
||||
message: 'A tag with this name already exists for this type',
|
||||
});
|
||||
}
|
||||
|
||||
return await prisma.tag.create({
|
||||
data: {
|
||||
name: normalizedName,
|
||||
normalizedName: normalizedNameKey,
|
||||
type,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
import { getTeamById } from '../team/get-team';
|
||||
|
||||
export type DeleteTagOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
tagId: string;
|
||||
};
|
||||
|
||||
export const deleteTag = async ({ userId, teamId, tagId }: DeleteTagOptions) => {
|
||||
await getTeamById({ userId, teamId });
|
||||
|
||||
const tag = await prisma.tag.findFirst({
|
||||
where: {
|
||||
id: tagId,
|
||||
team: buildTeamWhereQuery({ teamId, userId }),
|
||||
},
|
||||
});
|
||||
|
||||
if (!tag) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Tag not found',
|
||||
});
|
||||
}
|
||||
|
||||
return await prisma.tag.delete({
|
||||
where: {
|
||||
id: tag.id,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
import type { TTagType } from '../../types/tag-type';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
import { getTeamById } from '../team/get-team';
|
||||
|
||||
export type FindTagsOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
type?: TTagType;
|
||||
query?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
};
|
||||
|
||||
export const findTags = async ({ userId, teamId, type, query, page = 1, perPage = 10 }: FindTagsOptions) => {
|
||||
await getTeamById({ userId, teamId });
|
||||
|
||||
const whereClause: Prisma.TagWhereInput = {
|
||||
team: buildTeamWhereQuery({ teamId, userId }),
|
||||
type,
|
||||
name: query ? { contains: query, mode: 'insensitive' } : undefined,
|
||||
};
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
prisma.tag.findMany({
|
||||
where: whereClause,
|
||||
skip: Math.max(page - 1, 0) * perPage,
|
||||
take: perPage,
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
}),
|
||||
prisma.tag.count({
|
||||
where: whereClause,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
count,
|
||||
currentPage: Math.max(page, 1),
|
||||
perPage,
|
||||
totalPages: Math.ceil(count / perPage),
|
||||
} satisfies FindResultResponse<typeof data>;
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { TEAM_DOCUMENT_VISIBILITY_MAP } from '../../constants/teams';
|
||||
import { mapEnvelopeTagsToTags } from '../../utils/tags';
|
||||
import { getTeamById } from '../team/get-team';
|
||||
|
||||
export type GetEnvelopeTagsOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
envelopeId: string;
|
||||
};
|
||||
|
||||
export const getEnvelopeTags = async ({ userId, teamId, envelopeId }: GetEnvelopeTagsOptions) => {
|
||||
const team = await getTeamById({ userId, teamId });
|
||||
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: {
|
||||
id: envelopeId,
|
||||
OR: [
|
||||
{ userId },
|
||||
{
|
||||
teamId: team.id,
|
||||
visibility: { in: TEAM_DOCUMENT_VISIBILITY_MAP[team.currentTeamRole] },
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
if (!envelope) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Envelope not found',
|
||||
});
|
||||
}
|
||||
|
||||
const envelopeTags = await prisma.envelopeTag.findMany({
|
||||
where: { envelopeId },
|
||||
include: {
|
||||
tag: true,
|
||||
},
|
||||
});
|
||||
|
||||
return mapEnvelopeTagsToTags(envelopeTags);
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { TEAM_DOCUMENT_VISIBILITY_MAP } from '../../constants/teams';
|
||||
import { TagType } from '../../types/tag-type';
|
||||
import { mapEnvelopeTagsToTags } from '../../utils/tags';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
import { getTeamById } from '../team/get-team';
|
||||
|
||||
export type SetEnvelopeTagsOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
envelopeId: string;
|
||||
tagIds: string[];
|
||||
};
|
||||
|
||||
export const setEnvelopeTags = async ({ userId, teamId, envelopeId, tagIds }: SetEnvelopeTagsOptions) => {
|
||||
const team = await getTeamById({ userId, teamId });
|
||||
|
||||
// Verify the envelope exists and the user has access.
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: {
|
||||
id: envelopeId,
|
||||
OR: [
|
||||
{ userId },
|
||||
{
|
||||
teamId: team.id,
|
||||
visibility: { in: TEAM_DOCUMENT_VISIBILITY_MAP[team.currentTeamRole] },
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
if (!envelope) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Envelope not found',
|
||||
});
|
||||
}
|
||||
|
||||
// Determine the expected tag type based on the envelope type.
|
||||
const expectedTagType = envelope.type === EnvelopeType.DOCUMENT ? TagType.DOCUMENT : TagType.TEMPLATE;
|
||||
|
||||
// Verify all tagIds belong to the same team and match the envelope type.
|
||||
if (tagIds.length > 0) {
|
||||
const tags = await prisma.tag.findMany({
|
||||
where: {
|
||||
id: { in: tagIds },
|
||||
team: buildTeamWhereQuery({ teamId, userId }),
|
||||
type: expectedTagType,
|
||||
},
|
||||
});
|
||||
|
||||
if (tags.length !== tagIds.length) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'One or more tags are invalid or do not match the envelope type',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// Delete EnvelopeTag rows not in the new set.
|
||||
await tx.envelopeTag.deleteMany({
|
||||
where: {
|
||||
envelopeId,
|
||||
tagId: { notIn: tagIds },
|
||||
},
|
||||
});
|
||||
|
||||
// Fetch current assignments to find which ones need to be created.
|
||||
const existing = await tx.envelopeTag.findMany({
|
||||
where: { envelopeId },
|
||||
select: { tagId: true },
|
||||
});
|
||||
|
||||
const existingTagIds = new Set(existing.map((et) => et.tagId));
|
||||
const toCreate = tagIds.filter((tagId) => !existingTagIds.has(tagId));
|
||||
|
||||
if (toCreate.length > 0) {
|
||||
await tx.envelopeTag.createMany({
|
||||
data: toCreate.map((tagId) => ({
|
||||
envelopeId,
|
||||
tagId,
|
||||
assignedBy: userId,
|
||||
})),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const tags = await prisma.envelopeTag.findMany({
|
||||
where: { envelopeId },
|
||||
include: {
|
||||
tag: true,
|
||||
},
|
||||
});
|
||||
|
||||
return mapEnvelopeTagsToTags(tags);
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
import { getTeamById } from '../team/get-team';
|
||||
|
||||
export type UpdateTagOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
tagId: string;
|
||||
data: {
|
||||
name?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const updateTag = async ({ userId, teamId, tagId, data }: UpdateTagOptions) => {
|
||||
const { name } = data;
|
||||
|
||||
await getTeamById({ userId, teamId });
|
||||
|
||||
const tag = await prisma.tag.findFirst({
|
||||
where: {
|
||||
id: tagId,
|
||||
team: buildTeamWhereQuery({ teamId, userId }),
|
||||
},
|
||||
});
|
||||
|
||||
if (!tag) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Tag not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (name !== undefined) {
|
||||
const normalizedName = name.trim().replace(/\s+/g, ' ');
|
||||
const normalizedNameKey = normalizedName.toLowerCase();
|
||||
|
||||
if (!normalizedName) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Tag name cannot be empty',
|
||||
});
|
||||
}
|
||||
|
||||
if (normalizedNameKey !== tag.normalizedName) {
|
||||
const existing = await prisma.tag.findFirst({
|
||||
where: {
|
||||
teamId,
|
||||
normalizedName: normalizedNameKey,
|
||||
type: tag.type,
|
||||
id: { not: tagId },
|
||||
},
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
throw new AppError(AppErrorCode.ALREADY_EXISTS, {
|
||||
message: 'A tag with this name already exists for this type',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return await prisma.tag.update({
|
||||
where: { id: tagId },
|
||||
data: {
|
||||
name: normalizedName,
|
||||
normalizedName: normalizedNameKey,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return tag;
|
||||
};
|
||||
@@ -58,6 +58,11 @@ export const findOrganisationTemplates = async ({
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
include: {
|
||||
tag: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
|
||||
@@ -13,6 +13,8 @@ export type FindTemplatesOptions = {
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
folderId?: string;
|
||||
includeAllFolders?: boolean;
|
||||
tagIds?: string[];
|
||||
};
|
||||
|
||||
export const findTemplates = async ({
|
||||
@@ -22,6 +24,8 @@ export const findTemplates = async ({
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
folderId,
|
||||
includeAllFolders = false,
|
||||
tagIds,
|
||||
}: FindTemplatesOptions) => {
|
||||
const { teamRole } = await getMemberRoles({
|
||||
teamId,
|
||||
@@ -46,7 +50,8 @@ export const findTemplates = async ({
|
||||
{ userId, teamId },
|
||||
],
|
||||
},
|
||||
folderId ? { folderId } : { folderId: null },
|
||||
...(includeAllFolders ? [] : [folderId ? { folderId } : { folderId: null }]),
|
||||
...(tagIds && tagIds.length > 0 ? [{ tags: { some: { tagId: { in: tagIds } } } }] : []),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -67,6 +72,11 @@ export const findTemplates = async ({
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
include: {
|
||||
tag: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
|
||||
@@ -6,9 +6,9 @@ import { TeamSchema } from '@documenso/prisma/generated/zod/modelSchema/TeamSche
|
||||
import { UserSchema } from '@documenso/prisma/generated/zod/modelSchema/UserSchema';
|
||||
import { LegacyDocumentSchema } from '@documenso/prisma/types/document-legacy-schema';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFieldSchema } from './field';
|
||||
import { ZRecipientLiteSchema } from './recipient';
|
||||
import { ZTagLiteSchema } from './tag';
|
||||
|
||||
/**
|
||||
* The full document response schema.
|
||||
@@ -169,6 +169,7 @@ export const ZDocumentManySchema = LegacyDocumentSchema.pick({
|
||||
id: true,
|
||||
url: true,
|
||||
}).nullable(),
|
||||
tags: ZTagLiteSchema.array(),
|
||||
});
|
||||
|
||||
export type TDocumentMany = z.infer<typeof ZDocumentManySchema>;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const TagType = {
|
||||
DOCUMENT: 'DOCUMENT',
|
||||
TEMPLATE: 'TEMPLATE',
|
||||
} as const;
|
||||
|
||||
export const ZTagTypeSchema = z.enum([TagType.DOCUMENT, TagType.TEMPLATE]);
|
||||
export type TTagType = z.infer<typeof ZTagTypeSchema>;
|
||||
@@ -0,0 +1,10 @@
|
||||
import TagSchema from '@documenso/prisma/generated/zod/modelSchema/TagSchema';
|
||||
import type { z } from 'zod';
|
||||
|
||||
export const ZTagLiteSchema = TagSchema.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
type: true,
|
||||
});
|
||||
|
||||
export type TTagLite = z.infer<typeof ZTagLiteSchema>;
|
||||
@@ -6,9 +6,9 @@ import TeamSchema from '@documenso/prisma/generated/zod/modelSchema/TeamSchema';
|
||||
import { UserSchema } from '@documenso/prisma/generated/zod/modelSchema/UserSchema';
|
||||
import { LegacyTemplateDirectLinkSchema, TemplateSchema } from '@documenso/prisma/types/template-legacy-schema';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFieldSchema } from './field';
|
||||
import { ZRecipientLiteSchema } from './recipient';
|
||||
import { ZTagLiteSchema } from './tag';
|
||||
|
||||
/**
|
||||
* The full template response schema.
|
||||
@@ -156,6 +156,7 @@ export const ZTemplateManySchema = TemplateSchema.pick({
|
||||
}).nullable(),
|
||||
// Backwards compatibility.
|
||||
templateDocumentDataId: z.string().default(''),
|
||||
tags: ZTagLiteSchema.array(),
|
||||
});
|
||||
|
||||
export type TTemplateMany = z.infer<typeof ZTemplateManySchema>;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { DEFAULT_DOCUMENT_EMAIL_SETTINGS } from '../types/document-email';
|
||||
import { SignatureLevel } from '../types/signature-level';
|
||||
import { mapSecondaryIdToDocumentId } from './envelope';
|
||||
import { mapRecipientToLegacyRecipient } from './recipients';
|
||||
import { type EnvelopeTagWithTag, mapEnvelopeTagsToTags } from './tags';
|
||||
|
||||
export const isDocumentCompleted = (document: Pick<Envelope, 'status'> | DocumentStatus) => {
|
||||
const status = typeof document === 'string' ? document : document.status;
|
||||
@@ -109,6 +110,7 @@ type MapEnvelopeToDocumentManyOptions = Envelope & {
|
||||
user: Pick<User, 'id' | 'name' | 'email'>;
|
||||
team: Pick<Team, 'id' | 'url'>;
|
||||
recipients: Recipient[];
|
||||
tags?: EnvelopeTagWithTag<TDocumentMany['tags'][number]>[];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -150,5 +152,6 @@ export const mapEnvelopesToDocumentMany = (envelope: MapEnvelopeToDocumentManyOp
|
||||
url: envelope.team.url,
|
||||
},
|
||||
recipients: envelope.recipients.map((recipient) => mapRecipientToLegacyRecipient(recipient, envelope)),
|
||||
tags: mapEnvelopeTagsToTags(envelope.tags),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export type EnvelopeTagWithTag<TTag> = {
|
||||
tag: TTag;
|
||||
};
|
||||
|
||||
export const mapEnvelopeTagsToTags = <TTag>(envelopeTags?: EnvelopeTagWithTag<TTag>[] | null) => {
|
||||
return (envelopeTags ?? []).map((envelopeTag) => envelopeTag.tag);
|
||||
};
|
||||
Reference in New Issue
Block a user