feat: T6 wave 6 – treatment form with medicine and lot, notices and settings UI, restricted zone UI
- Treatment form and bulk entry: catalogue medicine or free text, lot with oldest preselected on create, lot fields from the lot, dose with at most 3 decimals, catalogue prefill with explicit take-over of the withdrawal period on a medicine switch, ?editTreatment deep link. - Authority notices: list with year filter, create with draft dedupe link, detail steps with PDF download and mark-sent, settings with MM-TT check, digit-rule editor and "Annahme – bitte prüfen" notes; COMPLIANCE label in notifications and Telegram categories. - Restricted zone: page with active/planned/ended, create and end dialogs (endsOn inclusive), red band at the apiary, guards on move, merge, edit and new colony, override only with compliance:manage, harvest warning. - ARCHITECTURE.md §12.11, Annahmen 91–94; TASKS.md T6 done with open items. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
13dda0e139
commit
8188e4cab3
90 files changed
+8069
-265
No files matched your search
+36
-4
@@ -211,6 +211,10 @@ Fachliche Annahmen, getroffen weil das Lastenheft an der Stelle keine eindeutige
|
||||
88. **„1.000“ wird als mehrdeutig abgelehnt**: Die Mengenfelder der Tierarzneimittel-Oberfläche lesen das Komma als Dezimalzeichen und lehnen einen einzelnen Punkt mit genau drei Nachkommastellen („1.000“, „12.345“) sowie jeden Text mit mehreren Trennzeichen ab, weil er Tausender oder Dezimalstelle meinen könnte. Kosten bei falscher Annahme: Wer „1.000“ als eins meint, muss „1“ oder „1,0“ schreiben. Abschnitt 12.11.
|
||||
89. **Eine neue, ungespeicherte Etikettvorlage prüft nichts, bis sie einen Wert hat**: Der Check-Endpunkt verlangt die Mindest-x-Höhe; das Frontend kennt keinen eigenen Vorgabewert (kein Rechtswert im Code). Eine neue Vorlage startet mit leerem Feld, sendet beim Anlegen keinen Wert (der Server setzt seinen Standard) und zeigt das Panel erst nach Eingabe oder nach dem ersten Speichern. Kosten bei falscher Annahme: Im Designer fehlt bei einer ganz neuen Vorlage die Prüfung, bis der Wert gesetzt ist. Abschnitt 12.11.
|
||||
90. **Das Prüfbericht-PDF druckt Zählungen, keine Namen**: Es nennt je Punkt nur die Zahl der betroffenen Objekte, nie Stände, Völker, Mittel, Packungen oder Aktenzeichen, damit der Bericht ohne Rechteprüfung der Namen weitergegeben werden kann. Kosten bei falscher Annahme: Wer die Objekte braucht, öffnet die Seite `/compliance` mit den Rechten. Abschnitt 12.10.
|
||||
91. **Eine Sperre, die heute endet, gilt bis Tagesende; ein Aufheben am selben Tag gibt es nicht**: `endsOn` ist inklusive (aktiv bei `startsOn` ≤ Berliner Tag ≤ `endsOn`), das Backend lehnt `endsOn` vor `startsOn` ab. Die Oberfläche schlägt „gestern“ als Endtag vor; wer heute oder später wählt, bekommt „Die Sperre gilt noch bis einschließlich …“. Kosten bei falscher Annahme: Wer eine Sperre heute am Vormittag aufheben will, hat sie bis Mitternacht Berliner Zeit weiter am Stand. Abschnitt 12.11.
|
||||
92. **Beim Bearbeiten einer Behandlung wählt die Oberfläche nie selbst eine Packung vor**: Nur das Anlegen schlägt die Packung mit dem frühesten Verfall vor; im Bearbeiten-Dialog steht nach einem Mittelwechsel „Ohne Packung“, bis der Nutzer wählt. Kosten bei falscher Annahme: Wer eine Abbuchung erwartet, muss die Packung ausdrücklich wählen. Abschnitt 12.11.
|
||||
93. **Die Glocke zeigt für `COMPLIANCE` das Kurz-Etikett „Behörden“**: Die Badges der Glocke sind Einwort-Etiketten („Aufgabe“, „Vorschlag“); „Behörden & Prüfung“ würde die Zeile umbrechen und steht darum nur in `lib/notifications/labels.ts` und der Telegram-Kategorienauswahl. Kosten bei falscher Annahme: Zwei Bezeichnungen für dieselbe Kategorie. Abschnitt 12.11.
|
||||
94. **Die Dosis ist auf drei Nachkommastellen begrenzt**: Das Eingabefeld lehnt mehr als drei Nachkommastellen mit „Höchstens 3 Nachkommastellen.“ ab (Packungsmengen rechnen auf Tausendstel, `roundToThousandths`). Kosten bei falscher Annahme: Eine feinere Dosis lässt sich nicht erfassen. Abschnitt 12.11.
|
||||
|
||||
## 7. Kubernetes
|
||||
|
||||
@@ -736,7 +740,7 @@ Logs nennen nur Zustellungs-IDs, nie Token, Chat-ID oder Text.
|
||||
|
||||
Prüfbereitschaft für Tierarzneimittel, Etiketten und Behörden. Rechtswerte (Fristen, Paragraphen, Ziffernhöhen, x-Höhe, Landesrecht Bayern) stehen nie im Code, sondern sind Einstellungen mit dem Vermerk „Annahme – vom Inhaber zu prüfen“; sie sind vor dem Rollout gegen gesetze-im-internet.de und EUR-Lex abzugleichen. Belege, Rechnungsnummern, Anschriften, Koordinaten und Aktenzeichen sehen nur Inhaber von `compliance:read` bzw. `veterinary-medicines:read`; sie erscheinen nie in öffentlichen Endpunkten, im Shop-Feed, in Webhooks oder in Ereignis-Payloads. Entscheidungen des Inhabers vom 2026-10-15: eigene Meldungs-Kategorie `COMPLIANCE`, Stand vor Volk beim Sperren, kein Step-up für die Ausnahme vom Sperrbezirk.
|
||||
|
||||
**Stand:** Umgesetzt sind Task 1 (Schema, Rechte, Sperren, Backup), 2 (Tierarzneimittel), 3 (Behandlung aus Packung), 4 (Dokumente), 5 (Etikett-Pflichtangaben), 6 (Druck-Sperre und Probedruck), 7 (Ereignisse), 8 (Sperrbezirk und Sperr-Umbau der Völker) und 9 (Behördenpaket I), 10 (Anzeigen-PDFs), 11 (Hinweise und Tagesauswertung) und 12 (Prüfbereitschaft I), 13 (Prüfbereitschaft II), 14 (Oberfläche Prüfbereitschaft), 15 (Oberfläche Tierarzneimittel) und 17 (Oberfläche Etikett). Task 1 bis 15 und 17 sind erledigt; Task 16, 18 und 19 (Behandlungsmaske, Anzeigen- und Sperrbezirk-Oberfläche, Abschluss) folgen und ergänzen diesen Abschnitt.
|
||||
**Stand:** Umgesetzt sind Task 1 (Schema, Rechte, Sperren, Backup), 2 (Tierarzneimittel), 3 (Behandlung aus Packung), 4 (Dokumente), 5 (Etikett-Pflichtangaben), 6 (Druck-Sperre und Probedruck), 7 (Ereignisse), 8 (Sperrbezirk und Sperr-Umbau der Völker) und 9 (Behördenpaket I), 10 (Anzeigen-PDFs), 11 (Hinweise und Tagesauswertung) und 12 (Prüfbereitschaft I), 13 (Prüfbereitschaft II), 14 (Oberfläche Prüfbereitschaft), 15 (Oberfläche Tierarzneimittel) 17 (Oberfläche Etikett), 16 (Behandlungsmaske), 18 (Anzeigen und Einstellungen) und 19 (Oberfläche Sperrbezirk). Task 1 bis 19 sind erledigt (T6a Backend: Task 1 bis 13; T6b Frontend: Task 14 bis 19). Offen sind die Browserprüfung aller T6-Seiten, die Rechtswerte-Prüfung durch den Inhaber und das Ausrollen (TASKS.md).
|
||||
|
||||
### 12.1 Schema und Rechte
|
||||
|
||||
@@ -830,7 +834,7 @@ Vier neue Typen im Katalog (`domain-event-types.ts`, 38 Typen), Aggregattyp `Com
|
||||
- **Sperrmuster und 409 „neu laden“.** Die Pfade lesen die Stand-Ids ungesperrt, sperren die Stände `FOR SHARE` aufsteigend, dann die Völker (beim Vereinigen Überlebendes und Aufgelöstes in einer Anweisung), vergleichen unter der Sperre den Stand des Volkes mit dem gelesenen (sonst 409 „Der Stand wurde gerade geändert. Bitte neu laden.“, kein automatischer Neuversuch; ein verschwundenes Volk ist 404), lesen `assertApiaryActive` über `tx` und prüfen erst dann die Sperre. `ApiaryRestrictionsService` (`POST`, `PATCH`) sperrt die Stände `FOR UPDATE`: Ein laufendes Umsetzen und eine neu beginnende Sperre reihen sich hintereinander ein, eine Sperrenänderung wartet auf laufende Umsetzungen. `updateIn` nimmt selbst keinen Standlock; ein Patch mit Standwechsel ohne den Wächter aus `update()` wirft (Programmierfehler). Die Durchsicht-Übernahme (ohne `apiaryId`) sperrt nur das Volk.
|
||||
- **Endpunkte.** `POST /apiary-restrictions` (`compliance:manage`, `@Idempotent`, 1 bis 50 verschiedene Stände, je Stand eine Zeile und ein Audit, eine Transaktion, unbekannter Stand 404 mit Rollback, `endsOn >= startsOn` sonst 400), `PATCH /apiary-restrictions/:id` (Stand `FOR UPDATE`, dann Sperre, `before` danach, `endsOn: null` öffnet wieder), `GET /apiary-restrictions?active=true|false`, `GET /apiaries/:id/restrictions` (`apiaries:read`).
|
||||
- **Einzelheiten nur mit `compliance:read`:** Behörde, Aktenzeichen, Notiz und Ersteller liefern beide GET-Pfade nur dann; mit nur `apiaries:read` besteht eine Zeile aus `id`, `apiaryId`, `disease`, `startsOn`, `endsOn`. `active=true` liefert für alle die schlanke `ActiveRestrictionInfo` (ohne diese Felder). Öffentliche Seiten und der Shop-Feed nennen weder Seuche noch Sperre (e2e mit echter öffentlicher Charge).
|
||||
- Das Frontend (Task 18/19) bietet den Ausnahmedialog nur mit `compliance:manage` an und sendet `restrictionOverrideReason`.
|
||||
- Das Frontend (Task 19, Abschnitt 12.11) bietet den Ausnahmedialog nur mit `compliance:manage` an und sendet `restrictionOverrideReason`.
|
||||
|
||||
### 12.8 Behördenpaket I: Einstellungen, Bestandsbild, Anzeigen
|
||||
|
||||
@@ -908,11 +912,11 @@ Task 12 liefert den Rahmen der Prüfbereitschaft mit den ersten sieben Regeln, T
|
||||
|
||||
### 12.11 Oberfläche (Frontend)
|
||||
|
||||
Die Oberfläche des Compliance-Blocks entsteht in mehreren Tasks; dieser Stand umfasst Prüfbereitschaft (Task 14), Tierarzneimittel (Task 15) und Etikett (Task 17). Behandlungsmaske (Task 16) sowie Anzeigen, Sperrbezirke und Einstellungen (Task 18, 19) folgen. Alle Aktionen sind online-only: Auslöser sind offline deaktiviert und nennen den sichtbaren Grund „Nur mit Verbindung“ (`aria-describedby`). Der Service-Worker-Ausschluss steht in Abschnitt 2.
|
||||
Die Oberfläche des Compliance-Blocks entstand in Task 14 bis 19: Prüfbereitschaft (14), Tierarzneimittel (15), Behandlungsmaske (16), Etikett (17), Anzeigen und Einstellungen (18) und Sperrbezirk (19). Alle Aktionen sind online-only: Auslöser sind offline deaktiviert und nennen den sichtbaren Grund „Nur mit Verbindung“ (`aria-describedby`). Der Service-Worker-Ausschluss steht in Abschnitt 2.
|
||||
|
||||
**Prüfbereitschaft (Task 14).** Code in `lib/compliance/` (`types`, `api`, `fix-links`, `errors`, `labels`, `use-readiness`; Query-Schlüssel `['compliance', …]`) und `app/(app)/compliance/`.
|
||||
- **Seite `/compliance`** zeigt `GET /compliance/readiness`. Oben der Status: „Prüfbereit“ (grün) oder „n offen“ (rot), jeweils mit Symbol und Wort. **Jeder offene Punkt ist rot** (Token `destructive`) und trägt Symbol, das Wort „Offen“, die **Schwere als Wort „Pflicht“ oder „Prüfen“** und den Titel; dazu ein Satz Detail und klein die Norm. Betroffene Objekte stehen eingeklappt mit einem „Beheben“-Link je Objekt, „und n weitere“ für den Rest; fehlt das Recht auf die Namen, steht nur „n betroffen“. Erledigte Punkte stehen eingeklappt unter „Erledigt (n)“, Quittierte ruhig mit Nutzer, Datum und Notiz. Die Zuordnung Schwere → Wort und offen → rot liegt allein in `labels.ts` und hat einen eigenen Test. Hauptaktion ist „Nächsten Punkt lösen“ (erster offener Pflicht-Punkt); ist dessen Ziel noch nicht gebaut, heißt sie „Nächsten Punkt ansehen“ und springt auf die Karte (`#item-<Schlüssel>-<Subjekt>`). Bei grünem Status steht dort „Bestandsbuch {Jahr} drucken“. Hinter „Mehr“ liegen der Bericht (PDF über `downloadReadinessReport`, als Blob mit Bearer-Header, Dateiname `pruefbericht.pdf`), das Bestandsbuch, Anzeigen, Sperrbezirke und Einstellungen. Downloads sind offline aus.
|
||||
- **Beheben-Links aus einer Stelle:** `fixLinkFor(itemKey, subject)` in `fix-links.ts` bildet Subjekte auf Seiten ab (Typen ohne Beachtung der Groß-/Kleinschreibung). Die Ziele `/compliance/notices(/[id])`, `/compliance/restrictions` und `/compliance/settings` gibt es erst mit Task 18/19; bis dahin blendet die Menge `ROUTE_AVAILABLE` solche Links aus (`available: false`), und die jeweilige Task trägt ihre Route dort ein. Eine Behandlung verlinkt auf `/colonies/{parentId}`, Stammdaten auf `/settings/automation?tab=social-media` (Reiter „Stammdaten & Web“). `herd-change` führt zur Anzeige (`/compliance/notices`), nicht zur Sperrenliste; `restriction-active` ohne Subjekt zur Sperrenliste.
|
||||
- **Beheben-Links aus einer Stelle:** `fixLinkFor(itemKey, subject)` in `fix-links.ts` bildet Subjekte auf Seiten ab (Typen ohne Beachtung der Groß-/Kleinschreibung). Die Ziele `/compliance/notices(/[id])`, `/compliance/restrictions` und `/compliance/settings` schaltet `ROUTE_AVAILABLE` seit Task 18 und 19 ein (vorher blendete die Menge solche Links aus, `available: false`). Eine Behandlung verlinkt auf `/colonies/{parentId}`, Stammdaten auf `/settings/automation?tab=social-media` (Reiter „Stammdaten & Web“). `herd-change` führt zur Anzeige (`/compliance/notices`), nicht zur Sperrenliste; `restriction-active` ohne Subjekt zur Sperrenliste.
|
||||
- **Quittieren und Zurücknehmen** (`acknowledge-dialog.tsx`): nur bei `acknowledgeable` und offenem Punkt bzw. quittiertem Punkt, nur mit `compliance:manage`. Notiz 10 bis 2000 Zeichen, vor dem Senden geprüft; Zurücknehmen über `ConfirmDialog`. Fehler laufen über `readinessConflictText`, der die drei Codes `READINESS_FINGERPRINT_STALE`, `READINESS_ITEM_NOT_OPEN` und `READINESS_ACK_REVOKED` mit je eigenem deutschem Text auswertet (der Servertext ist nur Rückfall); bei jedem 409 laden alle `compliance`-Queries neu. Nach Erfolg und Neuladen geht der Fokus auf die Kartenüberschrift (`tabIndex=-1`), eine `role="status"`-Region sagt „Punkt quittiert“ bzw. „Quittung zurückgenommen“. `lib/compliance/errors.ts` liest außerdem `DRAFT_EXISTS` und `RESTRICTED_ZONE`, alles andere läuft über `saveErrorText`; es gibt keine zweite Fehlerlogik.
|
||||
- **Dashboard-Zeile** (`components/today/compliance-line.tsx`): „n Punkte offen in der Prüfbereitschaft“, nur mit `compliance:read` und `openCount > 0`, aus `GET /compliance/readiness/summary` (60 s gecacht, Annahme 86). Schlägt das Laden fehl (kein Netzfehler; offline bleibt es still), steht eine **Fehlerzeile** „Prüfbereitschaft nicht geladen.“ mit „Erneut versuchen“.
|
||||
- **Navigation:** `nav-config.ts` hat „Prüfbereitschaft“ (Symbol `ShieldCheck`, Gruppe „Betrieb“, Recht `compliance:read`).
|
||||
@@ -933,3 +937,31 @@ Die Oberfläche des Compliance-Blocks entsteht in mehreren Tasks; dieser Stand u
|
||||
- **Druckkarte der Abfüllung** (`bottling/[id]/print-labels-card.tsx`): Vor dem Anzeigen läuft `GET /label-templates/:id/check/:bottlingBatchId` (`gcTime: 0`, keine Persistenz, `cache: "no-store"`). Bei Lücken zeigt sie die **rote Lückenliste** (Region, Symbol und „Fehlt:“) mit Links zu Stammdaten, Produkt und Designer (der Produktlink entfällt ohne Produkt an der Abfüllung); „Etiketten als PDF“ ist dann `aria-disabled` (fokussierbar, Grund per `aria-describedby`). Der **Probedruck** („Probedruck (MUSTER)“, `proof=true`) bleibt immer möglich; sein Dateiname enthält „muster“, der des echten Drucks nicht. Antwortet der Druck mit 409 `LABEL_PARTICULARS_MISSING`, zeigt die Karte die Liste `gaps` des Servers und **setzt den Fokus darauf**. 409 `COMPLIANCE_SETTINGS_INVALID` und 403 erscheinen als deutsche Meldung (`labelCheckErrorText`). Ein zweiter Klick während der Wartezeit löst keinen zweiten Aufruf aus. Ohne `label-templates:read` steht nur ein Hinweis, ohne `:print` gibt es keine Druckknöpfe, ohne Netz „Nur mit Verbindung“. Prüf- und Druckaufrufe laufen mit `cache: "no-store"` (`apiFetchBlob` reicht `init` durch).
|
||||
- **Stammdaten.** Im Reiter „Stammdaten & Web“ (Verwaltung > Automatisierung) stehen `foodBusinessName` (200) und `foodBusinessAddress` (300); **leere oder nur aus Leerzeichen bestehende Felder werden als `null` gesendet** (`value.trim() || null`), damit sie serverseitig löschen. „Vom Bienenhalter übernehmen“ füllt nur die Felder, ohne zu speichern. Der Text nennt, dass sie weder im Shop noch auf der Herkunftsseite erscheinen.
|
||||
- **Produkt `legalName`.** Das Feld „Bezeichnung auf dem Etikett (z. B. Blütenhonig)“ steht im Typ `Product`, im Neu-Formular, im Bearbeiten-Dialog und auf der Detailseite; Platzhalter-Schlüssel und deutsche Labels liegen in `lib/label-templates/placeholder-keys.ts` (Wächter-Spec gegen `test/fixtures/placeholder-keys.json`).
|
||||
|
||||
**Behandlung mit Mittel, Packung und Dosis (Task 16).** Formular „Neue Behandlung“, Dialog „Behandlung bearbeiten“ und Sammelerfassung.
|
||||
- **Mittel.** „Aus dem Katalog wählen“ bietet die aktiven Mittel (nur mit `veterinary-medicines:read`; ohne das Recht gibt es nur „Anderes Mittel“, ohne Fehler); „Anderes Mittel“ ist die letzte Option mit dem Freitextfeld wie bisher. Ein Katalogmittel füllt Wirkstoff, Wartezeit, Methode und Dosis als sichtbaren, bearbeitbaren Vorschlag mit dem Vermerk „aus Katalog“. Er gewinnt gegen die Vorbelegung aus der letzten Behandlung, überschreibt aber nie, was der Nutzer getippt hat; der Server füllt nichts nach. Lade- und Fehlerzustand des Katalogs zeigt `PrefillStatus`, Speichern bleibt möglich.
|
||||
- **Packung.** Beim Anlegen wird die Packung mit dem frühesten Verfall vorgewählt (mit Hinweis); „Ohne Packung“ bucht nichts und wird nicht wieder überschrieben. **Beim Bearbeiten wählt die Oberfläche nie selbst eine Packung vor** (nach einem Mittelwechsel steht „Ohne Packung“, der Nutzer wählt; Annahme 92). Ist eine Packung gesetzt, stammen Chargennummer, Lieferant, Beleg und Tierarzt aus der Packung (Zeile „aus Packung: …“); die Nutzlast lässt diese Felder weg (der Server lehnt Text daneben mit 400 ab), `product` entfällt mit Katalogmittel. Über „Speichern“ steht die Buchungszeile („Bucht 30 ml je Volk ab (× 6 = 180 ml).“, in der Sammelerfassung mal Anzahl der Völker). Abgelaufene oder zu kleine Packungen warnen, blockieren nie. Der Temperaturhinweis („Heute 9–17 °C. Laut Katalog 12–25 °C.“) kommt aus `GET /apiaries/:id/weather` (`apiaries:read`) und entfällt still, wenn das Wetter fehlt.
|
||||
- **Dosis.** Dezimalfeld, kein `NumberStepper` (der kennt nur ganze Zahlen): Komma als Dezimalzeichen, mindestens 0,001, **höchstens 3 Nachkommastellen** („Höchstens 3 Nachkommastellen.“, Annahme 94), „1.000“ wird als mehrdeutig abgelehnt (Annahme 88).
|
||||
- **Mittelwechsel beim Bearbeiten.** Der Dialog nutzt dieselbe Katalog-Vorbelegung wie das Anlegen; was die Behandlung schon enthält, gehört dem Nutzer, nur leere Felder füllt der Katalog. Eine vorhandene Wartezeit wird beim Wechsel nie still ersetzt: Weicht sie vom Katalogwert des neuen Mittels ab (oder kennt der Katalog keinen), steht am Feld „Wartezeit stammt vom vorherigen Mittel – bitte prüfen.“, dazu der Knopf „aus Katalog übernehmen“ (nur mit Katalogwert) als ausdrückliche Übernahme. Eine Eingabe im Feld entfernt die Warnung.
|
||||
- **Patch.** `medicinePatch` sendet die Mittel-Gruppe (`product`, `medicineId`, `medicineLotId`, `doseQuantity`, vier Packungstexte) nur, wenn sich in ihr etwas geändert hat; eine reine Notiz-Änderung bleibt so immer möglich.
|
||||
- **Fehler und Deep-Link.** Anlegen und Dialog lesen Fehler über **einen** Leser, `saveErrorText` (kennt auch Feldfehler-Listen des Servers). `?editTreatment=<id>` an der Volksseite öffnet den Dialog dieser Behandlung; findet die geladene Liste die ID nicht, sagt eine Live-Region „Behandlung nicht gefunden.“.
|
||||
- **Entwurf wiederherstellen** (`treatments/new`): Ist das Mittel des Entwurfs nicht mehr im Katalog, nicht aktiv oder fehlt das Leserecht, fallen `medicineId` und `medicineLotId` weg, `product` bleibt als Freitext, und eine Statuszeile sagt „Das Mittel aus dem Entwurf ist im Katalog nicht mehr aktiv. Bitte wähle es neu.“ Ist die Katalogliste noch nicht geladen, wird nichts verworfen.
|
||||
|
||||
**Anzeigen und Einstellungen (Task 18).** Seiten unter `app/(app)/compliance/`, Fehlertexte über `complianceErrorText` (`lib/compliance/errors.ts`).
|
||||
- **Liste `/compliance/notices`:** Anzeigen ans Veterinäramt (Art, Jahr, Status mit Symbol und Wort, Völkerzahl, Stichtag), Filter nach Art und **Jahresfilter** (vier Ziffern, sonst Hinweis am Feld; Server-Query `year`). Ab 200 Einträgen steht der Hinweis „Es werden die neuesten 200 angezeigt.“ (Obergrenze der Liste).
|
||||
- **Anlegen:** „Anzeige anlegen“ (nur `compliance:manage`) öffnet einen Dialog (Erstanzeige, Bestandsänderung, TSK-Zählung mit Jahr; „Bereits angezeigt“ mit Datum legt direkt eine gesendete an). Der Aufruf trägt einen `Idempotency-Key`. Gibt es schon einen Entwurf, antwortet der Server 409 `DRAFT_EXISTS` mit `existingId`; der Dialog verlinkt auf den vorhandenen Entwurf.
|
||||
- **Detail `/compliance/notices/[id]`** mit drei sichtbaren Schritten (1. PDF öffnen, 2. ans Amt schicken, 3. hier bestätigen; je Symbol und Wort Jetzt/Danach/Erledigt). Der **PDF-Download** (authentifiziert, kein Token im Link) meldet in der `aria-live`-Region „PDF heruntergeladen. Schick es jetzt ans Amt.“ und setzt den Fokus auf die neue Hauptaktion. **„Als gesendet markieren“ ist im Entwurf immer erreichbar**: als zweite Aktion (Umrissknopf neben „Neu zählen“), nach dem PDF als Hauptaktion; so geht es auch nach einem Neuladen ohne erneutes PDF. Das Sendedatum und „Gesendet am“ nutzen den **Berliner Kalendertag** (`berlinCalendarDay`); ein geleertes Datum zeigt „Das Datum kann nicht entfernt werden.“ „Neu zählen“ und „Anzeige stornieren“ gibt es nur im Entwurf, „Wieder öffnen“ nur bei gesendeter Anzeige; Kopie und Eingangsbestätigung sind Dokumente der Art `AUTHORITY_NOTICE`. Mit nur `compliance:read` fehlen die Aktionsknöpfe. `complianceErrorText` übersetzt 403 („Dazu fehlt dir das Recht.“) und 5xx („Der Server hat einen Fehler gemeldet. Versuch es gleich noch einmal.“) und fügt eine 400-Antwort mit `message: string[]` zusammen; Nests Englisch erscheint nie.
|
||||
- **Einstellungen `/compliance/settings`:** Veterinäramt, Erinnerungs-Schalter (Völkerzahl, Stand), TSK-Stichtag und Meldefrist, Mitgliedsnummer. Jeder Rechts- und Fristwert trägt am Feld „Annahme – bitte prüfen“. Stichtag und Meldefrist (leer erlaubt) prüft der Client vor dem Senden auf `MM-TT` (`isValidMonthDay`, Tage je Monat im Nichtschaltjahr, „02-29“ ungültig; Hinweis „Bitte einen Tag im Format MM-TT eingeben, zum Beispiel 03-31. Den 29. Februar gibt es hier nicht.“, `aria-describedby`, kein Request). Unter „Erweitert“ stehen die gezählten Völkerarten und der **Editor der Ziffernregeln** (1 bis 10 Regeln; „Regel hinzufügen“ fügt vor der letzten ein, „Entfernen, Regel n“; Höhe 0,5 bis 20 mm, Obergrenzen > 0 und aufsteigend, die letzte Regel hat keine Grenze und geht als `null` hinaus). Die Schranken sind die Grenzen des Backends (`assertDigitRules`, Konstanten in `compliance-settings.service.ts`), keine Rechtswerte; Fehler stehen an der Zeile (`role="alert"`) und als Satz „Bitte die Ziffernhöhen unter „Erweitert“ prüfen.“, weil der Abschnitt eingeklappt sein kann. Ohne `compliance:manage` sind die Felder nur lesbar.
|
||||
- **Formular-Schutz:** Ein Hintergrund-Refetch setzt eine Eingabe nie zurück. Ohne Eingabe werden neue Serverdaten still übernommen; mit ungespeicherter Eingabe bleibt sie stehen, und „Daten wurden geändert – neu laden?“ mit „Neu laden“ erscheint. Nach eigenem Speichern wird der Zustand direkt übernommen.
|
||||
- **Meldungskategorie `COMPLIANCE`** („Behörden & Prüfung“) steht in `lib/notifications/labels.ts` und damit in der Kategorienauswahl der Telegram-Karte. Die Glocke zeigt dafür das Kurz-Etikett „Behörden“ (Annahme 93).
|
||||
|
||||
**Sperrbezirk (Task 19).** Aktive Sperren lädt jede Seite einmal (`useActiveRestrictions`, Schlüssel `['compliance','restrictions','active']`, `staleTime` 30 s, Neuladen beim Fokus; Umsetzen- und Vereinigen-Dialog laden beim Öffnen neu). Der Server entscheidet endgültig, der Browser blendet nur früh aus.
|
||||
- **Seite `/compliance/restrictions`** (Recht `compliance:read`, Pflege mit `compliance:manage`; ohne Leserecht `NoComplianceAccess` „Dazu fehlt dir das Recht.“ und keine Abfragen) mit drei Abschnitten **Aktiv / Geplant / Beendet** (Symbol und Wort; `restrictionState` rechnet nach dem Berliner Kalendertag, eine Sperre mit Beginn in der Zukunft steht unter „Geplant“, nie unter „Beendet“). Die Live-Region sagt nur Laden und Fehler. Die Verfügung hängt als Dokument (`APIARY_RESTRICTION`, `DocumentUploadCard`, schreibend nur mit `compliance:manage`; ohne Leserecht entfällt die Zeile „Verfügung“).
|
||||
- **Anlegen:** „Sperrbezirk anlegen“ legt je gewähltem Stand (1 bis 50) einen Eintrag an (Seuche: Amerikanische Faulbrut, Kleiner Beutenkäfer oder Andere mit Freitext; Beginn, optional Ende, Behörde, Aktenzeichen, Notizen) mit `Idempotency-Key`.
|
||||
- **Beenden:** Der Dialog setzt nur `endsOn` (der Eintrag wird nie gelöscht). **Vorgabe ist „gestern“** (nicht vor dem Beginn); `endsOn` gilt **einschließlich**. Ist der gewählte Endtag heute oder später, zeigen Hinweis und Toast „Die Sperre gilt noch bis einschließlich TT.MM.JJJJ.“. **Ein Aufheben am selben Tag ist nicht möglich** (Annahme 91): Ist die Sperre heute aufgehoben, trägt man gestern ein, außer sie begann heute, dann gilt sie bis einschließlich heute.
|
||||
- **Rotes Band** am Stand, solange eine Sperre aktiv ist, mit dem Link „Sperrbezirke ansehen“ (Link nur mit `compliance:read`).
|
||||
- **Wächter** bei Umsetzen (Quell- und Zielstand), Vereinigen über verschiedene Stände, Standkorrektur am Volk und neuem Volk: rote Meldung („Stand {Name} liegt im Sperrbezirk ({Krankheit}, seit {Datum}). … ist gesperrt.“; ohne Servertext der Rückfall „Der Stand liegt im Sperrbezirk.“) mit Symbol und Wort in einer persistenten `aria-live`-Region. Der Hauptknopf ist `aria-disabled` mit `aria-describedby` auf den Grund (bleibt fokussierbar, Klick und Submit sind gesperrt; `ConfirmDialog` hat dafür die additive Eigenschaft `triggerAriaDisabled`). Ableger (`split`) bleiben frei. Der Umsetzen-Dialog trägt den festen Hinweis zu BienSeuchV § 5 (Gesundheitsbescheinigung, im Zweifel beim Veterinäramt nachfragen) und keine Rechtswerte.
|
||||
- **Ausnahme.** Nur mit `compliance:manage` erscheint der aufklappbare Abschnitt „Ausnahme mit Begründung“ (10 bis 500 Zeichen; Leerzeichen allein genügen nicht); die Begründung gilt erst nach der Rückfrage im `ConfirmDialog`, ohne Passwort-Abfrage (Inhaber-Entscheidung 2026-10-15). `restrictionOverrideReason` wird nur gesendet, wenn die Ausnahme genutzt wird; ändern sich die Sperren, verfällt sie. Ohne das Recht wäre die Antwort immer 403, darum wird das Feld nicht angeboten.
|
||||
- **Ernte:** nur die Warnung „Im Sperrbezirk gelten Auflagen auch für Honig und Waben. Verfügung prüfen.“; sie speichert immer.
|
||||
- **409.** `RESTRICTED_ZONE` und „Der Stand wurde gerade geändert. Bitte neu laden.“ erscheinen mit dem Servertext über dem Knopf, der Fokus springt auf die Meldung, die aktiven Sperren laden neu, und ein Knopf „Neu laden“ (Umsetzen, Vereinigen, Standkorrektur) lädt Volk und Stände neu und löscht die Meldung. Kein automatischer Neuversuch.
|
||||
- **Service Worker.** Die Sperrbezirk-Pfade sind seit Task 14 nie zwischengespeichert; offline fehlt die frühe Sperre, der Server entscheidet.
|
||||
Reference in new issue
Block a user