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
+22 -14
View File
@@ -15,6 +15,7 @@ import type {
} from '@mento/shared';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { buildSearchWhere, ci } from '../common/search';
import { type Actor, ownerWhere } from '../common/record-scope';
import { AutomationsService } from '../automations/automations.service';
import { MessageAutomationsService } from '../message-automations/message-automations.service';
@@ -77,7 +78,7 @@ export class InvoicesService {
const dateFilter = buildDateRangeFilter(opts.from, opts.to);
const where: Prisma.InvoiceWhereInput = {
organizationId,
...ownerWhere(actor),
...ownerWhere(actor, 'finance'),
...(opts.status ? { status: opts.status as never } : {}),
...(opts.kind ? { kind: opts.kind } : {}),
...(opts.dealId ? { dealId: opts.dealId } : {}),
@@ -85,15 +86,7 @@ export class InvoicesService {
...(opts.companyId ? { companyId: opts.companyId } : {}),
...(opts.quoteId ? { quoteId: opts.quoteId } : {}),
...(dateFilter ? dateFilter : {}),
...(opts.q?.trim()
? {
OR: [
{ title: { contains: opts.q.trim(), mode: 'insensitive' } },
{ number: { contains: opts.q.trim(), mode: 'insensitive' } },
{ notes: { contains: opts.q.trim(), mode: 'insensitive' } },
],
}
: {}),
...invoiceSearchWhere(opts.q),
};
const hasAmountFilter =
@@ -143,7 +136,7 @@ export class InvoicesService {
actor: InvoiceActor,
): Promise<InvoiceDto> {
const row = await this.prisma.invoice.findFirst({
where: { id, organizationId, ...ownerWhere(actor) },
where: { id, organizationId, ...ownerWhere(actor, 'finance') },
include,
});
if (!row) throw new NotFoundException('فاکتور پیدا نشد');
@@ -306,7 +299,7 @@ export class InvoicesService {
dto: UpdateInvoiceRequest,
): Promise<InvoiceDto> {
const existing = await this.prisma.invoice.findFirst({
where: { id, organizationId, ...ownerWhere(actor) },
where: { id, organizationId, ...ownerWhere(actor, 'finance') },
select: { id: true, status: true, currency: true },
});
if (!existing) throw new NotFoundException('فاکتور پیدا نشد');
@@ -447,7 +440,7 @@ export class InvoicesService {
dto: CreatePaymentRequest,
): Promise<InvoiceDto> {
const invoice = await this.prisma.invoice.findFirst({
where: { id: invoiceId, organizationId, ...ownerWhere(actor) },
where: { id: invoiceId, organizationId, ...ownerWhere(actor, 'finance') },
select: {
id: true,
status: true,
@@ -681,7 +674,7 @@ export class InvoicesService {
actor: InvoiceActor,
) {
const found = await this.prisma.invoice.findFirst({
where: { id, organizationId, ...ownerWhere(actor) },
where: { id, organizationId, ...ownerWhere(actor, 'finance') },
select: { id: true },
});
if (!found) throw new NotFoundException('فاکتور پیدا نشد');
@@ -874,6 +867,21 @@ function roundMoney(value: number): number {
return Math.round((value + Number.EPSILON) * 100) / 100;
}
function invoiceSearchWhere(q?: string): Prisma.InvoiceWhereInput {
const search = buildSearchWhere<Prisma.InvoiceWhereInput>(q, (t) => [
{ title: ci(t) },
{ number: ci(t) },
{ notes: ci(t) },
{
person: {
OR: [{ firstName: ci(t) }, { lastName: ci(t) }, { mobile: { contains: t } }],
},
},
{ company: { name: ci(t) } },
]);
return search ? { AND: [search] } : {};
}
function buildDateRangeFilter(
from?: string,
to?: string,