Steuer-Manager
Generation workspace for UStVA transfer aids and EÜR drafts based on client or uploaded documents.
What it does
tax-manager is the bundled Finanzen module for generating two reviewable tax-work products from documents:
- a UStVA overview for manual transfer into ELSTER, for one month or quarter
- an EÜR draft for the tax adviser, for one calendar year
The UStVA output is a transfer aid only. The module does not use ERiC, does not submit or file anything with ELSTER or a tax authority, and does not replace the checks required in ELSTER. The EÜR output is a draft for review by a tax adviser, not a completed tax return.
The module is generation-focused: users select source documents, configure the output, let the specialized agent extract and classify every document, and receive deterministically calculated HTML and PDF output. It is not an account, category, bookkeeping, or document-matching workspace.
How to open and enable
- An admin installs the module via
POST /api/module-store/install-bundledwith slugtax-manager, or uses the install action in Module Store. - Module migrations run once and are tracked in
module_schema_migrations. - The module appears as
Finanzenin the launchpad and opens at/modules/tax-managerwith its React renderer insrc/components/modules/tax-manager/.
Migrations 001 through 004 remain in the repository and their legacy tables may remain in existing databases so deployments do not lose data. The active generation workflow does not read or write those tables. Migration 005_generation_workspace.sql creates the current data model; migration 006_reseed_specialized_agent.sql updates the bundled steuer-manager specialist to the current four-tool contract.
Workflow
The UI has two top-level views. Übersicht combines a compact upload area with a searchable, month-grouped list of every visible beleg, rechnung, and ust document plus every visible file stored in the global Steuerbelege folder. Uploads may be assigned to a Mandant before transfer. Berichte remains the generation step machine with its persistent history rail:
- Source: optionally choose a Mandant and/or drop files/folders in the always-visible upload area. A selected Mandant is clearable. Dropped files are uploaded into the central
dokumentearchive and assigned to the globalSteuerbelegefolder; when a Mandant is selected, it is sent with each upload. - Configure: choose UStVA or EÜR and the applicable period. UStVA accepts a month or quarter; EÜR accepts a year. When a Mandant is selected, the module merges matching
beleg,rechnung, andustdocuments from the selected period with the files uploaded during the current source step. Session uploads remain visible, are deduplicated by document ID, and start pre-selected. - Generate: the module runs every selected file through the shared
scripts/document-extract.mjspipeline with bounded concurrency. The extractor reuses the file/config-hash OCR cache and performs local text/PDF extraction, Tesseract OCR, and configured vision fallback. Its capped text, method, and vision flag are stored with the generation document before the heuristic first pass. Extraction failures remain non-fatal: the item stayspendingwithextraction_failed. Thesteuer-manageragent then classifies every document, including unreadable, excluded, or duplicate items. - Calculate and render: the agent does not calculate totals. The module applies deterministic cent-based calculations, creates Prüfhinweise, and renders the stored HTML report.
- Review: the result view shows the report preview beside a compact Quelldokumente list with status, gross amount, quick view, and original-file download. The same preview/download actions are available in Übersicht and the candidate list. Deleted source documents retain their line-item snapshot without actions. Prüfhinweise remain in the generated HTML report. A correction is saved through the line-items endpoint and followed by another deterministic finalization.
The page context published through onContextChange contains the selected mandantId, generationId, workflow step, report kind, and top-level view (overview or reports) so chat and live-agent surfaces can understand the open workspace.
Both upload surfaces use POST /api/documents/upload with folder_id set to the tax_folder_id returned by bootstrap. The handler ensures a root-level global folder named Steuerbelege exists using an insert-with-conflict/re-select sequence, so concurrent first loads converge on the same folder ID.
Calculation rules
UStVA Kennzahlen subset
The first version supports only the following practical subset:
| Kennzahl | Meaning in this module | Deterministic treatment |
|---|---|---|
81 | Taxable revenue at 19% | Adds classified net and VAT cents |
86 | Taxable revenue at 7% | Adds classified net and VAT cents |
35_36 | Revenue at other tax rates | Adds classified net and VAT cents; rendered as 35/36 |
66 | Deductible input VAT on expenses | Adds classified VAT cents as input VAT |
83 | Zahllast | Calculated as VAT from 81 + 86 + 35_36 minus VAT from 66 |
sonstige | Not supported or not clearly assignable | Listed separately, excluded from all sums, and emitted as a Prüfhinweis |
Kennzahlen 48, 61, and 89 are out of scope in this version. Users must verify the supported subset and transfer the reviewed figures manually into ELSTER.
EÜR categories
The exact euer_category values accepted by reporting.mjs are:
| Direction | Values |
|---|---|
| Income | umsatz_19, umsatz_7, umsatz_steuerfrei, sonstige_einnahmen |
| Expense | waren_material, fremdleistungen, personal, raumkosten, versicherungen_beitraege, kfz, reise, werbung, telekommunikation_internet, buerobedarf, fortbildung_fachliteratur, bewirtung, geschenke, sonstige_ausgaben |
| Computed/reserved | gezahlte_vorsteuer is accepted by the line-item schema, while paid input VAT is calculated from expense VAT and rendered as its own report line |
For classified line items without a supported category, the calculator falls back to sonstige_einnahmen or sonstige_ausgaben according to direction. It totals net income plus received VAT, net expenses plus paid input VAT, and calculates Gewinn/Verlust as total income minus total expenses. The output is an adviser-facing draft; special cases such as Kleinunternehmer treatment remain outside this version's automated scope.
AfA handling
Line items marked afa_candidate are listed separately for tax-adviser review. They are excluded from the EÜR income and expense sums, contribute to the AfA candidate count and informational net total, and produce an afa_candidate Prüfhinweis. The marker is a review signal, not an automated depreciation decision.
Prüfhinweise
The deterministic report collector can emit these codes:
| Code | Trigger |
|---|---|
unreadable_document | Document status is unreadable |
pending_document | Classification is still pending |
possible_duplicate | The item is marked duplicate, carries that issue, or repeats counterparty + invoice number + gross amount |
missing_vat | A classified item has no VAT amount or carries that issue |
unassigned_kennzahl | UStVA classification is sonstige |
afa_candidate | The item requires AfA review |
date_outside_period | Document date is outside the configured period or the issue was submitted explicitly |
gross_net_mismatch | Net plus VAT differs from gross by more than 2 cents, or the issue was submitted explicitly |
vat_rate_mismatch | Calculated VAT from net and rate differs from the supplied VAT by more than 2 cents |
Additional submitted issue strings are preserved and shown with a generic message. Inconsistencies become review notes; the calculator never silently changes source amounts.
API reference
Base: /api/modules/tax-manager/api. All generation reads and writes are scoped to the authenticated owner_user_id.
| Method and path | Contract |
|---|---|
GET / | Returns module identity, legal_notice, and the active endpoint inventory |
GET /health | Returns { ok: true, module: "tax-manager", status: "running" } |
GET /bootstrap | Ensures the global Steuerbelege folder and returns its UUID as tax_folder_id together with up to 500 Mandanten, the owner's 50 most recent generations, per-user settings, and legal_notice |
GET /settings | Returns { settings: { agent_model_ref } } for the authenticated owner; the default is null |
PUT /settings, POST /settings | Upserts { agent_model_ref?: string | null } for the authenticated owner. Values are trimmed and capped at 240 characters; an empty value stores NULL and inherits the normal runtime default |
GET /overview-documents?q=…&mandant_id=…&limit=… | Returns visible beleg, rechnung, and ust documents plus visible documents in Steuerbelege, including untyped folder entries. Returns tax_folder_id and document id, titel, type/date/amount/currency, Mandant identity, and in_tax_folder; q filters titles with ILIKE, mandant_id filters primary or related Mandanten, and limit defaults to 200 with a maximum of 500 |
GET /candidate-documents?mandant_id=…&kind=…&year=…&period_kind=…&period_index=… | Requires mandant_id; resolves the month, quarter, or year and returns accessible beleg, rechnung, and ust documents linked to that Mandant. month/quarter are accepted aliases for period_index; limit defaults to 200 and is capped at 500 |
POST /generations | Body: { kind, period_kind, year, period_index?, mandant_id?, source, document_ids[] }. kind is ustva or euer; source is mandant or upload. Mandant source requires mandant_id; UStVA requires month/quarter and EÜR requires year. One to 500 accessible document IDs are required. Runs the shared cached OCR/vision extractor (maximum three files concurrently, 60-second per-file timeout), stores capped extraction output, and creates a draft generation with a heuristic line-item baseline |
GET /generations?mandant_id=…&kind=…&year=…&limit=… | Lists owner-scoped history newest first; default limit 50, maximum 200 |
GET /generations/:id | Returns one generation, all source-document line items, and legal_notice. Each document includes extracted_text (capped to 12,000 characters), extraction_method, and extraction_used_vision |
POST /generations/:id/line-items | Bulk-upserts line_items (alias items), each with a source document_id. Accepts statuses pending, classified, unreadable, excluded, duplicate; directions income, expense; supported EÜR categories and UStVA Kennzahlen; non-negative integer cent amounts; VAT rate 0–100; confidence 0–1; issues, extraction source, and notes. Sets generation status to extracted |
POST /generations/:id/finalize | Re-runs deterministic UStVA/EÜR calculations, stores totals/Kennzahlen/Prüfhinweise/HTML, and sets status finalized. It is safe to run again after corrections |
GET /generations/:id/html | Returns the stored finalized report as private, non-cached text/html; returns 409 before finalization |
GET /generations/:id/pdf | Renders the stored finalized HTML as an A4 PDF download; returns 409 before finalization |
DELETE /generations/:id | Deletes an owner-scoped generation; linked generation-document rows cascade |
POST /api/modules/tax-manager/run-agent is outside the bundled-module API base. It accepts { generation_id }, resolves steuer-manager, creates a dedicated new chat session per run (titled e.g. Steuer: UStVA-Übersicht Q2 2026 – <Mandant>, never the user's Hauptchat), creates the background specialist task in that session, marks the generation extracting, and returns the task and chat/pending-message IDs. The owner's agent_model_ref is copied to the dedicated session and passed as the specialist task's fallback model. Migration 007_module_settings.sql clears the bundled specialist's hardcoded default so this per-user fallback can apply; if an admin later sets a specialist default in the Agents panel, that default intentionally takes precedence. GET /api/modules/tax-manager/run-agent?task_id=… returns task state, tool statuses, and an optional reply preview for the generating step.
Data model
The active generation tables are created by bundled-modules/tax-manager/migrations/005_generation_workspace.sql; migration 007_module_settings.sql adds the per-user settings table, and migration 008_generation_document_text.sql adds the shared-extractor snapshot fields:
| Table | Purpose | Notable columns |
|---|---|---|
tax_manager_generations | Owner-scoped report generation and rendered result | mandant_id, source (mandant/upload), kind (ustva/euer), period fields, status (draft/extracting/extracted/finalized/failed), agent_task_id, totals_json, kennzahlen_json, pruefhinweise_json, html, error/finalization timestamps |
tax_manager_generation_documents | Per-source-document extraction and classification | generation_id, nullable document_id, filename snapshot, status, direction, euer_category, kennzahl, integer cent amounts, VAT rate, document date, counterparty/invoice number, afa_candidate, confidence, issues, extraction source, notes, extracted_text, extraction_method, extraction_used_vision |
tax_manager_settings | Per-user module settings | Text owner_user_id primary key, nullable agent_model_ref, created/updated timestamps |
The (generation_id, document_id) pair is unique while document_id is present. Deleting a generation cascades to its line items; deleting a central document keeps the line-item snapshot and sets document_id to NULL.
Legacy tables introduced by migrations 001 through 004 remain in deployed databases for compatibility and data preservation, but the current handler and UI do not use them. They must not be treated as the active Steuer-Manager model.
Specialized agent and tools
The bundled specialized agent has handle steuer-manager and uses the clapilot-tax-manager plus clapilot-document-extraction skills. Its current Steuer-Manager tools are:
| Tool | Purpose |
|---|---|
tax_manager_list_generations | List owner-scoped generations with optional Mandant, kind, and year filters |
tax_manager_get_generation | Load one generation and every current source-document line item, including ready-to-classify extracted_text and extraction metadata |
tax_manager_submit_extraction | Submit bulk per-document extraction/classification results; no source document may be silently omitted |
tax_manager_finalize_generation | Invoke deterministic calculation and HTML rendering, then request a module refresh |
The specialist also receives documents_list, documents_get, mandanten_list, and mandanten_get. It treats extracted_text as the primary source, calls documents_get only when that field is missing or empty, and marks a document unreadable only if both sources have no usable content. Live Voice intentionally exposes only the two read tools; extraction and finalization belong to the specialized-agent workflow. The complete tool contracts are documented in Agent Tool Contracts.
Legal and operational boundaries
- Every report carries: “Dieser Bericht ist ein Entwurf und dient ausschließlich der Vorbereitung. Er stellt keine Steuerberatung dar und wird weder an ELSTER noch an Finanzbehörden oder andere Dritte übermittelt.”
- No endpoint performs ERiC integration, ELSTER submission, tax-office filing, or transmission to a third party.
- Agent classifications, OCR/vision output, Kennzahlen, categories, AfA candidates, dates, and amounts require human review.
- UStVA output is only a manual ELSTER transfer aid. EÜR output is only a draft for the tax adviser.
- The operator remains responsible for legal basis, access control, retention, data-processing agreements, and GDPR compliance for Mandanten data.
Troubleshooting
- No Mandant documents are offered: the document must be
beleg,rechnung, orust, fall inside the selected period bydatumor creation date, be accessible to the user, and be linked throughdokumente.mandant_idordocument_mandanten. - A file is missing from Übersicht: it must be visible to the user and either use a tax-candidate type or belong to the global
Steuerbelegefolder. - Generation does not advance: inspect the run-agent task status and confirm that
steuer-manageris enabled with the current four-tool allowlist. - A document cannot be read: submit it as
unreadablewith issues and notes; do not omit it. The finalized output will retain a Prüfhinweis. - HTML/PDF returns
409: finalize the generation first. After editing line items, finalize again to refresh deterministic totals and rendered output.
