mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
Arbitrage du référent sur la revue pixel : tout corriger, activer la recherche. - Stock : filtre fournisseur, sous-seuil en tête, « Entrée de stock » depuis la liste ; fiche pièce : fournisseur → lien Tiers. - Statistiques : période 3/6/12 mois (paramètre months au contrat). - Tiers : rattachements syndic→site (migration r3_recette_fixes, Location.partnerId gardé CLIENT), éditable sur la fiche site, seedé. - Bibliothèque : filtre « Rattaché à » + glisser-déposer (modale préremplie, rattachement toujours requis). - Recherche globale : GET /search (73 opérations) — familles OT/ ascenseurs/sites filtrées par la matrice, « voir autre » respecté ; topbar ⌘K, debounce, résultats groupés, navigation clavier. 74 tests API (8 nouveaux sur le scoping de la recherche), 14/14 Playwright dont un parcours « recette corrigée », 18/18 contrôles en navigateur réel, zéro erreur console. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
106 lines
3.5 KiB
TypeScript
106 lines
3.5 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import type { Prisma } from '@prisma/client';
|
|
import { SEARCH_MAX_PER_KIND, SEARCH_MIN_CHARS, type SearchResponse } from '@siop/shared';
|
|
import type { AuthenticatedUser } from '../auth/current-user.decorator';
|
|
import { PermissionsService } from '../permissions/permissions.service';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
|
|
/** Recherche globale de la topbar (⌘K). Chaque famille n'est interrogée que
|
|
* si le rôle a la permission `view` du domaine ; les OT respectent en plus
|
|
* l'invariant « voir autre » (même règle que la liste des OT). */
|
|
@Injectable()
|
|
export class SearchService {
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly permissions: PermissionsService,
|
|
) {}
|
|
|
|
async search(user: AuthenticatedUser, q: string): Promise<SearchResponse> {
|
|
const terme = q.trim();
|
|
const vide: SearchResponse = { workOrders: [], assets: [], sites: [] };
|
|
if (terme.length < SEARCH_MIN_CHARS) return vide;
|
|
|
|
const [voitOT, voitParc, voitSites] = await Promise.all([
|
|
this.permissions.can(user.roleId, 'WORK_ORDERS', 'view'),
|
|
this.permissions.can(user.roleId, 'ASSETS', 'view'),
|
|
this.permissions.can(user.roleId, 'LOCATIONS', 'view'),
|
|
]);
|
|
const contient = (champ: string): Prisma.StringFilter => ({
|
|
contains: champ,
|
|
mode: 'insensitive',
|
|
});
|
|
|
|
const [workOrders, assets, sites] = await Promise.all([
|
|
voitOT
|
|
? this.prisma.workOrder.findMany({
|
|
where: {
|
|
AND: [
|
|
{ OR: [{ reference: contient(terme) }, { title: contient(terme) }] },
|
|
await this.scopeOT(user),
|
|
],
|
|
},
|
|
select: { id: true, reference: true, title: true, status: true },
|
|
orderBy: { createdAt: 'desc' },
|
|
take: SEARCH_MAX_PER_KIND,
|
|
})
|
|
: [],
|
|
voitParc
|
|
? this.prisma.asset.findMany({
|
|
where: {
|
|
OR: [
|
|
{ reference: contient(terme) },
|
|
{ brand: contient(terme) },
|
|
{ model: contient(terme) },
|
|
],
|
|
},
|
|
select: {
|
|
id: true,
|
|
reference: true,
|
|
brand: true,
|
|
model: true,
|
|
location: { select: { name: true, parent: { select: { name: true } } } },
|
|
},
|
|
orderBy: { reference: 'asc' },
|
|
take: SEARCH_MAX_PER_KIND,
|
|
})
|
|
: [],
|
|
voitSites
|
|
? this.prisma.location.findMany({
|
|
where: {
|
|
parentId: null,
|
|
OR: [{ name: contient(terme) }, { city: contient(terme) }],
|
|
},
|
|
select: { id: true, name: true, city: true },
|
|
orderBy: { name: 'asc' },
|
|
take: SEARCH_MAX_PER_KIND,
|
|
})
|
|
: [],
|
|
]);
|
|
|
|
return {
|
|
workOrders,
|
|
assets: assets.map((a) => ({
|
|
id: a.id,
|
|
reference: a.reference,
|
|
brand: a.brand,
|
|
model: a.model,
|
|
siteName: a.location.parent?.name ?? a.location.name,
|
|
})),
|
|
sites,
|
|
};
|
|
}
|
|
|
|
/** Même invariant que WorkOrdersService.scope — sans le droit « voir
|
|
* autre », on ne trouve que SES OT. */
|
|
private async scopeOT(user: AuthenticatedUser): Promise<Prisma.WorkOrderWhereInput> {
|
|
const viewOther = await this.permissions.can(user.roleId, 'WORK_ORDERS', 'viewOther');
|
|
if (viewOther) return {};
|
|
return {
|
|
OR: [
|
|
{ assignees: { some: { id: user.userId } } },
|
|
{ createdById: user.userId },
|
|
],
|
|
};
|
|
}
|
|
}
|