feat(laa-03): binde die Je-Treffer-Haelften der Hintergrunddienste und schliesse die Dokumente
tender-digest.scheduler.ts: die uebergreifende Kandidatenabfrage (tenderMatch.findMany mit distinct:['userId']) bleibt bewusst ungebunden und waehlt zusaetzlich das denormalisierte tenantId der Treffer-Zeile mit aus; innerhalb der Schleife binden tenderNotificationPref.findUnique, tenderMatch.findMany/updateMany und user.findUnique an den Mandanten DIESER Kandidatenzeile. tender-matching.service.ts: die Profilabfrage (tenderSavedSearch.findMany) und der Lesezugriff auf den plattformweiten Tender-Katalog (D-03) bleiben ungebunden; innerhalb der Profilschleife bindet die Treffer-Anlage (tenderMatch.upsert), im nachgelagerten Instant-Dispatch binden tenderMatch.findMany/updateMany und user.findUnique — je EIN gebundener Client pro Profil, nicht neu je Treffer. Beide Dateien tragen Codekommentare, die die uebergreifenden Abfragen ausdruecklich als Etappe-3-Uebergabe benennen — die Trennlinie zwischen Etappe 2 und Etappe 3 wird hier gezogen, nicht verwischt. Alle drei betroffenen Testdateien (inkl. der gemeinsamen Integrationsdatei) bekommen den Zwei-Client-Nachweis, Tests fuer Zwei-Mandanten-Laeufe und die lautlose Fehlerform (kein Versand, notifiedAt bleibt NULL). Falsifiziert: ein probeweiser Rueckbau der user.findUnique-Bindung im Instant-Dispatch von tender-matching.service.ts machte genau den erwarteten Test rot, danach zurueckgenommen. rls-access-inventory.spec.ts gemessen und docs/mandantentrennung-zugriffsklassifikation.md nachgezogen: Stand der fuenf betroffenen Paare (tender-digest.scheduler.ts/ tenderMatch,tenderNotificationPref,user; tender-matching.service.ts/ tenderMatch,user) auf gemischt bzw. gebunden; Bereichsuebersicht und Klassen-Verteilung neu gemessen (tenders jetzt 36 ungebunden/26 gebunden); "Der Hintergrunddienst als Falle" um den Abschluss beider Dateien ergaenzt. docs/mandantentrennung-etappe2-fehlerrichtung.md: (t4) um den Nachtrag ergaenzt, dass die Je-Treffer-Haelften geschlossen sind und die uebergreifenden Haelften an Etappe 3 uebergeben bleiben. 770 Tests gruen, Typpruefung sauber, Wegwerf-Werkzeug 32/32, kein Schema-/Migrations-/Compose-/Umgebungsdatei-Diff. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { forTenant } from '../prisma/prisma-tenant.extension';
|
||||
import { TenderMailService } from './tender-mail.service';
|
||||
import { buildTenderWhere } from './tender-query.builder';
|
||||
import type { TenderQueryDto } from './dto/tender-query.dto';
|
||||
@@ -63,6 +64,10 @@ export class TenderMatchingService {
|
||||
async matchDelta(newTenderIds: string[]): Promise<void> {
|
||||
if (!newTenderIds.length) return;
|
||||
|
||||
// BEWUSST UNGEBUNDEN (260909-laa, Aufgabe 3) — Profile aller Mandanten
|
||||
// werden gegen neue Treffer geprueft, der bewusste Fan-out dieses
|
||||
// Bereichs; Etappe-3-Uebergabe (Systemkontext fuer Hintergrundlaeufe
|
||||
// wird dort entschieden, hier NICHT vorweggenommen).
|
||||
const savedSearches = await this.prisma.tenderSavedSearch.findMany();
|
||||
|
||||
for (const search of savedSearches) {
|
||||
@@ -74,13 +79,20 @@ export class TenderMatchingService {
|
||||
AND: [filterWhere, { id: { in: newTenderIds } }],
|
||||
};
|
||||
|
||||
// BEWUSST UNGEBUNDEN (D-03) — liest den plattformweiten
|
||||
// Ausschreibungskatalog, kein tenantId.
|
||||
const hits = await this.prisma.tender.findMany({
|
||||
where,
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
// Gebunden an den Mandanten DIESES Profils (260909-laa, Aufgabe 3)
|
||||
// — EIN gebundener Client je Profil, nicht je Treffer, sonst
|
||||
// entstuende pro Zeile eine eigene Transaktion.
|
||||
const tenantPrisma = forTenant(this.prisma, search.tenantId) as any;
|
||||
|
||||
for (const hit of hits) {
|
||||
await this.prisma.tenderMatch.upsert({
|
||||
await tenantPrisma.tenderMatch.upsert({
|
||||
where: {
|
||||
tenderId_savedSearchId: {
|
||||
tenderId: hit.id,
|
||||
@@ -114,7 +126,11 @@ export class TenderMatchingService {
|
||||
|
||||
for (const profile of instantProfiles) {
|
||||
try {
|
||||
const fresh = await this.prisma.tenderMatch.findMany({
|
||||
// Gebunden an den Mandanten DIESES Profils (260909-laa, Aufgabe 3)
|
||||
// — EIN gebundener Client je Profil.
|
||||
const tenantPrisma = forTenant(this.prisma, profile.tenantId) as any;
|
||||
|
||||
const fresh = await tenantPrisma.tenderMatch.findMany({
|
||||
where: {
|
||||
savedSearchId: profile.id,
|
||||
notifiedAt: null,
|
||||
@@ -124,7 +140,7 @@ export class TenderMatchingService {
|
||||
});
|
||||
if (!fresh.length) continue; // nothing new for this profile this tick
|
||||
|
||||
const user = await this.prisma.user.findUnique({ where: { id: profile.userId } });
|
||||
const user = await tenantPrisma.user.findUnique({ where: { id: profile.userId } });
|
||||
// Kein Konto, oder ein Konto ohne Adresse (WINDOWS #15, kollidierte
|
||||
// AD-Adresse) -- die Zugehoerigkeit funktioniert, nur der
|
||||
// Mailversand wird uebersprungen (zugesagtes Verhalten).
|
||||
@@ -134,12 +150,12 @@ export class TenderMatchingService {
|
||||
{ email: user.email },
|
||||
profile.tenantId,
|
||||
{ name: profile.name },
|
||||
fresh.map((match) => match.tender),
|
||||
fresh.map((match: { tender: unknown }) => match.tender),
|
||||
);
|
||||
|
||||
if (sent) {
|
||||
await this.prisma.tenderMatch.updateMany({
|
||||
where: { id: { in: fresh.map((match) => match.id) } },
|
||||
await tenantPrisma.tenderMatch.updateMany({
|
||||
where: { id: { in: fresh.map((match: { id: string }) => match.id) } },
|
||||
data: { notifiedAt: new Date(), notifiedChannel: 'instant' },
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user