feat(dokumente): DOC-004 Original-vs-Kopie-Kennzeichnung
Neues Feld ist_original (Boolean, Migration 0029) - explizit beim Upload und beim Ersetzen (DOC-003) setzbar, kein Default in eine Richtung erraten. Wird beim Ersetzen bewusst NICHT vom Vorgänger übernommen (neue Version könnte ein nachgereichtes Original sein, das eine bisherige Kopie ersetzt, oder umgekehrt). Frontend: Checkbox "Original (nicht nur Kopie/Scan)" im Upload-Formular, "Original"-Badge in der Dokumentenliste. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
@@ -217,7 +217,7 @@ vergeben statt geraten.
|
|||||||
| DOC-001 | Documents | Dokument-Grundgerüst (Upload) | P1 | M | MEDIUM | FILE-001 | ✅ (`dokument.py` Model+Endpunkt, inkl. SHA-256-Duplikaterkennung, Migration 0024/0016 referenzieren DOC-001) |
|
| DOC-001 | Documents | Dokument-Grundgerüst (Upload) | P1 | M | MEDIUM | FILE-001 | ✅ (`dokument.py` Model+Endpunkt, inkl. SHA-256-Duplikaterkennung, Migration 0024/0016 referenzieren DOC-001) |
|
||||||
| DOC-002 | Documents | Dokumenttypen | P2 | XS | LOW | DOC-001 | ✅ (`DokumentTyp`-Enum, Migration 0026, Filter beim Listen, Auswahl beim Upload) |
|
| DOC-002 | Documents | Dokumenttypen | P2 | XS | LOW | DOC-001 | ✅ (`DokumentTyp`-Enum, Migration 0026, Filter beim Listen, Auswahl beim Upload) |
|
||||||
| DOC-003 | Documents | Versionierung | P2 | M | MEDIUM | DOC-001 | ✅ (`vorgaenger_id`-Kette, Migration 0027, `/ersetzen`+`/versionen`-Endpunkte, alte Version bleibt erhalten) |
|
| DOC-003 | Documents | Versionierung | P2 | M | MEDIUM | DOC-001 | ✅ (`vorgaenger_id`-Kette, Migration 0027, `/ersetzen`+`/versionen`-Endpunkte, alte Version bleibt erhalten) |
|
||||||
| DOC-004 | Documents | Original-vs-Kopie-Kennzeichnung | P2 | S | LOW | DOC-001 | ⬜ (kein entsprechendes Feld gefunden) |
|
| DOC-004 | Documents | Original-vs-Kopie-Kennzeichnung | P2 | S | LOW | DOC-001 | ✅ (`ist_original`-Feld, Migration 0029, beim Upload/Ersetzen explizit setzbar) |
|
||||||
| DOC-005 | Documents | Zugriffsrechte je Dokument | P2 | S | MEDIUM | DOC-001, FOUND-004 | ✅ (nach Dokumenttyp: Rechnungen nur für Materialverantwortliche+, per `EINGESCHRAENKTE_DOKUMENTTYPEN`) |
|
| DOC-005 | Documents | Zugriffsrechte je Dokument | P2 | S | MEDIUM | DOC-001, FOUND-004 | ✅ (nach Dokumenttyp: Rechnungen nur für Materialverantwortliche+, per `EINGESCHRAENKTE_DOKUMENTTYPEN`) |
|
||||||
| MOBILE-001 | Mobile | PWA-Grundgerüst | P1 | M | MEDIUM | FOUND-005 | ✅ (Vite-PWA-Build erzeugt `manifest.webmanifest`/`sw.js`, `vite-plugin-pwa` in package.json) |
|
| MOBILE-001 | Mobile | PWA-Grundgerüst | P1 | M | MEDIUM | FOUND-005 | ✅ (Vite-PWA-Build erzeugt `manifest.webmanifest`/`sw.js`, `vite-plugin-pwa` in package.json) |
|
||||||
| MOBILE-002 | Mobile | QR-Scan-Workflow (Kernablauf) | P1 | M | MEDIUM | MOBILE-001, IDENT-008 | ✅ (`BarcodeScanner.tsx` verdrahtet in `ObjektListPage.tsx`) |
|
| MOBILE-002 | Mobile | QR-Scan-Workflow (Kernablauf) | P1 | M | MEDIUM | MOBILE-001, IDENT-008 | ✅ (`BarcodeScanner.tsx` verdrahtet in `ObjektListPage.tsx`) |
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
"""DOC-004: Original-vs-Kopie-Kennzeichnung bei Dokumenten.
|
||||||
|
|
||||||
|
Revision ID: 0029_dokument_original
|
||||||
|
Revises: 0028_fk_indizes
|
||||||
|
Create Date: 2026-09-08
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
revision: str = "0029_dokument_original"
|
||||||
|
down_revision: Union[str, None] = "0028_fk_indizes"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.execute(
|
||||||
|
"ALTER TABLE dokument ADD COLUMN ist_original BOOLEAN NOT NULL DEFAULT false"
|
||||||
|
)
|
||||||
|
# Default nur für den Backfill nötig - künftige Inserts geben es immer
|
||||||
|
# explizit an (Anwendungsebene).
|
||||||
|
op.execute("ALTER TABLE dokument ALTER COLUMN ist_original DROP DEFAULT")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.execute("ALTER TABLE dokument DROP COLUMN ist_original")
|
||||||
@@ -72,6 +72,7 @@ async def lade_dokument_hoch(
|
|||||||
entitaet_id: str = Form(...),
|
entitaet_id: str = Form(...),
|
||||||
dokumenttyp: DokumentTyp = Form(...),
|
dokumenttyp: DokumentTyp = Form(...),
|
||||||
beschreibung: str | None = Form(None),
|
beschreibung: str | None = Form(None),
|
||||||
|
ist_original: bool = Form(False),
|
||||||
datei: UploadFile = File(...),
|
datei: UploadFile = File(...),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user=Depends(_mitarbeiter_plus),
|
current_user=Depends(_mitarbeiter_plus),
|
||||||
@@ -88,6 +89,7 @@ async def lade_dokument_hoch(
|
|||||||
dokumenttyp=dokumenttyp,
|
dokumenttyp=dokumenttyp,
|
||||||
beschreibung=beschreibung,
|
beschreibung=beschreibung,
|
||||||
hochgeladen_von=current_user.id,
|
hochgeladen_von=current_user.id,
|
||||||
|
ist_original=ist_original,
|
||||||
)
|
)
|
||||||
except DateityperlaubtError as exc:
|
except DateityperlaubtError as exc:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@@ -128,6 +130,7 @@ async def lade_dokument_herunter(
|
|||||||
async def dokument_ersetzen(
|
async def dokument_ersetzen(
|
||||||
dokument_id: uuid.UUID,
|
dokument_id: uuid.UUID,
|
||||||
beschreibung: str | None = Form(None),
|
beschreibung: str | None = Form(None),
|
||||||
|
ist_original: bool = Form(False),
|
||||||
datei: UploadFile = File(...),
|
datei: UploadFile = File(...),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user=Depends(_mitarbeiter_plus),
|
current_user=Depends(_mitarbeiter_plus),
|
||||||
@@ -148,6 +151,7 @@ async def dokument_ersetzen(
|
|||||||
inhalt=inhalt,
|
inhalt=inhalt,
|
||||||
beschreibung=beschreibung,
|
beschreibung=beschreibung,
|
||||||
hochgeladen_von=current_user.id,
|
hochgeladen_von=current_user.id,
|
||||||
|
ist_original=ist_original,
|
||||||
)
|
)
|
||||||
except DateityperlaubtError as exc:
|
except DateityperlaubtError as exc:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import enum
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from sqlalchemy import ForeignKey, Integer, String
|
from sqlalchemy import Boolean, ForeignKey, Integer, String
|
||||||
from sqlalchemy.dialects.postgresql import ENUM as PgEnum, TIMESTAMP, UUID
|
from sqlalchemy.dialects.postgresql import ENUM as PgEnum, TIMESTAMP, UUID
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
@@ -67,3 +67,8 @@ class Dokument(Base):
|
|||||||
vorgaenger_id: Mapped[uuid.UUID | None] = mapped_column(
|
vorgaenger_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||||
UUID(as_uuid=True), ForeignKey("dokument.id"), nullable=True
|
UUID(as_uuid=True), ForeignKey("dokument.id"), nullable=True
|
||||||
)
|
)
|
||||||
|
# DOC-004: rechtlich/organisatorisch relevant bei Zulassungsdokumenten/
|
||||||
|
# Rechnungen - ist das hier das Original (z.B. eingescannt vom Papier-
|
||||||
|
# Original) oder nur eine Kopie/ein Duplikat davon? Explizite Angabe beim
|
||||||
|
# Upload statt Annahme, kein Default in eine Richtung.
|
||||||
|
ist_original: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||||
|
|||||||
@@ -27,3 +27,4 @@ class DokumentRead(BaseModel):
|
|||||||
hochgeladen_von: int
|
hochgeladen_von: int
|
||||||
hochgeladen_am: datetime
|
hochgeladen_am: datetime
|
||||||
vorgaenger_id: uuid.UUID | None
|
vorgaenger_id: uuid.UUID | None
|
||||||
|
ist_original: bool
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ async def _pruefe_und_speichere(
|
|||||||
beschreibung: str | None,
|
beschreibung: str | None,
|
||||||
hochgeladen_von: int,
|
hochgeladen_von: int,
|
||||||
vorgaenger_id: uuid.UUID | None,
|
vorgaenger_id: uuid.UUID | None,
|
||||||
|
ist_original: bool,
|
||||||
) -> Dokument:
|
) -> Dokument:
|
||||||
if mime_type not in ERLAUBTE_MIME_TYPES:
|
if mime_type not in ERLAUBTE_MIME_TYPES:
|
||||||
raise DateityperlaubtError(mime_type)
|
raise DateityperlaubtError(mime_type)
|
||||||
@@ -94,6 +95,7 @@ async def _pruefe_und_speichere(
|
|||||||
hochgeladen_von=hochgeladen_von,
|
hochgeladen_von=hochgeladen_von,
|
||||||
hochgeladen_am=datetime.now(timezone.utc),
|
hochgeladen_am=datetime.now(timezone.utc),
|
||||||
vorgaenger_id=vorgaenger_id,
|
vorgaenger_id=vorgaenger_id,
|
||||||
|
ist_original=ist_original,
|
||||||
)
|
)
|
||||||
db.add(dokument)
|
db.add(dokument)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
@@ -111,6 +113,7 @@ async def speichere_dokument(
|
|||||||
dokumenttyp: DokumentTyp,
|
dokumenttyp: DokumentTyp,
|
||||||
beschreibung: str | None,
|
beschreibung: str | None,
|
||||||
hochgeladen_von: int,
|
hochgeladen_von: int,
|
||||||
|
ist_original: bool,
|
||||||
) -> Dokument:
|
) -> Dokument:
|
||||||
return await _pruefe_und_speichere(
|
return await _pruefe_und_speichere(
|
||||||
db,
|
db,
|
||||||
@@ -123,6 +126,7 @@ async def speichere_dokument(
|
|||||||
beschreibung=beschreibung,
|
beschreibung=beschreibung,
|
||||||
hochgeladen_von=hochgeladen_von,
|
hochgeladen_von=hochgeladen_von,
|
||||||
vorgaenger_id=None,
|
vorgaenger_id=None,
|
||||||
|
ist_original=ist_original,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -135,11 +139,14 @@ async def ersetze_dokument(
|
|||||||
inhalt: bytes,
|
inhalt: bytes,
|
||||||
beschreibung: str | None,
|
beschreibung: str | None,
|
||||||
hochgeladen_von: int,
|
hochgeladen_von: int,
|
||||||
|
ist_original: bool,
|
||||||
) -> Dokument:
|
) -> Dokument:
|
||||||
"""DOC-003: neue Version anlegen statt zu überschreiben - altes_dokument
|
"""DOC-003: neue Version anlegen statt zu überschreiben - altes_dokument
|
||||||
bleibt unverändert als Historieneintrag erhalten, entitaet_typ/entitaet_id/
|
bleibt unverändert als Historieneintrag erhalten, entitaet_typ/entitaet_id/
|
||||||
dokumenttyp werden von ihm übernommen (nicht neu wählbar, es ist dieselbe
|
dokumenttyp werden von ihm übernommen (nicht neu wählbar, es ist dieselbe
|
||||||
Sache in neuer Fassung)."""
|
Sache in neuer Fassung). ist_original wird bewusst NICHT vom Vorgänger
|
||||||
|
übernommen (DOC-004) - die neue Version könnte z.B. ein nachgereichtes
|
||||||
|
Original sein, das die bisherige Kopie ersetzt."""
|
||||||
return await _pruefe_und_speichere(
|
return await _pruefe_und_speichere(
|
||||||
db,
|
db,
|
||||||
entitaet_typ=altes_dokument.entitaet_typ,
|
entitaet_typ=altes_dokument.entitaet_typ,
|
||||||
@@ -151,6 +158,7 @@ async def ersetze_dokument(
|
|||||||
beschreibung=beschreibung,
|
beschreibung=beschreibung,
|
||||||
hochgeladen_von=hochgeladen_von,
|
hochgeladen_von=hochgeladen_von,
|
||||||
vorgaenger_id=altes_dokument.id,
|
vorgaenger_id=altes_dokument.id,
|
||||||
|
ist_original=ist_original,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -368,3 +368,44 @@ async def test_versionshistorie_respektiert_zugriffsrechte(
|
|||||||
mitarbeiter_token = await login(client, "mitarbeiter1")
|
mitarbeiter_token = await login(client, "mitarbeiter1")
|
||||||
verweigert = await client.get(f"/api/v1/dokumente/{v1_id}/versionen", headers=auth_header(mitarbeiter_token))
|
verweigert = await client.get(f"/api/v1/dokumente/{v1_id}/versionen", headers=auth_header(mitarbeiter_token))
|
||||||
assert verweigert.status_code == 403
|
assert verweigert.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_original_kennzeichnung_default_false_und_explizit_setzbar(
|
||||||
|
client, objekt_mit_position, mitarbeiter_user
|
||||||
|
):
|
||||||
|
"""DOC-004: ohne Angabe gilt eine Datei als Kopie (False), kann aber
|
||||||
|
explizit als Original markiert werden - auch beim Ersetzen unabhängig
|
||||||
|
vom Vorgänger neu wählbar."""
|
||||||
|
objekt, _material = objekt_mit_position
|
||||||
|
token = await login(client, "mitarbeiter1")
|
||||||
|
|
||||||
|
ohne_angabe = await client.post(
|
||||||
|
"/api/v1/dokumente",
|
||||||
|
data={"entitaet_typ": "objekt", "entitaet_id": str(objekt.id), "dokumenttyp": "zulassungsdokument"},
|
||||||
|
files={"datei": ("kopie.pdf", b"%PDF-1.4 kopie", "application/pdf")},
|
||||||
|
headers=auth_header(token),
|
||||||
|
)
|
||||||
|
assert ohne_angabe.json()["ist_original"] is False
|
||||||
|
|
||||||
|
original = await client.post(
|
||||||
|
"/api/v1/dokumente",
|
||||||
|
data={
|
||||||
|
"entitaet_typ": "objekt",
|
||||||
|
"entitaet_id": str(objekt.id),
|
||||||
|
"dokumenttyp": "zulassungsdokument",
|
||||||
|
"ist_original": "true",
|
||||||
|
},
|
||||||
|
files={"datei": ("original.pdf", b"%PDF-1.4 original", "application/pdf")},
|
||||||
|
headers=auth_header(token),
|
||||||
|
)
|
||||||
|
original_body = original.json()
|
||||||
|
assert original_body["ist_original"] is True
|
||||||
|
|
||||||
|
ersetzt = await client.post(
|
||||||
|
f"/api/v1/dokumente/{original_body['id']}/ersetzen",
|
||||||
|
data={"ist_original": "false"},
|
||||||
|
files={"datei": ("nachtraeglich_kopie.pdf", b"%PDF-1.4 v2", "application/pdf")},
|
||||||
|
headers=auth_header(token),
|
||||||
|
)
|
||||||
|
assert ersetzt.json()["ist_original"] is False
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ export async function ladeDokumentHoch(felder: {
|
|||||||
entitaet_id: string;
|
entitaet_id: string;
|
||||||
dokumenttyp: string;
|
dokumenttyp: string;
|
||||||
beschreibung?: string;
|
beschreibung?: string;
|
||||||
|
istOriginal: boolean;
|
||||||
datei: File;
|
datei: File;
|
||||||
}): Promise<unknown> {
|
}): Promise<unknown> {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@@ -76,6 +77,7 @@ export async function ladeDokumentHoch(felder: {
|
|||||||
formData.append("entitaet_id", felder.entitaet_id);
|
formData.append("entitaet_id", felder.entitaet_id);
|
||||||
formData.append("dokumenttyp", felder.dokumenttyp);
|
formData.append("dokumenttyp", felder.dokumenttyp);
|
||||||
if (felder.beschreibung) formData.append("beschreibung", felder.beschreibung);
|
if (felder.beschreibung) formData.append("beschreibung", felder.beschreibung);
|
||||||
|
formData.append("ist_original", String(felder.istOriginal));
|
||||||
formData.append("datei", felder.datei);
|
formData.append("datei", felder.datei);
|
||||||
|
|
||||||
const headers: Record<string, string> = {};
|
const headers: Record<string, string> = {};
|
||||||
@@ -93,10 +95,12 @@ export async function ladeDokumentHoch(felder: {
|
|||||||
export async function ladeDokumentErsetzen(felder: {
|
export async function ladeDokumentErsetzen(felder: {
|
||||||
dokumentId: string;
|
dokumentId: string;
|
||||||
beschreibung?: string;
|
beschreibung?: string;
|
||||||
|
istOriginal: boolean;
|
||||||
datei: File;
|
datei: File;
|
||||||
}): Promise<unknown> {
|
}): Promise<unknown> {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
if (felder.beschreibung) formData.append("beschreibung", felder.beschreibung);
|
if (felder.beschreibung) formData.append("beschreibung", felder.beschreibung);
|
||||||
|
formData.append("ist_original", String(felder.istOriginal));
|
||||||
formData.append("datei", felder.datei);
|
formData.append("datei", felder.datei);
|
||||||
|
|
||||||
const headers: Record<string, string> = {};
|
const headers: Record<string, string> = {};
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ export interface Dokument {
|
|||||||
hochgeladen_von: number;
|
hochgeladen_von: number;
|
||||||
hochgeladen_am: string;
|
hochgeladen_am: string;
|
||||||
vorgaenger_id: string | null;
|
vorgaenger_id: string | null;
|
||||||
|
ist_original: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Lagerbewegung {
|
export interface Lagerbewegung {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export function DokumentePanel({ entitaetTyp, entitaetId, onFehler }: Props) {
|
|||||||
const [dokumente, setDokumente] = useState<Dokument[]>([]);
|
const [dokumente, setDokumente] = useState<Dokument[]>([]);
|
||||||
const [laedt, setLaedt] = useState(true);
|
const [laedt, setLaedt] = useState(true);
|
||||||
const [beschreibung, setBeschreibung] = useState("");
|
const [beschreibung, setBeschreibung] = useState("");
|
||||||
|
const [istOriginal, setIstOriginal] = useState(false);
|
||||||
const [dokumenttyp, setDokumenttyp] = useState<DokumentTyp>("sonstiges");
|
const [dokumenttyp, setDokumenttyp] = useState<DokumentTyp>("sonstiges");
|
||||||
const [filterTyp, setFilterTyp] = useState<DokumentTyp | "">("");
|
const [filterTyp, setFilterTyp] = useState<DokumentTyp | "">("");
|
||||||
const [wirdHochgeladen, setWirdHochgeladen] = useState(false);
|
const [wirdHochgeladen, setWirdHochgeladen] = useState(false);
|
||||||
@@ -79,8 +80,16 @@ export function DokumentePanel({ entitaetTyp, entitaetId, onFehler }: Props) {
|
|||||||
if (!datei) return;
|
if (!datei) return;
|
||||||
setWirdHochgeladen(true);
|
setWirdHochgeladen(true);
|
||||||
try {
|
try {
|
||||||
await ladeDokumentHoch({ entitaet_typ: entitaetTyp, entitaet_id: entitaetId, dokumenttyp, beschreibung, datei });
|
await ladeDokumentHoch({
|
||||||
|
entitaet_typ: entitaetTyp,
|
||||||
|
entitaet_id: entitaetId,
|
||||||
|
dokumenttyp,
|
||||||
|
beschreibung,
|
||||||
|
istOriginal,
|
||||||
|
datei,
|
||||||
|
});
|
||||||
setBeschreibung("");
|
setBeschreibung("");
|
||||||
|
setIstOriginal(false);
|
||||||
e.target.value = "";
|
e.target.value = "";
|
||||||
await laden();
|
await laden();
|
||||||
} catch (fehler) {
|
} catch (fehler) {
|
||||||
@@ -99,7 +108,7 @@ export function DokumentePanel({ entitaetTyp, entitaetId, onFehler }: Props) {
|
|||||||
const datei = e.target.files?.[0];
|
const datei = e.target.files?.[0];
|
||||||
if (!datei) return;
|
if (!datei) return;
|
||||||
try {
|
try {
|
||||||
await ladeDokumentErsetzen({ dokumentId, datei });
|
await ladeDokumentErsetzen({ dokumentId, istOriginal, datei });
|
||||||
e.target.value = "";
|
e.target.value = "";
|
||||||
delete versionenOffen[dokumentId];
|
delete versionenOffen[dokumentId];
|
||||||
await laden();
|
await laden();
|
||||||
@@ -177,6 +186,10 @@ export function DokumentePanel({ entitaetTyp, entitaetId, onFehler }: Props) {
|
|||||||
placeholder="Beschreibung (optional)"
|
placeholder="Beschreibung (optional)"
|
||||||
style={{ maxWidth: "16rem" }}
|
style={{ maxWidth: "16rem" }}
|
||||||
/>
|
/>
|
||||||
|
<label className="row" style={{ gap: "0.3rem", alignItems: "center", fontSize: "0.9rem" }}>
|
||||||
|
<input type="checkbox" checked={istOriginal} onChange={(e) => setIstOriginal(e.target.checked)} />
|
||||||
|
Original (nicht nur Kopie/Scan)
|
||||||
|
</label>
|
||||||
<input type="file" accept=".pdf,.jpg,.jpeg,.png,.webp" onChange={hochladen} disabled={wirdHochgeladen} />
|
<input type="file" accept=".pdf,.jpg,.jpeg,.png,.webp" onChange={hochladen} disabled={wirdHochgeladen} />
|
||||||
</div>
|
</div>
|
||||||
{dokumente.length > 0 || filterTyp ? (
|
{dokumente.length > 0 || filterTyp ? (
|
||||||
@@ -205,6 +218,15 @@ export function DokumentePanel({ entitaetTyp, entitaetId, onFehler }: Props) {
|
|||||||
<span className="badge badge-neutral" style={{ marginRight: "0.4rem" }}>
|
<span className="badge badge-neutral" style={{ marginRight: "0.4rem" }}>
|
||||||
{DOKUMENTTYP_LABEL[d.dokumenttyp]}
|
{DOKUMENTTYP_LABEL[d.dokumenttyp]}
|
||||||
</span>
|
</span>
|
||||||
|
{d.ist_original && (
|
||||||
|
<span
|
||||||
|
className="badge badge-success"
|
||||||
|
style={{ marginRight: "0.4rem" }}
|
||||||
|
title="Als Original gekennzeichnet, nicht nur Kopie/Scan"
|
||||||
|
>
|
||||||
|
Original
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => ansehen(d.id)}
|
onClick={() => ansehen(d.id)}
|
||||||
title="In neuem Tab ansehen"
|
title="In neuem Tab ansehen"
|
||||||
|
|||||||
Reference in New Issue
Block a user