Selection of multiple officials and role sub-categories
This commit is contained in:
@@ -41,6 +41,7 @@ type ActivityRow = {
|
||||
company: { id: string; name: string } | null;
|
||||
deal: { id: string; title: string } | null;
|
||||
assignee: { id: string; fullName: string } | null;
|
||||
assignees: Array<{ id: string; fullName: string }>;
|
||||
createdBy: { id: string; fullName: string } | null;
|
||||
};
|
||||
|
||||
@@ -49,6 +50,7 @@ const include = {
|
||||
company: { select: { id: true, name: true } },
|
||||
deal: { select: { id: true, title: true } },
|
||||
assignee: { select: { id: true, fullName: true } },
|
||||
assignees: { select: { id: true, fullName: true }, orderBy: { fullName: 'asc' as const } },
|
||||
createdBy: { select: { id: true, fullName: true } },
|
||||
} satisfies Prisma.ActivityInclude;
|
||||
|
||||
@@ -186,8 +188,15 @@ export class ActivitiesService {
|
||||
): Promise<ActivityDto> {
|
||||
const linkActor = actor ?? { userId, role: 'owner' };
|
||||
await this.assertLinks(organizationId, linkActor, dto);
|
||||
const assigneeId = emptyToNull(dto.assigneeId ?? null) ?? userId;
|
||||
if (assigneeId) await this.assertUserInOrg(organizationId, assigneeId);
|
||||
const assigneeIds = await this.normalizeAssigneeIds(
|
||||
organizationId,
|
||||
dto.assigneeIds?.length
|
||||
? dto.assigneeIds
|
||||
: dto.assigneeId
|
||||
? [dto.assigneeId]
|
||||
: [userId],
|
||||
);
|
||||
const assigneeId = assigneeIds[0] ?? userId;
|
||||
|
||||
const row = await this.prisma.activity.create({
|
||||
data: {
|
||||
@@ -206,15 +215,18 @@ export class ActivitiesService {
|
||||
companyId: emptyToNull(dto.companyId ?? null),
|
||||
dealId: emptyToNull(dto.dealId ?? null),
|
||||
assigneeId,
|
||||
assignees: assigneeIds.length
|
||||
? { connect: assigneeIds.map((id) => ({ id })) }
|
||||
: undefined,
|
||||
createdById: userId,
|
||||
},
|
||||
include,
|
||||
});
|
||||
|
||||
if (assigneeId) {
|
||||
for (const uid of assigneeIds) {
|
||||
await this.notifications.create({
|
||||
organizationId,
|
||||
userId: assigneeId,
|
||||
userId: uid,
|
||||
type: 'activity_assigned',
|
||||
title: 'فعالیت جدید برای شما',
|
||||
body: row.title,
|
||||
@@ -262,10 +274,23 @@ export class ActivitiesService {
|
||||
if ('personId' in dto) data.personId = emptyToNull(dto.personId ?? null);
|
||||
if ('companyId' in dto) data.companyId = emptyToNull(dto.companyId ?? null);
|
||||
if ('dealId' in dto) data.dealId = emptyToNull(dto.dealId ?? null);
|
||||
if ('assigneeId' in dto) {
|
||||
let assigneeConnect: Array<{ id: string }> | undefined;
|
||||
if (dto.assigneeIds !== undefined) {
|
||||
const assigneeIds = await this.normalizeAssigneeIds(
|
||||
organizationId,
|
||||
dto.assigneeIds,
|
||||
);
|
||||
if (!assigneeIds.length) {
|
||||
throw new BadRequestException('حداقل یک مسئول لازم است');
|
||||
}
|
||||
data.assigneeId = assigneeIds[0]!;
|
||||
assigneeConnect = assigneeIds.map((uid) => ({ id: uid }));
|
||||
} else if ('assigneeId' in dto) {
|
||||
const assigneeId = emptyToNull(dto.assigneeId ?? null);
|
||||
if (assigneeId) await this.assertUserInOrg(organizationId, assigneeId);
|
||||
data.assigneeId = assigneeId;
|
||||
if (assigneeId) assigneeConnect = [{ id: assigneeId }];
|
||||
else assigneeConnect = [];
|
||||
}
|
||||
if (dto.status !== undefined) {
|
||||
data.status = dto.status;
|
||||
@@ -279,7 +304,12 @@ export class ActivitiesService {
|
||||
});
|
||||
const row = await this.prisma.activity.update({
|
||||
where: { id },
|
||||
data,
|
||||
data: {
|
||||
...data,
|
||||
...(assigneeConnect
|
||||
? { assignees: { set: assigneeConnect } }
|
||||
: {}),
|
||||
},
|
||||
include,
|
||||
});
|
||||
if (dto.status === 'done' && existing?.status !== 'done') {
|
||||
@@ -340,37 +370,55 @@ export class ActivitiesService {
|
||||
},
|
||||
): Prisma.ActivityWhereInput {
|
||||
const { start, end } = dayBounds();
|
||||
const scope =
|
||||
desk === 'mine' ? { assigneeId: actor.userId } : assigneeWhere(actor);
|
||||
const facetWhere: Prisma.ActivityWhereInput = {
|
||||
...(facets?.type ? { type: facets.type } : {}),
|
||||
...(facets?.status ? { status: facets.status } : {}),
|
||||
...(facets?.assigneeId?.trim() ? { assigneeId: facets.assigneeId.trim() } : {}),
|
||||
};
|
||||
const base: Prisma.ActivityWhereInput = {
|
||||
organizationId,
|
||||
...scope,
|
||||
...facetWhere,
|
||||
...(entity?.personId ? { personId: entity.personId } : {}),
|
||||
...(entity?.companyId ? { companyId: entity.companyId } : {}),
|
||||
...(entity?.dealId ? { dealId: entity.dealId } : {}),
|
||||
...(q?.trim()
|
||||
const scope: Prisma.ActivityWhereInput =
|
||||
desk === 'mine'
|
||||
? {
|
||||
OR: [
|
||||
{ title: { contains: q.trim(), mode: 'insensitive' } },
|
||||
{ description: { contains: q.trim(), mode: 'insensitive' } },
|
||||
{
|
||||
person: {
|
||||
OR: [
|
||||
{ firstName: { contains: q.trim(), mode: 'insensitive' } },
|
||||
{ lastName: { contains: q.trim(), mode: 'insensitive' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ deal: { title: { contains: q.trim(), mode: 'insensitive' } } },
|
||||
{ assigneeId: actor.userId },
|
||||
{ assignees: { some: { id: actor.userId } } },
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
: assigneeWhere(actor);
|
||||
const facetAssignee = facets?.assigneeId?.trim();
|
||||
const andParts: Prisma.ActivityWhereInput[] = [
|
||||
scope,
|
||||
...(facets?.type ? [{ type: facets.type }] : []),
|
||||
...(facets?.status ? [{ status: facets.status }] : []),
|
||||
...(facetAssignee
|
||||
? [
|
||||
{
|
||||
OR: [
|
||||
{ assigneeId: facetAssignee },
|
||||
{ assignees: { some: { id: facetAssignee } } },
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(entity?.personId ? [{ personId: entity.personId }] : []),
|
||||
...(entity?.companyId ? [{ companyId: entity.companyId }] : []),
|
||||
...(entity?.dealId ? [{ dealId: entity.dealId }] : []),
|
||||
];
|
||||
if (q?.trim()) {
|
||||
const needle = q.trim();
|
||||
andParts.push({
|
||||
OR: [
|
||||
{ title: { contains: needle, mode: 'insensitive' } },
|
||||
{ description: { contains: needle, mode: 'insensitive' } },
|
||||
{
|
||||
person: {
|
||||
OR: [
|
||||
{ firstName: { contains: needle, mode: 'insensitive' } },
|
||||
{ lastName: { contains: needle, mode: 'insensitive' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ deal: { title: { contains: needle, mode: 'insensitive' } } },
|
||||
],
|
||||
});
|
||||
}
|
||||
const base: Prisma.ActivityWhereInput = {
|
||||
organizationId,
|
||||
AND: andParts,
|
||||
};
|
||||
|
||||
const fromDate = range?.from ? new Date(range.from) : null;
|
||||
@@ -517,11 +565,35 @@ export class ActivitiesService {
|
||||
company: row.company,
|
||||
deal: row.deal,
|
||||
assignee: row.assignee,
|
||||
assignees: (() => {
|
||||
const list = [...(row.assignees ?? [])];
|
||||
if (row.assignee && !list.some((a) => a.id === row.assignee!.id)) {
|
||||
list.unshift(row.assignee);
|
||||
}
|
||||
return list;
|
||||
})(),
|
||||
createdBy: row.createdBy,
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
updatedAt: row.updatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
private async normalizeAssigneeIds(
|
||||
organizationId: string,
|
||||
raw?: string[] | null,
|
||||
): Promise<string[]> {
|
||||
const ids = [
|
||||
...new Set((raw ?? []).map((id) => id.trim()).filter(Boolean)),
|
||||
];
|
||||
if (!ids.length) return [];
|
||||
const count = await this.prisma.user.count({
|
||||
where: { organizationId, id: { in: ids }, isActive: true },
|
||||
});
|
||||
if (count !== ids.length) {
|
||||
throw new BadRequestException('یکی از مسئولان انتخابشده معتبر نیست');
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
|
||||
function emptyToNull(value?: string | null): string | null {
|
||||
|
||||
Reference in New Issue
Block a user