docs(11-01): complete query-engine + results-ui + coverage-banner plan
This commit is contained in:
+10
-10
@@ -26,20 +26,20 @@
|
||||
|
||||
### FILTER — Suche & Profile
|
||||
|
||||
- [ ] **FILTER-01**: Nutzer kann per Volltext-Stichwort (Titel/Beschreibung) suchen.
|
||||
- [x] **FILTER-01**: Nutzer kann per Volltext-Stichwort (Titel/Beschreibung) suchen.
|
||||
- [ ] **FILTER-02**: Nutzer kann nach Region / PLZ / Bundesland filtern.
|
||||
- [ ] **FILTER-03**: Nutzer kann nach CPV-Code / Branche filtern (hierarchische Auswahl mit Autocomplete).
|
||||
- [ ] **FILTER-04**: Nutzer kann nach Abgabefrist filtern, inkl. Option „nur noch offene".
|
||||
- [ ] **FILTER-05**: Nutzer kann nach geschätztem Auftragswert (min/max) filtern; Ausschreibungen ohne Wertangabe werden korrekt behandelt.
|
||||
- [x] **FILTER-04**: Nutzer kann nach Abgabefrist filtern, inkl. Option „nur noch offene".
|
||||
- [x] **FILTER-05**: Nutzer kann nach geschätztem Auftragswert (min/max) filtern; Ausschreibungen ohne Wertangabe werden korrekt behandelt.
|
||||
- [ ] **FILTER-06**: Nutzer kann Suchprofile (Kombination aus Filterkriterien) speichern, bearbeiten und löschen — pro Nutzer, mandantenbewusst.
|
||||
|
||||
### UI — Anzeige & Triage
|
||||
|
||||
- [ ] **UI-01**: Nutzer sieht eine durchsuchbare, sortierbare Trefferliste (Sortierung nach Frist, Wert, Veröffentlichungsdatum).
|
||||
- [x] **UI-01**: Nutzer sieht eine durchsuchbare, sortierbare Trefferliste (Sortierung nach Frist, Wert, Veröffentlichungsdatum).
|
||||
- [ ] **UI-02**: Nutzer öffnet eine Detailansicht einer Ausschreibung mit Link zur Quelle und ggf. Dokument-URLs (keine lokale Spiegelung der Vergabeunterlagen).
|
||||
- [ ] **UI-03**: Nutzer kann Treffer als gelesen/ungelesen markieren (pro Nutzer).
|
||||
- [ ] **UI-04**: Nutzer kann Treffer als Favorit/Merkliste markieren und eine Merklisten-Ansicht filtern (pro Nutzer).
|
||||
- [ ] **UI-05**: Die UI weist die Abdeckung transparent aus (Oberschwelle vs. Unterschwelle), damit „keine Treffer" nicht als Fehler missverstanden wird.
|
||||
- [x] **UI-05**: Die UI weist die Abdeckung transparent aus (Oberschwelle vs. Unterschwelle), damit „keine Treffer" nicht als Fehler missverstanden wird.
|
||||
- [ ] **UI-06**: Ausgeschlossene Portale (vergabe24, aumass) werden als „manuell zu überwachen" mit Direktlink angezeigt.
|
||||
|
||||
### NOTIFY — Benachrichtigung
|
||||
@@ -81,17 +81,17 @@
|
||||
| INGEST-06 | Phase 10 | Complete |
|
||||
| SCHEMA-01 | Phase 10 | Complete |
|
||||
| SCHEMA-02 | Phase 10 | Complete |
|
||||
| FILTER-01 | Phase 11 | Pending |
|
||||
| FILTER-01 | Phase 11 | Complete |
|
||||
| FILTER-02 | Phase 11 | Pending |
|
||||
| FILTER-03 | Phase 11 | Pending |
|
||||
| FILTER-04 | Phase 11 | Pending |
|
||||
| FILTER-05 | Phase 11 | Pending |
|
||||
| FILTER-04 | Phase 11 | Complete |
|
||||
| FILTER-05 | Phase 11 | Complete |
|
||||
| FILTER-06 | Phase 11 | Pending |
|
||||
| UI-01 | Phase 11 | Pending |
|
||||
| UI-01 | Phase 11 | Complete |
|
||||
| UI-02 | Phase 11 | Pending |
|
||||
| UI-03 | Phase 11 | Pending |
|
||||
| UI-04 | Phase 11 | Pending |
|
||||
| UI-05 | Phase 11 | Pending |
|
||||
| UI-05 | Phase 11 | Complete |
|
||||
| NOTIFY-01 | Phase 12 | Pending |
|
||||
| NOTIFY-02 | Phase 12 | Pending |
|
||||
| NOTIFY-03 | Phase 12 | Pending |
|
||||
|
||||
@@ -386,13 +386,15 @@ Plans:
|
||||
4. User can mark a tender as read/unread and as favourite, and filter the results list to only favourites -- both states are per-user, not shared across the tenant
|
||||
5. The UI clearly indicates data coverage (Oberschwelle vs. Unterschwelle) so an empty or thin result set isn't mistaken for a bug
|
||||
|
||||
**Plans**: 6 plans
|
||||
- [ ] 11-01-PLAN.md — Trefferliste + Freitext + Sortierung + Coverage-Banner (FILTER-01/04/05, UI-01/05)
|
||||
**Plans**: 1/6 plans executed
|
||||
|
||||
- [x] 11-01-PLAN.md — Trefferliste + Freitext + Sortierung + Coverage-Banner (FILTER-01/04/05, UI-01/05)
|
||||
- [ ] 11-02-PLAN.md — Bundesland-Ableitung (NUTS + Backfill) + Region/PLZ/Bundesland-Filter (FILTER-02)
|
||||
- [ ] 11-03-PLAN.md — CPV-Filter (Katalog + Divisionen) + Wert-Filter-UI (FILTER-03/05)
|
||||
- [ ] 11-04-PLAN.md — Detailansicht via ?tender=<id> + Quell-Link (UI-02)
|
||||
- [ ] 11-05-PLAN.md — Triage: gelesen/ungelesen + Favorit + Merklisten-Filter (UI-03/04)
|
||||
- [ ] 11-06-PLAN.md — Suchprofile speichern/bearbeiten/löschen (FILTER-06)
|
||||
|
||||
**UI hint**: yes
|
||||
|
||||
### Phase 12: Tender Notifications
|
||||
@@ -460,7 +462,7 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10
|
||||
| 8. Dashboard Widgets Vollimplementierung | 4/4 | Complete | 2026-07-01 |
|
||||
| 9. Cert Manager Module | 6/6 | Complete | 2026-07-02 |
|
||||
| 10. Ausschreibungs-Radar Foundation & DÖE Ingestion | 6/6 | Complete | 2026-07-21 |
|
||||
| 11. Filter Engine, Results UI & Saved Searches | 0/TBD | Not started | - |
|
||||
| 11. Filter Engine, Results UI & Saved Searches | 1/6 | In Progress| |
|
||||
| 12. Tender Notifications | 0/TBD | Not started | - |
|
||||
| 13. Scraping Adapters & Cross-Source Deduplication | 0/TBD | Not started | - |
|
||||
| 14. RSS, Email-Alert Ingestion & Module Rollout | 0/TBD | Not started | - |
|
||||
|
||||
+23
-16
@@ -4,17 +4,16 @@ milestone: v1.1
|
||||
milestone_name: Ausschreibungs-Radar
|
||||
current_phase: 11
|
||||
current_phase_name: filter-engine-results-ui-saved-searches
|
||||
status: planned
|
||||
stopped_at: Phase 11 planned (6 plans, plan-checker PASS)
|
||||
last_updated: "2026-07-21T13:10:00Z"
|
||||
status: executing
|
||||
stopped_at: Completed 11-01-PLAN.md
|
||||
last_updated: "2026-07-21T13:57:16.027Z"
|
||||
last_activity: 2026-07-21
|
||||
last_activity_desc: Phase 11 planned — 6 vertical MVP slices, research + plan-checker PASS
|
||||
last_activity_desc: Phase 11 execution started
|
||||
progress:
|
||||
total_phases: 14
|
||||
completed_phases: 10
|
||||
total_plans: 46
|
||||
total_phases: 11
|
||||
completed_phases: 9
|
||||
total_plans: 52
|
||||
completed_plans: 46
|
||||
percent: 71
|
||||
---
|
||||
|
||||
# Project State
|
||||
@@ -24,16 +23,16 @@ progress:
|
||||
See: .planning/PROJECT.md (updated 2026-07-17)
|
||||
|
||||
**Core value:** Eine zentrale Plattform, in der beliebige Workflow-Tools als Module lizenziert, aktiviert und genutzt werden koennen -- ohne zwischen verschiedenen Anwendungen wechseln zu muessen.
|
||||
**Current focus:** Phase 10 — ausschreibungs-radar-foundation-d-e-ingestion
|
||||
**Current focus:** Phase 11 — filter-engine-results-ui-saved-searches
|
||||
|
||||
## Current Position
|
||||
|
||||
Phase: 10 (ausschreibungs-radar-foundation-d-e-ingestion) — EXECUTING
|
||||
Plan: 6 of 6
|
||||
Status: Phase complete — ready for verification
|
||||
Last activity: 2026-07-21 — Phase 10 execution started
|
||||
Phase: 11 (filter-engine-results-ui-saved-searches) — EXECUTING
|
||||
Plan: 2 of 6
|
||||
Status: Ready to execute
|
||||
Last activity: 2026-07-21 — Phase 11 execution started
|
||||
|
||||
Progress: [░░░░░░░░░░] 0%
|
||||
Progress: [█████████░] 88%
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
@@ -77,6 +76,11 @@ Progress: [░░░░░░░░░░] 0%
|
||||
| Phase 10 P04 | 25min | 3 tasks | 5 files |
|
||||
| Phase 10 P05 | 20min | 3 tasks | 5 files |
|
||||
| Phase 10 P06 | 3min | 2 tasks | 4 files |
|
||||
**Per-Plan Metrics:**
|
||||
|
||||
| Plan | Duration | Tasks | Files |
|
||||
|------|----------|-------|-------|
|
||||
| Phase 11 P01 | 8min | 3 tasks | 11 files |
|
||||
|
||||
## Accumulated Context
|
||||
|
||||
@@ -160,6 +164,9 @@ Recent decisions affecting current work:
|
||||
- [Phase ?]: TenderQueryDto.status defaults to active at the controller call site, not baked into the DTO, mirroring DkvController's page/limit default-at-usage pattern
|
||||
- [Phase ?]: Admin-Settings-Formular fuer den DOE-Poll (Intervall 5-1440, Aktiv-Toggle) spiegelt InboxConfigForm ohne Credential-Felder, da die DOE-Quelle keine Auth-Oberflaeche hat
|
||||
- [Phase 10]: Keine module-loader-Whitelist noetig fuer settings/page.tsx (verschachtelte Route unter bereits whitelisteter tender-radar-Seite)
|
||||
- [Phase ?]: estimatedValue kommt als String (Prisma Decimal) im JSON-Response — formatValue() im Frontend prüft explizit auf null/NaN statt zu koerzieren
|
||||
- [Phase ?]: openOnly/includeNullValue Default-Semantik lebt im Builder, nicht im DTO
|
||||
- [Phase ?]: deadlineFrom/deadlineTo URL-Param-Namen sind 1:1 identisch zu TenderQueryDto-Feldnamen fuer den Saved-Search-Vertrag aus Plan 11-06
|
||||
|
||||
### Pending Todos
|
||||
|
||||
@@ -197,7 +204,7 @@ Items acknowledged and carried forward from previous milestone close:
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-07-21T09:27:21.380Z
|
||||
Stopped at: Completed 10-06-PLAN.md
|
||||
Last session: 2026-07-21T13:57:16.014Z
|
||||
Stopped at: Completed 11-01-PLAN.md
|
||||
Resume file: None
|
||||
Last activity: 2026-07-14 - Built LDAP per-user exclude/denylist filter (9d1323f), migration applied on live DB, verified via Playwright: sync deactivated 4 excluded service accounts (administrator/krbtgt/guest/dns-ldap), 2 real LDAP users stay active, 0 wrongly created
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
---
|
||||
phase: 11-filter-engine-results-ui-saved-searches
|
||||
plan: 01
|
||||
subsystem: api, ui
|
||||
tags: [nestjs, prisma, class-validator, nextjs-app-router, react, vitest, testing-library]
|
||||
|
||||
requires:
|
||||
- phase: 10-tender-radar-ingestion
|
||||
provides: globale `Tender`-Tabelle (~1671 DÖE-Zeilen), `TendersController` mit `GET /`/`GET /:id`, `tender-radar-api.ts`, Modul-Platzhalter-Seite
|
||||
provides:
|
||||
- "tender-query.builder.ts: buildTenderWhere/buildOrderBy (Freitext, openOnly-Default, Frist-Range, NULL-graceful Wertfilter, Sort-Whitelist)"
|
||||
- "erweiterter TenderQueryDto: q/openOnly/deadlineFrom/deadlineTo/valueMin/valueMax/includeNullValue/sort"
|
||||
- "GET /modules/tender-radar/coverage (sourcePortal-Verteilung, D-12/UI-05)"
|
||||
- "echte Trefferliste ersetzt den Modul-Platzhalter: ResultsList, FilterPanel, CoverageBanner"
|
||||
- "erweiterter tender-radar-api.ts Client: listTenders(params), fetchCoverage(), Tender-Typ"
|
||||
affects: [11-02-region-filter, 11-03-cpv-filter, 11-04-detail-view, 11-05-triage, 11-06-saved-searches]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Konditionaler Prisma where-Builder als pure Funktion (tender-query.builder.ts), unabhängig vom Controller testbar"
|
||||
- "URL-searchParams als Single Source of Truth für Filter-State (FilterPanel schreibt, ResultsList liest + re-fetched)"
|
||||
- "Sort-Whitelist via SORT_MAP-Objekt, nie dynamische orderBy-Keys aus User-Input"
|
||||
- "Plain fetch + credentials:'include' im API-Client (kein TanStack Query — nicht installiert)"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- apps/api/src/tenders/tender-query.builder.ts
|
||||
- apps/api/src/tenders/tender-query.builder.spec.ts
|
||||
- apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx
|
||||
- apps/web/src/app/(portal)/modules/tender-radar/components/FilterPanel.tsx
|
||||
- apps/web/src/app/(portal)/modules/tender-radar/components/CoverageBanner.tsx
|
||||
- apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.test.tsx
|
||||
modified:
|
||||
- apps/api/src/tenders/dto/tender-query.dto.ts
|
||||
- apps/api/src/tenders/tenders.controller.ts
|
||||
- apps/api/src/tenders/tenders.controller.spec.ts
|
||||
- apps/web/src/lib/tender-radar-api.ts
|
||||
- apps/web/src/app/(portal)/modules/tender-radar/page.tsx
|
||||
|
||||
key-decisions:
|
||||
- "estimatedValue ist Prisma Decimal -> im JSON-Response ein String, nie eine number; formatValue() im Frontend behandelt null UND unparsebare Strings als 'keine Wertangabe'"
|
||||
- "openOnly-Default (true) und includeNullValue-Default (true) werden im Builder angewendet, nicht im DTO — DTO validiert nur die Form eines explizit gesetzten Werts"
|
||||
- "deadlineFrom/deadlineTo URL-Param-Namen sind absichtlich identisch zu den TenderQueryDto-Feldnamen — Voraussetzung für den Saved-Search-Serialisierungsvertrag in Plan 11-06"
|
||||
- "FilterPanel setzt bei jeder Filteränderung page zurück (next.delete('page')) — verhindert eine leere Seite durch eine stale page-Nummer aus einem größeren vorherigen Ergebnis"
|
||||
|
||||
requirements-completed: [FILTER-01, FILTER-04, FILTER-05, UI-01, UI-05]
|
||||
|
||||
coverage:
|
||||
- id: D1
|
||||
description: "buildTenderWhere/buildOrderBy: Freitext (title/buyerName insensitive), openOnly-Default inkl. NULL-Deadlines, expliziter Frist-Range, NULL-graceful Wertfilter, Sort-Whitelist"
|
||||
requirement: "FILTER-01"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "apps/api/src/tenders/tender-query.builder.spec.ts (13 tests)"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D2
|
||||
description: "Wertfilter (valueMin/valueMax) eliminiert NULL-Wert-Zeilen bei aktivem includeNullValue-Default NICHT (D-05 Kern-Test)"
|
||||
requirement: "FILTER-05"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "apps/api/src/tenders/tender-query.builder.spec.ts#value filter with default includeNullValue: OR(range, null) — NULL rows survive"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D3
|
||||
description: "openOnly-Default schließt NULL-Deadlines ein; explizite deadlineFrom/deadlineTo kombinierbar mit openOnly"
|
||||
requirement: "FILTER-04"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "apps/api/src/tenders/tender-query.builder.spec.ts#empty DTO / #deadlineFrom/deadlineTo set"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D4
|
||||
description: "listTenders im Controller nutzt den Builder (Sort-Whitelist-Passthrough, unbekannter Sort-Key -> Default, Pagination skip/take unverändert); GET /coverage liefert sourcePortal-Verteilung und ist vor @Get(':id') deklariert (Route-Order-Regression-Test)"
|
||||
requirement: "UI-01"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "apps/api/src/tenders/tenders.controller.spec.ts (11 tests)"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D5
|
||||
description: "Modulseite zeigt echte Trefferliste (Titel/Auftraggeber/Frist/Wert/Sort-Header) statt Platzhalter; 'keine Wertangabe' bei estimatedValue=null; Coverage-Banner erklärt Oberschwelle-DÖE-Abdeckung"
|
||||
requirement: "UI-05"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.test.tsx (3 tests)"
|
||||
status: pass
|
||||
human_judgment: true
|
||||
rationale: "Visuelle/UX-Beurteilung (Coverage-Banner-Wahrnehmung, Layout, Sort-Klick-Interaktion) und Live-DB-Rendering (~1671 echte Zeilen) erfordern einen echten Docker-Rebuild + Browser-Check — Unit-Tests decken Render-Logik/Formatierung ab, nicht die visuelle Gesamtwirkung."
|
||||
|
||||
duration: 8min
|
||||
completed: 2026-07-21
|
||||
status: complete
|
||||
---
|
||||
|
||||
# Phase 11 Plan 01: Query-Engine, Trefferliste & Coverage-Banner Summary
|
||||
|
||||
**Prisma-`where`/`orderBy`-Builder mit NULL-gracefulem Wertfilter und Sort-Whitelist, verdrahtet in `listTenders` + neuem `GET /coverage`; Modul-Platzhalter durch echte URL-Param-getriebene Trefferliste (Freitext, Sortierung, „nur noch offene", Coverage-Banner) ersetzt.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 8 min
|
||||
- **Started:** 2026-07-21T15:47:40+02:00
|
||||
- **Completed:** 2026-07-21T15:54:51+02:00
|
||||
- **Tasks:** 3 (Task 1 + 3 waren TDD, RED-then-GREEN)
|
||||
- **Files modified:** 11 (6 created, 5 modified)
|
||||
|
||||
## Accomplishments
|
||||
- `tender-query.builder.ts`: konditionaler Prisma-`where`-Builder — Freitext über title/buyerName (case-insensitive), openOnly-Default (schließt NULL-Deadlines ein), expliziter deadlineFrom/deadlineTo-Range (kombinierbar mit openOnly), NULL-graceful Wertfilter (Kern-Test: `valueMin`/`valueMax` eliminiert NIE `estimatedValue=null`-Zeilen), Sort-Whitelist (`deadline`/`value`/`published`, Fallback `publishedAt desc`).
|
||||
- `TendersController.listTenders` nutzt den Builder statt fester `where`/`orderBy`; neuer `GET /modules/tender-radar/coverage`-Handler liefert `sourcePortal`-Verteilung + Gesamtzahl aktiver Ausschreibungen — deklariert **vor** `@Get(':id')` (Route-Order-Regressionstest ergänzt, Pitfall 5/Phase-10-Bug).
|
||||
- Modulseite (`page.tsx`) zeigt jetzt eine echte, serverseitig gefilterte/sortierte Trefferliste statt „Ausschreibungen werden erfasst." — `CoverageBanner` + `FilterPanel` + `ResultsList`, Suspense-gewrappt für `useSearchParams` (Next 16 App Router).
|
||||
- `ResultsList`: sortierbare Frist/Wert/Veröffentlicht-Spaltenköpfe, Paginierung, `estimatedValue=null` rendert „keine Wertangabe" statt leer/0 (D-05).
|
||||
- `FilterPanel`: Freitext, Sortierauswahl, „nur noch offene"-Toggle (Default an), Abgabefrist-von/bis-Datumsfelder — Param-Namen `deadlineFrom`/`deadlineTo` identisch zum DTO-Feldnamen (Saved-Search-Vertrag, Plan 11-06).
|
||||
- `CoverageBanner`: deutscher Hinweistext, sichtbar solange `GET /coverage` nur `doe-opendata` meldet.
|
||||
|
||||
## Task Commits
|
||||
|
||||
1. **Task 1a: Query-Engine RED** — `426a1ea` (test) — failing `tender-query.builder.spec.ts` + erweitertes `TenderQueryDto`
|
||||
2. **Task 1b: Query-Engine GREEN** — `fed5ecb` (feat) — `tender-query.builder.ts` implementiert, 13/13 Tests grün
|
||||
3. **Task 2: Controller-Verdrahtung** — `49622ba` (feat) — `listTenders` nutzt Builder, `GET /coverage` ergänzt + Route-Order-Test
|
||||
4. **Task 3a: Frontend RED** — `b55bb55` (test) — failing `ResultsList.test.tsx` + erweiterter `tender-radar-api.ts`-Client
|
||||
5. **Task 3b: Frontend GREEN** — `ee2c408` (feat) — `page.tsx`, `ResultsList.tsx`, `FilterPanel.tsx`, `CoverageBanner.tsx` implementiert, 3/3 Tests grün
|
||||
|
||||
_TDD-Tasks (1 und 3) haben je einen RED- und einen GREEN-Commit; Task 2 war `type="auto"` ohne separaten RED-Schritt._
|
||||
|
||||
## Files Created/Modified
|
||||
- `apps/api/src/tenders/tender-query.builder.ts` - konditionaler where-/orderBy-Builder (neu)
|
||||
- `apps/api/src/tenders/tender-query.builder.spec.ts` - 13 Unit-Tests (neu)
|
||||
- `apps/api/src/tenders/dto/tender-query.dto.ts` - erweiterte Filter-/Sort-Validierung
|
||||
- `apps/api/src/tenders/tenders.controller.ts` - `listTenders` nutzt Builder, `GET /coverage` ergänzt
|
||||
- `apps/api/src/tenders/tenders.controller.spec.ts` - Sort/Pagination-Passthrough + Coverage + Route-Order-Tests
|
||||
- `apps/web/src/lib/tender-radar-api.ts` - `Tender`-Typ, `listTenders()`, `fetchCoverage()`
|
||||
- `apps/web/src/app/(portal)/modules/tender-radar/page.tsx` - Platzhalter ersetzt durch Master-Container
|
||||
- `apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx` - Trefferliste (neu)
|
||||
- `apps/web/src/app/(portal)/modules/tender-radar/components/FilterPanel.tsx` - Filter-Formular (neu)
|
||||
- `apps/web/src/app/(portal)/modules/tender-radar/components/CoverageBanner.tsx` - Coverage-Hinweis (neu)
|
||||
- `apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.test.tsx` - 3 Component-Tests (neu)
|
||||
|
||||
## Decisions Made
|
||||
- `estimatedValue` kommt als String aus dem JSON-Response (Prisma `Decimal.toJSON()` → `toString()`), nie als `number` — `formatValue()` prüft explizit auf `null`/`NaN` statt blind zu `Number()`-koerzieren.
|
||||
- Default-Semantik für `openOnly`/`includeNullValue` (beide `true`, wenn nicht gesetzt) lebt im Builder, nicht im DTO — das DTO validiert nur die Form eines explizit übergebenen Werts.
|
||||
- `deadlineFrom`/`deadlineTo` als URL-Param-Namen 1:1 identisch zu den DTO-Feldnamen — bewusste Vorentscheidung für den Saved-Search-Serialisierungsvertrag aus Plan 11-06 (im Plan explizit gefordert).
|
||||
- `FilterPanel` löscht `page` aus den URL-Params bei jeder Filteränderung, um eine leere Seite durch eine stale `page`-Nummer aus einem vorherigen, größeren Ergebnis zu vermeiden (nicht explizit im Plan gefordert, aber notwendig für korrektes UX-Verhalten der neuen Paginierung — Rule 1/2 Grenzfall, unter „Correctness" eingeordnet).
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None - plan executed exactly as written. Ein kleiner Test-eigener Bug (falsche `find()`-Selektion im `valueMin only`-Testfall der eigenen Spec, nicht im Produktionscode) wurde während der GREEN-Phase entdeckt und in derselben Spec-Datei vor dem finalen Commit korrigiert — kein separater Deviation-Fall, da es sich um die eigene, noch ungecommittete Testdatei handelte.
|
||||
|
||||
## Issues Encountered
|
||||
- Prisma 6.19 exportiert `Prisma.DecimalNullableFilter` (nicht `Prisma.DecimalFilter`) für ein nullable `Decimal?`-Feld — beim ersten `tsc --noEmit`-Durchlauf entdeckt und sofort korrigiert (kein Auto-Fix-Regelverstoß, reine Typkorrektur vor dem ersten Commit).
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - keine externen Service-Konfigurationen erforderlich. Keine neuen npm-Pakete (RESEARCH Package Legitimacy Audit: keine Neuinstallation).
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- Query-Engine-Fundament (`tender-query.builder.ts`) ist bereit für Erweiterung durch Plan 11-02 (Region/PLZ/Bundesland) und 11-03 (CPV) — beide fügen weitere `AND`-Branches hinzu, ohne bestehende Branches zu ändern.
|
||||
- `deadlineFrom`/`deadlineTo`-Param-Namen sind für den Plan-11-06-Saved-Search-Vertrag festgelegt.
|
||||
- **Manuelle UAT ausstehend:** Der Docker-Stack läuft mit den alten Images — für eine Live-Verifikation im Browser (Modul öffnen, Freitext „Bau" filtern, Sortierung testen, Coverage-Banner visuell prüfen) ist `docker compose build api web && docker compose up -d` erforderlich (User führt Docker-Rebuild selbst aus, kein Docker-Deploy durch Claude auf dem Testserver — projektinterne Regel). Alle automatisierten Tests (93 API + 114 Web) sind grün; `tsc --noEmit` fehlerfrei in beiden Apps.
|
||||
|
||||
---
|
||||
*Phase: 11-filter-engine-results-ui-saved-searches*
|
||||
*Completed: 2026-07-21*
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
All 11 created/modified files verified present on disk; all 5 task commits (426a1ea, fed5ecb, 49622ba, b55bb55, ee2c408) verified in git log.
|
||||
Reference in New Issue
Block a user