feat(legal): Als-Entwurf-kopieren für Rechtstexte
Some checks failed
Deploy Development / deploy (push) Successful in 35s
Test Suite / pytest-backend (push) Successful in 33s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 8s
Test Suite / playwright-tests (push) Failing after 51s
Some checks failed
Deploy Development / deploy (push) Successful in 35s
Test Suite / pytest-backend (push) Successful in 33s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 8s
Test Suite / playwright-tests (push) Failing after 51s
POST /api/admin/legal-documents/{id}/copy-as-draft übernimmt Titel +
Inhalt des Quelldokuments und legt einen neuen Entwurf mit
nächster Versionsnummer an. Funktioniert für alle Status (draft/published/archived).
UI: Copy-Button (⎘) in jeder Dokumentzeile; nach Kopie wird die
Liste automatisch aktualisiert und der neue Entwurf ist sichtbar.
version: 0.8.72
module: legal_documents 1.1.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b9adf6da84
commit
8992c300f1
|
|
@ -384,6 +384,70 @@ def archive_legal_document(
|
|||
return r2d(updated)
|
||||
|
||||
|
||||
@router.post("/api/admin/legal-documents/{doc_id}/copy-as-draft", status_code=201)
|
||||
def copy_legal_document_as_draft(
|
||||
doc_id: int,
|
||||
session: dict = Depends(require_auth),
|
||||
):
|
||||
"""
|
||||
Kopiert ein beliebiges Dokument (egal welcher Status) als neuen Entwurf mit
|
||||
nächster Versionsnummer. Inhalt und Titel werden übernommen.
|
||||
"""
|
||||
_require_superadmin(session)
|
||||
|
||||
profile_id = session["profile_id"]
|
||||
|
||||
with get_db() as conn:
|
||||
cur = get_cursor(conn)
|
||||
cur.execute(
|
||||
"SELECT id, document_type, title, content_sections FROM legal_documents WHERE id = %s",
|
||||
(doc_id,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Dokument nicht gefunden")
|
||||
|
||||
src = r2d(row)
|
||||
|
||||
import json as _json
|
||||
sections_json = _json.dumps(src["content_sections"], ensure_ascii=False)
|
||||
|
||||
with get_db() as conn:
|
||||
cur = get_cursor(conn)
|
||||
cur.execute(
|
||||
"SELECT COALESCE(MAX(version), 0) FROM legal_documents WHERE document_type = %s",
|
||||
(src["document_type"],),
|
||||
)
|
||||
row2 = cur.fetchone()
|
||||
next_version = list(row2.values())[0] + 1
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO legal_documents
|
||||
(document_type, version, title, content_sections, status, change_note, created_by_profile_id)
|
||||
VALUES (%s, %s, %s, %s::jsonb, 'draft', NULL, %s)
|
||||
RETURNING id, document_type, version, title, content_sections,
|
||||
status, change_note, created_at, updated_at
|
||||
""",
|
||||
(src["document_type"], next_version, src["title"], sections_json, profile_id),
|
||||
)
|
||||
new_row = cur.fetchone()
|
||||
new_id = list(new_row.values())[0]
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO legal_document_audit
|
||||
(legal_document_id, action, changed_by_profile_id, change_note)
|
||||
VALUES (%s, 'created', %s, %s)
|
||||
""",
|
||||
(new_id, profile_id, f"Kopie von Version {src.get('version', '?')} (ID {doc_id})"),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
return r2d(new_row)
|
||||
|
||||
|
||||
@router.get("/api/admin/legal-documents/{doc_id}/audit")
|
||||
def get_legal_document_audit(doc_id: int, session: dict = Depends(require_auth)):
|
||||
"""Änderungslog für ein Dokument."""
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
# Shinkan Jinkendo Version Information
|
||||
|
||||
APP_VERSION = "0.8.71"
|
||||
APP_VERSION = "0.8.72"
|
||||
BUILD_DATE = "2026-05-10"
|
||||
DB_SCHEMA_VERSION = "20260510047"
|
||||
|
||||
MODULE_VERSIONS = {
|
||||
"legal_documents": "1.0.0", # P-01c: Admin-konfigurierbare Rechtstexte (legal_documents + legal_document_audit)
|
||||
"legal_documents": "1.1.0", # Als-Entwurf-kopieren: POST /api/admin/legal-documents/{id}/copy-as-draft
|
||||
"auth": "1.2.3", # P-05b: reset-password min_length=8 via Pydantic PasswordResetConfirm
|
||||
"profiles": "1.7.0", # exercise_list_prefs JSONB (Standard Übungsfilter); Patch via ProfileUpdate + Json()
|
||||
"tenant_context": "1.0.5", # Plattform-Admin: effective_club ohne Header aus Profil active_club_id wenn Verein existiert
|
||||
|
|
@ -30,6 +30,13 @@ MODULE_VERSIONS = {
|
|||
}
|
||||
|
||||
CHANGELOG = [
|
||||
{
|
||||
"version": "0.8.72",
|
||||
"date": "2026-05-10",
|
||||
"changes": [
|
||||
"Rechtstexte: Als-Entwurf-kopieren — POST /api/admin/legal-documents/{id}/copy-as-draft; Inhalt und Titel werden uebernommen, Versionsnummer inkrementiert",
|
||||
],
|
||||
},
|
||||
{
|
||||
"version": "0.8.71",
|
||||
"date": "2026-05-10",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { FileText, Plus, Eye, Edit2, Archive, CheckCircle, Clock, ChevronDown, ChevronUp } from 'lucide-react'
|
||||
import { FileText, Plus, Edit2, Archive, CheckCircle, Clock, Copy } from 'lucide-react'
|
||||
import api from '../utils/api'
|
||||
|
||||
const DOC_TYPES = [
|
||||
|
|
@ -95,7 +95,7 @@ function DocTypeTab({ docType, active, onClick }) {
|
|||
)
|
||||
}
|
||||
|
||||
function DocumentRow({ doc, onPublish, onArchive, onEdit, onViewAudit }) {
|
||||
function DocumentRow({ doc, onPublish, onArchive, onEdit, onCopy, onViewAudit }) {
|
||||
return (
|
||||
<div
|
||||
className="card"
|
||||
|
|
@ -156,6 +156,14 @@ function DocumentRow({ doc, onPublish, onArchive, onEdit, onViewAudit }) {
|
|||
<Archive size={13} />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
style={{ padding: '4px 10px', fontSize: '0.78rem' }}
|
||||
onClick={() => onCopy(doc)}
|
||||
title="Als neuen Entwurf kopieren"
|
||||
>
|
||||
<Copy size={13} />
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
style={{ padding: '4px 10px', fontSize: '0.78rem' }}
|
||||
|
|
@ -372,6 +380,15 @@ export default function AdminLegalDocumentsPage() {
|
|||
}
|
||||
}
|
||||
|
||||
const handleCopy = async (doc) => {
|
||||
try {
|
||||
await api.copyLegalDocumentAsDraft(doc.id)
|
||||
load()
|
||||
} catch (e) {
|
||||
alert('Fehler: ' + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
const handleEdit = (doc) => {
|
||||
setEditDoc(doc)
|
||||
setShowForm(true)
|
||||
|
|
@ -492,6 +509,7 @@ export default function AdminLegalDocumentsPage() {
|
|||
onPublish={handlePublish}
|
||||
onArchive={handleArchive}
|
||||
onEdit={handleEdit}
|
||||
onCopy={handleCopy}
|
||||
onViewAudit={handleViewAudit}
|
||||
/>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -1532,6 +1532,8 @@ export const api = {
|
|||
}),
|
||||
archiveLegalDocument: (id) =>
|
||||
request(`/api/admin/legal-documents/${id}/archive`, { method: 'POST' }),
|
||||
copyLegalDocumentAsDraft: (id) =>
|
||||
request(`/api/admin/legal-documents/${id}/copy-as-draft`, { method: 'POST' }),
|
||||
getLegalDocumentAudit: (id) =>
|
||||
request(`/api/admin/legal-documents/${id}/audit`),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
// Shinkan Jinkendo Frontend Version
|
||||
|
||||
export const APP_VERSION = "0.8.71"
|
||||
export const APP_VERSION = "0.8.72"
|
||||
export const BUILD_DATE = "2026-05-10"
|
||||
|
||||
export const PAGE_VERSIONS = {
|
||||
LoginPage: "1.0.2",
|
||||
LegalPage: "1.1.0",
|
||||
SettingsLegalPage: "1.0.0",
|
||||
AdminLegalDocumentsPage: "1.0.0",
|
||||
AdminLegalDocumentsPage: "1.1.0",
|
||||
Dashboard: "1.0.0",
|
||||
AccountSettingsPage: "1.0.1",
|
||||
ExercisesPage: "1.5.0", // Fokus +/- Regeln, nur ohne Fokusbereich; Filterprefs
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user