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
+28 -38
View File
@@ -10,7 +10,8 @@ import type {
} from '@mento/shared';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { type Actor, elevated, ownerWhere } from '../common/record-scope';
import { type ScopedActor, ownerWhere, scopedOwnerIds } from '../common/record-scope';
import { buildSearchWhere, ci } from '../common/search';
const CHEQUE_STATUSES = ['pending', 'cleared', 'bounced'] as const;
@@ -20,7 +21,7 @@ export class ChequesService {
async list(
organizationId: string,
actor: Pick<Actor, 'userId' | 'role'>,
actor: ScopedActor,
opts?: { status?: string; q?: string; direction?: string },
): Promise<ChequeDto[]> {
const rows = await this.prisma.cheque.findMany({
@@ -31,37 +32,7 @@ export class ChequesService {
...(opts?.direction === 'in' || opts?.direction === 'out'
? { direction: opts.direction }
: {}),
...(opts?.q?.trim()
? {
OR: [
{ number: { contains: opts.q.trim(), mode: 'insensitive' } },
{ bank: { contains: opts.q.trim(), mode: 'insensitive' } },
{
invoice: {
number: { contains: opts.q.trim(), mode: 'insensitive' },
},
},
{
person: {
OR: [
{
firstName: {
contains: opts.q.trim(),
mode: 'insensitive',
},
},
{
lastName: {
contains: opts.q.trim(),
mode: 'insensitive',
},
},
],
},
},
],
}
: {}),
...this.searchWhere(opts?.q),
},
include: this.include,
orderBy: [{ dueDate: 'asc' }, { createdAt: 'desc' }],
@@ -70,9 +41,27 @@ export class ChequesService {
return rows.map((row) => this.toDto(row));
}
private searchWhere(q?: string): Prisma.ChequeWhereInput {
const search = buildSearchWhere<Prisma.ChequeWhereInput>(q, (t) => [
{ number: ci(t) },
{ bank: ci(t) },
{ invoice: { number: ci(t) } },
{
person: {
OR: [
{ firstName: ci(t) },
{ lastName: ci(t) },
{ mobile: { contains: t } },
],
},
},
]);
return search ? { AND: [search] } : {};
}
async create(
organizationId: string,
actor: Pick<Actor, 'userId' | 'role'>,
actor: ScopedActor,
dto: CreateChequeRequest,
): Promise<ChequeDto> {
const person = await this.prisma.person.findFirst({
@@ -112,7 +101,7 @@ export class ChequesService {
async update(
organizationId: string,
id: string,
actor: Pick<Actor, 'userId' | 'role'>,
actor: ScopedActor,
dto: UpdateChequeRequest,
): Promise<ChequeDto> {
const existing = await this.prisma.cheque.findFirst({
@@ -144,11 +133,12 @@ export class ChequesService {
}
private accessWhere(
actor: Pick<Actor, 'userId' | 'role'>,
actor: ScopedActor,
): Prisma.ChequeWhereInput {
if (elevated(actor.role)) return {};
const ids = scopedOwnerIds(actor, 'finance');
if (ids === null) return {};
return {
OR: [{ ownerId: actor.userId }, { invoice: { ownerId: actor.userId } }],
OR: [{ ownerId: { in: ids } }, { invoice: { ownerId: { in: ids } } }],
};
}