From 27c249993ade545fcd5b905ea30e120c97904528 Mon Sep 17 00:00:00 2001 From: patrick Date: Sat, 5 Sep 2026 15:37:32 +0200 Subject: [PATCH] =?UTF-8?q?feat(fahrzeug):=20Funkkenner-Feld=20erg=C3=A4nz?= =?UTF-8?q?t=20(BOS-Digitalfunk-Kennung)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nutzer-Fund: funkkenner (z.B. OPTA-Nummer) fehlte in fahrzeugdetails komplett - eigenes Feld, nicht dasselbe wie funkrufname (gesprochener Rufname vs. formale Funk-Identifikationsnummer im Digitalfunknetz). Migration 0018, Model/Schema/Frontend-Formular ergänzt. Auch im Kachel- Backlog (arbeitskacheln/05_fleet.md, FLEET-002) nachgezogen. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01KC8HYvv6UkCVYheYiTw9DD --- DEVLOG.md | 137 ++++++++++++++++++++ arbeitskacheln/05_fleet.md | 15 ++- backend/alembic/versions/0018_funkkenner.py | 25 ++++ backend/app/models/fahrzeugdetails.py | 3 + backend/app/schemas/objekt.py | 2 + backend/tests/test_fahrzeugdetails.py | 8 +- frontend/src/api/types.ts | 1 + frontend/src/pages/admin/ObjektSection.tsx | 7 + 8 files changed, 192 insertions(+), 6 deletions(-) create mode 100644 backend/alembic/versions/0018_funkkenner.py diff --git a/DEVLOG.md b/DEVLOG.md index df21fa9..58d62c0 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -4221,3 +4221,140 @@ Keine Commits in dieser Session. - frontend/src/styles/global.css | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --- +## 2026-09-05 15:07 – 15:11 (3m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- 95e04b5 style: Sidebar-Layout statt Topbar (Vorbild OpenFleet-Dashboard-Screenshot) +- 024576c style: schlicheres Design (flach statt Schatten, kleinerer Radius) + +### Geänderte Dateien +- frontend/src/styles/global.css | 55 +++++++++++++++++++++++++++++++++++++++++-------------- + +--- +## 2026-09-05 15:13 – 15:16 (3m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- 5524ef9 docs: Arbeitskacheln-Backlog als Dateien angelegt (arbeitskacheln/) + +### Geänderte Dateien +- arbeitskacheln/00_index.md | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/01_foundation.md | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/02_identity.md | 209 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/03_digital_file.md | 123 +++++++++++++++++++++++++++++++++++++++++++++++++ + +--- +## 2026-09-05 15:16 – 15:18 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- 862236f docs: nächste 10 Arbeitskacheln (FILE-006/007, ASSET-001..008) + +### Geänderte Dateien +- arbeitskacheln/00_index.md | 6 ++- +- arbeitskacheln/03_digital_file.md | 46 +++++++++++++++++++++- +- arbeitskacheln/04_assets.md | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +--- +## 2026-09-05 15:19 – 15:20 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- c7784f0 docs: nächste 10 Arbeitskacheln (FLEET-001..006, INV-001..004) + +### Geänderte Dateien +- arbeitskacheln/00_index.md | 4 ++- +- arbeitskacheln/05_fleet.md | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/06_inventory.md | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +--- +## 2026-09-05 15:22 – 15:23 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- b7c7276 docs: Fehler korrigiert - Betriebsstunden fälschlich fahrzeug-only geplant + +### Geänderte Dateien +- arbeitskacheln/00_index.md | 7 ++++--- +- arbeitskacheln/04_assets.md | 39 ++++++++++++++++++++++++++++++++++++++- +- arbeitskacheln/05_fleet.md | 54 ++++++++++++++++++++++++++++++------------------------ + +--- +## 2026-09-05 15:23 – 15:25 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- 8fc5453 docs: Inventory-Epic komplettiert (INV-005..007) + +### Geänderte Dateien +- arbeitskacheln/00_index.md | 6 +++--- +- arbeitskacheln/06_inventory.md | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ + +--- +## 2026-09-05 15:25 – 15:26 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- 6aa45a3 docs: Warehouse-Epic komplettiert (WH-001..007) + +### Geänderte Dateien +- arbeitskacheln/00_index.md | 3 +- +- arbeitskacheln/07_warehouse.md | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +--- +## 2026-09-05 15:28 – 15:29 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- f7070c5 docs: Loadout-Epic komplettiert (LOAD-001..006) + +### Geänderte Dateien +- arbeitskacheln/00_index.md | 3 +- +- arbeitskacheln/08_loadout.md | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +--- +## 2026-09-05 15:30 – 15:31 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- 81a7271 docs: Inspections-Epic komplettiert (INSP-001..006) + +### Geänderte Dateien +- arbeitskacheln/00_index.md | 3 +- +- arbeitskacheln/09_inspections.md | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +--- +## 2026-09-05 15:31 – 15:32 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- 71dc679 docs: Maintenance-Epic komplettiert (MAINT-001..006) + +### Geänderte Dateien +- arbeitskacheln/00_index.md | 5 ++-- +- arbeitskacheln/10_maintenance.md | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +--- +## 2026-09-05 15:32 – 15:33 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- 11a62db docs: Defects-Epic komplettiert (DEFECT-001..005) + +### Geänderte Dateien +- arbeitskacheln/00_index.md | 3 ++- +- arbeitskacheln/11_defects.md | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +--- diff --git a/arbeitskacheln/05_fleet.md b/arbeitskacheln/05_fleet.md index 74023a8..f7b95cf 100644 --- a/arbeitskacheln/05_fleet.md +++ b/arbeitskacheln/05_fleet.md @@ -23,16 +23,19 @@ Details zu FLEET-001 … FLEET-006 (vollständig). - **Tests:** CRUD-Test. - **DoD:** mind. ein Fahrzeugtyp angelegt. -## FLEET-002 — Fahrzeuge (Kennzeichen/Funkrufname) +## FLEET-002 — Fahrzeuge (Kennzeichen/Funkrufname/Funkkenner) +- **Nachtrag (Nutzer-Fund):** Funkkenner (BOS-Digitalfunk-Kennung, z.B. OPTA-Nummer) fehlte + — ist ein eigenes Feld, nicht dasselbe wie Funkrufname (gesprochener Rufname wie + „Florian München 1/44" vs. formale Funk-Identifikationsnummer im Digitalfunknetz). - **Ziel:** Fahrzeug-Spezialfelder am Asset. -- **Beschreibung:** Kennzeichen, Fahrgestellnummer, Funkrufname als 1:1-Erweiterungstabelle - zu Asset. +- **Beschreibung:** Kennzeichen, Fahrgestellnummer, Funkrufname, Funkkenner als + 1:1-Erweiterungstabelle zu Asset. - **Benutzerwert:** Fahrzeuge sind die wichtigste Ressourcenklasse im Rettungsdienst — eigene Kennfelder sind Pflicht. - **Abhängigkeiten:** ASSET-003, FLEET-001 - **Datenmodell:** `fahrzeugdetails` (asset_id, kennzeichen, fahrgestellnummer, - funkrufname) + funkrufname, funkkenner) - **Backend:** CRUD/Upsert - **Frontend:** Formular - **Mobile:** Anzeige @@ -139,4 +142,6 @@ Betriebsstunden-Feld) — und selbst der Fahrzeug-Kilometerstand hat **keine Rückwärtslauf-Validierung** (reiner Zahlen-Input). Beides zusammen: ASSET-009 wäre in MABEA kein reiner Nachzug, sondern verlangt (a) ein neues generisches Zählerfeld an Objekt/ GeraetInstanz und (b) eine bisher fehlende Validierungsregel. FLEET-006 ist in MABEA (noch) -nicht relevant, da kein Operations-Epic existiert. +nicht relevant, da kein Operations-Epic existiert. **FLEET-002-Nachtrag:** `funkkenner` +fehlte in MABEAs `fahrzeugdetails` komplett (nur `funkrufname` vorhanden) — echte, kleine +Feld-Lücke, wird direkt nachgezogen (siehe Commit). diff --git a/backend/alembic/versions/0018_funkkenner.py b/backend/alembic/versions/0018_funkkenner.py new file mode 100644 index 0000000..cca5d2d --- /dev/null +++ b/backend/alembic/versions/0018_funkkenner.py @@ -0,0 +1,25 @@ +"""fahrzeugdetails.funkkenner (Nutzer-Fund): BOS-Digitalfunk-Kennung (z.B. +OPTA-Nummer) ist ein eigenes Feld, nicht dasselbe wie funkrufname (gesprochener +Rufname wie "Florian München 1/44" vs. formale Funk-Identifikationsnummer im +Digitalfunknetz). Additiv, nullable. + +Revision ID: 0018_funkkenner +Revises: 0017_granulare_rollen +Create Date: 2026-09-05 +""" +from typing import Sequence, Union + +from alembic import op + +revision: str = "0018_funkkenner" +down_revision: Union[str, None] = "0017_granulare_rollen" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.execute("ALTER TABLE fahrzeugdetails ADD COLUMN funkkenner TEXT") + + +def downgrade() -> None: + op.execute("ALTER TABLE fahrzeugdetails DROP COLUMN funkkenner") diff --git a/backend/app/models/fahrzeugdetails.py b/backend/app/models/fahrzeugdetails.py index 725d7ca..bf157b5 100644 --- a/backend/app/models/fahrzeugdetails.py +++ b/backend/app/models/fahrzeugdetails.py @@ -17,6 +17,9 @@ class Fahrzeugdetails(Base): objekt_id: Mapped[int] = mapped_column(ForeignKey("objekt.id"), primary_key=True) kennzeichen: Mapped[str | None] = mapped_column(String) funkrufname: Mapped[str | None] = mapped_column(String) + # BOS-Digitalfunk-Kennung (z.B. OPTA-Nummer) - eigenes Feld, nicht dasselbe wie + # funkrufname (gesprochener Rufname vs. formale Funk-Identifikationsnummer). + funkkenner: Mapped[str | None] = mapped_column(String) baujahr: Mapped[int | None] = mapped_column(Integer) kilometerstand: Mapped[int | None] = mapped_column(Integer) betriebsstunden: Mapped[Decimal | None] = mapped_column(Numeric) diff --git a/backend/app/schemas/objekt.py b/backend/app/schemas/objekt.py index 542be7c..2a3814d 100644 --- a/backend/app/schemas/objekt.py +++ b/backend/app/schemas/objekt.py @@ -38,6 +38,7 @@ class ObjektUpdate(BaseModel): class FahrzeugdetailsUpsert(BaseModel): kennzeichen: str | None = None funkrufname: str | None = None + funkkenner: str | None = None baujahr: int | None = None kilometerstand: int | None = None betriebsstunden: Decimal | None = None @@ -50,6 +51,7 @@ class FahrzeugdetailsRead(BaseModel): objekt_id: int kennzeichen: str | None funkrufname: str | None + funkkenner: str | None baujahr: int | None kilometerstand: int | None betriebsstunden: Decimal | None diff --git a/backend/tests/test_fahrzeugdetails.py b/backend/tests/test_fahrzeugdetails.py index 6b54a75..3ad60d5 100644 --- a/backend/tests/test_fahrzeugdetails.py +++ b/backend/tests/test_fahrzeugdetails.py @@ -15,12 +15,18 @@ async def test_fahrzeugdetails_upsert(client, objekt_mit_position, admin_user): gesetzt = await client.put( f"/api/v1/objekte/{objekt.id}/fahrzeugdetails", - json={"kennzeichen": "XX-YY 123", "kilometerstand": 42315, "naechste_hu": "2027-05-01"}, + json={ + "kennzeichen": "XX-YY 123", + "kilometerstand": 42315, + "naechste_hu": "2027-05-01", + "funkkenner": "OPTA-12345", + }, headers=auth_header(token), ) assert gesetzt.status_code == 200 assert gesetzt.json()["kennzeichen"] == "XX-YY 123" assert gesetzt.json()["kilometerstand"] == 42315 + assert gesetzt.json()["funkkenner"] == "OPTA-12345" aktualisiert = await client.put( f"/api/v1/objekte/{objekt.id}/fahrzeugdetails", diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index 15b4587..77d065b 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -2,6 +2,7 @@ export interface Fahrzeugdetails { objekt_id: number; kennzeichen: string | null; funkrufname: string | null; + funkkenner: string | null; baujahr: number | null; kilometerstand: number | null; betriebsstunden: string | null; diff --git a/frontend/src/pages/admin/ObjektSection.tsx b/frontend/src/pages/admin/ObjektSection.tsx index ef53fd7..7ce8322 100644 --- a/frontend/src/pages/admin/ObjektSection.tsx +++ b/frontend/src/pages/admin/ObjektSection.tsx @@ -117,6 +117,7 @@ export function ObjektSection({ body: { kennzeichen: details.kennzeichen || null, funkrufname: details.funkrufname || null, + funkkenner: details.funkkenner || null, baujahr: details.baujahr || null, kilometerstand: details.kilometerstand || null, betriebsstunden: details.betriebsstunden || null, @@ -431,6 +432,12 @@ export function ObjektSection({ value={details.funkrufname ?? ""} onChange={(e) => setDetails((d) => ({ ...d, funkrufname: e.target.value }))} /> + setDetails((d) => ({ ...d, funkkenner: e.target.value }))} + />