feat(inventory): INV-007 Ausgabe/Rückgabe von Material und Leihgeräten
CI / backend-tests (push) Successful in 2m1s
CI / frontend-build (push) Successful in 18s

Personenbezogene Ausgabe ergänzt die reine Standort-/Lagerplatz-Sicht
um "wer hat das Ding gerade": neues Modell ausgabe (Material +
optional geraet_instanz_id), POST /ausgaben, GET /ausgaben (Filter
status/empfaenger), POST /ausgaben/{id}/rueckgabe mit Schutz vor
Doppel-Rueckgabe. Frontend als Admin-Tab "Ausgabe/Rückgabe" +
Command-Palette-Eintrag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
2026-09-06 13:19:31 +02:00
co-authored by Claude Sonnet 5
parent 16a42c6b85
commit b6ada2b5a1
13 changed files with 495 additions and 7 deletions
+13 -7
View File
@@ -175,13 +175,19 @@ Details zu INV-001 … INV-007 (vollständig).
- **Akzeptanzkriterien:** Ausgabe erzeugt offenen Vorgang, Rückgabe schließt ihn, - **Akzeptanzkriterien:** Ausgabe erzeugt offenen Vorgang, Rückgabe schließt ihn,
überfällige Rückgaben erkennbar. überfällige Rückgaben erkennbar.
- **Tests:** Ausgabe-/Rückgabe-Test, Überfällig-Test. - **Tests:** Ausgabe-/Rückgabe-Test, Überfällig-Test.
- **DoD:** mind. ein vollständiger Ausgabe-Rückgabe-Zyklus getestet. - **DoD:** umgesetzt (2026-09-06). Modell `ausgabe` (Material + optional
`geraet_instanz_id`), `POST /ausgaben`, `GET /ausgaben?status_filter=&empfaenger_id=`,
`POST /ausgaben/{id}/rueckgabe`. Frontend als Admin-Tab "Ausgabe/Rückgabe"
(Empfänger-Auswahl braucht die Benutzerliste, die nur Verantwortliche laden
dürfen - Backend selbst erlaubt jedem eingeloggten Nutzer Ausgabe/Rückgabe,
Feld-Erfassung durch Mitarbeiter wäre ein späterer Ausbauschritt mit eigener
Namenssuche statt vollem `/benutzer`-Endpunkt). Vollständiger
Ausgabe-Rückgabe-Zyklus + Doppel-Rückgabe-Schutz getestet.
--- ---
**MABEA-Ist-Stand-Abgleich:** INV-001…003 sowie INV-005/006 vollständig vorhanden — INV-006 **MABEA-Ist-Stand-Abgleich (aktualisiert 2026-09-06):** INV-001…003 sowie INV-005/006/007
(Fehlbestand+Mindermenge) ist sogar eines der am weitesten ausgereiften Module im gesamten vollständig vorhanden — INV-006 (Fehlbestand+Mindermenge) ist eines der am weitesten
Projekt. **INV-004 (echter struktureller Umbau, siehe oben) und INV-007 (Ausgabe/Rückgabe) ausgereiften Module im gesamten Projekt, INV-007 (Ausgabe/Rückgabe) beantwortet jetzt
fehlen komplett:** MABEA kennt nur Standortzuordnung von Objekten (Lagerbewegung), aber kein "wer hat gerade das Funkgerät". **Nur INV-004 (echter struktureller Umbau, siehe oben)
personenbezogenes Ausleih-Konzept für Verbrauchsmaterial/Einzelteile — „wer hat gerade das fehlt noch.**
Funkgerät" ist aktuell nicht beantwortbar.
+41
View File
@@ -0,0 +1,41 @@
"""INV-007: personenbezogene Ausgabe/Rueckgabe von Material/Leihgeraeten.
Revision ID: 0021_ausgabe
Revises: 0020_sollmenge_vorlage
Create Date: 2026-09-06
"""
from typing import Sequence, Union
from alembic import op
revision: str = "0021_ausgabe"
down_revision: Union[str, None] = "0020_sollmenge_vorlage"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.execute("CREATE TYPE ausgabe_status AS ENUM ('offen', 'zurueck')")
op.execute(
"""
CREATE TABLE ausgabe (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
material_id INTEGER NOT NULL REFERENCES material(id),
geraet_instanz_id UUID REFERENCES geraet_instanz(id),
menge NUMERIC NOT NULL,
empfaenger_id INTEGER NOT NULL REFERENCES benutzer(id),
ausgegeben_von INTEGER NOT NULL REFERENCES benutzer(id),
ausgegeben_am TIMESTAMPTZ NOT NULL,
rueckgabe_am TIMESTAMPTZ,
status ausgabe_status NOT NULL DEFAULT 'offen',
zweck TEXT
)
"""
)
op.execute("CREATE INDEX idx_ausgabe_empfaenger ON ausgabe (empfaenger_id)")
op.execute("CREATE INDEX idx_ausgabe_status ON ausgabe (status)")
def downgrade() -> None:
op.execute("DROP TABLE ausgabe")
op.execute("DROP TYPE ausgabe_status")
+2
View File
@@ -2,6 +2,7 @@ from fastapi import APIRouter
from app.api.v1.endpoints import ( from app.api.v1.endpoints import (
akte, akte,
ausgabe,
auth, auth,
benutzer, benutzer,
dashboard, dashboard,
@@ -44,3 +45,4 @@ api_router.include_router(dokument.router, tags=["dokument"])
api_router.include_router(permission.router, tags=["permission"]) api_router.include_router(permission.router, tags=["permission"])
api_router.include_router(akte.router, tags=["akte"]) api_router.include_router(akte.router, tags=["akte"])
api_router.include_router(lager.router, tags=["lager"]) api_router.include_router(lager.router, tags=["lager"])
api_router.include_router(ausgabe.router, tags=["ausgabe"])
+63
View File
@@ -0,0 +1,63 @@
import uuid
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.deps import get_current_user
from app.db.session import get_db
from app.models.ausgabe import Ausgabe, AusgabeStatus
from app.models.auth import Benutzer
from app.schemas.ausgabe import AusgabeCreate, AusgabeRead
from app.services.ausgabe import AusgabeBereitsZurueckError, ausgeben, zurueckgeben
router = APIRouter()
@router.get("/ausgaben", response_model=list[AusgabeRead])
async def liste_ausgaben(
status_filter: AusgabeStatus | None = None,
empfaenger_id: int | None = None,
db: AsyncSession = Depends(get_db),
_=Depends(get_current_user),
) -> list[Ausgabe]:
stmt = select(Ausgabe)
if status_filter is not None:
stmt = stmt.where(Ausgabe.status == status_filter)
if empfaenger_id is not None:
stmt = stmt.where(Ausgabe.empfaenger_id == empfaenger_id)
stmt = stmt.order_by(Ausgabe.ausgegeben_am.desc())
result = await db.execute(stmt)
return list(result.scalars().all())
@router.post("/ausgaben", response_model=AusgabeRead, status_code=status.HTTP_201_CREATED)
async def erstelle_ausgabe(
payload: AusgabeCreate,
db: AsyncSession = Depends(get_db),
current_user: Benutzer = Depends(get_current_user),
) -> Ausgabe:
return await ausgeben(
db,
material_id=payload.material_id,
geraet_instanz_id=payload.geraet_instanz_id,
menge=payload.menge,
empfaenger_id=payload.empfaenger_id,
ausgegeben_von=current_user.id,
zweck=payload.zweck,
)
@router.post("/ausgaben/{ausgabe_id}/rueckgabe", response_model=AusgabeRead)
async def route_rueckgabe(
ausgabe_id: uuid.UUID, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)
) -> Ausgabe:
ausgabe = await db.get(Ausgabe, ausgabe_id)
if ausgabe is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ausgabe nicht gefunden")
try:
return await zurueckgeben(db, ausgabe=ausgabe)
except AusgabeBereitsZurueckError as exc:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Ausgabe wurde bereits zurückgegeben"
) from exc
+3
View File
@@ -1,3 +1,4 @@
from app.models.ausgabe import Ausgabe, AusgabeStatus
from app.models.auth import Benutzer, BenutzerRolle, RolleTyp, Systemknoten, KnotenTyp from app.models.auth import Benutzer, BenutzerRolle, RolleTyp, Systemknoten, KnotenTyp
from app.models.dokument import Dokument from app.models.dokument import Dokument
from app.models.eskalation import EskalationKonfiguration from app.models.eskalation import EskalationKonfiguration
@@ -26,6 +27,8 @@ from app.models.vorlage import Beladungsvorlage, VorlageStatus, Vorlagenposition
from app.models.zustaendigkeit import Kontrollverantwortung, Zustaendigkeit from app.models.zustaendigkeit import Kontrollverantwortung, Zustaendigkeit
__all__ = [ __all__ = [
"Ausgabe",
"AusgabeStatus",
"Benutzer", "Benutzer",
"BenutzerRolle", "BenutzerRolle",
"RolleTyp", "RolleTyp",
+41
View File
@@ -0,0 +1,41 @@
import enum
import uuid
from datetime import datetime
from decimal import Decimal
from sqlalchemy import ForeignKey, Numeric, String
from sqlalchemy.dialects.postgresql import ENUM as PgEnum, TIMESTAMP, UUID
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base
class AusgabeStatus(str, enum.Enum):
offen = "offen"
zurueck = "zurueck"
ausgabe_status_pg = PgEnum(AusgabeStatus, name="ausgabe_status", create_type=False)
class Ausgabe(Base):
"""INV-007: personenbezogene Ausgabe/Rückgabe von Material/Leihgeräten -
ergänzt die reine Standort-/Lagerplatz-Sicht (Lagerbewegung/Materialbewegung)
um "wer hat das Ding gerade", solange die Ausgabe offen ist."""
__tablename__ = "ausgabe"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
material_id: Mapped[int] = mapped_column(ForeignKey("material.id"), nullable=False)
geraet_instanz_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True), ForeignKey("geraet_instanz.id")
)
menge: Mapped[Decimal] = mapped_column(Numeric, nullable=False)
empfaenger_id: Mapped[int] = mapped_column(ForeignKey("benutzer.id"), nullable=False)
ausgegeben_von: Mapped[int] = mapped_column(ForeignKey("benutzer.id"), nullable=False)
ausgegeben_am: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), nullable=False)
rueckgabe_am: Mapped[datetime | None] = mapped_column(TIMESTAMP(timezone=True))
status: Mapped[AusgabeStatus] = mapped_column(
ausgabe_status_pg, nullable=False, default=AusgabeStatus.offen
)
zweck: Mapped[str | None] = mapped_column(String)
+29
View File
@@ -0,0 +1,29 @@
import uuid
from datetime import datetime
from decimal import Decimal
from pydantic import BaseModel, ConfigDict
from app.models.ausgabe import AusgabeStatus
class AusgabeCreate(BaseModel):
material_id: int
geraet_instanz_id: uuid.UUID | None = None
menge: Decimal
empfaenger_id: int
zweck: str | None = None
class AusgabeRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
material_id: int
geraet_instanz_id: uuid.UUID | None
menge: Decimal
empfaenger_id: int
ausgegeben_von: int
ausgegeben_am: datetime
rueckgabe_am: datetime | None
status: AusgabeStatus
zweck: str | None
+45
View File
@@ -0,0 +1,45 @@
from datetime import datetime, timezone
from decimal import Decimal
from uuid import UUID
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.ausgabe import Ausgabe, AusgabeStatus
class AusgabeBereitsZurueckError(Exception):
"""Eine bereits zurückgegebene Ausgabe kann nicht erneut zurückgegeben werden."""
async def ausgeben(
db: AsyncSession,
*,
material_id: int,
geraet_instanz_id: UUID | None,
menge: Decimal,
empfaenger_id: int,
ausgegeben_von: int,
zweck: str | None,
) -> Ausgabe:
ausgabe = Ausgabe(
material_id=material_id,
geraet_instanz_id=geraet_instanz_id,
menge=menge,
empfaenger_id=empfaenger_id,
ausgegeben_von=ausgegeben_von,
ausgegeben_am=datetime.now(timezone.utc),
status=AusgabeStatus.offen,
zweck=zweck,
)
db.add(ausgabe)
await db.flush()
return ausgabe
async def zurueckgeben(db: AsyncSession, *, ausgabe: Ausgabe) -> Ausgabe:
if ausgabe.status == AusgabeStatus.zurueck:
raise AusgabeBereitsZurueckError
ausgabe.status = AusgabeStatus.zurueck
ausgabe.rueckgabe_am = datetime.now(timezone.utc)
await db.flush()
return ausgabe
+53
View File
@@ -0,0 +1,53 @@
import pytest
from tests.conftest import auth_header, login
@pytest.mark.asyncio
async def test_ausgabe_rueckgabe_zyklus(client, admin_user, materialverantwortlicher_user, material_factory):
token = await login(client, "admin1")
material = await material_factory("Funkgerät")
ausgabe = await client.post(
"/api/v1/ausgaben",
json={
"material_id": material.id,
"menge": "1",
"empfaenger_id": materialverantwortlicher_user.id,
"zweck": "Übung",
},
headers=auth_header(token),
)
assert ausgabe.status_code == 201
daten = ausgabe.json()
assert daten["status"] == "offen"
assert daten["rueckgabe_am"] is None
assert daten["ausgegeben_von"] == admin_user.id
offene = await client.get("/api/v1/ausgaben?status_filter=offen", headers=auth_header(token))
assert len(offene.json()) == 1
rueckgabe = await client.post(
f"/api/v1/ausgaben/{daten['id']}/rueckgabe", headers=auth_header(token)
)
assert rueckgabe.status_code == 200
assert rueckgabe.json()["status"] == "zurueck"
assert rueckgabe.json()["rueckgabe_am"] is not None
doppelt = await client.post(
f"/api/v1/ausgaben/{daten['id']}/rueckgabe", headers=auth_header(token)
)
assert doppelt.status_code == 409
offene2 = await client.get("/api/v1/ausgaben?status_filter=offen", headers=auth_header(token))
assert len(offene2.json()) == 0
@pytest.mark.asyncio
async def test_rueckgabe_unbekannte_ausgabe_404(client, admin_user):
token = await login(client, "admin1")
response = await client.post(
"/api/v1/ausgaben/00000000-0000-0000-0000-000000000000/rueckgabe",
headers=auth_header(token),
)
assert response.status_code == 404
+13
View File
@@ -130,6 +130,19 @@ export interface Materialbewegung {
grund: string | null; grund: string | null;
} }
export interface Ausgabe {
id: string;
material_id: number;
geraet_instanz_id: string | null;
menge: string;
empfaenger_id: number;
ausgegeben_von: number;
ausgegeben_am: string;
rueckgabe_am: string | null;
status: "offen" | "zurueck";
zweck: string | null;
}
export interface Fehlbestand { export interface Fehlbestand {
id: string; id: string;
objekt_id: number; objekt_id: number;
@@ -69,6 +69,7 @@ export function CommandPalette() {
gehe: () => navigate("/admin?tab=lager"), gehe: () => navigate("/admin?tab=lager"),
}, },
{ id: "nav-lager", label: "Lager", gehe: () => navigate("/admin?tab=lager") }, { id: "nav-lager", label: "Lager", gehe: () => navigate("/admin?tab=lager") },
{ id: "nav-ausgabe", label: "Ausgabe/Rückgabe", gehe: () => navigate("/admin?tab=ausgabe") },
{ id: "nav-personal", label: "Personal", gehe: () => navigate("/admin?tab=personal") }, { id: "nav-personal", label: "Personal", gehe: () => navigate("/admin?tab=personal") },
{ {
id: "nav-material", id: "nav-material",
+10
View File
@@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
import { apiRequest } from "../api/client"; import { apiRequest } from "../api/client";
import { AusgabeSection } from "./admin/AusgabeSection";
import { useAuth } from "../auth/AuthContext"; import { useAuth } from "../auth/AuthContext";
import type { Benutzer, Bereich, Beladungsvorlage, Einheit, Fach, Kategorie, Material, Objekt, Objekttyp, Qualifikationstyp, Standort } from "../api/types"; import type { Benutzer, Bereich, Beladungsvorlage, Einheit, Fach, Kategorie, Material, Objekt, Objekttyp, Qualifikationstyp, Standort } from "../api/types";
import { BenutzerSection } from "./admin/BenutzerSection"; import { BenutzerSection } from "./admin/BenutzerSection";
@@ -37,6 +38,7 @@ type Tab =
| "personal" | "personal"
| "lagerbewegungen" | "lagerbewegungen"
| "lager" | "lager"
| "ausgabe"
| "rollen"; | "rollen";
// Deckt sich mit den require_roles()-Gates der jeweiligen Backend-Endpunkte // Deckt sich mit den require_roles()-Gates der jeweiligen Backend-Endpunkte
@@ -69,6 +71,11 @@ const TABS: { key: Tab; label: string; rollen: ("administration" | "materialvera
rollen: ["administration", "materialverantwortlicher", "leitungsverantwortlicher"], rollen: ["administration", "materialverantwortlicher", "leitungsverantwortlicher"],
}, },
{ key: "lager", label: "Lager", rollen: ["administration", "materialverantwortlicher"] }, { key: "lager", label: "Lager", rollen: ["administration", "materialverantwortlicher"] },
{
key: "ausgabe",
label: "Ausgabe/Rückgabe",
rollen: ["administration", "materialverantwortlicher", "leitungsverantwortlicher"],
},
{ key: "rollen", label: "Rollen & Rechte", rollen: ["administration"] }, { key: "rollen", label: "Rollen & Rechte", rollen: ["administration"] },
]; ];
@@ -237,6 +244,9 @@ export function AdminPage() {
<LagerbewegungSection objekte={objekte} standorte={standorte} onFehler={setFehler} /> <LagerbewegungSection objekte={objekte} standorte={standorte} onFehler={setFehler} />
)} )}
{aktiverTab === "lager" && <LagerSection materialien={materialien} onFehler={setFehler} />} {aktiverTab === "lager" && <LagerSection materialien={materialien} onFehler={setFehler} />}
{aktiverTab === "ausgabe" && (
<AusgabeSection materialien={materialien} benutzer={benutzer} onFehler={setFehler} />
)}
{aktiverTab === "rollen" && <RolleSection benutzer={benutzer} onFehler={setFehler} />} {aktiverTab === "rollen" && <RolleSection benutzer={benutzer} onFehler={setFehler} />}
{aktiverTab === "eskalation" && <EskalationSection onFehler={setFehler} />} {aktiverTab === "eskalation" && <EskalationSection onFehler={setFehler} />}
{aktiverTab === "personal" && ( {aktiverTab === "personal" && (
+181
View File
@@ -0,0 +1,181 @@
import { useEffect, useState } from "react";
import { apiRequest } from "../../api/client";
import type { Ausgabe, Benutzer, Material } from "../../api/types";
interface Props {
materialien: Material[];
benutzer: Benutzer[];
onFehler: (text: string) => void;
}
// INV-007: personenbezogene Ausgabe/Rückgabe - ergänzt die reine Standort-
// Sicht (Lagerbewegung/Materialbewegung) um "wer hat das Ding gerade".
export function AusgabeSection({ materialien, benutzer, onFehler }: Props) {
const [ausgaben, setAusgaben] = useState<Ausgabe[]>([]);
const [nurOffene, setNurOffene] = useState(true);
const [laedt, setLaedt] = useState(true);
const [neuMaterial, setNeuMaterial] = useState<number | "">("");
const [neuMenge, setNeuMenge] = useState("1");
const [neuEmpfaenger, setNeuEmpfaenger] = useState<number | "">("");
const [neuZweck, setNeuZweck] = useState("");
function materialName(id: number): string {
const m = materialien.find((x) => x.id === id);
return m ? m.name : `#${id}`;
}
function benutzerName(id: number): string {
const b = benutzer.find((x) => x.id === id);
return b ? b.name : `#${id}`;
}
async function laden() {
setLaedt(true);
try {
const query = nurOffene ? "?status_filter=offen" : "";
setAusgaben(await apiRequest<Ausgabe[]>(`/ausgaben${query}`));
} catch {
onFehler("Ausgaben konnten nicht geladen werden.");
} finally {
setLaedt(false);
}
}
useEffect(() => {
laden();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nurOffene]);
async function ausgeben() {
if (neuMaterial === "" || neuEmpfaenger === "" || !neuMenge) {
onFehler("Material, Menge und Empfänger angeben.");
return;
}
try {
await apiRequest("/ausgaben", {
method: "POST",
body: {
material_id: neuMaterial,
menge: neuMenge,
empfaenger_id: neuEmpfaenger,
zweck: neuZweck || null,
},
});
setNeuMenge("1");
setNeuZweck("");
await laden();
} catch {
onFehler("Ausgabe fehlgeschlagen.");
}
}
async function rueckgabe(id: string) {
try {
await apiRequest(`/ausgaben/${id}/rueckgabe`, { method: "POST" });
await laden();
} catch {
onFehler("Rückgabe fehlgeschlagen.");
}
}
return (
<section className="section">
<h2>Ausgabe / Rückgabe</h2>
<div className="card">
<h3 style={{ marginTop: 0 }}>Neue Ausgabe</h3>
<div className="row" style={{ flexWrap: "wrap" }}>
<select className="input" value={neuMaterial} onChange={(e) => setNeuMaterial(Number(e.target.value) || "")}>
<option value="">Material wählen</option>
{materialien.map((m) => (
<option key={m.id} value={m.id}>
{m.name}
</option>
))}
</select>
<input
className="input"
type="number"
placeholder="Menge"
style={{ width: "6rem" }}
value={neuMenge}
onChange={(e) => setNeuMenge(e.target.value)}
/>
<select className="input" value={neuEmpfaenger} onChange={(e) => setNeuEmpfaenger(Number(e.target.value) || "")}>
<option value="">Empfänger wählen</option>
{benutzer.map((b) => (
<option key={b.id} value={b.id}>
{b.name}
</option>
))}
</select>
<input
className="input"
placeholder="Zweck (optional)"
value={neuZweck}
onChange={(e) => setNeuZweck(e.target.value)}
/>
<button className="btn btn-primary" onClick={ausgeben}>
Ausgeben
</button>
</div>
</div>
<div className="card" style={{ marginTop: "1rem" }}>
<div className="row-between" style={{ marginBottom: "0.75rem" }}>
<h3 style={{ margin: 0 }}>Ausgaben</h3>
<label className="row" style={{ gap: "0.4rem", fontSize: "0.9rem" }}>
<input type="checkbox" checked={nurOffene} onChange={(e) => setNurOffene(e.target.checked)} />
nur offene
</label>
</div>
{laedt && <p className="text-muted">Lade</p>}
{!laedt && ausgaben.length === 0 && <p className="text-muted">Keine Ausgaben.</p>}
{!laedt && ausgaben.length > 0 && (
<div style={{ overflowX: "auto" }}>
<table className="table">
<thead>
<tr>
<th>Ausgegeben am</th>
<th>Material</th>
<th>Menge</th>
<th>Empfänger</th>
<th>Zweck</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
{ausgaben.map((a) => (
<tr key={a.id}>
<td>{new Date(a.ausgegeben_am).toLocaleString("de-DE")}</td>
<td>{materialName(a.material_id)}</td>
<td>{a.menge}</td>
<td>{benutzerName(a.empfaenger_id)}</td>
<td>{a.zweck ?? ""}</td>
<td>
<span className={a.status === "offen" ? "badge badge-warning" : "badge badge-neutral"}>
{a.status === "offen" ? "offen" : "zurück"}
</span>
</td>
<td>
{a.status === "offen" && (
<button className="btn btn-secondary" onClick={() => rueckgabe(a.id)}>
Rückgabe
</button>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</section>
);
}