feat(quick-260914-eym): Mail-Transport je Versand nach Mandant (WINDOWS #30), ldap/digest/matching ueber Systemkontext, vier Tabellen im Werkzeug, Erlaubnisliste vollstaendig
- mail: MailerModule-Fabrik und DB-Startpfad (findFirst beim Boot) ersatzlos entfernt; MailService baut je Versand einen nodemailer-Transport aus getDecryptedSmtpConfig(tenantId) des Empfaenger-Mandanten, Umgebungs-Kette (MAIL_* -> TESSERA_SMTP_* -> localhost:1025) nur als Rueckfall; Fehler weiter verschluckt (T-02-12), close() im finally; neue mail.service.spec.ts (4 Tests, T-GWH-03 geschlossen) - settings: Startpfad-Methode samt vier Spec-Tests geloescht; auth: requestPasswordReset reicht user.tenantId durch (Spec-Zusicherung) - ldap: getAllActiveConfigs und Nachverschluesselung lesen ueber forSystem (zwei Zuweisungen), Schreibzeile je Altzeile ueber forTenant(config.tenantId); Tests 301/306 umgedreht, neuer Altzeilen-Test - tender-digest: Kandidatenabfrage ueber forSystem, Schleife gebunden (+1 Test) - tender-matching: Profilabfrage ueber forSystem, Katalog (D-03) ungebunden (+1 Test) - tender-notifications.integration.spec: Mock um forSystem - Werkzeug: LdapConfig (15 Spalten), LdapFieldMapping (6), TenderMatch (8), TenderSavedSearch (8) je neun Kennungen plus Relations-Kennung ldapconfig-systemkontext-include-fieldmappings-beider-mandanten -> Alle 253 Pruefungen bestanden - Detektor: FORSYSTEM_ALLOWED_CALL_SITES auf 4 Dateien / 5 Aufrufe; Proben-Empfaenger sysPrisma (Gate-Zaehlung, Name nicht hartkodiert) - Klassifikation: 6 Zeilen system-gebunden, settings/smtpConfig gebunden - Falsifizierung durch Rueckbau ausgefuehrt und zurueckgenommen: (a) FOR SELECT bei TenderMatch entfernt -> 5 von 253 rot (Insert gelingt, cmd ALL); (b) Regel TenderSavedSearch aus der Datei entfernt -> 1 von 245 rot (Extraktion), lebende DB bleibt bei 34; (c) local=false -> gruen, plus Reset entfernt -> 5 rot (Erben sichtbar); (d) Zahl 0 -> 2 rot, Fremddatei admin-seed -> 3 rot - Baseline: 64 Dateien / 1054 Tests, tsc 0, Werkzeug 253 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
||||
import { SchedulerRegistry } from '@nestjs/schedule';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { forTenant } from '../prisma/prisma-tenant.extension';
|
||||
import { forSystem, forTenant } from '../prisma/prisma-tenant.extension';
|
||||
import { TenderMailItem, TenderMailService } from './tender-mail.service';
|
||||
|
||||
/**
|
||||
@@ -105,9 +105,13 @@ export class TenderDigestScheduler implements OnModuleInit {
|
||||
async runDigest(now: Date = new Date()): Promise<void> {
|
||||
// Candidate users: distinct userId with at least one un-notified match,
|
||||
// across ALL tenants — a single findMany, never a per-tenant iteration.
|
||||
// BEWUSST UNGEBUNDEN (260909-laa, Aufgabe 3) — der bewusste Fan-out
|
||||
// über alle Mandanten dieses Bereichs; Etappe-3-Uebergabe (Systemkontext
|
||||
// für Hintergrundläufe wird dort entschieden, hier NICHT vorweggenommen).
|
||||
// SYSTEMGEBUNDEN (Etappe 3c, 260914-eym; die Etappe-3-Uebergabe aus
|
||||
// 260909-laa ist damit eingeloest): `forSystem()` liest TenderMatch
|
||||
// ALLER Mandanten NUR lesend (`system_read_policy ... FOR SELECT`,
|
||||
// Migration 20260914120000) — ohne diese Regel saehe der Digest nach dem
|
||||
// Scharfschalten 0 Kandidaten und wuerde stumm. Die Schleife unten
|
||||
// bleibt je Kandidatenzeile GEBUNDEN (bewusst ohne Benutzer, wie in 3b).
|
||||
// Eine LEERE Kandidatenliste ist Nichtstun: `notifiedAt` bleibt NULL.
|
||||
//
|
||||
// Zusaetzlich das denormalisierte tenantId der Treffer-Zeile mit
|
||||
// ausgewaehlt (nicht Teil von `distinct`), damit die Schleife unten
|
||||
@@ -117,7 +121,8 @@ export class TenderDigestScheduler implements OnModuleInit {
|
||||
// `distinct(['userId'])` liefert dann nur EINE der moeglichen
|
||||
// tenantId-Werte je Nutzer, welche ist von der internen Zeilenreihenfolge
|
||||
// abhaengig. Siehe docs/mandantentrennung-etappe2-fehlerrichtung.md.
|
||||
const candidates = await this.prisma.tenderMatch.findMany({
|
||||
const systemPrisma = forSystem(this.prisma) as any;
|
||||
const candidates: { userId: string; tenantId: string }[] = await systemPrisma.tenderMatch.findMany({
|
||||
where: { notifiedAt: null },
|
||||
select: { userId: true, tenantId: true },
|
||||
distinct: ['userId'],
|
||||
|
||||
Reference in New Issue
Block a user