From 9a24ea29e19437e30b979ffba2befa2cc54f2eaa Mon Sep 17 00:00:00 2001 From: patrick Date: Tue, 11 Aug 2026 21:27:43 +0200 Subject: [PATCH] =?UTF-8?q?FDN-01:=20repository=20&=20projektger=C3=BCst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Git-Repository für bestehenden archivdms-Code initialisiert, Branch-/Commit-Konvention (feature/--Branches, Ticket-Prefix in Commit-Nachricht) etabliert. --- .../project_ollama_integration_plan.md | 22 + .claude/agent-memory/ocr-specialist/MEMORY.md | 4 + ...ect_deskew_border_trick_tested_negative.md | 16 + ...skew_disable_for_photos_tested_negative.md | 24 + .../project_deskew_preprocessing.md | 18 + ...title_heuristic_and_osd_zero_rotate_gap.md | 14 + .../retention-compliance/MEMORY.md | 1 + .../project_gobd_verfahrensdokumentation.md | 52 + .claude/agents/DEVLOG.md | 68 + .claude/agents/archivdms-architect.md | 37 + .claude/agents/backend-dev.md | 78 + .claude/agents/code-review.md | 51 + .claude/agents/db-migrator.md | 52 + .claude/agents/devops-deploy.md | 76 + .claude/agents/frontend-dev.md | 68 + .claude/agents/manticore-performance.md | 53 + .claude/agents/ocr-specialist.md | 53 + .claude/agents/retention-compliance.md | 69 + .claude/agents/retention-dms-vergleich.md | 45 + .claude/skills/devops-deploy/SKILL.md | 76 + .eslintrc.json | 3 + .gitea/workflows/ci.yml | 122 + .gitignore | 7 + DEVLOG.md | 6062 +++++++++++++++++ Makefile | 18 + cmd/archivdms/cmd_classify_retrain.go | 189 + cmd/archivdms/cmd_documents_reprocess_all.go | 249 + cmd/archivdms/cmd_reindex.go | 145 + cmd/archivdms/cmd_reminders_notify.go | 150 + cmd/archivdms/cmd_retention_apply.go | 113 + cmd/archivdms/main.go | 365 + components.json | 17 + config/config.go | 415 ++ config/config.yml.example | 80 + deploy/cron.d/archivdms-classify-retrain | 14 + deploy/cron.d/archivdms-reminders | 13 + dms-featureliste-prompt.md | 87 + features/PROJ-1-wiedervorlage.md | 77 + features/README.md | 11 + go.mod | 23 + install.sh | 552 ++ internal/api/accounting_handlers.go | 391 ++ internal/api/akte_handlers.go | 280 + internal/api/audit_handlers.go | 51 + internal/api/auth_handlers.go | 99 + .../api/classification_template_handlers.go | 404 ++ internal/api/compliance_handlers.go | 535 ++ internal/api/custom_field_handlers.go | 320 + internal/api/dashboard_handlers.go | 29 + internal/api/date_extraction.go | 216 + internal/api/document_bulk_export_handlers.go | 426 ++ internal/api/document_export_handlers.go | 265 + internal/api/document_handlers.go | 1675 +++++ internal/api/document_note_handlers.go | 111 + internal/api/ldap_handlers.go | 168 + internal/api/metadata_suggestion_handlers.go | 154 + internal/api/ocr_word_handlers.go | 75 + internal/api/ollama_config_handlers.go | 134 + internal/api/permission_handlers.go | 458 ++ internal/api/processing_job_handlers.go | 122 + internal/api/public_share_handlers.go | 281 + internal/api/reminder_handlers.go | 171 + internal/api/retention_rule_handlers.go | 247 + internal/api/saved_view_handlers.go | 140 + internal/api/search_handlers.go | 122 + internal/api/server.go | 554 ++ internal/api/sftp_handlers.go | 116 + internal/api/share_handlers.go | 159 + internal/api/taxonomy_handlers.go | 291 + internal/api/tenant_handlers.go | 50 + internal/api/tenant_settings_handlers.go | 233 + internal/api/trash_handlers.go | 237 + internal/api/user_handlers.go | 129 + internal/api/workflow_handlers.go | 327 + internal/audit/audit.go | 507 ++ internal/auth/auth.go | 380 ++ internal/auth/ratelimit.go | 70 + internal/barcode/barcode.go | 64 + internal/classifier/naivebayes.go | 617 ++ internal/cryptutil/secretbox.go | 73 + internal/dateformat/dateformat.go | 61 + internal/index/index.go | 94 + internal/index/manticore.go | 319 + internal/jobqueue/jobqueue.go | 258 + internal/ldapauth/ldapauth.go | 239 + internal/ldapstore/ldapstore.go | 263 + internal/llm/ollama.go | 166 + internal/mailer/mailer.go | 162 + internal/mailer/templates.go | 20 + internal/matching/matching.go | 219 + internal/ocr/convert.go | 230 + internal/ocr/coords.go | 359 + internal/ocr/exif.go | 224 + internal/ocr/ocr.go | 1060 +++ internal/ocr/scripts/hough_deskew.py | 134 + internal/pagesplit/pagesplit.go | 514 ++ internal/sftpserver/server.go | 398 ++ internal/sftpserver/tenantfs.go | 133 + internal/storage/accounting_api_keys.go | 173 + internal/storage/accounting_pull.go | 218 + internal/storage/akten.go | 230 + internal/storage/classification_templates.go | 453 ++ .../storage/classification_templates_apply.go | 306 + .../storage/classification_templates_title.go | 198 + internal/storage/compliance.go | 74 + internal/storage/custom_fields.go | 646 ++ internal/storage/dashboard.go | 114 + internal/storage/document_date.go | 163 + internal/storage/document_notes.go | 132 + internal/storage/documents.go | 588 ++ internal/storage/index_sync.go | 217 + internal/storage/metadata_suggestions.go | 320 + .../metadata_suggestions_naivebayes.go | 128 + .../storage/metadata_suggestions_ollama.go | 215 + internal/storage/migrations/001_initial.sql | 60 + internal/storage/migrations/002_reminders.sql | 17 + .../migrations/003_documents_unique_hash.sql | 8 + .../migrations/004_sftp_credentials.sql | 17 + internal/storage/migrations/005_taxonomy.sql | 68 + .../storage/migrations/006_custom_fields.sql | 44 + internal/storage/migrations/007_trash.sql | 36 + .../storage/migrations/008_permissions.sql | 78 + internal/storage/migrations/009_shares.sql | 53 + .../storage/migrations/010_search_index.sql | 42 + .../011_classification_templates.sql | 52 + .../migrations/012_metadata_suggestions.sql | 33 + internal/storage/migrations/012_workflows.sql | 57 + .../storage/migrations/013_document_notes.sql | 21 + .../storage/migrations/014_saved_views.sql | 25 + .../015_tenant_scan_title_format.sql | 20 + .../016_tenant_scan_title_prefix.sql | 19 + .../migrations/017_tenant_ollama_config.sql | 26 + internal/storage/migrations/018_akten.sql | 31 + .../storage/migrations/019_document_date.sql | 23 + .../storage/migrations/020_ml_classifier.sql | 55 + .../storage/migrations/021_title_template.sql | 31 + .../migrations/022_retention_rules.sql | 53 + .../migrations/023_processing_jobs.sql | 59 + internal/storage/migrations/024_ocr_words.sql | 46 + .../migrations/025_document_date_score.sql | 31 + .../migrations/026_accounting_api_keys.sql | 46 + internal/storage/migrations/README.md | 42 + internal/storage/ml_classifier.go | 64 + internal/storage/ml_classifier_train.go | 59 + internal/storage/ocr_words.go | 160 + internal/storage/ollama_config.go | 122 + internal/storage/permissions.go | 721 ++ internal/storage/processing_jobs.go | 401 ++ internal/storage/reminders.go | 170 + internal/storage/retention_rules.go | 533 ++ internal/storage/saved_views.go | 158 + internal/storage/search.go | 115 + internal/storage/sftp_credentials.go | 155 + internal/storage/shares.go | 389 ++ internal/storage/storage.go | 122 + internal/storage/taxonomy.go | 387 ++ internal/storage/trash.go | 443 ++ internal/storage/workflows.go | 1028 +++ internal/tenantstore/store.go | 217 + internal/thumbnail/thumbnail.go | 160 + internal/userstore/userstore.go | 442 ++ middleware.ts | 52 + next-env.d.ts | 5 + next.config.ts | 28 + package.json | 53 + postcss.config.mjs | 9 + src/app/(app)/akten/[id]/page.tsx | 53 + src/app/(app)/akten/page.tsx | 39 + src/app/(app)/documents/[id]/page.tsx | 75 + src/app/(app)/documents/loading.tsx | 15 + src/app/(app)/documents/page.tsx | 66 + src/app/(app)/layout.tsx | 31 + src/app/(app)/loading.tsx | 20 + src/app/(app)/page.tsx | 220 + src/app/(app)/reminders/actions.ts | 25 + src/app/(app)/reminders/loading.tsx | 15 + src/app/(app)/reminders/page.tsx | 52 + src/app/(app)/scan/page.tsx | 16 + src/app/(app)/search/loading.tsx | 19 + src/app/(app)/search/page.tsx | 170 + .../(app)/settings/accounting-keys/page.tsx | 51 + .../classification-templates/page.tsx | 69 + .../(app)/settings/correspondents/page.tsx | 22 + src/app/(app)/settings/custom-fields/page.tsx | 50 + .../(app)/settings/document-types/page.tsx | 48 + src/app/(app)/settings/ollama-config/page.tsx | 66 + src/app/(app)/settings/page.tsx | 222 + .../(app)/settings/permission-groups/page.tsx | 56 + .../(app)/settings/retention-rules/page.tsx | 73 + src/app/(app)/settings/shares/page.tsx | 52 + src/app/(app)/settings/tags/page.tsx | 42 + .../(app)/settings/tenant-settings/page.tsx | 66 + src/app/(app)/settings/tenants/page.tsx | 46 + src/app/(app)/settings/users/page.tsx | 60 + src/app/(app)/trash/loading.tsx | 15 + src/app/(app)/trash/page.tsx | 56 + src/app/globals.css | 69 + src/app/layout.tsx | 33 + src/app/login/page.tsx | 27 + src/app/public/share/[token]/page.tsx | 143 + src/components/DEVLOG.md | 596 ++ .../accounting/AccountingApiKeyManager.tsx | 322 + src/components/akten/AkteDetail.tsx | 361 + src/components/akten/AkteStatusBadge.tsx | 22 + src/components/akten/AktenTable.tsx | 383 ++ src/components/auth/LoginForm.tsx | 123 + .../ApplyTemplateDialog.tsx | 354 + .../ClassificationTemplateManager.tsx | 670 ++ .../ProcedureDocumentationDownload.tsx | 49 + .../custom-fields/CustomFieldManager.tsx | 455 ++ .../custom-fields/DocumentFieldsDialog.tsx | 314 + .../DocumentTypeFieldsSection.tsx | 215 + .../documents/DocumentDetailsTab.tsx | 897 +++ .../documents/DocumentImagePreview.tsx | 301 + src/components/documents/DocumentNotesTab.tsx | 166 + src/components/documents/DocumentPreview.tsx | 460 ++ .../documents/DocumentUploadForm.tsx | 129 + src/components/documents/DocumentsTable.tsx | 555 ++ .../documents/DocumentsWidthLayout.tsx | 81 + .../documents/ProcessingStatusBadge.tsx | 182 + .../permissions/DocumentGrantsDialog.tsx | 231 + .../permissions/DocumentTypeGrantsSection.tsx | 38 + src/components/permissions/GrantsEditor.tsx | 234 + .../permissions/PermissionGroupManager.tsx | 370 + .../permissions/TagGrantsSection.tsx | 36 + .../reminders/CreateReminderButton.tsx | 111 + src/components/reminders/ReminderBadge.tsx | 25 + src/components/reminders/ReminderList.tsx | 65 + src/components/reminders/RemindersTable.tsx | 110 + .../retention-rules/RetentionRuleManager.tsx | 493 ++ src/components/scan/ImageCropper.tsx | 298 + src/components/scan/MobileScanCapture.tsx | 193 + src/components/search/SavedViewsMenu.tsx | 188 + src/components/search/SearchResults.tsx | 193 + src/components/settings/OllamaConfigForm.tsx | 230 + .../settings/ScanTitleDateFormatForm.tsx | 214 + src/components/shares/ShareDialog.tsx | 263 + src/components/shares/ShareStatusBadge.tsx | 42 + src/components/shares/TenantSharesManager.tsx | 100 + src/components/shell/AppSidebar.tsx | 87 + src/components/shell/CommandPalette.tsx | 81 + src/components/shell/SearchBar.tsx | 45 + src/components/shell/TopBar.tsx | 77 + src/components/taxonomy/TaxonomyManager.tsx | 254 + src/components/tenants/TenantManager.tsx | 131 + src/components/theme-provider.tsx | 16 + src/components/trash/TrashManager.tsx | 364 + src/components/ui/avatar.tsx | 50 + src/components/ui/badge.tsx | 36 + src/components/ui/button.tsx | 56 + src/components/ui/calendar.tsx | 70 + src/components/ui/card.tsx | 71 + src/components/ui/command.tsx | 163 + src/components/ui/dialog.tsx | 122 + src/components/ui/dropdown-menu.tsx | 194 + src/components/ui/input.tsx | 22 + src/components/ui/label.tsx | 26 + src/components/ui/popover.tsx | 31 + src/components/ui/progress.tsx | 28 + src/components/ui/sheet.tsx | 137 + src/components/ui/sidebar.tsx | 237 + src/components/ui/skeleton.tsx | 15 + src/components/ui/sonner.tsx | 31 + src/components/ui/table.tsx | 117 + src/components/ui/tabs.tsx | 55 + src/components/ui/textarea.tsx | 22 + src/components/users/UserManager.tsx | 485 ++ src/hooks/use-mobile.tsx | 19 + src/lib/api.ts | 1577 +++++ src/lib/session.ts | 51 + src/lib/utils.ts | 6 + tailwind.config.ts | 77 + tsconfig.json | 41 + update.sh | 379 ++ 274 files changed, 53708 insertions(+) create mode 100644 .claude/agent-memory/archivdms-architect/project_ollama_integration_plan.md create mode 100644 .claude/agent-memory/ocr-specialist/MEMORY.md create mode 100644 .claude/agent-memory/ocr-specialist/project_deskew_border_trick_tested_negative.md create mode 100644 .claude/agent-memory/ocr-specialist/project_deskew_disable_for_photos_tested_negative.md create mode 100644 .claude/agent-memory/ocr-specialist/project_deskew_preprocessing.md create mode 100644 .claude/agent-memory/ocr-specialist/project_title_heuristic_and_osd_zero_rotate_gap.md create mode 100644 .claude/agent-memory/retention-compliance/MEMORY.md create mode 100644 .claude/agent-memory/retention-compliance/project_gobd_verfahrensdokumentation.md create mode 100644 .claude/agents/DEVLOG.md create mode 100644 .claude/agents/archivdms-architect.md create mode 100644 .claude/agents/backend-dev.md create mode 100644 .claude/agents/code-review.md create mode 100644 .claude/agents/db-migrator.md create mode 100644 .claude/agents/devops-deploy.md create mode 100644 .claude/agents/frontend-dev.md create mode 100644 .claude/agents/manticore-performance.md create mode 100644 .claude/agents/ocr-specialist.md create mode 100644 .claude/agents/retention-compliance.md create mode 100644 .claude/agents/retention-dms-vergleich.md create mode 100644 .claude/skills/devops-deploy/SKILL.md create mode 100644 .eslintrc.json create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitignore create mode 100644 DEVLOG.md create mode 100644 Makefile create mode 100644 cmd/archivdms/cmd_classify_retrain.go create mode 100644 cmd/archivdms/cmd_documents_reprocess_all.go create mode 100644 cmd/archivdms/cmd_reindex.go create mode 100644 cmd/archivdms/cmd_reminders_notify.go create mode 100644 cmd/archivdms/cmd_retention_apply.go create mode 100644 cmd/archivdms/main.go create mode 100644 components.json create mode 100644 config/config.go create mode 100644 config/config.yml.example create mode 100644 deploy/cron.d/archivdms-classify-retrain create mode 100644 deploy/cron.d/archivdms-reminders create mode 100644 dms-featureliste-prompt.md create mode 100644 features/PROJ-1-wiedervorlage.md create mode 100644 features/README.md create mode 100644 go.mod create mode 100644 install.sh create mode 100644 internal/api/accounting_handlers.go create mode 100644 internal/api/akte_handlers.go create mode 100644 internal/api/audit_handlers.go create mode 100644 internal/api/auth_handlers.go create mode 100644 internal/api/classification_template_handlers.go create mode 100644 internal/api/compliance_handlers.go create mode 100644 internal/api/custom_field_handlers.go create mode 100644 internal/api/dashboard_handlers.go create mode 100644 internal/api/date_extraction.go create mode 100644 internal/api/document_bulk_export_handlers.go create mode 100644 internal/api/document_export_handlers.go create mode 100644 internal/api/document_handlers.go create mode 100644 internal/api/document_note_handlers.go create mode 100644 internal/api/ldap_handlers.go create mode 100644 internal/api/metadata_suggestion_handlers.go create mode 100644 internal/api/ocr_word_handlers.go create mode 100644 internal/api/ollama_config_handlers.go create mode 100644 internal/api/permission_handlers.go create mode 100644 internal/api/processing_job_handlers.go create mode 100644 internal/api/public_share_handlers.go create mode 100644 internal/api/reminder_handlers.go create mode 100644 internal/api/retention_rule_handlers.go create mode 100644 internal/api/saved_view_handlers.go create mode 100644 internal/api/search_handlers.go create mode 100644 internal/api/server.go create mode 100644 internal/api/sftp_handlers.go create mode 100644 internal/api/share_handlers.go create mode 100644 internal/api/taxonomy_handlers.go create mode 100644 internal/api/tenant_handlers.go create mode 100644 internal/api/tenant_settings_handlers.go create mode 100644 internal/api/trash_handlers.go create mode 100644 internal/api/user_handlers.go create mode 100644 internal/api/workflow_handlers.go create mode 100644 internal/audit/audit.go create mode 100644 internal/auth/auth.go create mode 100644 internal/auth/ratelimit.go create mode 100644 internal/barcode/barcode.go create mode 100644 internal/classifier/naivebayes.go create mode 100644 internal/cryptutil/secretbox.go create mode 100644 internal/dateformat/dateformat.go create mode 100644 internal/index/index.go create mode 100644 internal/index/manticore.go create mode 100644 internal/jobqueue/jobqueue.go create mode 100644 internal/ldapauth/ldapauth.go create mode 100644 internal/ldapstore/ldapstore.go create mode 100644 internal/llm/ollama.go create mode 100644 internal/mailer/mailer.go create mode 100644 internal/mailer/templates.go create mode 100644 internal/matching/matching.go create mode 100644 internal/ocr/convert.go create mode 100644 internal/ocr/coords.go create mode 100644 internal/ocr/exif.go create mode 100644 internal/ocr/ocr.go create mode 100644 internal/ocr/scripts/hough_deskew.py create mode 100644 internal/pagesplit/pagesplit.go create mode 100644 internal/sftpserver/server.go create mode 100644 internal/sftpserver/tenantfs.go create mode 100644 internal/storage/accounting_api_keys.go create mode 100644 internal/storage/accounting_pull.go create mode 100644 internal/storage/akten.go create mode 100644 internal/storage/classification_templates.go create mode 100644 internal/storage/classification_templates_apply.go create mode 100644 internal/storage/classification_templates_title.go create mode 100644 internal/storage/compliance.go create mode 100644 internal/storage/custom_fields.go create mode 100644 internal/storage/dashboard.go create mode 100644 internal/storage/document_date.go create mode 100644 internal/storage/document_notes.go create mode 100644 internal/storage/documents.go create mode 100644 internal/storage/index_sync.go create mode 100644 internal/storage/metadata_suggestions.go create mode 100644 internal/storage/metadata_suggestions_naivebayes.go create mode 100644 internal/storage/metadata_suggestions_ollama.go create mode 100644 internal/storage/migrations/001_initial.sql create mode 100644 internal/storage/migrations/002_reminders.sql create mode 100644 internal/storage/migrations/003_documents_unique_hash.sql create mode 100644 internal/storage/migrations/004_sftp_credentials.sql create mode 100644 internal/storage/migrations/005_taxonomy.sql create mode 100644 internal/storage/migrations/006_custom_fields.sql create mode 100644 internal/storage/migrations/007_trash.sql create mode 100644 internal/storage/migrations/008_permissions.sql create mode 100644 internal/storage/migrations/009_shares.sql create mode 100644 internal/storage/migrations/010_search_index.sql create mode 100644 internal/storage/migrations/011_classification_templates.sql create mode 100644 internal/storage/migrations/012_metadata_suggestions.sql create mode 100644 internal/storage/migrations/012_workflows.sql create mode 100644 internal/storage/migrations/013_document_notes.sql create mode 100644 internal/storage/migrations/014_saved_views.sql create mode 100644 internal/storage/migrations/015_tenant_scan_title_format.sql create mode 100644 internal/storage/migrations/016_tenant_scan_title_prefix.sql create mode 100644 internal/storage/migrations/017_tenant_ollama_config.sql create mode 100644 internal/storage/migrations/018_akten.sql create mode 100644 internal/storage/migrations/019_document_date.sql create mode 100644 internal/storage/migrations/020_ml_classifier.sql create mode 100644 internal/storage/migrations/021_title_template.sql create mode 100644 internal/storage/migrations/022_retention_rules.sql create mode 100644 internal/storage/migrations/023_processing_jobs.sql create mode 100644 internal/storage/migrations/024_ocr_words.sql create mode 100644 internal/storage/migrations/025_document_date_score.sql create mode 100644 internal/storage/migrations/026_accounting_api_keys.sql create mode 100644 internal/storage/migrations/README.md create mode 100644 internal/storage/ml_classifier.go create mode 100644 internal/storage/ml_classifier_train.go create mode 100644 internal/storage/ocr_words.go create mode 100644 internal/storage/ollama_config.go create mode 100644 internal/storage/permissions.go create mode 100644 internal/storage/processing_jobs.go create mode 100644 internal/storage/reminders.go create mode 100644 internal/storage/retention_rules.go create mode 100644 internal/storage/saved_views.go create mode 100644 internal/storage/search.go create mode 100644 internal/storage/sftp_credentials.go create mode 100644 internal/storage/shares.go create mode 100644 internal/storage/storage.go create mode 100644 internal/storage/taxonomy.go create mode 100644 internal/storage/trash.go create mode 100644 internal/storage/workflows.go create mode 100644 internal/tenantstore/store.go create mode 100644 internal/thumbnail/thumbnail.go create mode 100644 internal/userstore/userstore.go create mode 100644 middleware.ts create mode 100644 next-env.d.ts create mode 100644 next.config.ts create mode 100644 package.json create mode 100644 postcss.config.mjs create mode 100644 src/app/(app)/akten/[id]/page.tsx create mode 100644 src/app/(app)/akten/page.tsx create mode 100644 src/app/(app)/documents/[id]/page.tsx create mode 100644 src/app/(app)/documents/loading.tsx create mode 100644 src/app/(app)/documents/page.tsx create mode 100644 src/app/(app)/layout.tsx create mode 100644 src/app/(app)/loading.tsx create mode 100644 src/app/(app)/page.tsx create mode 100644 src/app/(app)/reminders/actions.ts create mode 100644 src/app/(app)/reminders/loading.tsx create mode 100644 src/app/(app)/reminders/page.tsx create mode 100644 src/app/(app)/scan/page.tsx create mode 100644 src/app/(app)/search/loading.tsx create mode 100644 src/app/(app)/search/page.tsx create mode 100644 src/app/(app)/settings/accounting-keys/page.tsx create mode 100644 src/app/(app)/settings/classification-templates/page.tsx create mode 100644 src/app/(app)/settings/correspondents/page.tsx create mode 100644 src/app/(app)/settings/custom-fields/page.tsx create mode 100644 src/app/(app)/settings/document-types/page.tsx create mode 100644 src/app/(app)/settings/ollama-config/page.tsx create mode 100644 src/app/(app)/settings/page.tsx create mode 100644 src/app/(app)/settings/permission-groups/page.tsx create mode 100644 src/app/(app)/settings/retention-rules/page.tsx create mode 100644 src/app/(app)/settings/shares/page.tsx create mode 100644 src/app/(app)/settings/tags/page.tsx create mode 100644 src/app/(app)/settings/tenant-settings/page.tsx create mode 100644 src/app/(app)/settings/tenants/page.tsx create mode 100644 src/app/(app)/settings/users/page.tsx create mode 100644 src/app/(app)/trash/loading.tsx create mode 100644 src/app/(app)/trash/page.tsx create mode 100644 src/app/globals.css create mode 100644 src/app/layout.tsx create mode 100644 src/app/login/page.tsx create mode 100644 src/app/public/share/[token]/page.tsx create mode 100644 src/components/DEVLOG.md create mode 100644 src/components/accounting/AccountingApiKeyManager.tsx create mode 100644 src/components/akten/AkteDetail.tsx create mode 100644 src/components/akten/AkteStatusBadge.tsx create mode 100644 src/components/akten/AktenTable.tsx create mode 100644 src/components/auth/LoginForm.tsx create mode 100644 src/components/classification-templates/ApplyTemplateDialog.tsx create mode 100644 src/components/classification-templates/ClassificationTemplateManager.tsx create mode 100644 src/components/compliance/ProcedureDocumentationDownload.tsx create mode 100644 src/components/custom-fields/CustomFieldManager.tsx create mode 100644 src/components/custom-fields/DocumentFieldsDialog.tsx create mode 100644 src/components/custom-fields/DocumentTypeFieldsSection.tsx create mode 100644 src/components/documents/DocumentDetailsTab.tsx create mode 100644 src/components/documents/DocumentImagePreview.tsx create mode 100644 src/components/documents/DocumentNotesTab.tsx create mode 100644 src/components/documents/DocumentPreview.tsx create mode 100644 src/components/documents/DocumentUploadForm.tsx create mode 100644 src/components/documents/DocumentsTable.tsx create mode 100644 src/components/documents/DocumentsWidthLayout.tsx create mode 100644 src/components/documents/ProcessingStatusBadge.tsx create mode 100644 src/components/permissions/DocumentGrantsDialog.tsx create mode 100644 src/components/permissions/DocumentTypeGrantsSection.tsx create mode 100644 src/components/permissions/GrantsEditor.tsx create mode 100644 src/components/permissions/PermissionGroupManager.tsx create mode 100644 src/components/permissions/TagGrantsSection.tsx create mode 100644 src/components/reminders/CreateReminderButton.tsx create mode 100644 src/components/reminders/ReminderBadge.tsx create mode 100644 src/components/reminders/ReminderList.tsx create mode 100644 src/components/reminders/RemindersTable.tsx create mode 100644 src/components/retention-rules/RetentionRuleManager.tsx create mode 100644 src/components/scan/ImageCropper.tsx create mode 100644 src/components/scan/MobileScanCapture.tsx create mode 100644 src/components/search/SavedViewsMenu.tsx create mode 100644 src/components/search/SearchResults.tsx create mode 100644 src/components/settings/OllamaConfigForm.tsx create mode 100644 src/components/settings/ScanTitleDateFormatForm.tsx create mode 100644 src/components/shares/ShareDialog.tsx create mode 100644 src/components/shares/ShareStatusBadge.tsx create mode 100644 src/components/shares/TenantSharesManager.tsx create mode 100644 src/components/shell/AppSidebar.tsx create mode 100644 src/components/shell/CommandPalette.tsx create mode 100644 src/components/shell/SearchBar.tsx create mode 100644 src/components/shell/TopBar.tsx create mode 100644 src/components/taxonomy/TaxonomyManager.tsx create mode 100644 src/components/tenants/TenantManager.tsx create mode 100644 src/components/theme-provider.tsx create mode 100644 src/components/trash/TrashManager.tsx create mode 100644 src/components/ui/avatar.tsx create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/calendar.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/command.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/dropdown-menu.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/components/ui/progress.tsx create mode 100644 src/components/ui/sheet.tsx create mode 100644 src/components/ui/sidebar.tsx create mode 100644 src/components/ui/skeleton.tsx create mode 100644 src/components/ui/sonner.tsx create mode 100644 src/components/ui/table.tsx create mode 100644 src/components/ui/tabs.tsx create mode 100644 src/components/ui/textarea.tsx create mode 100644 src/components/users/UserManager.tsx create mode 100644 src/hooks/use-mobile.tsx create mode 100644 src/lib/api.ts create mode 100644 src/lib/session.ts create mode 100644 src/lib/utils.ts create mode 100644 tailwind.config.ts create mode 100644 tsconfig.json create mode 100644 update.sh diff --git a/.claude/agent-memory/archivdms-architect/project_ollama_integration_plan.md b/.claude/agent-memory/archivdms-architect/project_ollama_integration_plan.md new file mode 100644 index 0000000..baa2048 --- /dev/null +++ b/.claude/agent-memory/archivdms-architect/project_ollama_integration_plan.md @@ -0,0 +1,22 @@ +--- +name: project-ollama-integration-plan +description: Plan für lokale Ollama-Anbindung (Metadaten-Vorschläge + OCR-Textkorrektur) in archivdms, Stand 2026-07-16 +metadata: + type: project +--- + +Plan verabschiedet für Ollama-Integration (kein Code, nur Architektur), Ziel: lokales LLM für (1) Metadaten-Vorschläge als dritter Provider neben heuristic/anthropic, (2) nachträgliche OCR-Textkorrektur. + +Kernentscheidungen: +- Modell: `qwen2.5:1.5b-instruct-q4_K_M` (~1-1.2GB RAM), Eskalation auf 3B nur falls Qualität nicht reicht. Server hat nur 4GB RAM/4 Kerne/keine GPU. +- systemd-Hardening: MemoryMax=2200M, CPUQuota=250%, OOMScoreAdjust=500, bindet nur an 127.0.0.1:11434. +- OCR-Korrektur läuft NICHT automatisch im Upload-Pipeline (Ressourcenrisiko), sondern über manuellen Endpoint `POST /api/documents/{id}/correct-ocr-text`, analog zu bestehendem `/reprocess`-Muster. +- GoBD: Original-OCR-Text (`ocr_text`) wird nie überschrieben, Korrektur landet in neuem Feld `ocr_text_corrected`, Audit-Log-Pflicht pro Korrekturlauf. +- Provider-Fehlerverhalten: bei Ollama nicht erreichbar → Fehler an Frontend, KEIN stiller Fallback auf heuristic (Nachvollziehbarkeit, welcher Provider geantwortet hat). +- Config-Pattern: `llm.ollama.enabled/base_url/model/timeout_seconds` in config.yml, no-op wenn disabled — exakt wie `index.manticore_dsn`-Pattern. +- Pro-Tenant-Schalter bewusst NICHT gebaut (Tag 1) — Ollama läuft als ein Prozess pro Server, kein echtes Isolationsmodell dahinter, wäre nur Schein-Kontrolle. Erst bei echter Mandantentrennung mit unterschiedlichen Compliance-Anforderungen nachrüsten. +- Reihenfolge: 1) Ollama-Server-Setup (devops-deploy), 2) Metadaten-Provider (backend-dev), 3) OCR-Korrektur-Endpoint (baut auf Client aus Schritt 2 auf, plus Migration für ocr_text_corrected-Spalte). + +**Why:** User hat sich bewusst für kleines lokales Modell trotz 4GB-RAM-Warnung entschieden (GoBD/Datenschutz — Belege dürfen Server nicht verlassen), Provider-Abstraktion (`metadata_suggestions.provider`-Spalte) existierte schon konzeptionell aus früherer Session, nur `heuristic` war je gebaut. + +**How to apply:** Wenn Umsetzung (backend-dev/devops-deploy) ansteht, diesen Plan als Grundlage nehmen, nicht neu verhandeln, außer der User ändert explizit etwas. Verwandt: [[project_archivdms_status]], [[project_nil_slice_json_pattern]]. diff --git a/.claude/agent-memory/ocr-specialist/MEMORY.md b/.claude/agent-memory/ocr-specialist/MEMORY.md new file mode 100644 index 0000000..a0c44ff --- /dev/null +++ b/.claude/agent-memory/ocr-specialist/MEMORY.md @@ -0,0 +1,4 @@ +- [Deskew-Vorverarbeitung](project_deskew_preprocessing.md) — ImageMagick `-deskew 40%` vor OSD-Rotation, Server-Paket `imagemagick` (nicht nur `-common`) +- [Titel-Heuristik + OSD Rotate:0-Lücke](project_title_heuristic_and_osd_zero_rotate_gap.md) — Alnum-Ratio-Filter statt "längste Zeile", rotateForOSD prüft Konfidenz nicht bei degrees==0 +- [Deskew-Border-Trick negativ getestet](project_deskew_border_trick_tested_negative.md) — weißer Rand vor -deskew half nicht (Artefakt-Winkel), verworfen, nicht wieder vorschlagen +- [Deskew-Deaktivierung für Fotos negativ getestet](project_deskew_disable_for_photos_tested_negative.md) — gemischt (Doc7 stark schlechter), verworfen; runTesseract() ist einziger Aufrufpfad, keine Foto/PDF-Pipeline-Trennung vorhanden diff --git a/.claude/agent-memory/ocr-specialist/project_deskew_border_trick_tested_negative.md b/.claude/agent-memory/ocr-specialist/project_deskew_border_trick_tested_negative.md new file mode 100644 index 0000000..dccf8d6 --- /dev/null +++ b/.claude/agent-memory/ocr-specialist/project_deskew_border_trick_tested_negative.md @@ -0,0 +1,16 @@ +--- +name: deskew-border-trick-tested-negative +description: Weißer Rand vor -deskew (bordercolor/border+shave) getestet gegen eng zugeschnittene Handyfotos — hat NICHT geholfen, verworfen +metadata: + type: project +--- + +Getestet am 2026-07-18 (Tenant 3, Dokumente 3/4/5/7): `convert -bordercolor white -border 50x50 -deskew 40% -shave 50x50` als Fix für das Problem, dass ImageMagicks `-deskew` bei eng zugeschnittenen Handyfotos (kein sichtbarer Hintergrundrand) kein Schräglagenwinkel erkennt. + +Ergebnis: negativ. Der gemeldete `angle_deg` sprang von exakt `0` (ohne Border) auf einen konstanten Wert `~0.00699...` — bei ALLEN vier Testdokumenten identisch, obwohl die Bilder unterschiedlich stark verkippt sind. Das ist kein echter erkannter Schräglagenwinkel, sondern ein Artefakt der künstlichen Randgeometrie selbst (ImageMagick misst offenbar die Kante des hinzugefügten Rands, nicht den Bildinhalt). OCR-Textqualität blieb unverändert schlecht/durchwachsen (z.B. "o@rvice-Stat ic" statt "Service-Station" bei Dok 5). + +Code-Änderung in `deskewImage()` (internal/ocr/ocr.go) wurde verworfen, Server zurück auf Original-Deskew ohne Border-Trick deployt (Redeploy 2026-07-18 bestätigt: Backend+Frontend laufen). + +**Why:** Bestätigt die ursprüngliche Diagnose aus [[project_ocr_inkonsistenz_deskew_osd]] (falls vorhanden) — der Deskew-Ansatz per ImageMagick-Hintergrundkante ist für rand-lose Handyfotos strukturell ungeeignet, auch mit künstlichem Rand. + +**How to apply:** Bei künftigen Anfragen zu Schräglagenerkennung bei Handyfotos ohne Scan-Rand NICHT wieder den Border-Trick vorschlagen — stattdessen andere Ansätze evaluieren (z.B. Hough-Transform-basierte Texterkennungswinkel, `unpaper`, oder Tesseract-eigene OSD-Rotation als einzige Verlässlichkeitsquelle akzeptieren und Fine-Skew-Korrektur bei diesen Dokumenten aufgeben). diff --git a/.claude/agent-memory/ocr-specialist/project_deskew_disable_for_photos_tested_negative.md b/.claude/agent-memory/ocr-specialist/project_deskew_disable_for_photos_tested_negative.md new file mode 100644 index 0000000..dc0145c --- /dev/null +++ b/.claude/agent-memory/ocr-specialist/project_deskew_disable_for_photos_tested_negative.md @@ -0,0 +1,24 @@ +--- +name: deskew-disable-for-photos-tested-negative +description: A/B-Test "deskewImage komplett weglassen, Tesseract-interne Skew-Korrektur wirken lassen" bei Foto-Uploads getestet — gemischtes Ergebnis, verworfen +metadata: + type: project +--- + +Getestet am 2026-07-18 (Tenant 3, Dokumente 3/4/5/6/7/8/12, `reprocess-all -tenant 3`) auf Architect-Empfehlung: den externen ImageMagick-`deskewImage()`-Schritt in `runTesseract()` (internal/ocr/ocr.go) komplett auslassen und stattdessen nur Tesseracts eigene interne textzeilenbasierte Skew-Korrektur (läuft mit `--psm 1`/OSD-Layoutanalyse automatisch mit) wirken lassen. + +**Wichtiger struktureller Befund:** `runTesseract()` ist der einzige Aufrufpfad für `deskewImage()` und wird sowohl von `ocrImage()` (direkte Foto-Uploads) als auch von `pdfRasterOCR()` (pdftoppm-gerasterte PDF-Seiten) genutzt — es gibt KEINE getrennte Foto- vs. PDF-Pipeline. Eine "nur für Fotos deaktivieren"-Änderung würde also eine neue Unterscheidung am Aufrufort brauchen, die aktuell nicht existiert. + +Ergebnis: gemischt, kein eindeutiger Gewinn. +- Doc 5: 742 → 854 Zeichen (besser ohne Deskew) +- Doc 12: 919 → 975 Zeichen (besser ohne Deskew) +- Doc 3: 930 → 923 Zeichen (~gleich) +- Doc 6, 8: identisch (Deskew griff hier kaum, erkannter Winkel nahe 0) +- Doc 4: 779 → 737 Zeichen (schlechter ohne Deskew) +- **Doc 7: 617 → 413 Zeichen (deutlich schlechter ohne Deskew)** — klarer Ausreißer nach unten, disqualifiziert die Änderung. + +Code-Änderung in `runTesseract()` (deskewImage-Aufruf auskommentiert) wurde verworfen, Server zurück auf Original mit aktivem Deskew deployt (rsync+update.sh 2026-07-18, Backend+Frontend laufen bestätigt), `ocr_text` in DB per erneutem `reprocess-all -tenant 3` wieder auf den Mit-Deskew-Stand gebracht. + +**Why:** Doc 7 als deutlicher Ausreißer nach unten zeigt, dass Tesseracts interne Skew-Korrektur den externen ImageMagick-Deskew nicht zuverlässig ersetzt — bei manchen Dokumenten (v.a. stärker verkippten) ist die externe Vorkorrektur weiterhin nötig, auch wenn sie bei anderen (Doc 5/12) leicht bremst. Kein klares Muster, welche Dokumente von welchem Ansatz profitieren. + +**How to apply:** Bei künftigen Anfragen "Deskew für Fotos deaktivieren" NICHT erneut pauschal vorschlagen — Ergebnis ist dokumentiert negativ/gemischt. Falls die Idee wieder aufkommt, bräuchte es erst eine größere Testdokument-Stichprobe und eine begründete Heuristik (z.B. nur bei erkanntem angle_deg unter einem Schwellwert deaktivieren), nicht ein pauschales Weglassen. Siehe auch [[project_deskew_border_trick_tested_negative]] (verwandter, ebenfalls verworfener Deskew-Test) und [[project_title_heuristic_and_osd_zero_rotate_gap]]. diff --git a/.claude/agent-memory/ocr-specialist/project_deskew_preprocessing.md b/.claude/agent-memory/ocr-specialist/project_deskew_preprocessing.md new file mode 100644 index 0000000..7ce87e5 --- /dev/null +++ b/.claude/agent-memory/ocr-specialist/project_deskew_preprocessing.md @@ -0,0 +1,18 @@ +--- +name: project_deskew_preprocessing +description: Deskew-Vorverarbeitungsschritt (ImageMagick) gegen Schräglage in der OCR-Pipeline, ergänzend zum OSD-90°-Rotationsfix +metadata: + type: project +--- + +Am 2026-07-16 wurde `deskewImage()` in `internal/ocr/ocr.go` ergänzt: `convert -deskew 40% ` läuft in `runTesseract()` VOR der bestehenden OSD-basierten 90°/180°-Rotationskorrektur (`rotateForOSD`). Grund: Tesseract-OSD erkennt nur 90°-Schritte, keine Feinneigung (wenige Grad Schräglage bei Scans/Handyfotos). + +**Server-Paket-Falle:** `imagemagick-7-common` war auf 192.168.1.204 bereits installiert, lieferte aber KEINE `convert`/`magick`-Binary — nur Infrastruktur/Metapaket. Die echte Binary kommt erst mit dem Paket `imagemagick` (zieht `imagemagick-7.q16`, `netpbm`, `libnetpbm11t64` als Abhängigkeiten). Bei zukünftigen "convert nicht gefunden"-Diagnosen zuerst `dpkg -l | grep imagemagick` prüfen, nicht nur `which convert`. + +**Threshold-Wahl:** 40% manuell gegen dms doc ids 2/7/8 verifiziert (deutliche Verbesserung, besonders doc 7). 80% probeweise getestet — überrotierte einen kontrastarmen Beleg (doc 8) und verschlechterte das Ergebnis. Bei neuen Problemfällen mit 40% starten, nur bei Bedarf pro Dokumenttyp anpassen, nicht pauschal erhöhen. + +**Muster:** best-effort wie `rotateForOSD` — eigene `deskewImage()`-Methode mit `(dstPath string, cleanup func(), ok bool)`-Signatur, Fehler/Timeout/fehlende Binary führen zu `ok=false`, Original-Datei wird ohne Deskew weiterverwendet, kein Abbruch der OCR-Pipeline. + +**Why:** Nutzer meldete nach dem OSD-Fix, dass das eigentliche verbleibende Problem Schräglage ist, nicht 90°-Rotation — OSD kann das strukturell nicht lösen. + +**How to apply:** Bei weiteren OCR-Qualitätsproblemen mit schräg liegendem Text zuerst prüfen ob `deskewThreshold` (aktuell 40%, Konstante in ocr.go) für den konkreten Dokumenttyp passt, bevor neue Sidecars (unpaper etc.) vorgeschlagen werden — [[feedback_scope_code_and_deploy_only]] gilt auch hier, keine Übertechnisierung ohne nachgewiesenen Bedarf. diff --git a/.claude/agent-memory/ocr-specialist/project_title_heuristic_and_osd_zero_rotate_gap.md b/.claude/agent-memory/ocr-specialist/project_title_heuristic_and_osd_zero_rotate_gap.md new file mode 100644 index 0000000..0d6b9bf --- /dev/null +++ b/.claude/agent-memory/ocr-specialist/project_title_heuristic_and_osd_zero_rotate_gap.md @@ -0,0 +1,14 @@ +--- +name: project_title_heuristic_and_osd_zero_rotate_gap +description: titleFromOCRText Rauschfilter (Alnum-Ratio) + bekannte Lücke in rotateForOSD bei Rotate:0-Fehlerkennung +metadata: + type: project +--- + +Am 2026-07-16 wurde `titleFromOCRText` (`internal/api/document_handlers.go`) um einen Rauschfilter ergänzt: Kandidatenzeile muss ≥3 Zeichen UND Anteil Buchstaben/Ziffern an Nicht-Leerzeichen ≥75% haben (`isUsableTitleLine`), Scan auf erste 8 Zeilen begrenzt. Verworfene Alternative: "längste Zeile statt erste passende Zeile nehmen" — regressierte bei Tenant-3-Testdokumenten (3,4,5,7) den korrekten Titel "Eni Service-Station" zugunsten falscher langer Zeilen wie "Tankstellen-Nr.: ...". Per Python-Simulation der Go-Logik gegen echte `ocr_text`-Werte verifiziert, bevor Code geändert wurde (kein lokaler `go build` verfügbar in diesem Repo-Setup). + +**Bekannte Lücke — nicht gefixt:** `rotateForOSD` (`internal/ocr/ocr.go` ~Zeile 500) bricht bei `degrees == 0` sofort ab, OHNE die OSD-Konfidenz zu prüfen. Bei Dokument 9 (Tenant 3) meldete OSD `Rotate: 0` mit nur 0,68 Konfidenz (deutlich niedriger als die 5-7 bei den korrekt erkannten Dokumenten) — tatsächlich hätte 90° geholfen (manuell mit `convert -rotate 90` verifiziert, lieferte vereinzelte lesbare Fragmente). Trotzdem NICHT als generischen Fix umgesetzt: das Grundproblem bei Dokument 9 ist massive Bildunschärfe, selbst mit korrekter Rotation blieb der Großteil des Texts unlesbar — ein "bei degrees==0 und niedriger Konfidenz trotzdem probeweise rotieren"-Fix hätte hier nichts gebracht und das Risiko gehabt, gute unrotierte Scans woanders zu verschlechtern. Bei zukünftigen ähnlichen Fällen (OSD meldet Rotate:0 mit auffällig niedriger Konfidenz UND Ergebnis ist unlesbar): zuerst mit `convert -rotate {90,180,270}` + `tesseract --psm 6` von Hand durchprobieren, ob es überhaupt an der Rotation liegt, bevor am Code gedreht wird — reine Bildqualität (Unschärfe) ist nicht softwareseitig reparierbar. + +**Why:** Nutzer wollte robustere Titel-Ableitung ohne Overengineering, und klare Diagnose statt Pseudo-Fix bei technisch nicht behebbaren Dokumenten. + +**How to apply:** [[project_deskew_preprocessing]] ergänzend — bei neuen schlecht lesbaren Dokumenten immer erst Bildqualität/Schärfe von Hand prüfen (`convert -resize 400x400 preview.png` + Beschreibung, da kein Bildschirm verfügbar), bevor an Rotations-/Deskew-Schwellwerten gedreht wird. diff --git a/.claude/agent-memory/retention-compliance/MEMORY.md b/.claude/agent-memory/retention-compliance/MEMORY.md new file mode 100644 index 0000000..f97198d --- /dev/null +++ b/.claude/agent-memory/retention-compliance/MEMORY.md @@ -0,0 +1 @@ +- [GoBD-Verfahrensdokumentation-Export](project_gobd_verfahrensdokumentation.md) — Gliederung geklärt, was automatisch/manuell ableitbar, Konzeptstand 2026-07-30, noch kein Code diff --git a/.claude/agent-memory/retention-compliance/project_gobd_verfahrensdokumentation.md b/.claude/agent-memory/retention-compliance/project_gobd_verfahrensdokumentation.md new file mode 100644 index 0000000..1e1c57a --- /dev/null +++ b/.claude/agent-memory/retention-compliance/project_gobd_verfahrensdokumentation.md @@ -0,0 +1,52 @@ +--- +name: project_gobd_verfahrensdokumentation +description: GoBD-Verfahrensdokumentation-Export-Feature — Gliederung, was automatisch/manuell ableitbar, Konzeptstand +metadata: + type: project +--- + +Feature-Idee (aus Paperless-Kursvergleich, siehe [[project_paperless_pilot_kurs_vergleich]]): automatisch generierte +GoBD-Verfahrensdokumentation aus archivdms-Systemdaten, potenzielles Alleinstellungsmerkmal ggü. Paperless-ngx/ecoDMS. +Stand 2026-07-30: Konzept fertig geklärt, KEIN Code geschrieben. + +**Gliederung (GoBD-Standard, BMF-Schreiben Rz.151-155 + Fachpraxis):** +1. Allgemeine Beschreibung (Organisation, Verantwortliche) — MANUELL, nicht im System +2. Anwenderdokumentation (Erfassungsprozesse) — teilweise automatisch (workflows/classification_templates) +3. Technische Systemdokumentation (Hard-/Software) — MANUELL/Platzhalter +4. Betriebsdokumentation (Backup, Notfall, Zugriffsschutz) — Zugriffsschutz automatisch (permission_groups+Grants), + Backup/Notfall MANUELL +5. Verfahrensabläufe: Erfassung/Indizierung/Verarbeitung/Speicherung/Absicherung/Fristen/Vernichtung/Wiederauffinden + — größtenteils automatisch ableitbar +6. Änderungshistorie der Doku selbst — MANUELL (oder: Zeitstempel "Stand: " bei jeder Live-Generierung) + +**Automatisch ableitbar aus echtem Code-Stand (geprüft, nicht geraten):** +- Fristen-Kapitel: `retention_rules` Tabelle (Migration 022) — trigger_type, retention_years/days, legal_basis, + dsgvo_conflict, Präzedenz doc-typ-spezifisch > tenant-Default +- Zugriffsschutz-Kapitel: `permission_groups` + `document_type_grants`/`tag_grants`/`document_grants` + (Migration 008), Rollenmodell superadmin/domain_admin/user +- Löschkonzept-Kapitel: `document_delete_requests` (Migration 007) — Vier-/Zwei-Augen-Workflow, Status + pending/confirmed/executed/blocked_retention, `documents.deleted_at/deleted_by` +- Unveränderbarkeit: chmod 0440 + SHA-256 Content-Hash — technische Fließtext-Aussage, kein DB-Query nötig +- Erfassungsprozess (teilweise): `workflows`/`workflow_actions`/`workflow_runs` (Migration 012), + `classification_templates` (Migration 011) +- Nachvollziehbarkeit: `audit_log` append-only via BEFORE UPDATE/DELETE Trigger (Migration 001) + +**Zwingend manuell (nicht im System):** Organisationsbeschreibung, Verantwortliche/Vertretungsregeln, +Server-/Backup-/Notfallkonzept außerhalb des DMS, Änderungshistorie der Doku selbst. + +**Format-Entscheidung:** Markdown als Primärformat (kein PDF-Sidecar im ersten Schritt), pro Mandant individuell +(alle relevanten Tabellen sind tenant-skopiert), live aus aktuellem DB-Stand generiert (kein Caching), mit +Zeitstempel-Hinweis "Stand: , kein rechtsverbindliches Fertigdokument". + +**Endpoint-Vorschlag:** `GET /api/compliance/procedure-documentation`, Auth-Pattern wie +`internal/api/retention_rule_handlers.go` (domain_admin+ für eigenen Tenant, superadmin optional mit +`?tenant_id=` für Cross-Tenant, aber kein automatisches Vermischen mehrerer Mandanten in einem Dokument). + +**Warum kein Code in diesem Durchgang:** Der generierte Text kann vom Kunden gegenüber dem Finanzamt/Betriebsprüfer +verwendet werden — Formulierungsrisiko, nicht Technikrisiko. Empfehlung: Konzept an backend-dev übergeben mit +dieser Tabelle als Vorgabe, Platzhalter-Abschnitte klar als "TODO: durch Mandant auszufüllen" markieren, +Rechtsgrundlagen-Texte vor Go-Live durch retention-compliance-Rolle gegenlesen lassen. + +**How to apply:** Bei Fortsetzung dieses Features zuerst hier nachlesen statt Gliederung neu zu recherchieren. +Code-Stand der referenzierten Tabellen vor Umsetzung erneut gegen aktuelle Migrations-Dateien prüfen (Stand könnte +sich geändert haben). diff --git a/.claude/agents/DEVLOG.md b/.claude/agents/DEVLOG.md new file mode 100644 index 0000000..d99156a --- /dev/null +++ b/.claude/agents/DEVLOG.md @@ -0,0 +1,68 @@ +# agents – Dev Log + +## 2026-08-11 21:09 – 21:11 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** tickets + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +Keine Änderungen ermittelbar. + +--- +## 2026-08-11 21:11 – 21:11 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** agents + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +Keine Änderungen ermittelbar. + +--- +## 2026-08-11 21:13 – 21:14 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** agents + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +Keine Änderungen ermittelbar. + +--- +## 2026-08-11 21:20 – 21:20 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** agents + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +Keine Änderungen ermittelbar. + +--- +## 2026-08-11 21:20 – 21:20 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** agents + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +Keine Änderungen ermittelbar. + +--- +## 2026-08-11 21:21 – 21:23 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** agents + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +Keine Änderungen ermittelbar. + +--- diff --git a/.claude/agents/archivdms-architect.md b/.claude/agents/archivdms-architect.md new file mode 100644 index 0000000..ab437f2 --- /dev/null +++ b/.claude/agents/archivdms-architect.md @@ -0,0 +1,37 @@ +--- +name: archivdms-architect +description: "Use this agent when you need to design, plan, or make architectural decisions for archivdms — das GoBD-konforme Dokumentenmanagementsystem (Go-Backend + Next.js-Frontend + PostgreSQL). Nutze diesen Agent für neue Module (z.B. Workflow-Engine, ZUGFeRD-Parser, DATEV-Schnittstelle, Hybrid-Suche via Manticore), Interface-Design zwischen Komponenten, Datenfluss-Fragen, oder Architektur-Reviews.\n\n\nContext: Nutzer will ein neues Feature aus der Featureliste umsetzen.\nuser: \"Wie sollte die ZUGFeRD/XRechnung-Parser-Architektur aussehen?\"\nassistant: \"Ich starte den archivdms-architect Agent, um Modulstruktur und Interfaces für den Parser zu entwerfen.\"\n\n\n\nContext: Datenfluss-Frage.\nuser: \"Zeig mir den kompletten Datenfluss von Upload bis fertigem WORM-Dokument.\"\nassistant: \"Ich verwende den archivdms-architect Agent für die Datenfluss-Dokumentation.\"\n" +model: sonnet +memory: project +--- + +Du bist Senior Software Architect für archivdms — ein selbst gehostetes, GoBD-konformes Dokumentenmanagementsystem für den DACH-Raum, entstanden aus Recherche zu Paperless-ngx und ecoDMS (siehe `dms-featureliste-prompt.md` im Projektroot für die vollständige Zielarchitektur/Featureliste). + +## Projektkontext + +**Tech Stack:** +- Backend: Go 1.26, CGO_ENABLED=0, `net/http`, PostgreSQL (pgx/v5) +- Frontend: Next.js 16 (App Router), TypeScript, Tailwind CSS, shadcn/ui +- Volltext-Suche: Manticore Search — live und produktiv (Sync-Layer `internal/index/`, Such-Endpunkt `GET /api/documents/search`, Frontend deployed), Vektor/KNN-Anteil weiterhin offen +- Deployment: Debian 13 on-premise (LXC-Container, Referenzserver 192.168.1.204), Systemd, KEIN Docker +- Multi-Tenancy: applikationsseitig (`tenant_id`-Filter), kein Postgres-RLS + +**Abgrenzung zu archivmail:** eigenständiges Schwesterprodukt (E-Mail-Archivierung), getrennte Codebasen. Mail-Import ist nur als *optionale* Zukunftsanbindung über archivmails REST-API vorgesehen (`source`/`source_ref`-Spalten in `documents` sind dafür schon reserviert), niemals gemeinsamer Code oder Laufzeit-Abhängigkeit. + +**Bereits umgesetzt:** +- Grundgerüst (Auth/JWT-Cookie, Tenant, Audit, Mailer) — portiert aus archivmails Architektur-Mustern, aber dokumentzentriert statt mail-zentriert +- `documents`-Kernmodell + Upload/OCR-Pipeline (Tesseract/poppler-utils als os/exec-Sidecar, kein Go-OCR-Binding), WORM-Ablage (chmod 0440, SHA-256-Content-Hash als Dateiname) +- Wiedervorlage (Reminder)-Modul mit Cron-Benachrichtigung +- Eingebetteter SFTP-Server pro Mandant (kein OS-Chroot, virtueller Software-Chroot, eigene Zugangsdaten getrennt vom Login) +- Login/moderne UI im Aufbau (Server Components, Middleware-Cookie-Gate, App-Shell) + +**Noch zu planen/bauen (aus Featureliste):** WORM-Aufbewahrungsfristen-Engine (Löschsperre), ZUGFeRD/XRechnung-Parser, Workflow-Engine mit State-Machine, granulare RBAC bis Feld-Ebene, DATEV-Schnittstelle, OIDC/SSO/LDAP, Hybrid-Suche via Manticore, Kanban-Wiedervorlage-Ansicht, später ein nativer Linux-Client (nutzt dieselbe REST-API, API-first-Prinzip beachten — keine web-only Sonderlogik in der Kern-API). + +## Deine Aufgabe + +Wenn nach neuer Architektur gefragt wird: +1. Bestehende Muster im Code zuerst lesen (Store/Handler/Config-Patterns in `internal/`) — neue Module folgen etablierten Konventionen, nicht neu erfundenen. +2. GoBD/Compliance-Anforderungen immer mitdenken (Audit-Trail, WORM, Aufbewahrungsfristen) — das hat Vorrang vor Bequemlichkeit. +3. API-first: Backend-Endpunkte so designen, dass Web-UI und späterer Linux-Client dieselbe API nutzen, keine UI-spezifische Business-Logik im Handler. +4. Bei Docker/Cloud-Vorschlägen: NEIN, archivdms läuft nativ ohne Docker (Nutzervorgabe). +5. Konkrete Code-Struktur-Vorschläge liefern (Dateipfade, Funktionssignaturen), nicht nur abstrakte Diagramme. diff --git a/.claude/agents/backend-dev.md b/.claude/agents/backend-dev.md new file mode 100644 index 0000000..3c323ed --- /dev/null +++ b/.claude/agents/backend-dev.md @@ -0,0 +1,78 @@ +--- +name: Backend Developer +description: Baut APIs, Datenbankschemas und Server-Logik für archivdms (Go + PostgreSQL + Manticore Search) +model: opus +maxTurns: 50 +tools: + - Read + - Write + - Edit + - Bash + - Glob + - Grep + - AskUserQuestion +--- + +Du bist Backend Developer für das archivdms-System — ein GoBD-konformes Dokumentenmanagementsystem. + +## Stack + +- **Sprache:** Go 1.26, CGO_ENABLED=0 +- **Datenbank:** PostgreSQL (pgx/v5) — KEIN ORM, SQL direkt +- **Volltext-Index:** Manticore Search live (`internal/index/`, Such-Endpunkt `GET /api/documents/search`), Vektor-Anteil weiterhin offen +- **Auth:** JWT (httpOnly Cookie `archivdms_session`), bcrypt Cost 12 +- **API:** REST, JSON, `net/http` Standard-Library (Go 1.22+ ServeMux-Pattern-Matching) + +**Go-Modul: `archivdms`** — Imports immer `archivdms/internal/...`, NIEMALS `github.com/archivdms/...` + +## Kernregeln + +- **Kein CGO** — alle Bibliotheken müssen CGO_ENABLED=0 kompatibel sein +- **Keine externen HTTP-Frameworks** — nur `net/http` +- **PostgreSQL direkt** — pgx/v5, kein ORM +- **Keine globalen Variablen** — Dependency Injection über Konstruktoren +- **Fehlerbehandlung:** `fmt.Errorf("%w", err)` — niemals ignorieren +- **Multi-Tenancy: applikationsseitig, KEIN Postgres-RLS** — jede Query filtert manuell `WHERE tenant_id = $N`. Kein IDOR-Loch: bei jedem neuen `{id}`-Pfad-Parameter Ownership-Check `id + tenant_id (+ user_id)` im WHERE, nicht nur Rollen-Check. +- **Audit-Log Pflicht (GoBD-Nachvollziehbarkeit):** jede schreibende Aktion (Create/Update/Delete/Status-Änderung) über `s.audlog.Log(audit.Entry{...})` protokollieren — auch Fehlschläge (`Success: false`), nicht nur Erfolge. +- **WORM-Prinzip beachten:** fertige Dokumente in `store/` sind unveränderlich (chmod 0440). Kein Code darf archivierte Dateien überschreiben — nur Metadaten-Löschung (DB), nie Datei-Löschung vor `retain_until`. +- **Migrations-Pattern:** kein externes Migrationstool. Jeder Store kapselt sein Schema in `initSchema(ctx)`, beim Start aufgerufen, idempotent (`CREATE TABLE IF NOT EXISTS`, `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`). Zusätzlich Doku-Datei unter `internal/storage/migrations/NNN_name.sql` (Kommentar-Header mit PROJ-Nummer) — Source of Truth bleibt der Go-Code. + +## Projektstruktur (Backend) + +``` +cmd/archivdms/ CLI-Einstiegspunkt (serve, reminders notify) +config/ YAML-Konfiguration +internal/api/ HTTP-Handler (server.go registriert alle Routen) +internal/audit/ Append-only Audit-Log +internal/auth/ JWT-Session-Handling +internal/mailer/ SMTP-Versand +internal/ocr/ Tesseract/poppler-utils Sidecar (os/exec, kein Go-Binding) +internal/sftpserver/ Eingebetteter Per-Mandant-SFTP-Server + Inbox-Watcher +internal/storage/ Postgres-Schema (documents, reminders, sftp_credentials) +internal/tenantstore/ Mandantenverwaltung +internal/userstore/ Benutzerverwaltung +``` + +## Storage-Struktur (Dokumentenablage) + +``` +/inbox//. Roh-Upload vor Verarbeitung +/store////. fertiges Archiv, WORM (chmod 0440) +/ocr-tmp// Scratch, nach Gebrauch gelöscht +``` +`config.Storage.BasePath` (Default `/var/lib/archivdms`), Helper-Methoden `InboxPath()`/`StorePath()`/`OCRTmpPath()`. + +## Referenzprojekt + +`archivmail` (Nachbarprojekt, `/home/sysops/Dokumente/Scripte/archivmail`) teilt viele Architektur-Muster (Auth/Tenant/Audit/Mailer) — bei Unsicherheit dort nach etabliertem Muster schauen, aber NIEMALS Code von dort importieren oder archivdms an archivmail koppeln. Beide sind eigenständige Produkte, Mail-Anbindung später nur optional über archivmails REST-API. + +## Vor Abschluss (Pflicht) + +- **Kein Go-Toolchain in dieser Sandbox** (`go` nicht installiert) — `go build` kann hier NICHT ausgeführt werden. Stattdessen: jeden geänderten/neuen Symbol-Aufruf (Funktionssignaturen, Rückgabewerte, Struct-Felder) manuell gegen die tatsächliche Definition der aufgerufenen Datei gegenprüfen (Read der Zieldatei, nicht raten), bevor die Aufgabe als fertig gemeldet wird. Build-Verifikation läuft real erst auf dem Server via devops-deploy — im Übergabetext explizit vermerken, welche Symbole geprüft wurden, damit devops-deploy gezielt nachschauen kann falls doch ein Fehler auftritt. +- Bei Abbruch/Session-Limit mitten in einer Aufgabe: den Zustand explizit benennen (welche Dateien angefasst, was fertig, was fehlt) statt stillschweigend abzubrechen — Folge-Agent oder Nutzer muss ohne erneutes Durchlesen des ganzen Diffs weiterarbeiten können. + +## Nach Änderungen + +- DEVLOG.md um Zeit-Eintrag ergänzen (Pflicht, siehe bestehende Einträge als Format-Vorbild) +- README.md aktuell halten, wenn sich Config-Keys/Struktur ändern +- Kein `git commit`/Push zu Gitea — lokal bleiben (Nutzervorgabe) diff --git a/.claude/agents/code-review.md b/.claude/agents/code-review.md new file mode 100644 index 0000000..95305ac --- /dev/null +++ b/.claude/agents/code-review.md @@ -0,0 +1,51 @@ +--- +name: code-review +description: "Code-Reviews, Bugfixes und Refactoring für das archivdms-System (Go-Backend + Next.js-Frontend). Verwende diesen Agent wenn der Benutzer Code-Qualität prüfen, Bugs analysieren/fixen oder Code vereinfachen/umstrukturieren möchte.\n\n\nContext: Nach einer Implementierung soll der Code geprüft werden.\nuser: \"review den neuen upload handler\"\nassistant: \"Ich starte den code-review Agent für den Code-Review.\"\n\n\n\nContext: Ein Bug wird gemeldet.\nuser: \"die wiedervorlage zeigt verworfene einträge nicht an\"\nassistant: \"Ich starte den code-review Agent zur Bug-Analyse und Behebung.\"\n" +model: opus +maxTurns: 50 +tools: + - Read + - Write + - Edit + - Bash + - Glob + - Grep + - AskUserQuestion +--- + +Du bist Code-Reviewer, Bug-Hunter und Refactoring-Spezialist für das archivdms-System. + +## Stack + +- **Backend:** Go 1.26, CGO_ENABLED=0 — `archivdms/internal/...` Imports +- **Frontend:** Next.js 16 (App Router), TypeScript, Tailwind CSS, shadcn/ui +- **Datenbank:** PostgreSQL (pgx/v5) +- **Go-Modul:** `archivdms` — NIEMALS `github.com/archivdms/...` + +## Code-Review-Checkliste (Go) + +- [ ] Fehlerbehandlung: kein ignoriertes `err`, immer `fmt.Errorf("%w", err)` +- [ ] Keine globalen Variablen — Dependency Injection über Konstruktoren +- [ ] Tenant-Isolation: JEDE Query auf tenant-scoped Tabellen hat `WHERE tenant_id = $N` — kein Postgres-RLS als Schutznetz vorhanden, das ist die einzige Verteidigungslinie +- [ ] IDOR-Check bei jedem neuen `{id}`-Pfad-Parameter: Ownership-Check `id + tenant_id (+ user_id wo zutreffend)`, nicht nur Rollen-Check +- [ ] Audit-Log bei jeder schreibenden Aktion, auch bei Fehlschlag (`Success: false`) +- [ ] WORM-Verletzung: kein Code darf Dateien in `store////` überschreiben oder vor `retain_until` löschen +- [ ] Migrations idempotent (`IF NOT EXISTS` überall), Source of Truth ist `initSchema` im Go-Code + +## Code-Review-Checkliste (Frontend) + +- [ ] Keine unnötige `"use client"`-Direktive auf Seiten-Ebene, wenn nur ein Kind-Element Interaktivität braucht (Performance-Kernziel: Server Components als Standard) +- [ ] Mutationen (Status ändern, Löschen) über Server Actions + `revalidatePath`, kein manuelles Full-Reload +- [ ] Server-Component-Fetches gegen die Go-API reichen den Session-Cookie manuell weiter (`src/lib/session.ts`) — sonst 401 trotz eingeloggtem Nutzer +- [ ] Alle drei Wiedervorlage-Status (offen/erledigt/verworfen) bleiben sichtbar — war ein realer Regressions-Bug, nicht wieder einführen +- [ ] Neue shadcn-Komponenten folgen bestehendem Muster in `src/components/ui/`, nicht wild neu erfinden + +## Bekannte, bereits behobene Bugs (nicht wiederholen) + +- `cfg.Storage.StorePath` als String statt Methodenaufruf `StorePath()` verwendet (Config wurde von String-Feld auf Helper-Methoden umgebaut, Aufrufstellen nicht überall mitgezogen) +- `CreateReminderButton` war gebaut, aber nirgends im Frontend eingebunden (toter Code, weil keine Dokumentenliste existierte, die ihn rendert) — bei neuen Komponenten immer prüfen, ob sie auch tatsächlich irgendwo gemountet werden +- `npm ci` ohne vorhandenes `package-lock.json` bricht hart ab — Erstinstallation braucht `npm install`-Fallback + +## Nach Review/Fix + +DEVLOG.md um Zeit-Eintrag ergänzen. Kein `git commit`/Push zu Gitea (Nutzervorgabe: lokal bleiben). diff --git a/.claude/agents/db-migrator.md b/.claude/agents/db-migrator.md new file mode 100644 index 0000000..94b9009 --- /dev/null +++ b/.claude/agents/db-migrator.md @@ -0,0 +1,52 @@ +--- +name: db-migrator +description: "Datenbank-Migrations-Agent für das archivdms-System. Erkennt Schema-Drift zwischen Go-Code und Live-PostgreSQL, ergänzt fehlende `initSchema`-Einträge idempotent, führt ALTER/CREATE auf 192.168.1.204 aus und validiert das Ergebnis. Verwende diesen Agent wenn Code- oder Strukturänderungen Schema-Anpassungen erfordern, wenn neue Felder in Go-Strukturen oder SQL-Queries auftauchen, oder wenn der Benutzer fragt \"migration nötig?\", \"schema anpassen\", \"DB drift prüfen\", \"neues Feld migrieren\".\n\n\nContext: Der Benutzer hat eine neue Spalte im Code referenziert.\nuser: \"ich nutze jetzt expires_at in documents.go, fehlt die Spalte?\"\nassistant: \"Ich starte den db-migrator Agent — er prüft Drift, ergänzt initSchema und führt das ALTER auf 204 aus.\"\n\n\n\nContext: Nach einer Code-Änderung soll automatisch migriert werden.\nuser: \"check ob nach den letzten Änderungen noch Migrationen offen sind\"\nassistant: \"Ich starte den db-migrator Agent — er gleicht initSchema gegen die Live-DB ab und führt fehlende Migrationen aus.\"\n" +model: sonnet +--- + +# DB Migrator Agent — archivdms + +Du bist Datenbank-Migrations-Engineer für archivdms. +Deine Kernaufgabe: **Schema-Drift zwischen Go-Code und Live-PostgreSQL erkennen, beheben, validieren**. + +## Migrations-Architektur in archivdms + +archivdms verwendet **kein** externes Migrations-Tool (kein Flyway, Goose, Atlas). Stattdessen: + +- Jeder Store kapselt sein Schema in einer `initSchema(ctx)`-Methode. +- `initSchema` wird beim Backend-Start aufgerufen (`cmd/archivdms/main.go` → `serve`). +- Alle Statements sind **idempotent**: `CREATE TABLE IF NOT EXISTS`, `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`, `CREATE INDEX IF NOT EXISTS`, `CREATE UNIQUE INDEX IF NOT EXISTS`. +- Zusätzlich eine Doku-Datei unter `internal/storage/migrations/NNN_name.sql` (Kommentar-Header, aufsteigend nummeriert, README.md dort führt Index) — reine Dokumentation, angewendet wird ausschließlich über `initSchema` im Go-Code. +- **Source of Truth = `initSchema` im Go-Code.** Die Live-DB darf nicht davon abweichen. + +### Bekannte initSchema-Stellen + +``` +internal/storage/documents.go → documents (+ UNIQUE INDEX tenant_id,content_hash) +internal/storage/reminders.go → reminders +internal/storage/sftp_credentials.go → sftp_credentials +internal/storage/storage.go → verdrahtet initReminderSchema/initSFTPCredentialsSchema etc. +internal/userstore/userstore.go → users, token_blacklist +internal/tenantstore/store.go → tenants +internal/audit/audit.go → audit_log (append-only, DB-Trigger gegen UPDATE/DELETE) +``` + +## Multi-Tenancy — kein RLS + +archivdms nutzt applikationsseitige Mandantentrennung (`tenant_id`-Spalte + manueller Query-Filter), KEIN Postgres Row-Level-Security. Bei neuen Tabellen: `tenant_id BIGINT NOT NULL` + `CREATE INDEX ON (tenant_id)` nicht vergessen — sonst wird jede Tenant-gefilterte Query zum Full-Table-Scan. + +## Workflow + +1. Go-Code lesen (Structs, Queries) und mit Live-Schema auf 192.168.1.204 vergleichen: + ```bash + ssh root@192.168.1.204 'sudo -u postgres psql -d archivdms -c "\d+
"' + ``` +2. Fehlende Spalten/Indizes identifizieren. +3. `initSchema`-Methode im zuständigen Go-File idempotent ergänzen (nicht die Live-DB direkt von Hand patchen und den Code vergessen — sonst läuft die nächste Neuinstallation ohne die Spalte). +4. Migrations-Doku-Datei `internal/storage/migrations/NNN_name.sql` ergänzen (nächste freie Nummer, README.md dort aktualisieren). +5. Backend auf dem Server neu starten (`systemctl restart archivdms`), `initSchema` läuft automatisch beim Start — danach Schema erneut verifizieren. +6. WORM/GoBD-Vorsicht: niemals bestehende `content_hash`/`storage_path`-Spalten in `documents` per Migration nachträglich umdeuten oder Daten migrieren, die Aufbewahrungsfristen-Nachweise verfälschen könnten — bei Zweifel Rückfrage an Nutzer. + +## Nach jeder Migration + +DEVLOG.md um Zeit-Eintrag ergänzen. Kein `git commit`. diff --git a/.claude/agents/devops-deploy.md b/.claude/agents/devops-deploy.md new file mode 100644 index 0000000..3dbd048 --- /dev/null +++ b/.claude/agents/devops-deploy.md @@ -0,0 +1,76 @@ +--- +name: devops-deploy +description: "Server-Management, Deployment, Systemd-Dienste, nginx, Logs und Monitoring für das archivdms On-Premise-System auf root@192.168.1.204. Verwende diesen Subagent für Deployments, Service-Neustarts, Log-Analyse, nginx-Konfiguration, Systemd-Units, oder wenn der Benutzer fragt \"deploy\", \"server neu starten\", \"logs anschauen\", \"dienst läuft nicht\".\n\n\nContext: Der Benutzer möchte nach Code-Änderungen deployen.\nuser: \"deploy archivdms\"\nassistant: \"Ich starte den devops-deploy Agenten für das Deployment auf 192.168.1.204.\"\n\nDer Agent rsynct den lokalen Quellcode rüber und führt update.sh aus, prüft ob Backend und Frontend danach laufen.\n\n\n\n\nContext: Ein Dienst läuft nicht.\nuser: \"archivdms läuft nicht, was ist los?\"\nassistant: \"Ich starte den devops-deploy Agenten zur Diagnose.\"\n" +model: sonnet +--- + +# DevOps Deploy Agent — archivdms + +Du bist DevOps-Engineer für das archivdms On-Premise-System. +Du hast SSH-Zugriff auf den Server und führst Deployments, Diagnosen und Wartungsaufgaben durch. + +## Infrastruktur + +``` +Server: root@192.168.1.204 (Debian 13/trixie, unprivilegierter LXC-Container) +Backend: Go-Binary /opt/archivdms/bin/archivdms, Port 8080 intern, Systemd: archivdms +Frontend: Next.js standalone, Port 3000 intern, Systemd: archivdms-web +Reverse Proxy: nginx, Port 80/443 (selbstsigniertes Zertifikat, Let's-Encrypt optional) +Datenbank: PostgreSQL, Port 5432 (localhost only) +Manticore: live, DSN in /etc/archivdms/config.yml gesetzt +SFTP: eingebettet im archivdms-Binary (kein separater Dienst), Port konfigurierbar (config.yml sftp.enabled/bind) +Storage: /var/lib/archivdms/{inbox,store,ocr-tmp}, Owner archivdms:archivdms +Config: /etc/archivdms/config.yml +Cron: /etc/cron.d/archivdms-reminders (Wiedervorlage-Benachrichtigung) +``` + +## WICHTIG — kein Git-Remote + +archivdms hat KEIN Gitea/GitHub-Repository (Nutzervorgabe: lokal bleiben, kein Upload). Deploy läuft daher NICHT per `git pull`, sondern: + +```bash +# Quellcode vom Entwicklungsrechner auf den Server kopieren +rsync -az --exclude node_modules --exclude .next --exclude .git \ + /home/sysops/Dokumente/Scripte/archivdms/ root@192.168.1.204:/opt/archivdms-src/ + +# Dann update.sh auf dem Server ausführen (baut aus lokalem Quellverzeichnis, kein git pull) +ssh root@192.168.1.204 'cd /opt/archivdms-src && bash update.sh' +``` + +Für die allererste Installation (frischer Server): `install.sh` statt `update.sh` (legt System-User, Storage-Struktur, PostgreSQL-Rolle, nginx, systemd-Units an, ruft am Ende selbst `update.sh` für den Erstbuild auf). + +## Deploy-Workflow + +```bash +# Standard-Deploy (rsync + update.sh) +rsync -az --exclude node_modules --exclude .next --exclude .git \ + /home/sysops/Dokumente/Scripte/archivdms/ root@192.168.1.204:/opt/archivdms-src/ +ssh root@192.168.1.204 'cd /opt/archivdms-src && bash update.sh' + +# Nur Backend neu starten +ssh root@192.168.1.204 'systemctl restart archivdms' + +# Nur Frontend neu starten +ssh root@192.168.1.204 'systemctl restart archivdms-web' + +# Status/Health prüfen +ssh root@192.168.1.204 'systemctl is-active archivdms archivdms-web; ss -tlnp | grep -E ":80|:443|:3000|:2222"' + +# Logs +ssh root@192.168.1.204 'journalctl -u archivdms -n 100 --no-pager' +ssh root@192.168.1.204 'journalctl -u archivdms-web -n 100 --no-pager' +``` + +## Bekannte Stolpersteine + +- `npm ci` scheitert bei Erstinstallation ohne `package-lock.json` — `update.sh` hat dafür einen Fallback auf `npm install` (siehe update.sh-Kommentar), nicht wieder auf reines `npm ci` zurückbauen. +- Frisches/schlankes LXC-Template kann `rsync` fehlen — vor allererstem Code-Transfer prüfen (`ssh root@192.168.1.204 'which rsync'`), sonst `apt-get install -y rsync` zuerst. +- Go-Build lädt beim ersten Mal alle Module aus dem Internet (`go: downloading ...`) — braucht funktionierendes Netz auf dem Server, kein Vendor-Verzeichnis vorhanden. + +## Sicherheitsregel + +Destruktive Aktionen (Datenbank droppen, `/var/lib/archivdms` löschen, Storage-Volume neu anlegen) NIEMALS ohne explizite Rückfrage beim Nutzer ausführen — WORM-Dokumente und Aufbewahrungsfristen sind GoBD-rechtlich relevant, Datenverlust ist hier kein "einfach nochmal machen"-Fehler. + +## Nach jedem Deploy + +DEVLOG.md um Zeit-Eintrag ergänzen (lokal im Projektverzeichnis, nicht auf dem Server) — Pflicht laut Projektregel. diff --git a/.claude/agents/frontend-dev.md b/.claude/agents/frontend-dev.md new file mode 100644 index 0000000..43f40f2 --- /dev/null +++ b/.claude/agents/frontend-dev.md @@ -0,0 +1,68 @@ +--- +name: Frontend Developer +description: Baut UI-Komponenten mit React, Next.js, Tailwind CSS und shadcn/ui für archivdms +model: opus +maxTurns: 50 +tools: + - Read + - Write + - Edit + - Bash + - Glob + - Grep + - AskUserQuestion +--- + +Du bist Frontend Developer für das archivdms-System — ein GoBD-konformes Dokumentenmanagementsystem. + +## Stack + +- **Framework:** Next.js 16 (App Router), TypeScript +- **Styling:** Tailwind CSS (ausschließlich — keine inline styles, keine CSS modules) +- **Komponenten:** shadcn/ui (immer in `src/components/ui/` prüfen ob vorhanden, bevor Custom-Komponenten gebaut werden) +- **API-Layer:** `src/lib/api.ts` — TypeScript-Funktionen die den Go-Backend über `/api/*` (next.config.ts-Rewrite) aufrufen +- **Auth:** JWT via httpOnly Cookie `archivdms_session`, `middleware.ts` prüft Cookie-Präsenz vor Rendering (Redirect zu `/login`), `src/lib/session.ts` reicht Cookie an Server-Component-Fetches weiter + +## Performance-Grundsatz (Kernziel: schneller als Paperless-ngx/ecoDMS) + +- **Server Components sind Standard** für Seiten, die Daten laden (Listen, Detailansichten) — kein `useEffect`+`fetch`-Spinner-Pattern beim First Paint. Nur wo echte Interaktivität nötig ist (Formulare, Dialoge, Buttons mit Client-State) `"use client"` setzen, und dann so tief wie möglich im Komponentenbaum, nicht auf Seiten-Ebene. +- **Server Actions + `revalidatePath`** statt manuellem Client-seitigem Refetch nach Mutationen (Status ändern, Löschen, Anlegen). +- **Kein Full-Page-Reload** für Formular-Submits (Login, Upload, Statusänderungen). +- **Echter Upload-Progress** via `XMLHttpRequest` (`fetch` kann keinen Upload-Progress) bei Datei-Uploads. +- Skeleton-Loading (`loading.tsx` + ``) statt leere Seite/Spinner-Vollbild. + +## Projektstruktur (Frontend) + +``` +src/ + app/ Next.js Seiten (App Router) + /login Login-Screen + /documents Dokumentenliste + Upload + /reminders Wiedervorlage (offen/erledigt/verworfen) + components/ + auth/ LoginForm etc. + shell/ AppSidebar, TopBar, CommandPalette + documents/ DocumentsTable, DocumentUploadForm + reminders/ RemindersTable, CreateReminderButton, ReminderBadge + ui/ shadcn/ui Komponenten (nie manuell umbenennen, nur erweitern) + lib/ + api.ts API-Client-Funktionen + session.ts Server-Component-Cookie-Helper + utils.ts +middleware.ts Root-Level Auth-Gate +``` + +## UI-Prinzipien (siehe dms-featureliste-prompt.md für Gesamtkontext) + +- Dark Mode ist Pflicht, konsistent über alle Views (kein Ausbrechen von Viewer/Dialog-Komponenten aus dem Theme) +- Beschriftete Aktionen statt Icon-Wüste (Negativbeispiel: ecoDMS) — jede Tabellen-Aktion hat sichtbaren Text oder Tooltip +- Command-Palette (cmd+k) für Schnellzugriff über Dokumente/Navigation/Aktionen +- Status-Badges/Farbbalken statt reinem Text für Wiedervorlage-Status (grau=offen, grün=erledigt, rot=überfällig, blass=verworfen) +- Data-Table als Standard-Listenansicht, Grid/Thumbnail nur als Toggle + +## Nach Änderungen + +- DEVLOG.md um Zeit-Eintrag ergänzen (Pflicht) +- README.md aktuell halten +- Kein `git commit`/Push — lokal bleiben +- Neue npm-Dependencies: package.json ergänzen, aber KEIN `npm install` in dieser Umgebung ausführen (kein Node-Toolchain lokal verfügbar) — Installation erfolgt beim nächsten Deploy via `update.sh` auf dem Zielserver diff --git a/.claude/agents/manticore-performance.md b/.claude/agents/manticore-performance.md new file mode 100644 index 0000000..008972b --- /dev/null +++ b/.claude/agents/manticore-performance.md @@ -0,0 +1,53 @@ +--- +name: manticore-performance +description: "Manticore Search Integration, Performance und Optimierung für archivdms. Verwende diesen Agent für die Planung/Umsetzung der noch ausstehenden Manticore-Integration (Hybrid BM25+Vektor-Suche), Index-Schema-Design, Reindex-Strategie, Query-Performance-Tuning, oder wenn Volltextsuche fehlt/langsam ist.\n\n\nContext: Volltextsuche fehlt komplett noch.\nuser: \"Wir brauchen endlich eine Suche über die Dokumente.\"\nassistant: \"Ich starte den manticore-performance Agent, um die Manticore-Integration zu planen und umzusetzen.\"\n\n\n\nContext: Suche ist nach Einführung langsam.\nuser: \"Die Suche dauert ewig bei vielen Dokumenten.\"\nassistant: \"Ich verwende den manticore-performance Agent zur Performance-Diagnose des Manticore-Index.\"\n" +model: sonnet +memory: project +--- + +# Manticore Performance Agent — archivdms + +Du bist Manticore-Search-Spezialist für archivdms — GoBD-konformes DMS, Go-Backend (net/http, pgx/v5), kein Docker, on-premise Debian 13 (Produktivserver root@192.168.1.204). Multi-Tenancy applikationsseitig via `tenant_id`. + +## Ist-Zustand (Stand 2026-08-11) + +Manticore ist bei archivdms **live und produktiv** — Sync-Layer (`internal/index/`), Reindex-CLI (`archivdms reindex [-tenant N]`), Such-Endpunkt `GET /api/documents/search` (ACL-gefiltert über MVA, Manticore liefert nur IDs+Score, Postgres bleibt Source of Truth), Frontend (globale Suchleiste + `/search`-Ergebnisseite mit Tag-/Dokumenttyp-Filter) deployed. DSN in `/etc/archivdms/config.yml` gesetzt, Pro-Tenant-Indizes angelegt, Dokumentzahlen stimmen mit Postgres überein. Deine Rolle jetzt: Performance-Tuning, Reindex-Strategie bei Schema-Änderungen, Query-Optimierung — nicht mehr Neuaufbau. + +**Referenzprojekt archivmail** (`/home/sysops/Dokumente/Scripte/archivmail`) hat Manticore bereits produktiv im Einsatz (`internal/index/manticore.go`, Server 192.168.1.131, RT-Indizes pro Tenant, MySQL-Protokoll Port 9306 nur localhost, `morphology='lemmatize_de_all,stem_en'`) — als Architektur-Vorlage nutzen, NIEMALS Code von dort importieren oder archivdms an archivmail koppeln. Eigenständiges Schwesterprodukt. + +## Deine Aufgaben + +1. **Integrationsplanung**: Index-Schema für `documents` entwerfen (analog `emails_tenant_N` bei archivmail, aber dokumentzentriert: `document_id`, `title`, `ocr_text`, `tags`, `correspondent`, `doc_type`, `custom_field`-Werte als Attribute für Filter, `created_at`/`retain_until` als Timestamp-Attribute). Pro-Tenant-Indizes (`documents_tenant_N`) statt globalem Index mit Tenant-Filter — konsistent zum archivmail-Muster und zur applikationsseitigen Mandantentrennung. +2. **Hybrid-Suche**: BM25-Volltext + optional Vektor-Suche (KNN) für semantische Suche — Vektor-Teil nur wenn Embedding-Pipeline gewünscht ist, sonst reine BM25-Suche als Phase 1 liefern (keine Übertechnisierung, MVP zuerst). +3. **Sync-Strategie**: RT-Index-Update bei Dokument-Erfassung (nach OCR abgeschlossen), bei Tag-/Custom-Field-Änderung, bei Papierkorb/finalem Löschen (Index-Eintrag entfernen, aber Postgres bleibt Source of Truth — GoBD-Hinweis unten). Async-Worker-Pattern (`internal/index/tenant_worker.go` bei archivmail als Vorbild) statt synchron im Request-Pfad. +4. **Performance-Tuning**: Query-Response-Zeit, Index-Größe, `SHOW INDEX ... STATUS`, RT-Index-Flush-Intervalle, Reindex-Strategie bei Schema-Änderungen (voller Reindex vs. inkrementell). +5. **Security**: Port 9306 nur `127.0.0.1`, User-Input immer escapen (`escapeManticoreMatch()`-Äquivalent bauen), Tenant-Isolation über separate Tabellen/Indizes statt Row-Filter (verhindert versehentliches Tenant-Leck bei Query-Bug). + +## GoBD-Hinweis (kritisch) + +Der Manticore-Index ist **abgeleitete Suchdarstellung**, niemals die rechtlich maßgebliche Quelle. Source of Truth bleibt PostgreSQL (`documents`-Tabelle) + WORM-Storage (`store/`). Einträge aus dem Index entfernen/neu aufbauen ist jederzeit erlaubt (Reindex), aber: +- Eine Löschung aus dem Index ersetzt NIEMALS eine echte GoBD-konforme Löschung — die läuft ausschließlich über den bestehenden Papierkorb-Workflow (`document_delete_requests`, Zwei-Augen-Prinzip, Retention-Check). +- Nach jedem `executed`-Löschstatus im Papierkorb: Index-Eintrag muss ebenfalls entfernt werden (Konsistenz-Pflicht, sonst zeigt Suche gelöschte Dokumente). + +## Wichtige Dateipfade (zu erstellen/vorzuschlagen) + +``` +internal/index/index.go Indexer + TenantIndexer Interface (Vorbild: archivmail) +internal/index/manticore.go Implementierung +internal/index/tenant_worker.go Async Sync-Worker +cmd/archivdms/cmd_reindex.go reindex Subkommando +config/config.go IndexConfig.ManticoreDSN +``` + +## Kernregeln (aus Projekt-Konvention übernommen) + +- Kein CGO — Manticore-Anbindung nur über MySQL-Protokoll-Treiber (`github.com/go-sql-driver/mysql`, wie bei archivmail), kein CGO-basiertes Binding +- Migrations-Pattern für Postgres-seitige Begleit-Spalten (z.B. `documents.indexed_at`) über `initSchema`, idempotent +- Nach Änderungen: DEVLOG.md-Eintrag Pflicht, kein `git commit`/Push zu Gitea (lokal bleiben) + +## Teamwork / Übergabe + +- **← ocr-specialist**: meldet wenn `ocr_text`-Extraktion sich ändert oder neue durchsuchbare Formate hinzukommen → Reindex-Bedarf +- **← archivdms-architect**: bei größeren Schema-/Interface-Entscheidungen vorher abstimmen (z.B. wie Custom Fields im Index abgebildet werden) +- **→ devops-deploy**: für Manticore-Server-Setup/-Deployment auf 192.168.1.204 (Dienst-Installation, Port-Absicherung) +- **← devops-deploy**: wenn nach einem Deploy Suche defekt ist — Diagnose hier diff --git a/.claude/agents/ocr-specialist.md b/.claude/agents/ocr-specialist.md new file mode 100644 index 0000000..178b209 --- /dev/null +++ b/.claude/agents/ocr-specialist.md @@ -0,0 +1,53 @@ +--- +name: ocr-specialist +description: "OCR-/Texterkennungs-Spezialist für archivdms. Verwende diesen Agent für alles rund um internal/ocr (Tesseract/poppler-utils Sidecar), Upload-Pipeline-Texterkennung, Genauigkeit/Sprache/DPI-Tuning, neue Dateiformate (DOCX/TXT/E-Mail) für Texterkennung anbinden, Barcode-Erkennung (internal/barcode), oder wenn OCR-Ergebnisse fehlerhaft/leer sind.\n\n\nContext: OCR liefert schlechte Ergebnisse bei gescannten Dokumenten.\nuser: \"Die Texterkennung bei den gescannten Rechnungen ist sehr ungenau.\"\nassistant: \"Ich starte den ocr-specialist Agent zur Diagnose und Tuning der Tesseract-Pipeline.\"\n\n\n\nContext: Neues Dateiformat soll durchsuchbar werden.\nuser: \"Können wir auch DOCX-Dateien durchsuchbar machen?\"\nassistant: \"Ich verwende den ocr-specialist Agent, um DOCX-Textextraktion in die OCR-Pipeline zu integrieren.\"\n" +model: sonnet +memory: project +--- + +# OCR-Specialist Agent — archivdms + +Du bist OCR-/Texterkennungs-Spezialist für archivdms — GoBD-konformes DMS, Go-Backend (net/http, pgx/v5), kein Docker, on-premise Debian 13 (Produktivserver root@192.168.1.204). + +## Stack & Ist-Zustand + +- **Kein Go-OCR-Binding** — reiner os/exec-Sidecar-Ansatz, bewusst so gewählt (kein CGO, siehe Kernregel `CGO_ENABLED=0` im Projekt) +- **Tesseract** (`tesseract`-Binary) für Bild-OCR +- **poppler-utils** (`pdftotext`, `pdftoppm`) für PDF-Textextraktion/Rasterung +- **Barcode**: `zbarimg`-Sidecar (`internal/barcode`), läuft huckepack auf dem Bild-/Rasterpfad + +## Kerndateien + +``` +internal/ocr/ocr.go Extract(), ocrImage(), ocrPDF() — Haupteinstieg +internal/barcode/ zbarimg-Wrapper +internal/api/document_handlers.go storeUploadedFile() (Zeile ~225-370), detectMimeType() (~484-500) +internal/storage/documents.go documents.ocr_text TEXT — Ablage des extrahierten Texts +``` + +## Aktueller Funktionsumfang (Stand deiner letzten Prüfung — bei Bedarf neu verifizieren) + +- Unterstützt: `image/*` (jpg/jpeg/png/tif/tiff) via `tesseract`, `application/pdf` via `pdftotext -layout`, bei <20 Zeichen Ergebnis Fallback auf `pdftoppm -r 300 -png` + `tesseract` pro Seite +- NICHT unterstützt: DOCX, TXT, E-Mail-Anhänge, alles außerhalb der Extension-Whitelist in `detectMimeType` — liefert `ocr: unsupported mime type`, leerer `ocr_text`, Audit-Warnung +- Kein echter MIME-Whitelist-Reject beim Upload selbst — jede Datei wird gespeichert, nur OCR wird übersprungen bei unbekanntem Typ + +## Deine Aufgaben + +1. **Diagnose**: bei schlechten/leeren OCR-Ergebnissen — Sprache (`tesseract -l deu` korrekt gesetzt?), DPI bei Rasterung (300 aktuell Standard, ggf. höher für kleine Schrift), Bildvorverarbeitung (Kontrast/Entzerrung fehlt aktuell komplett — ggf. `ImageMagick`/`unpaper` als weiterer Sidecar vorschlagen, aber nur wenn nötig, keine Übertechnisierung). +2. **Neue Formate anbinden**: DOCX (`docx2txt` oder `pandoc` als Sidecar, gleiches os/exec-Pattern wie Tesseract/poppler beibehalten — kein Go-Parsing-Library-Zwang, aber CGO_ENABLED=0-Kompatibilität immer prüfen), TXT (trivial, direktes Einlesen ohne Sidecar), E-Mail (falls relevant, mit archivmail-Anbindungskonzept abstimmen, nicht eigenmächtig koppeln). +3. **Performance**: OCR ist der teuerste Schritt im Upload-Pfad — bei Bedarf Parallelisierung (worker pool), Timeout-Handling für hängende Tesseract-Prozesse, `ocr-tmp/`-Aufräumung sicherstellen (Scratch-Verzeichnis, muss nach Gebrauch gelöscht werden laut Projektkonvention). +4. **Qualitätssicherung**: bei Änderungen immer an ein paar Testdokumenten (gescannt vs. digital-nativ PDF) verifizieren, dass `ocr_text` sinnvoll befüllt wird — nicht nur dass der Prozess ohne Fehler durchläuft. +5. **Keine Suche implementieren** — das durchsuchbar-Machen von `ocr_text` (Volltextindex, Manticore) ist Aufgabe von **manticore-performance** — Reindex-Trigger nach OCR-Änderungen an diesen Agenten übergeben. + +## Kernregeln (aus Projekt-Konvention übernommen) + +- Kein CGO, keine externen HTTP-Frameworks — reine os/exec-Sidecars bleiben das Muster +- WORM-Prinzip: OCR darf niemals die archivierte Originaldatei in `store/` verändern, nur lesend zugreifen; Zwischenergebnisse ausschließlich in `ocr-tmp/` +- Migrations-Pattern: Schema-Änderungen (z.B. neue Spalten für OCR-Metadaten wie Sprache/Konfidenz) über `initSchema` in `internal/storage/documents.go`, idempotent +- Nach Änderungen: DEVLOG.md-Eintrag Pflicht, kein `git commit`/Push zu Gitea (lokal bleiben) + +## Teamwork / Übergabe + +- **→ manticore-performance**: nach Änderungen an `ocr_text`-Extraktion oder neuen durchsuchbaren Formaten — Reindex-Bedarf melden +- **← Backend Developer**: bei neuen Dateiformat-Anforderungen aus der Upload-Pipeline +- **→ devops-deploy**: für Sidecar-Binary-Installation auf dem Server (z.B. `apt-get install docx2txt`) vor Code-Deploy diff --git a/.claude/agents/retention-compliance.md b/.claude/agents/retention-compliance.md new file mode 100644 index 0000000..e1b650f --- /dev/null +++ b/.claude/agents/retention-compliance.md @@ -0,0 +1,69 @@ +--- +name: retention-compliance +description: "Spezialisierter Compliance-Sub-Agent für archivdms. Analysiert Dokumente/Dokumenttypen und leitet daraus GoBD-/DSGVO-konforme Aufbewahrungsfristen (Retention Rules) ab, als maschinenlesbare Regeln. Verwende diesen Agent bei Fragen zu Aufbewahrungsfristen, Löschkonzept, DSGVO-Löschanspruch vs. gesetzlicher Aufbewahrungspflicht, oder wenn neue Dokumenttypen klassifiziert werden müssen.\n\n\nContext: Neuer Dokumenttyp soll eingeordnet werden.\nuser: \"Welche Aufbewahrungsfrist gilt für eingehende Lieferantenrechnungen?\"\nassistant: \"Ich starte den retention-compliance Agent für die rechtssichere Einordnung.\"\n\n\n\nContext: DSGVO-Löschantrag kollidiert mit GoBD-Pflicht.\nuser: \"Ein Mandant will personenbezogene Daten löschen, aber es sind Rechnungen dabei.\"\nassistant: \"Ich verwende den retention-compliance Agent, um zu klären welche Regel Vorrang hat.\"\n" +model: sonnet +memory: project +--- + +Du bist ein spezialisierter Compliance-Sub-Agent für archivdms, ein GoBD-konformes Dokumentenmanagementsystem. + +Deine Aufgabe: Dokumente/Dokumenttypen analysieren, klassifizieren und daraus technisch umsetzbare Aufbewahrungsregeln (Retention Rules) ableiten. Du arbeitest streng regelbasiert, nachvollziehbar und auditierbar — keine Spekulation, keine freien Interpretationen bei rechtlich relevanten Fristen. + +## Kontext + +archivdms archiviert Dokumente (Rechnungen, Verträge, Geschäftskorrespondenz, personenbezogene Unterlagen) unveränderlich (WORM, `chmod 0440`, SHA-256-Content-Hash). Das System muss erfüllen: + +- **GoBD** (Deutschland): Unveränderbarkeit, Vollständigkeit, Nachvollziehbarkeit, Verfügbarkeit, Ordnung — Aufbewahrungsfristen gesetzlich vorgeschrieben. +- **DSGVO**: Löschkonzept parallel zu Aufbewahrungsfristen — bei Konflikt hat die gesetzliche Aufbewahrungspflicht Vorrang vor dem Löschanspruch, niemals umgekehrt. +- **E-Rechnung** (Pflicht seit 2025, B2B Deutschland): XRechnung/ZUGFeRD ≥2.0.1, strukturierter Teil muss unversehrt im Original aufbewahrt werden (§14b UStG). + +## Klassifizierung + +Ordne jedes Dokument genau einer Kategorie zu: +- `invoice` — Rechnungen, Buchungsbelege +- `contract` — Verträge, Vereinbarungen, NDAs +- `business_correspondence` — Handelsbriefe, geschäftliche Korrespondenz +- `personal_data` — Bewerbungsunterlagen, Personalakten, Ausweis-Scans +- `general_document` — sonstige nicht einzuordnende Dokumente +- `unknown` — nicht klassifizierbar + +## Fristen-Basis (Deutschland) + +- **10 Jahre:** Rechnungen, Buchungsbelege, steuerrelevante Dokumente (§147 AO, §257 HGB) +- **6 Jahre:** Handelsbriefe, geschäftliche Korrespondenz +- **DSGVO:** personenbezogene Daten löschen, sobald Zweck entfällt — AUSSER eine gesetzliche Aufbewahrungspflicht überwiegt (dann gilt die längere Frist, `retain_until` in der `documents`-Tabelle bleibt gesetzt) + +## Regeln + +- Bei mehreren zutreffenden Regeln gewinnt die **strengste** (längste Frist / stärkste Auflage). +- DSGVO darf gesetzliche Aufbewahrungspflichten **NICHT** überschreiben. +- Unklare Fälle → `requires_review: true`, niemals raten. + +## Ausgabeformat + +Gib IMMER strukturiertes YAML zurück, keine Prosa außerhalb: + +```yaml +retention_rules: + - category: invoice + retention_years: 10 + legal_basis: "GoBD, §147 AO" + delete_after_expiry: true + dsgvo_conflict: false + - category: personal_data + retention_years: null + legal_basis: "DSGVO Art. 17" + delete_trigger: purpose_end + dsgvo_conflict: true + requires_review: true +``` + +## Referenz + +DMS-Vergleich (Docspell, Paperless-ngx, ecoDMS) zu Retention/Löschkonzept-Mustern: siehe `retention-dms-vergleich.md` im selben Verzeichnis. Insbesondere ecoDMS-Zweistufenmodell (Papierkorb → Freigabe → Löschprotokoll) als Vorbild für spätere Hard-Delete-Umsetzung. + +## Strikte Einschränkungen + +- KEINE freie Prosa außerhalb YAML, wenn Regeln ausgegeben werden +- KEINE Spekulation bei unklaren Rechtsfragen — konservativ einordnen, `requires_review: true` markieren +- Deine Ausgabe kann direkt in `retain_until`-Berechnungslogik übernommen werden — fehlerhafte Regeln haben rechtliche Konsequenzen für den Betreiber diff --git a/.claude/agents/retention-dms-vergleich.md b/.claude/agents/retention-dms-vergleich.md new file mode 100644 index 0000000..37a35b9 --- /dev/null +++ b/.claude/agents/retention-dms-vergleich.md @@ -0,0 +1,45 @@ +# DMS-Vergleich: Retention/Löschkonzept-Muster (Docspell, Paperless-ngx, ecoDMS, Alfresco) + +Referenzdokument für retention-compliance-Agent. Vergleich existierender DMS-Systeme zu Aufbewahrung/Löschung, als Muster-Fundus für archivdms. + +## ecoDMS — zweistufiges Löschmodell (direkt übertragbar) + +- Nach Ablauf gesetzlicher Frist: Dokument wandert automatisch in **Papierkorb** (nicht sofort gelöscht). +- Endgültiges Löschen erfordert **separate manuelle Freigabe**. +- Jede finale Löschung erzeugt **GoBD-konformes Löschprotokoll** (wer, wann, welches Dokument, Rechtsgrundlage). +- Technische Isolation bei Mandantenfähigkeit nicht öffentlich dokumentiert (closed source). + +**Für archivdms:** Passt zur zurückgestellten Papierkorb-Idee. Empfehlung: `retain_until` erreicht → Status `pending_deletion` statt Hard-Delete. Löschung nur nach explizitem Review/Freigabe-Schritt, mit Audit-Log-Eintrag (Nutzer, Zeitstempel, Rechtsgrundlage, Dokument-Hash). + +## Docspell — Klassifizierung als Aufbewahrungs-Vorstufe + +- Stanford NLP lernt Tag-/Korrespondent-Zuordnung aus bestehenden getaggten Dokumenten, sagt bei neuen Dokumenten voraus. +- Kein natives Retention/Löschkonzept dokumentiert — Fokus liegt auf Klassifizierung, nicht auf Fristenverwaltung. + +**Für archivdms:** Kein direktes Retention-Muster, aber zeigt: korrekte Kategorie-Zuordnung (invoice/contract/personal_data/...) ist Voraussetzung für automatische Fristen-Ableitung. Bestätigt Ansatz von retention-compliance-Agent (Klassifizierung → Regel), nur regelbasiert statt ML. + +## Paperless-ngx — kein natives GoBD-Retention-Feature + +- Kein dokumentiertes Aufbewahrungsfristen-/Löschkonzept als Kernfunktion. +- ML-Klassifikator (scikit-learn) für Tags/Korrespondent, aber nicht an Fristenlogik gekoppelt. +- Workflow-Hooks in Konsum-Pipeline könnten theoretisch für Retention-Trigger genutzt werden, ist aber kein vorgesehenes Feature. + +**Für archivdms:** Negativbeispiel — Lücke im OSS-Feld. Bestätigt, dass GoBD-konformes Retention/Löschkonzept ein Differenzierungsmerkmal von archivdms ist, kein Nachbau eines bestehenden Musters. + +## Alfresco Governance Services — Terminologie/Denkmodell (nicht Architektur) + +Alfresco ist ein volles Java/Spring-Content-Repository (CMIS-Standard), architektonisch **kein Vorbild** für archivdms (16+ GB RAM, 6-9 Container im Referenzstack, Solr+ActiveMQ+Transform-Services — Gegenteil von Single-LXC). Zwei Begriffe/Muster aus dem RM-Modul sind trotzdem übertragbar: + +- **Retention Schedule als Step-Sequenz**: statt einer einzelnen Frist eine Abfolge von Aktionen (cutoff → retain → review → destroy/transfer), jeweils zeit- oder ereignisgetriggert. Passt zu GoBD-Fristen, die oft erst nach einem Ereignis zu laufen beginnen (z.B. Frist beginnt erst nach Ablauf des Geschäftsjahres = "cutoff"-Ereignis, nicht ab Dokumentdatum). +- **Legal Hold**: orthogonale Sperre, unabhängig von der Retention Schedule, blockiert jede Löschaktion (auch nach Fristablauf) bis explizit aufgehoben. Sauberes Vokabular für den DSGVO-vs-GoBD-Konfliktfall — Legal Hold als eigenes Flag/Objekt statt in die Fristenlogik selbst eingewoben. +- RM-Modul existiert weiterhin in Alfresco Community Edition (Grundfunktionen frei, eDiscovery/mehrstufige Freigabe-Workflows Enterprise-exklusiv). + +**Für archivdms:** Erweiterung von Punkt 1 im Fazit unten — `retain_until` könnte künftig als Step-Sequenz statt Einzelwert modelliert werden, sobald ereignisgetriggerte Fristen (Geschäftsjahresende, Vertragsende) gebraucht werden. Legal-Hold-Flag als eigenständiges Feld (unabhängig von `pending_deletion`-Status) vormerken für den DSGVO/GoBD-Konfliktfall. + +## Fazit für retention-compliance-Agent + +1. **Zweistufiges Löschen (ecoDMS-Muster)** in Retention-Regeln vorsehen: `retain_until` abgelaufen → `pending_deletion`, nicht sofort löschen. Feld `requires_review` bereits im Ausgabeformat vorhanden, gleiche Logik für finale Löschfreigabe nutzbar. +2. **Löschprotokoll** als eigenes Audit-Artefakt mitdenken, sobald Hard-Delete tatsächlich umgesetzt wird (aktuell nicht Scope des Agenten, aber Anschlussstelle). +3. Kein bestehendes OSS-System liefert vollständiges GoBD-Retention-Vorbild — archivdms-Ansatz (regelbasiert, strengste Regel gewinnt, DSGVO nie Vorrang vor gesetzlicher Pflicht) bleibt eigenständig zu verantworten. + +Quelle: siehe Memory `project_docspell_referenz` und `project_dms_vergleich_paperless_ecodms` (Recherche 2026-07-17). diff --git a/.claude/skills/devops-deploy/SKILL.md b/.claude/skills/devops-deploy/SKILL.md new file mode 100644 index 0000000..60f5442 --- /dev/null +++ b/.claude/skills/devops-deploy/SKILL.md @@ -0,0 +1,76 @@ +--- +name: devops-deploy +description: Server-Management, Deployment, Systemd-Dienste, nginx, Logs und Monitoring für das archivdms On-Premise-System auf root@192.168.1.204. Verwende diesen Skill für Deployments, Service-Neustarts, Log-Analyse, nginx-Konfiguration, Systemd-Units, oder wenn der Benutzer fragt "deploy", "server neu starten", "logs anschauen", "dienst läuft nicht". +--- + +# DevOps Deploy Agent — archivdms + +Du bist DevOps-Engineer für das archivdms On-Premise-System. +Du hast SSH-Zugriff auf den Server und führst Deployments, Diagnosen und Wartungsaufgaben durch. + +## Infrastruktur + +``` +Server: root@192.168.1.204 (Debian 13/trixie, unprivilegierter LXC-Container) +Backend: Go-Binary /opt/archivdms/bin/archivdms, Port 8080 intern, Systemd: archivdms +Frontend: Next.js standalone, Port 3000 intern, Systemd: archivdms-web +Reverse Proxy: nginx, Port 80/443 (selbstsigniertes Zertifikat, Let's-Encrypt optional) +Datenbank: PostgreSQL, Port 5432 (localhost only) +Manticore: geplant, noch nicht integriert +SFTP: eingebettet im archivdms-Binary (kein separater Dienst), Port konfigurierbar (config.yml sftp.enabled/bind) +Storage: /var/lib/archivdms/{inbox,store,ocr-tmp}, Owner archivdms:archivdms +Config: /etc/archivdms/config.yml +Cron: /etc/cron.d/archivdms-reminders (Wiedervorlage-Benachrichtigung) +``` + +## WICHTIG — kein Git-Remote + +archivdms hat KEIN Gitea/GitHub-Repository (Nutzervorgabe: lokal bleiben, kein Upload). Deploy läuft daher NICHT per `git pull`, sondern: + +```bash +# Quellcode vom Entwicklungsrechner auf den Server kopieren +rsync -az --exclude node_modules --exclude .next --exclude .git \ + /home/sysops/Dokumente/Scripte/archivdms/ root@192.168.1.204:/root/archivdms-src/ + +# Dann update.sh auf dem Server ausführen (baut aus lokalem Quellverzeichnis, kein git pull) +ssh root@192.168.1.204 'cd /root/archivdms-src && bash update.sh' +``` + +Für die allererste Installation (frischer Server): `install.sh` statt `update.sh` (legt System-User, Storage-Struktur, PostgreSQL-Rolle, nginx, systemd-Units an, ruft am Ende selbst `update.sh` für den Erstbuild auf). + +## Deploy-Workflow + +```bash +# Standard-Deploy (rsync + update.sh) +rsync -az --exclude node_modules --exclude .next --exclude .git \ + /home/sysops/Dokumente/Scripte/archivdms/ root@192.168.1.204:/root/archivdms-src/ +ssh root@192.168.1.204 'cd /root/archivdms-src && bash update.sh' + +# Nur Backend neu starten +ssh root@192.168.1.204 'systemctl restart archivdms' + +# Nur Frontend neu starten +ssh root@192.168.1.204 'systemctl restart archivdms-web' + +# Status/Health prüfen +ssh root@192.168.1.204 'systemctl is-active archivdms archivdms-web; ss -tlnp | grep -E ":80|:443|:3000|:2222"' + +# Logs +ssh root@192.168.1.204 'journalctl -u archivdms -n 100 --no-pager' +ssh root@192.168.1.204 'journalctl -u archivdms-web -n 100 --no-pager' +``` + +## Bekannte Stolpersteine + +- `npm ci` scheitert bei Erstinstallation ohne `package-lock.json` — `update.sh` hat dafür einen Fallback auf `npm install` (siehe update.sh-Kommentar), nicht wieder auf reines `npm ci` zurückbauen. +- Frisches/schlankes LXC-Template kann `rsync` fehlen — vor allererstem Code-Transfer prüfen (`ssh root@192.168.1.204 'which rsync'`), sonst `apt-get install -y rsync` zuerst. +- Go-Build lädt beim ersten Mal alle Module aus dem Internet (`go: downloading ...`) — braucht funktionierendes Netz auf dem Server, kein Vendor-Verzeichnis vorhanden. +- `update.sh` scheitert mit "Text file busy" wenn der Service beim Binary-Kopieren noch läuft — vorher explizit `systemctl stop archivdms`. + +## Sicherheitsregel + +Destruktive Aktionen (Datenbank droppen, `/var/lib/archivdms` löschen, Storage-Volume neu anlegen) NIEMALS ohne explizite Rückfrage beim Nutzer ausführen — WORM-Dokumente und Aufbewahrungsfristen sind GoBD-rechtlich relevant, Datenverlust ist hier kein "einfach nochmal machen"-Fehler. + +## Nach jedem Deploy + +DEVLOG.md um Zeit-Eintrag ergänzen (lokal im Projektverzeichnis, nicht auf dem Server) — Pflicht laut Projektregel. diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..f851214 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,122 @@ +# FDN-07: CI-Pipeline & Testharness +# +# WICHTIG: Dieser Workflow ist Gitea-Actions-Syntax (kompatibel zu GitHub +# Actions). Er wird erst aktiv, sobald dieses Repository zu einer Gitea- +# Instanz mit aktivierten Actions gepusht wird und dort ein Runner +# registriert ist. Aktuell (Stand FDN-07) existiert noch KEIN Gitea-Remote +# fuer archivdms - das Repo ist nur lokal mit `git init` angelegt. Bis zum +# Push/Runner-Setup laeuft diese Datei nicht, sie liegt bewusst schon bereit. +# +# Jobs: +# - backend-lint-test: go vet, go test ./... -cover gegen eine frische +# Postgres-Testdatenbank (Service-Container, pro +# Lauf neu erzeugt) +# - frontend-lint-test-build: npm ci, ESLint (next lint), tsc --noEmit, +# next build (Artefakt-Reproduzierbarkeit ueber +# Makefile-Target build-web) +# +# Roter Test/Lint bricht den jeweiligen Job ab (kein `continue-on-error`) +# und blockiert damit laut Branch-Protection-Regel (separat in Gitea zu +# konfigurieren: "Require status checks to pass before merging") den Merge. + +name: CI + +on: + push: + pull_request: + +env: + GO_VERSION: "1.26" + NODE_VERSION: "22" + +jobs: + backend-lint-test: + name: Backend (go vet, go test -cover) + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: archivdms_test + POSTGRES_PASSWORD: archivdms_test + POSTGRES_DB: archivdms_test + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U archivdms_test" + --health-interval 5s + --health-timeout 5s + --health-retries 10 + + env: + # Testdatenbank pro Lauf frisch (Service-Container startet leer und + # wird am Ende des Jobs verworfen). Verbindungsdaten ausschliesslich + # ueber Umgebungsvariablen, keine Zugangsdaten im Code. + ARCHIVDMS_TEST_DATABASE_URL: postgres://archivdms_test:archivdms_test@localhost:5432/archivdms_test?sslmode=disable + + steps: + - name: Code auschecken + uses: actions/checkout@v4 + + - name: Go einrichten + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + + - name: go vet + run: go vet ./... + + - name: go test mit Testabdeckung + run: go test ./... -cover -coverprofile=coverage.out + + - name: Testabdeckung ausweisen + run: go tool cover -func=coverage.out + + - name: Build-Artefakt reproduzierbar erzeugen + run: make build + + - name: Backend-Binary als Artefakt hochladen + uses: actions/upload-artifact@v4 + with: + name: archivdms-backend + path: bin/archivdms + + frontend-lint-test-build: + name: Frontend (ESLint, tsc, next build) + runs-on: ubuntu-latest + + steps: + - name: Code auschecken + uses: actions/checkout@v4 + + - name: Node einrichten + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - name: Abhaengigkeiten installieren + # Fallback auf `npm install`, falls package-lock.json fehlt oder + # nicht synchron ist (siehe Stolperstein in update.sh) - `npm ci` + # bricht in dem Fall hart ab, waehrend `npm install` den Lock + # aktualisiert und weiterlaeuft. + run: npm ci || npm install + + - name: ESLint + run: npm run lint + + - name: TypeScript-Typpruefung + run: npx tsc --noEmit + + - name: next build (Artefakt reproduzierbar erzeugen) + run: make build-web + env: + NEXT_PUBLIC_API_URL: http://localhost:8080 + + - name: Build-Ausgabe als Artefakt hochladen + uses: actions/upload-artifact@v4 + with: + name: archivdms-frontend + path: .next/standalone diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..959ffa7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +.next/ +bin/ +*.log +.env.local +config/config.yml +dms-kanban/ diff --git a/DEVLOG.md b/DEVLOG.md new file mode 100644 index 0000000..afdcb80 --- /dev/null +++ b/DEVLOG.md @@ -0,0 +1,6062 @@ +# archivdms – Dev Log + +## 2026-08-11 – FDN-07: CI-Pipeline & Testharness (Gitea Actions) + +**Zeit:** ca. 0,7 h (Ticket lesen, Makefile/go.mod/package.json/.eslintrc.json sichten, Workflow-Datei schreiben, README-Doku, Verifikation auf 192.168.1.204) +**Ziel:** Automatisierte Prüfstrecke für jeden Push, bisher gab es keine CI. + +`.gitea/workflows/ci.yml` neu angelegt (Gitea-Actions-Syntax, kompatibel zu GitHub Actions): Job `backend-lint-test` (`go vet ./...`, `go test ./... -cover` gegen frischen Postgres-Service-Container, danach `make build` als Artefakt), Job `frontend-lint-test-build` (`npm ci`/`npm install`-Fallback, `npm run lint`, `npx tsc --noEmit`, `make build-web` als Artefakt). Roter Schritt bricht den jeweiligen Job hart ab (kein `continue-on-error`) — blockiert Merge sobald in Gitea Branch-Protection auf diese Status-Checks gesetzt ist. README um Abschnitt "CI-Pipeline (FDN-07)" ergänzt. + +**Wichtig — Pipeline greift noch nicht:** archivdms hat kein Gitea-Remote, Repo ist nur lokal `git init`-isiert. Die Datei liegt bewusst bereit, wird aber erst nach Push zu einer Gitea-Instanz mit aktiviertem Runner scharf. + +**Verifikation auf 192.168.1.204 (nur Befehle geprüft, keine Produktivdienste verändert):** +- `go vet ./...` läuft durch (Exit 0), meldet aber pro Paket "missing go.sum entry" — **`go.sum` fehlt im Repo komplett** (weder lokal noch auf dem Server vorhanden). Das ist kein CI-Bug, sondern eine bestehende Repo-Lücke: ohne `go.sum` schlägt `go test ./...` in Paketen mit DB-/Crypto-Importen mit `[setup failed]` fehl (bestätigt: `internal/sftpserver`, `internal/storage`, `internal/tenantstore`, `internal/userstore`). Pakete ohne Fremdabhängigkeiten (`barcode`, `dateformat`, `llm`, `matching`, `ocr`, `thumbnail`) liefern `coverage: 0.0%` (keine Tests vorhanden — erwartet, `*_test.go`-Suche ergab 0 Treffer im ganzen Repo). +- `npm run lint` auf dem Server bricht mit `next: not found` ab, weil `/opt/archivdms-src` dort produktiv kein `node_modules` vorhält (Build läuft über `update.sh` in einem separaten Verzeichnis) — kein Hinweis auf ein Problem der CI-Konfiguration selbst, in der CI läuft `npm ci` vorher. +- **Offen/Folgeaufgabe (nicht Teil von FDN-07):** `go.sum` committen (`go mod tidy` mit funktionierender Toolchain), sonst schlägt der CI-Job `backend-lint-test` beim ersten echten Lauf fehl, sobald das Repo zu Gitea gepusht wird. + +**Nicht gemacht (Nutzerfreigabe nötig):** kein Commit, kein Branch `feature/fdn-07-ci-pipeline-testharness`, kein Push, kein Merge, kein Deploy — Ticket-Vorgabe "anhalten" nach Dateianlage befolgt. + +--- + +## 2026-08-02 – Bulk-Dokument-Export als ZIP (Backend) + +**Zeit:** ca. 0,8 h (Filter-Recherche ListDocuments/SearchQuery, Handler, CSV-Schema, Route, Audit-Event, Symbolprüfung) +**Ziel:** Mehrere Dokumente in einem Rutsch exportieren — als Vorstufe für den späteren DATEV-Formatter. + +**Endpunkt:** `POST /api/documents/export` (`s.auth`). Body entweder explizite IDs oder Filter: +```json +{"ids": [1,2,3]} +{"filter": {"doc_type_id": 4, "correspondent_id": 9, "tag_ids": [7,8], + "document_date_from": "2026-01-01", "document_date_to": "2026-03-31", + "uploaded_from": "2026-01-01", "uploaded_to": "2026-06-30"}} +``` +`ids` hat Vorrang, wenn beides gesetzt ist. Antwort: `application/zip`, `Content-Disposition: attachment; filename="export-bulk-.zip"`. Inhalt: pro Dokument `doc-/` mit Originaldatei + `metadata.json` + `ocr_text.txt` (identische Struktur/Feldnamen wie der Einzel-Export, `documentExportMetadata` wiederverwendet), auf oberster Ebene `index.csv` (Semikolon, UTF-8-BOM für Excel; Spalten `document_id;title;doc_type;correspondent;document_date;tags;uploaded_at` — bewusst gleiche snake_case-Namen wie in `metadata.json`, damit der DATEV-Formatter später aus einem Vokabular mappen kann) und `errors.txt` nur wenn etwas übersprungen wurde. + +**Filter statt neuer SQL:** `ListDocuments` kennt bislang keine Filterparameter (nur `tenantID` + `aclUserID`), `index.SearchQuery` deckt nur Volltext/Tag/DocType über Manticore ab. Deshalb Filterung in Go auf dem bereits tenant- und ACL-gescopten Ergebnis von `ListDocuments` — kein neuer Query-Pfad, an dem ein `tenant_id`-Filter fehlen könnte. Tag-Filter läuft zuletzt über `ListDocumentTags` je Kandidat. + +**ACL/Mandantentrennung:** jede ID einzeln — `GetDocument` (`WHERE tenant_id`) plus für Rolle `user` `IsDocumentVisible`; `domain_admin`/`superadmin` überspringen den Per-Dokument-Check. Nicht sichtbare/nicht gefundene/nicht lesbare Dokumente brechen den Request **nicht** ab, sondern landen mit Grund in `errors.txt`. + +**Grenze:** `maxBulkExportDocuments = 500`. Mehr IDs → 400 mit Klartext; ein Filter, der mehr als 500 trifft, wird ebenfalls mit 400 abgelehnt (statt still zu kürzen — ein unvollständiger Export darf nicht vollständig aussehen). + +**Audit:** neues Event `document_bulk_export` (`audit.EventDocumentBulkExport`), genau **ein** Eintrag pro Aufruf mit `Detail: "zip_bulk_export: exported= skipped="`, inkl. Fehlschlägen (decode_failed, empty_selection, too_many_ids, filter_too_broad, filter_invalid, list_failed, empty_result, stream_failed). + +**Dateien:** +- `internal/api/document_bulk_export_handlers.go` (neu): `handleBulkExportDocuments`, `writeBulkExportDoc`, `filterBulkExportDocs`, `parseBulkExportDate`, `bulkExportRequest`, `bulkExportFilter`, `bulkExportCSVHeader`, `maxBulkExportDocuments`. +- `internal/audit/audit.go`: `EventDocumentBulkExport`. +- `internal/api/server.go`: Route registriert (literaler Pfad, kollidiert nicht mit `/api/documents/{id}`). + +Kein Schema-Change → keine Migration. **Geprüfte Symbole** (kein Go-Toolchain lokal): `Store.GetDocument(ctx,id,tenantID) (*Document,error)`, `Store.ListDocuments(ctx,tenantID,*int64) ([]Document,error)`, `Store.IsDocumentVisible(ctx,id,tenantID,userID) (bool,error)`, `Store.DocumentTaxonomyNames(ctx,id,tenantID) (string,string,error)`, `Store.ListDocumentTags(ctx,docID,tenantID) ([]TaxonomyEntity,error)` (Feld `.ID`/`.Name`), `Store.ListDocumentFieldValues(ctx,docID,tenantID) ([]DocumentFieldValue,error)`, `auth.HasRole`, `userstore.RoleDomainAdmin`, `safeDownloadName(title,ext)`, `documentExportMetadata`/`exportCustomFields`/`exportCreatorName`, `audit.Entry`-Felder, `storage.Document`-Felder (`DocTypeID`, `CorrespondentID`, `DocumentDate`, `DocumentDateScore`, `CreatedAt`, `OCRText`). + +## 2026-08-01 – Export-Button in der Dokumentansicht (Frontend) + +**Zeit:** ca. 0,2 h (Aktionsleisten-Muster prüfen, Button ergänzen, TS-Konsistenz) +**Ziel:** Den fertigen Backend-Endpunkt `GET /api/documents/{id}/export` in der UI erreichbar machen. + +Umsetzung: In `DocumentPreview.tsx` in der Kopf-Aktionsleiste links neben "Neu verarbeiten" ein `