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

  1. An admin installs the module via POST /api/module-store/install-bundled with slug tax-manager, or uses the install action in Module Store.
  2. Module migrations run once and are tracked in module_schema_migrations.
  3. The module appears as Finanzen in the launchpad and opens at /modules/tax-manager with its React renderer in src/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:

  1. 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 dokumente archive and assigned to the global Steuerbelege folder; when a Mandant is selected, it is sent with each upload.
  2. 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, and ust documents 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.
  3. Generate: the module runs every selected file through the shared scripts/document-extract.mjs pipeline 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 stays pending with extraction_failed. The steuer-manager agent then classifies every document, including unreadable, excluded, or duplicate items.
  4. 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.
  5. 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:

KennzahlMeaning in this moduleDeterministic treatment
81Taxable revenue at 19%Adds classified net and VAT cents
86Taxable revenue at 7%Adds classified net and VAT cents
35_36Revenue at other tax ratesAdds classified net and VAT cents; rendered as 35/36
66Deductible input VAT on expensesAdds classified VAT cents as input VAT
83ZahllastCalculated as VAT from 81 + 86 + 35_36 minus VAT from 66
sonstigeNot supported or not clearly assignableListed 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:

DirectionValues
Incomeumsatz_19, umsatz_7, umsatz_steuerfrei, sonstige_einnahmen
Expensewaren_material, fremdleistungen, personal, raumkosten, versicherungen_beitraege, kfz, reise, werbung, telekommunikation_internet, buerobedarf, fortbildung_fachliteratur, bewirtung, geschenke, sonstige_ausgaben
Computed/reservedgezahlte_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:

CodeTrigger
unreadable_documentDocument status is unreadable
pending_documentClassification is still pending
possible_duplicateThe item is marked duplicate, carries that issue, or repeats counterparty + invoice number + gross amount
missing_vatA classified item has no VAT amount or carries that issue
unassigned_kennzahlUStVA classification is sonstige
afa_candidateThe item requires AfA review
date_outside_periodDocument date is outside the configured period or the issue was submitted explicitly
gross_net_mismatchNet plus VAT differs from gross by more than 2 cents, or the issue was submitted explicitly
vat_rate_mismatchCalculated 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 pathContract
GET /Returns module identity, legal_notice, and the active endpoint inventory
GET /healthReturns { ok: true, module: "tax-manager", status: "running" }
GET /bootstrapEnsures 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 /settingsReturns { settings: { agent_model_ref } } for the authenticated owner; the default is null
PUT /settings, POST /settingsUpserts { 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 /generationsBody: { 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/:idReturns 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-itemsBulk-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/finalizeRe-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/htmlReturns the stored finalized report as private, non-cached text/html; returns 409 before finalization
GET /generations/:id/pdfRenders the stored finalized HTML as an A4 PDF download; returns 409 before finalization
DELETE /generations/:idDeletes 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:

TablePurposeNotable columns
tax_manager_generationsOwner-scoped report generation and rendered resultmandant_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_documentsPer-source-document extraction and classificationgeneration_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_settingsPer-user module settingsText 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:

ToolPurpose
tax_manager_list_generationsList owner-scoped generations with optional Mandant, kind, and year filters
tax_manager_get_generationLoad one generation and every current source-document line item, including ready-to-classify extracted_text and extraction metadata
tax_manager_submit_extractionSubmit bulk per-document extraction/classification results; no source document may be silently omitted
tax_manager_finalize_generationInvoke 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, or ust, fall inside the selected period by datum or creation date, be accessible to the user, and be linked through dokumente.mandant_id or document_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 Steuerbelege folder.
  • Generation does not advance: inspect the run-agent task status and confirm that steuer-manager is enabled with the current four-tool allowlist.
  • A document cannot be read: submit it as unreadable with 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.