feat(dokumente): DOC-004 Original-vs-Kopie-Kennzeichnung
CI / backend-tests (push) Successful in 2m16s
CI / frontend-build (push) Successful in 27s

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:
2026-09-08 12:16:25 +02:00
co-authored by Claude Sonnet 5
parent 17cac8543f
commit ae09433c11
10 changed files with 118 additions and 5 deletions
+4
View File
@@ -69,6 +69,7 @@ export async function ladeDokumentHoch(felder: {
entitaet_id: string;
dokumenttyp: string;
beschreibung?: string;
istOriginal: boolean;
datei: File;
}): Promise<unknown> {
const formData = new FormData();
@@ -76,6 +77,7 @@ export async function ladeDokumentHoch(felder: {
formData.append("entitaet_id", felder.entitaet_id);
formData.append("dokumenttyp", felder.dokumenttyp);
if (felder.beschreibung) formData.append("beschreibung", felder.beschreibung);
formData.append("ist_original", String(felder.istOriginal));
formData.append("datei", felder.datei);
const headers: Record<string, string> = {};
@@ -93,10 +95,12 @@ export async function ladeDokumentHoch(felder: {
export async function ladeDokumentErsetzen(felder: {
dokumentId: string;
beschreibung?: string;
istOriginal: boolean;
datei: File;
}): Promise<unknown> {
const formData = new FormData();
if (felder.beschreibung) formData.append("beschreibung", felder.beschreibung);
formData.append("ist_original", String(felder.istOriginal));
formData.append("datei", felder.datei);
const headers: Record<string, string> = {};
+1
View File
@@ -145,6 +145,7 @@ export interface Dokument {
hochgeladen_von: number;
hochgeladen_am: string;
vorgaenger_id: string | null;
ist_original: boolean;
}
export interface Lagerbewegung {
+24 -2
View File
@@ -48,6 +48,7 @@ export function DokumentePanel({ entitaetTyp, entitaetId, onFehler }: Props) {
const [dokumente, setDokumente] = useState<Dokument[]>([]);
const [laedt, setLaedt] = useState(true);
const [beschreibung, setBeschreibung] = useState("");
const [istOriginal, setIstOriginal] = useState(false);
const [dokumenttyp, setDokumenttyp] = useState<DokumentTyp>("sonstiges");
const [filterTyp, setFilterTyp] = useState<DokumentTyp | "">("");
const [wirdHochgeladen, setWirdHochgeladen] = useState(false);
@@ -79,8 +80,16 @@ export function DokumentePanel({ entitaetTyp, entitaetId, onFehler }: Props) {
if (!datei) return;
setWirdHochgeladen(true);
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("");
setIstOriginal(false);
e.target.value = "";
await laden();
} catch (fehler) {
@@ -99,7 +108,7 @@ export function DokumentePanel({ entitaetTyp, entitaetId, onFehler }: Props) {
const datei = e.target.files?.[0];
if (!datei) return;
try {
await ladeDokumentErsetzen({ dokumentId, datei });
await ladeDokumentErsetzen({ dokumentId, istOriginal, datei });
e.target.value = "";
delete versionenOffen[dokumentId];
await laden();
@@ -177,6 +186,10 @@ export function DokumentePanel({ entitaetTyp, entitaetId, onFehler }: Props) {
placeholder="Beschreibung (optional)"
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} />
</div>
{dokumente.length > 0 || filterTyp ? (
@@ -205,6 +218,15 @@ export function DokumentePanel({ entitaetTyp, entitaetId, onFehler }: Props) {
<span className="badge badge-neutral" style={{ marginRight: "0.4rem" }}>
{DOKUMENTTYP_LABEL[d.dokumenttyp]}
</span>
{d.ist_original && (
<span
className="badge badge-success"
style={{ marginRight: "0.4rem" }}
title="Als Original gekennzeichnet, nicht nur Kopie/Scan"
>
Original
</span>
)}
<button
onClick={() => ansehen(d.id)}
title="In neuem Tab ansehen"