Unified search algorithm across all lists; per-section record visibility (contacts, deals, activities, leads, finance, tickets, notes, projects)

This commit is contained in:
2026-10-06 15:04:46 +03:30
parent 43717392b2
commit e08f1452ef
38 changed files with 699 additions and 420 deletions
+13 -56
View File
@@ -14,6 +14,7 @@ import type {
import { TICKET_SLA_HOURS } from '@mento/shared';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { buildSearchWhere, ci } from '../common/search';
import {
type Actor,
elevated,
@@ -63,9 +64,19 @@ export class TicketsService {
const limit = opts.limit ?? 50;
const now = new Date();
const access = ticketAccessWhere(actor);
const where: Prisma.TicketWhereInput = {
const search = buildSearchWhere<Prisma.TicketWhereInput>(opts.q, (t) => [
{ subject: ci(t) },
{ number: ci(t) },
{ description: ci(t) },
{
person: {
OR: [{ firstName: ci(t) }, { lastName: ci(t) }, { mobile: { contains: t } }],
},
},
]);
const finalWhere: Prisma.TicketWhereInput = {
organizationId: actor.organizationId,
...access,
AND: [access, ...(search ? [search] : [])],
...(opts.status ? { status: opts.status as never } : {}),
...(opts.priority ? { priority: opts.priority as never } : {}),
...(opts.personId ? { personId: opts.personId } : {}),
@@ -79,62 +90,8 @@ export class TicketsService {
status: { in: ['open', 'in_progress', 'waiting'] },
}
: {}),
...(opts.q?.trim()
? {
AND: [
access,
{
OR: [
{ subject: { contains: opts.q.trim(), mode: 'insensitive' } },
{ number: { contains: opts.q.trim(), mode: 'insensitive' } },
{
description: {
contains: opts.q.trim(),
mode: 'insensitive',
},
},
],
},
],
}
: {}),
};
// When q is set, avoid double-spreading access at top level
const finalWhere: Prisma.TicketWhereInput = opts.q?.trim()
? {
organizationId: actor.organizationId,
...(opts.status ? { status: opts.status as never } : {}),
...(opts.priority ? { priority: opts.priority as never } : {}),
...(opts.personId ? { personId: opts.personId } : {}),
...(opts.assigneeId && elevated(actor.role)
? { assigneeId: opts.assigneeId }
: {}),
...(opts.sla === 'overdue'
? {
slaDueAt: { lt: now },
firstRespondedAt: null,
status: { in: ['open', 'in_progress', 'waiting'] },
}
: {}),
AND: [
access,
{
OR: [
{ subject: { contains: opts.q.trim(), mode: 'insensitive' } },
{ number: { contains: opts.q.trim(), mode: 'insensitive' } },
{
description: {
contains: opts.q.trim(),
mode: 'insensitive',
},
},
],
},
],
}
: where;
const [rows, total] = await this.prisma.$transaction([
this.prisma.ticket.findMany({
where: finalWhere,