feat(fahrzeug): Funkkenner-Feld ergänzt (BOS-Digitalfunk-Kennung)
CI / backend-tests (push) Failing after 1m53s
CI / frontend-build (push) Successful in 17s

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KC8HYvv6UkCVYheYiTw9DD
This commit is contained in:
2026-09-05 15:37:32 +02:00
co-authored by Claude Sonnet 5
parent 11a62db3bd
commit 27c249993a
8 changed files with 192 additions and 6 deletions
+137
View File
@@ -4221,3 +4221,140 @@ Keine Commits in dieser Session.
- frontend/src/styles/global.css | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - 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 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
---
+10 -5
View File
@@ -23,16 +23,19 @@ Details zu FLEET-001 … FLEET-006 (vollständig).
- **Tests:** CRUD-Test. - **Tests:** CRUD-Test.
- **DoD:** mind. ein Fahrzeugtyp angelegt. - **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. - **Ziel:** Fahrzeug-Spezialfelder am Asset.
- **Beschreibung:** Kennzeichen, Fahrgestellnummer, Funkrufname als 1:1-Erweiterungstabelle - **Beschreibung:** Kennzeichen, Fahrgestellnummer, Funkrufname, Funkkenner als
zu Asset. 1:1-Erweiterungstabelle zu Asset.
- **Benutzerwert:** Fahrzeuge sind die wichtigste Ressourcenklasse im Rettungsdienst — eigene - **Benutzerwert:** Fahrzeuge sind die wichtigste Ressourcenklasse im Rettungsdienst — eigene
Kennfelder sind Pflicht. Kennfelder sind Pflicht.
- **Abhängigkeiten:** ASSET-003, FLEET-001 - **Abhängigkeiten:** ASSET-003, FLEET-001
- **Datenmodell:** `fahrzeugdetails` (asset_id, kennzeichen, fahrgestellnummer, - **Datenmodell:** `fahrzeugdetails` (asset_id, kennzeichen, fahrgestellnummer,
funkrufname) funkrufname, funkkenner)
- **Backend:** CRUD/Upsert - **Backend:** CRUD/Upsert
- **Frontend:** Formular - **Frontend:** Formular
- **Mobile:** Anzeige - **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 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/ 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) 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).
@@ -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")
+3
View File
@@ -17,6 +17,9 @@ class Fahrzeugdetails(Base):
objekt_id: Mapped[int] = mapped_column(ForeignKey("objekt.id"), primary_key=True) objekt_id: Mapped[int] = mapped_column(ForeignKey("objekt.id"), primary_key=True)
kennzeichen: Mapped[str | None] = mapped_column(String) kennzeichen: Mapped[str | None] = mapped_column(String)
funkrufname: 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) baujahr: Mapped[int | None] = mapped_column(Integer)
kilometerstand: Mapped[int | None] = mapped_column(Integer) kilometerstand: Mapped[int | None] = mapped_column(Integer)
betriebsstunden: Mapped[Decimal | None] = mapped_column(Numeric) betriebsstunden: Mapped[Decimal | None] = mapped_column(Numeric)
+2
View File
@@ -38,6 +38,7 @@ class ObjektUpdate(BaseModel):
class FahrzeugdetailsUpsert(BaseModel): class FahrzeugdetailsUpsert(BaseModel):
kennzeichen: str | None = None kennzeichen: str | None = None
funkrufname: str | None = None funkrufname: str | None = None
funkkenner: str | None = None
baujahr: int | None = None baujahr: int | None = None
kilometerstand: int | None = None kilometerstand: int | None = None
betriebsstunden: Decimal | None = None betriebsstunden: Decimal | None = None
@@ -50,6 +51,7 @@ class FahrzeugdetailsRead(BaseModel):
objekt_id: int objekt_id: int
kennzeichen: str | None kennzeichen: str | None
funkrufname: str | None funkrufname: str | None
funkkenner: str | None
baujahr: int | None baujahr: int | None
kilometerstand: int | None kilometerstand: int | None
betriebsstunden: Decimal | None betriebsstunden: Decimal | None
+7 -1
View File
@@ -15,12 +15,18 @@ async def test_fahrzeugdetails_upsert(client, objekt_mit_position, admin_user):
gesetzt = await client.put( gesetzt = await client.put(
f"/api/v1/objekte/{objekt.id}/fahrzeugdetails", 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), headers=auth_header(token),
) )
assert gesetzt.status_code == 200 assert gesetzt.status_code == 200
assert gesetzt.json()["kennzeichen"] == "XX-YY 123" assert gesetzt.json()["kennzeichen"] == "XX-YY 123"
assert gesetzt.json()["kilometerstand"] == 42315 assert gesetzt.json()["kilometerstand"] == 42315
assert gesetzt.json()["funkkenner"] == "OPTA-12345"
aktualisiert = await client.put( aktualisiert = await client.put(
f"/api/v1/objekte/{objekt.id}/fahrzeugdetails", f"/api/v1/objekte/{objekt.id}/fahrzeugdetails",
+1
View File
@@ -2,6 +2,7 @@ export interface Fahrzeugdetails {
objekt_id: number; objekt_id: number;
kennzeichen: string | null; kennzeichen: string | null;
funkrufname: string | null; funkrufname: string | null;
funkkenner: string | null;
baujahr: number | null; baujahr: number | null;
kilometerstand: number | null; kilometerstand: number | null;
betriebsstunden: string | null; betriebsstunden: string | null;
@@ -117,6 +117,7 @@ export function ObjektSection({
body: { body: {
kennzeichen: details.kennzeichen || null, kennzeichen: details.kennzeichen || null,
funkrufname: details.funkrufname || null, funkrufname: details.funkrufname || null,
funkkenner: details.funkkenner || null,
baujahr: details.baujahr || null, baujahr: details.baujahr || null,
kilometerstand: details.kilometerstand || null, kilometerstand: details.kilometerstand || null,
betriebsstunden: details.betriebsstunden || null, betriebsstunden: details.betriebsstunden || null,
@@ -431,6 +432,12 @@ export function ObjektSection({
value={details.funkrufname ?? ""} value={details.funkrufname ?? ""}
onChange={(e) => setDetails((d) => ({ ...d, funkrufname: e.target.value }))} onChange={(e) => setDetails((d) => ({ ...d, funkrufname: e.target.value }))}
/> />
<input
className="input"
placeholder="Funkkenner (BOS-Digitalfunk)"
value={details.funkkenner ?? ""}
onChange={(e) => setDetails((d) => ({ ...d, funkkenner: e.target.value }))}
/>
<div className="row"> <div className="row">
<input <input
className="input" className="input"