14 Commits

Author SHA1 Message Date
54e873c115 feat(tiere): Eltern direkt in der Tier-Akte bearbeiten (QOL)
All checks were successful
CI / Backend Tests (.NET) (push) Successful in 1m15s
CI / Frontend Tests (Node/Vite) (push) Successful in 9m37s
CI / Docker Build & Push (push) Successful in 7m36s
CI / Deploy auf TrueNAS (Custom App) (push) Successful in 8s
Im Tier-Formular gibt es jetzt den Block „Abstammung" mit Wurf-Auswahl und
Vater-/Mutter-Picker — die Eltern müssen nicht mehr über die Wurf-Seite
gesucht und dort editiert werden. Das Datenmodell bleibt unverändert: Eltern
hängen weiterhin am Geburtswurf.

- Backend: PUT /gerbils/{id}/parents schreibt Litter.FatherId/MotherId des
  Geburtswurfs. Ohne Wurf wird ein bestehender mit gleichem Elternpaar +
  gleichem Datum verknüpft, sonst ein Träger-Wurf angelegt
  ("Wurf von X + Y", ShowInChronicle=false, IsManual=true).
  Geschlechts-Regel wiederverwendet LitterEndpoints.ValidateParents,
  Selbstbezug (Tier als eigener Elternteil) wird abgewiesen.
- UI: Vorbelegung aus dem gewählten Wurf, Warnung mit Anzahl der Geschwister
  (Eltern gehören dem Wurf → Änderung gilt für alle), Hinweis wenn ein
  Wurf-Eintrag angelegt wird. Texte in de.ts.
- Nebenbei: Speichern nutzt im Edit-Modus die Route-Id (PUT /gerbils/{id}
  antwortet 204 ohne Body) und der vorher schon rote Spec-Locator
  „Würfe als Elternteil" ist auf den Abschnitt eingegrenzt.
- Tests: GerbilParentsTests (9), e2e tiere.spec (2 neu) + Mock-Route.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 20:43:28 +02:00
45b8533f18 feat(deploy): TrueNAS Custom-App + Auto-Deploy, plus aufgelaufene Arbeit
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m11s
CI / Frontend Tests (Node/Vite) (push) Failing after 4m59s
CI / Docker Build & Push (push) Has been skipped
CI / Deploy auf TrueNAS (Custom App) (push) Has been skipped
Deployment:
- custom-app.compose.yaml: self-contained Compose fuer TrueNAS "Custom App"
  (absolute Host-Bind-Pfade, postgres:18, pull_policy always, Port 8090)
- scripts/truenas-deploy.sh: Host-Skript create/redeploy via midclt (App
  bleibt unter Apps sichtbar) inkl. Image-Pull + Health-Check
- ci.yml Deploy-Job: laeuft auf ubuntu-latest-Runner, kopiert Deploy-Dateien
  per SSH auf den NAS-Host und triggert truenas-deploy.sh (statt runs-on goldeye)
- compose.yaml/.env.example: postgres:18 (Locale-Match zur Quell-DB), Port 8090
- .gitignore: .agents/, tools/rag/, deploy/truenas/.env (Secrets/Scratch)

Aufgelaufene Feature-Arbeit (verified/Freeze, Migrationen, Import-Triage):
- GerbilOverride/VerifiedGerbil-Endpoints + GerbilSnapshotService + Tests
- EF-Migrationen (ShowInChronicle, Stillborn, BirthOrder, ManualFlag, DSGVO)
- Frontend VerifizierteTierePage + verified-API + e2e-Spec
- diverse Import-/Triage-Skripte und -Tests

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 09:19:11 +02:00
9c98c37d2a feat(rennmausakte): Datenherkunft/Nachverfolgung pro Rennmaus
Neuer „Datenherkunft"-Button in der Rennmausakte öffnet einen Dialog, der zeigt,
aus welchen Quellen der Eintrag erzeugt wurde: Quelldateien (Stammbäume +
Wurfchronik), Anzahl zusammengeführter Datensätze, Eltern-Herleitung
(Methode/Konfidenz) und Hinweise (z. B. „per manueller Entscheidung zugeordnet",
„Konflikt gelöst", „aus N Datensätzen zusammengeführt").

Import: build_provenance() in merge_and_resolve.py sammelt die Herkunft über alle
deduplizierten Datensätze und schreibt sie als Provenance-JSON je Tier in
resolved_import.json. Backend: Gerbil.Provenance (nullable text) + Migration
AddGerbilProvenance, gemappt im Ingest und im GerbilDto zurückgegeben. Frontend:
ProvenanceDialog + Typen + Strings.

Tests: Ingest-Round-trip (vorhanden/abwesend), e2e provenance.spec.ts.
dotnet(212)/vitest(129)/playwright/tsc/eslint grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:45:15 +02:00
396d8f05d8 FEAT: Implement deceased/givenaway enclosure visibility rules, preserve external clan name, and hide receiver fields for deceased gerbils 2026-06-13 01:40:19 +02:00
d4dfe6c3ac Merge feature/status-model (STATUS-MODEL-BE P0): enum Active->Breeding(default)+Pet/ForSale; Deceased/GivenAway serverseitig abgeleitet (GerbilStatusService.Derive + Praezedenz + >7J presumed); Migration+Backfill+Startup-Sweep 2026-06-07 03:36:32 +02:00
1a5f3218fb STATUS-MODEL: Active→Breeding + Pet enum + derived status logic (P0)
GerbilStatus: Active→Breeding, add Pet; Deceased/GivenAway are now DERIVED.
GerbilStatusService.Derive(): central precedence: DateOfDeath→Deceased (1),
ReceiverContactId→GivenAway (2), age>7y→Deceased presumed (3), user choice (4).
All write paths (gerbil CRUD, contracts, importers) call GerbilStatusService.Apply().
Startup sweep flips >7y gerbils to Deceased at next app restart.
Migration StatusModel: Active→Breeding rename + backfill derived statuses.
10 new tests; 200/200 green; has-pending=No.

Enum string values: Breeding (was Active), Pet (new), Deceased, GivenAway, ForSale.
FE contract: status field values updated (see Done-Report).
2026-06-07 03:27:11 +02:00
70803efa1f PROFILEPHOTO: profilePhotoUrl computed field on GerbilDto
GET /gerbils/{id}: loads first photo by sortOrder (2 queries, no N+1).
GET /gerbils list: batch-loads first photos for the page in one IN query.
profilePhotoUrl = /photos/files/{fileName} or null. No migration.
3 new tests: no-photo=null, sortOrder priority, list batch.
2026-06-07 03:19:00 +02:00
8175b150f7 CR-2 FIX: PUT /gerbils PATCH-Semantik — partielle Updates zerstoeren keine Felder mehr
GerbilInput.Name + Gender auf nullable geaendert. Apply() wendet fuer alle Felder
?? g.X an (omittierte/null Felder behalten den DB-Wert). g.Name aus dem PUT-Handler
in Apply() verschoben. Verhindert stillen Datenverlust bei partiellen Frontend-Calls
(ForSale-Toggle schickt nur {status}, Charakterbogen-Save schickt nur {traits,note}).

+4 Regressionstests (PartialUpdateTests): ForSale-Toggle, Charakterbogen-Save,
EditForm-ohne-Import-Felder, minimaler POST — alle grueen (137/137 gesamt).
2026-06-06 17:40:15 +02:00
6775707387 OWNERSHIP: residency classification (Bestand vs external ancestors)
Per hive/agents/god/OWNERSHIP-residency.md (Julian): "Clan kleine Chaoten"
is the wife's own Zucht. Classify each animal:
 (a) RESIDENT if zuchtCanon contains 'kleinechaote' (separator/declension-
     insensitive: "v.d. Kleinen Chaoten", "Clan-Kleine-Chaoten", …); OR
 (b) PARENT EXCEPTION — a parent of a Clan offspring is resident even if its
     own Zuchtname is foreign (you can only breed a Clan litter if the parents
     were in your care). Runs AFTER the parentRefs→litter linking.
Otherwise EXTERNAL (pedigree ancestor, not living stock — kept, not deleted).

Stored as a dedicated Gerbil.IsResident bool (distinct from Status/Abgabe —
origin/identity, not current location), defaulting true (own stock unless
marked external; DB HasDefaultValue(true)). Additive migration
AddGerbilResidency; has-pending-model-changes clean. Round-trips in
GerbilDto/GerbilInput and is Gridify-filterable (isResident==true) so Kevin
can build a "nur Bestand" filter. ImportService computes it at (re-)import
and refreshes existing rows on the idempotent sweep. ResidencySummary added
to the import report.

Projected on real data: 306 loadable → 227 resident (188 rule a, +39 via
parent exception), 79 external ancestors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:58:29 +02:00
2f089a902d GEN-3b: import notation normalization (Uw=G, Sls, deaf flag, tags)
genotype.py:
- Uw/uw aliased to G/g (same locus) so the D2 conflict group + pure-Uw
  cases stop being conflicts (Gg == Uwuw).
- Sls/WP recognized as a SECOND spotting locus (S(l)s(l)=WP het); carried
  into mapped8locus alongside Sp (Sp+Sls = Superschecke).
- dea/Dea/taub/hörend -> hearing/deaf phenotype FLAG (not a locus).
- WFNZ/RV/GV/DP -> provenance/breeding tags (not genotype, not conflicts).
- test_genotype.py: zero-dep unit tests for all four.

extract.py: surface deaf+tags on animals; dedup conflict detection now
compares the NORMALIZED genotype key (mapped8locus) instead of the raw
string, so Uw=G no longer triggers a conflict. Result: Konflikte 32 -> 27,
Zucht-Splits stays 0. Dedup identity = name + DOB + Zucht.

Backend: Gerbil.IsDeaf (bool?) + additive migration AddGerbilDeafFlag
(has-pending-model-changes clean) + GerbilDto/GerbilInput round-trip.
ImportService sets IsDeaf from animal.deaf and preserves Sls + tags + deaf
in RawImportData (kept out of the 8-locus compact Genotype contract until
GEN-3a adopts them).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:26:01 +02:00
3afb04f646 FEAT-14a: Charakterbogen on Gerbil (traits + note) + sale-ad request extension
- Gerbil.CharacterTraits (List<string>, JSON text column, opaque labels) + CharacterNote
  (string?). On GerbilDto + Input; round-trips on GET/POST/PUT. Additive migration.
- SaleAdAnimal gains Traits (German labels) + CharacterNote; prompt builder emits
  "Charakter: …" + "Charakter-Notiz: …" lines for the AI Verkaufstext.
- Tests: traits/note round-trip (POST->GET via SQLite host), empty-default, prompt inclusion.
2026-06-06 09:10:30 +02:00
09f11cb4e3 SEARCH-1: separator-insensitive NameSearch + OriginBreeder (Herkunft) filter
- Gerbil.NameSearch (lowercase, strip whitespace/.-_) auto-synced in ApplicationContext
  .SaveChanges; Gridify-filterable so 'clan kleine chaoten' matches 'Clan-Kleine-Chaoten'
  (client strips separators the same way). GerbilSearch.Normalize shared helper.
- Gerbil.OriginBreeder (free-text Herkunft) on DTO+Input, set by the FEAT-8 import from the
  source Zucht (imported animals have no OriginContact). Gridify-filterable.
- GET /gerbils/breeders: distinct non-empty OriginBreeder values for the Herkunft dropdown.
- Migration AddSearchFields (+ NameSearch backfill SQL for existing rows).
2026-06-06 09:01:12 +02:00
7627468b82 DATA-2: optional list params + normalize frontend's '==' Gridify operator
- GridifyParams wrapper: nullable page/pageSize/filter/orderBy so list endpoints
  work with no query params (Gridify's non-nullable int Page made [AsParameters]
  treat them as required -> 400). Defaults page=1,pageSize=20.
- Normalize incoming filter '==' -> '=' : the frontend gridify.ts emits '==' for
  equals (its convention) but Gridify's equals is '='. Safe (values are escaped;
  != >= <= =* contain no '=='). Fixes status==Active (frontend default that 500'd).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 01:00:35 +02:00
180d53b203 DATA-2: new schema entities + Minimal API endpoints + Scalar + DAL teardown
- Entities: Breeder->Contact, +Enclosure/ColorVariety/GerbilPhoto/HealthRecord/WeightRecord;
  Gerbil expanded (single Genotype text col, Status/Gender enums-as-string, FK ids,
  ImportSource/ExternalRef provenance); Litter Strength->TotalBorn +ExpectedGoHomeDate/Notes.
  ColorVariety HasData seed = 18 from GEN-1 catalog. Gerbil<->Litter cycle handled
  (SetNull/Restrict). Enums stored as strings.
- Minimal API (no controllers): Endpoints/*.cs MapGroup+TypedResults for gerbils, litters,
  contacts, enclosures, color-varieties, health/weight-records, inbreeding (converted from
  controller, same routes/shapes), photos (Oscar contract: GET array/POST multipart/DELETE,
  url /photos/files/{fileName}). Gridify paged {items,totalCount,page,pageSize}, camelCase,
  409 conflict-deletes, flat FK ids, litter parent-gender validation (400 {code,...}).
- Scalar replaces Swashbuckle (AddOpenApi/MapOpenApi + MapScalarApiReference at /scalar);
  launchUrl swagger->scalar. GenericRepository/UnitOfWork/Converters deleted; DbContext direct.
- InbreedingService reads real FK props now; pure calculator + 8 tests untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 00:53:49 +02:00