No description
  • JavaScript 86.5%
  • CSS 11%
  • Shell 1.6%
  • PowerShell 0.4%
  • HTML 0.3%
  • Other 0.2%
Find a file
Daniel Heinze | ho.Systeme GmbH + Co. KG 8aaf20f919
All checks were successful
Tests / node (push) Successful in 19s
README: Node ab Version 22
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 16:40:09 +01:00
.forgejo/workflows Node 24 im Image, Sicherheitsupdates, immo.sh auffrischen mit Zeitplan, Test-Workflow 2026-10-25 16:01:58 +01:00
data Immobilienverwaltung 0.9.0 2026-09-09 20:59:21 +02:00
deploy auffrischen auch fuer den Caddy-Proxy, Fortschritt des Volltext-Laufs (0.34.0) 2026-10-25 16:25:45 +01:00
docs/bilder Immobilienverwaltung 0.9.0 2026-09-09 20:59:21 +02:00
public auffrischen auch fuer den Caddy-Proxy, Fortschritt des Volltext-Laufs (0.34.0) 2026-10-25 16:25:45 +01:00
src auffrischen auch fuer den Caddy-Proxy, Fortschritt des Volltext-Laufs (0.34.0) 2026-10-25 16:25:45 +01:00
test Test Texterkennung: ImageMagick-PDF-Modul, Pruefung des Testdokuments 2026-10-25 15:46:24 +01:00
werkzeuge Anmeldung, Rollen und Protokoll aus tool-defaults (0.25.0) 2026-10-14 20:12:30 +02:00
.dockerignore An tool-defaults angebunden: Update fuer zwei Quellen (0.22.0) 2026-10-14 19:40:58 +02:00
.gitattributes An tool-defaults angebunden: Update fuer zwei Quellen (0.22.0) 2026-10-14 19:40:58 +02:00
.gitignore Hintergrund einstellbar, Telefonansicht ohne Seitwaerts-Scrollen (0.27.0) 2026-10-14 21:04:11 +02:00
CHANGELOG.md auffrischen auch fuer den Caddy-Proxy, Fortschritt des Volltext-Laufs (0.34.0) 2026-10-25 16:25:45 +01:00
Dockerfile Node 24 im Image, Sicherheitsupdates, immo.sh auffrischen mit Zeitplan, Test-Workflow 2026-10-25 16:01:58 +01:00
package-lock.json Node 24 im Image, Sicherheitsupdates, immo.sh auffrischen mit Zeitplan, Test-Workflow 2026-10-25 16:01:58 +01:00
package.json auffrischen auch fuer den Caddy-Proxy, Fortschritt des Volltext-Laufs (0.34.0) 2026-10-25 16:25:45 +01:00
README.md README: Node ab Version 22 2026-10-25 16:40:09 +01:00
server.js Texterkennung unter Linux (Docker), Volltext der Ablage, Volltextsuche 2026-10-25 15:40:59 +01:00
start.cmd Immobilienverwaltung 0.9.0 2026-09-09 20:59:21 +02:00

Immobilienverwaltung

Private Verwaltung der vermieteten Eigentumswohnungen: Stammdaten, Mietverhältnisse, Mieterhöhungen, Darlehen, Konten, Fristen — und die Dokumentenablage direkt in der Dropbox (oder per Abgleich mit Dropbox bzw. Google Drive).

Läuft lokal (Node ab Version 22, empfohlen 24 LTS, einmalig npm install) oder auf einem Server als Docker-Container. Lokal hört der Server nur auf 127.0.0.1. Gemeinsame Funktionen kommen aus tool-defaults (src/defaults/, public/assets/).

Ausführliche Anleitung zu jedem Bereich: Wiki.

Übersicht mit Portfolio-Kennzahlen und Jahresrechnung

Funktionsumfang

Bereich Was es kann
Portfolio-Übersicht Kennzahlen über alle Objekte, Objekttabelle, Jahresrechnung mit AfA und Steuerwirkung, anstehende Fristen
Objekt-Assistent Neues Objekt Schritt für Schritt: Stammdaten, Kauf, Verwaltung und Konten, Einheiten und Mieter, Finanzierung, Cloud-Ablage – Pflichtangaben einstellbar
Cloud-Abgleich Ablage je Objekt mit einem Ordner in Dropbox oder Google Drive abgleichen, in beide Richtungen, per Knopf und nach Zeitplan
Objekt Stammdaten, Kaufkosten, laufende Rechnung, steuerliche Wirkung, Kostenstände mit Gültigkeitsdatum, Darlehen und Konten
Eigenkapital-Rückfluss Ab wann Anzahlung und Kaufnebenkosten wieder da sind – über die Kasse und mit Tilgung, mit und ohne geplante Mieterhöhungen, je Objekt und fürs Portfolio
Einheiten & Mieter Mietverhältnisse je Einheit, Kaution, Historie und Planung der Mietanpassungen (Kaltmiete, Nebenkosten, Mietzuschuss)
Zahlungen Soll/Ist je Monat, Einbehalte von Verwaltung und SEV, Mietzuschuss als eigene Buchung, Erfassen direkt am Monat, Serienerfassung über Zeiträume
Finanzierung Darlehen mit Restschuld, Tilgungsplan, Zinsbindung, Bereitstellungsphase
Konten & Verwaltung WEG-Hausverwaltung und SEV aus dem Adressbuch, Mietkonten, Kautionskonten
Dokumente Einheitliche Ordnerstruktur, Upload per Drag & Drop, Texterkennung (Windows oder Tesseract im Docker-Image), „Dokumente einlesen“ mit Zuordnungsvorschlag, Suche in Dateinamen und Volltext
Adressbuch Verwaltungen, Banken, Handwerker objektübergreifend, mit Verwendungsnachweis
Unterlagen Vollständigkeitsprüfung: welche Unterlagen zu einem vermieteten Objekt gehören und welche davon in der Ablage liegen – am Dateinamen oder an der Überschrift im Dokument erkannt
Instanzen & Anmeldung Mehrere getrennte Instanzen als Container, Anmeldung über Microsoft Entra ID (Gruppenfreigabe) oder Passwort, Name, Farben und eigenes CSS je Instanz
Hinweis-Mails Fristen, fehlende Pflichtunterlagen und ausbleibende Zahlungen per E-Mail, ohne Wiederholungen
Fristen Abgeleitet aus Zinsbindung, Mieterhöhungen, Sperrfrist (§ 558 BGB) und Betriebskostenfrist (§ 556 Abs. 3 BGB)

Für welche Situationen es gebaut ist

  • Miete läuft über die Sondereigentumsverwaltung – die zieht Hausgeld und Gebühren ab und überweist nur den Rest. Wirtschaftlicher Cashflow und tatsächliche Liquidität werden getrennt ausgewiesen.
  • Beträge ändern sich über die Jahre – Kostenstände gelten ab einem Monat, rückwirkend erfasste Zeiträume rechnen mit den damaligen Werten.
  • Zeit vor dem Nutzen-Lasten-Übergang – Bereitstellungszinsen laufen, Miete gibt es noch nicht, der Verkäufer zahlt vielleicht eine Entschädigung.
  • Papierberg aus dem Kauf – ein Regelwerk sortiert Altbestand und Posteingang nach Dateinamen vor, entschieden wird per Häkchen.

Starten

cd <projektordner>
npm start

Danach im Browser: http://localhost:4711 Bequemer: Doppelklick auf start.cmd.

Wo liegen die Daten?

Was Wo
Dokumente %USERPROFILE%\Dropbox\Immobilien (die Dropbox wird automatisch gefunden)
Stammdaten data\daten.json (im Projektordner)
Gelöschte Dokumente …\Dropbox\Immobilien\_Papierkorb\<Datum>\…

Anderer Ablageort: Umgebungsvariable IMMO_BASIS vor dem Start setzen.

Ordnerstruktur je Objekt

Jedes Objekt bekommt dieselben Ordner — im Explorer wie in der Web-Oberfläche:

<Objekt>/
  00_Posteingang                     noch nicht zugeordnet
  01_Objektunterlagen                Exposé, Grundriss, Wohnfläche, Energieausweis
  02_Kauf-und-Notar                  Kaufvertrag, Notar, Grunderwerbsteuer, Überweisungen
  03_Grundbuch-und-Recht             Grundbuch, Teilungserklärung, Vollmachten
  04_Finanzierung                    Darlehen, ESIS, Grundschuld, Zinsbescheinigung
  05_WEG-Hausverwaltung              Hausgeld, Wirtschaftsplan, ETV, Verwaltervertrag
  06_SEV-Sondereigentumsverwaltung   SE-Vertrag, SE-Vollmacht, SEV-Abrechnungen
  07_Mietverhaeltnisse/<Einheit>     Mietvertrag, Mieterhöhung, Übergabe, Kündigung
  08_Nebenkosten-und-Heizkosten      Betriebskosten- und Heizkostenabrechnung
  09_Instandhaltung-und-Handwerker   Angebote, Rechnungen, Gewährleistung
  10_Mietkonto-und-Zahlungen         Kontoauszüge, Mieteingänge, Kaution
  11_Steuer-und-AfA                  Anlage V, Nutzungsdauergutachten
  12_Versicherungen                  Gebäudeversicherung, Schäden
  13_Fotos-und-Plaene                Fotos, Zustandsdokumentation

Daneben liegt _Portfolio für alles ohne Objektbezug (Steuerbescheide, persönliche Unterlagen, Bankunterlagen, Ankaufsprüfungen, Auswertungen).

Neue Objekte und neue Einheiten legen ihre Ordner automatisch an. „Einstellungen → Dokumentenablage → Standardstruktur anlegen / ergänzen" zieht fehlende Ordner nach.

Altbestand einsortieren

npm run vorschlag       # Zuordnungsliste erzeugen, nichts wird geschrieben
npm run einsortieren    # Dateien kopieren (Quelle bleibt unverändert)

Der Vorschlag landet in berichte\einsortier-vorschlag.html (zum Durchsehen) und …csv (für Excel). Quelle ist …\Dropbox\Daniel Rabe; die Regeln stehen in src\regeln.js und lassen sich dort ergänzen.

Inhaltsgleiche Dateien, die im Ziel schon liegen, werden übersprungen — der Lauf lässt sich also gefahrlos wiederholen.

Was die Oberfläche kann

  • Übersicht — Mieteinnahmen, Cashflow, tatsächliche Liquidität, Restschuld und Rendite je Objekt und in Summe
  • Objekt — Stammdaten, Kaufkosten, laufende Rechnung, steuerliche Wirkung, Kostenstände
  • Einheiten & Mieter — Mietverhältnisse, Kaution, Historie und Planung der Mietanpassungen; ein doppelt angelegtes Mietverhältnis entfernt der Papierkorb neben „+ Mieterhöhung“
  • Zahlungen — Soll/Ist je Monat: was hätte kommen müssen, was kam an, was SEV oder Hausverwaltung vorher einbehalten haben. Gleichbleibende Beträge lassen sich über „Für Monat … bis Monat" in einem Rutsch für einen ganzen Zeitraum buchen; bereits gebuchte Monate werden dabei übersprungen. Jede Buchung ist nachträglich änderbar. Jede Monatszeile ohne Eingang hat „+ Eingang“, Monate mit erwartetem Mietzuschuss „+ Zuschuss“ – vorbelegt mit der letzten Buchung derselben Art davor (Betrag, Einbehalte, Konto, Tag im Monat). Den Mietzuschuss bucht „+ Mietzuschuss“ eigens, auch als Sammelzahlung über mehrere Monate.
  • Finanzierung — Darlehen mit Restschuld und Tilgungsplan
  • Konten & Verwaltung — Hausverwaltung, SEV, Mietkonten
  • Dokumente — Ordner durchblättern, hochladen (Drag & Drop), umbenennen, verschieben, suchen
  • Adressbuch — Verwaltungen, Banken, Handwerker objektübergreifend
  • Fristen — abgeleitet aus Zinsbindung, geplanten Mieterhöhungen, Sperrfrist (§ 558 BGB) und Betriebskostenfrist (§ 556 Abs. 3 BGB), dazu eigene Termine

Neues Objekt anlegen (Assistent)

„+ Objekt anlegen“ öffnet den Assistenten (#/neu). Er fragt in sieben Schritten ab: Stammdaten → Kauf & Steuer (mit Eigenkapital-Einlagen) → Verwaltung & Konten (WEG-Verwaltung und SEV aus dem Adressbuch oder neu, erster Kostenstand mit Hausgeld, Konten) → Einheiten & Mieter (je Einheit optional das Mietverhältnis, mit Miet- und Kautionskonto aus dem vorigen Schritt) → Finanzierung (Darlehen mit Darlehenskonto) → Cloud-Ablage → Prüfen & anlegen. Angelegt wird erst am Ende, alles in einem Zug samt Ordnerstruktur; bis dahin bleibt der Entwurf im Browser erhalten.

Was Pflicht ist, legt ein Administrator unter „Einstellungen → Objekt-Assistent“ fest: je Gruppe die Felder (bei Listen je Eintrag) und was mindestens erfasst sein muss. Vorgabe: Straße, PLZ, Ort und Status am Objekt, Bezeichnung je Einheit, Mieter/Beginn/Kaltmiete je Mietverhältnis, Bank/Betrag/Zins je Darlehen und mindestens eine Einheit.

Abgleich mit Dropbox und Google Drive

Je Objekt lässt sich die Ablage mit einem Ordner in der Dropbox oder in Google Drive abgleichen – gedacht vor allem für Instanzen auf dem Server, deren Ablage sonst nur per Hochladen gefüllt wird. Verbunden wird einmal unter „Einstellungen → Cloud-Speicher“ (Anleitung für die Dropbox-App bzw. den Google-OAuth-Client steht dort, samt der Redirect URI); verknüpft wird je Objekt auf der Dokumentenseite oder im Assistenten. Abgeglichen wird per Knopf, nach dem Anlegen und nach Zeitplan (Vorgabe: stündlich, 0 = aus).

  • Beide Richtungen: Neues und Geändertes geht jeweils auf die andere Seite.
  • Die erste Runde – auch nach einem Wechsel des Ordners oder Anbieters – löscht nichts.
  • Hier gelöscht → beim Anbieter gelöscht (dort 30 Tage wiederherstellbar); dort gelöscht → hier im Papierkorb der Ablage.
  • Beide Seiten geändert → beide Fassungen bleiben, die hiesige als „… (Konflikt hier)“.
  • Würde eine Runde mehr als die Hälfte der Dateien löschen oder fehlt ein Ordner, hält sie an.
  • Google-Dokumente, -Tabellen usw. haben keinen Dateiinhalt und bleiben außen vor.
  • Bei Google den OAuth-Zustimmungsbildschirm auf „In Produktion“ stellen – im Status „Test“ laufen die Zugänge nach 7 Tagen ab.
  • Liegt die Ablage lokal schon im Dropbox-Ordner, bleibt der Dropbox-Abgleich aus (Google Drive geht trotzdem).

Hausgeld, SEV-Gebühr und Nebenkosten

Am Objekt (Stammdaten) werden getrennt erfasst:

Feld Bedeutung
Hausgeld an die WEG / Monat Gesamtbetrag laut Wirtschaftsplan
davon umlagefähig holt der Mieter über die Nebenkostenvorauszahlung zurück
davon Instandhaltungsrücklage erst bei Verwendung durch die WEG steuerlich absetzbar
SEV-Gebühr / Monat Sondereigentumsverwaltung, geht nicht an die WEG

Ist beim Mieter eine Nebenkostenvorauszahlung hinterlegt, rechnet der Cashflow mit Warmmiete gegen volles Hausgeld. Ist keine hinterlegt, wird nur der nicht umlagefähige Teil belastet — sonst wäre das Ergebnis künstlich negativ.

Einzelposten wie Grundsteuer oder Kontogebühren gehören unter „Laufende Kosten" auf der Objektseite (Bezeichnung, Betrag je Monat oder Jahr).

Objektseite mit SEV-Rechnung

Miete läuft über die Sondereigentumsverwaltung

Kassiert die SEV die Miete, zahlt davon Hausgeld und Gebühren und überweist nur den Rest, wird am Objekt „Miete läuft über die SEV" gesetzt und die vereinbarte Ausschüttung eingetragen. Dann gilt:

  • Cashflow = wirtschaftliches Ergebnis (Einnahmen − alle Kosten inkl. Rate)
  • Auf dein Konto = Ausschüttung + Mietzuschuss direkt vom Verkäufer − Darlehensrate; das ist, was du tatsächlich merkst
  • Die Differenz zwischen rechnerischem Überschuss und Ausschüttung wird als Puffer bei der SEV ausgewiesen
  • Im Reiter „Zahlungen" ist dann die Ausschüttung das Soll (plus Mietzuschuss) — nicht die volle Miete, denn nur sie kommt bei dir an. Fehlt für Monate nach dem Nutzen-Lasten-Übergang die Ausschüttung im Kostenstand, nennt der Hinweis diese Monate.

Mietzuschuss des Verkäufers

Zahlt der Verkäufer eine Zeit lang einen Mietzuschuss, steht er am Mietverhältnis („Mietzuschuss Verkäufer“) und lässt sich über Mietanpassungen ändern oder beenden. Er zählt zu den Mieteinnahmen. Läuft die Miete über die SEV, zahlt der Verkäufer ihn meist direkt: Dann kommt er zur Ausschüttung hinzu – im Soll der Zahlungen wie bei „Auf dein Konto“ – und nicht in den rechnerischen Überschuss der SEV. Steckt er stattdessen schon in der Ausschüttung, das Häkchen „Zuschuss läuft über die SEV“ am Mietverhältnis setzen; dann wird er nicht eigens erwartet.

Eingänge bucht „+ Mietzuschuss“ (Reiter „Zahlungen“) als eigene Buchungsart; sie zählen zum Eingang und stehen in der Aufschlüsselung.

Zahlungen mit Soll/Ist je Monat

Kostenstände: was ab wann gilt

Hausgeld, SEV-Gebühr, Einzelposten und Ausschüttung ändern sich über die Jahre. Sie stehen deshalb nicht fest am Objekt, sondern in Kostenständen mit „gültig ab Monat" (Objektseite → Überblick → Kostenstände). Der jüngste Stand, der zum betrachteten Monat schon galt, ist maßgeblich — rückwirkend erfasste Monate rechnen also weiter mit den damaligen Beträgen, auch im Reiter „Zahlungen".

Ein neuer Stand übernimmt den bisherigen als Vorlage; meist ändert man nur ein, zwei Beträge und trägt den Monat ein, ab dem er gilt. Über „⋯" werden die Einzelposten (Grundsteuer, Kontogebühren …) eines Standes gepflegt.

Beim Mieter funktioniert dasselbe über die Mietanpassungen: Eine Anpassung mit Status „vertraglich vereinbart" oder „erfolgt" setzt ab ihrem Datum neue Kaltmiete, Nebenkostenvorauszahlung und/oder Mietzuschuss. Leere Felder bleiben unverändert.

Vor dem Nutzen-Lasten-Übergang

Zwischen Kaufvertrag und Übergang gehören Miete und Bewirtschaftung noch dem Verkäufer, während beim Käufer schon Finanzierungskosten laufen. Dafür gibt es:

  • Nutzen und Lasten ab (Stammdaten) — vorher rechnet das Tool weder Miete noch Hausgeld
  • Bereitstellungszinsen am Darlehen (Betrag je Monat, von/bis) — reine Kosten ohne Tilgung, steuerlich sofort abziehbar; ab dem Beginn der Auszahlung greift die reguläre Rate
  • Sonstige Einnahmen im Kostenstand — etwa eine Entschädigung des Verkäufers für diese Zeit, mit eigener Bezeichnung

Adressbuch

Hinweise per E-Mail

Unter „Einstellungen → Hinweis-Mails“ lässt sich der Versand einrichten – über einen SMTP-Server (Server, Port, Benutzer, Passwort) oder über Microsoft Graph (Exchange Online, App-Registrierung mit Mail.Send), dazu Absender und Empfänger. Gemeldet werden wahlweise:

  • Fristen und Termine – anstehende Mieterhöhungen, Zinsbindung, Betriebskostenfrist
  • fehlende Pflichtunterlagen aus der Vollständigkeitsprüfung
  • ausbleibende Zahlungseingänge – wenn für den Vormonat keiner erfasst ist

Dieselbe Sache landet nicht jeden Tag erneut im Postfach: Der Speicher merkt sich, was gemeldet wurde, und meldet erst wieder, wenn eine Frist in eine dringendere Stufe rutscht (später → dieser Monat → diese Woche → überfällig). Die Vorschau in den Einstellungen zeigt jederzeit, was gemeldet würde und was davon neu ist.

Das Passwort wird verschlüsselt gespeichert (AES-256-GCM). Der Schlüssel steht in IMMO_KEY oder wird beim ersten Mal in .cache\secret.key erzeugt; beides liegt außerhalb der Datendatei. An die Oberfläche wird das Passwort nie zurückgegeben.

Solange der Server läuft, wird zyklisch geprüft. Wer den Rechner nicht durchlaufen lässt, richtet zusätzlich eine geplante Aufgabe ein:

npm run hinweise:vorschau   # anzeigen, was gemeldet würde
npm run hinweise:test       # Testmail verschicken
npm run hinweise            # prüfen und ggf. verschicken

schtasks /create /tn "Immobilien-Hinweise" /sc daily /st 08:00 ^
  /tr "node \"<projektordner>\werkzeuge\benachrichtigen.js\""

Versendet wird über mailer.js aus tool-defaults (src/defaults/mailer.js, SMTP über nodemailer oder Microsoft Graph).

Adressbuch

Hausverwaltungen, SEV, Banken und Handwerker stehen unter „Adressbuch" und werden am Objekt nur referenziert — eine Verwaltung, die mehrere Objekte betreut, wird also einmal gepflegt. Objektbezogene Angaben (Objektnummer, Vertrag seit, Kündigungsfrist, Gebühr) bleiben beim Objekt. Die Adressbuchliste zeigt zu jedem Eintrag, bei welchen Objekten er verwendet wird.

Dokumentenablage

Posteingang und „Dokumente einlesen“

Hochgeladene Dateien landen auf Objektebene standardmäßig in 00_Posteingang; im Upload-Bereich lässt sich der Zielordner aber direkt auswählen. Der Knopf „Dokumente einlesen“ (Seite „Dokumente“, Hinweis in der Übersicht, Dokumente-Reiter eines Objekts, „Unterlagen“, „Einstellungen“) prüft die Posteingänge: das Regelwerk aus src\regeln.js – dasselbe wie beim Einsortieren des Altbestands – schlägt je Datei einen Zielordner vor, änderbar über ein Auswahlfeld (alle Ordner aller Objekte, auch zum Umhängen auf ein anderes Objekt). Verschoben wird nur, was angehakt ist — unsichere Vorschläge sind bewusst nicht vorausgewählt. Liegt im Zielordner schon eine inhaltsgleiche Datei (unter beliebigem Namen), wird das gemeldet und die Datei nicht vorausgewählt. Nach einem Upload in den Posteingang öffnet sich der Vorschlag automatisch.

Inhalt statt nur Dateiname. PDFs und Bilder werden mit einer Texterkennung gelesen (src/texterkennung.js), je nach Rechner auf einem von zwei Wegen:

Wo Wie
Windows (lokal) die Texterkennung, die Windows mitbringt (src\texterkennung.ps1: Windows.Data.Pdf + Windows.Media.Ocr)
Linux / Docker-Image pdftotext liest die Textebene digital erzeugter PDFs (ganzer Text, exakt); Scans rendert pdftoppm, tesseract (deutsch) erkennt den Text

Gelesen werden bis zu zehn Seiten je Scan (IMMO_OCR_SEITEN); immer nur eine Erkennung zur Zeit, Tesseract auf einem Kern. Der Dateiname hat Vorrang; gibt er nichts her, gilt der Inhalt: das Objekt, das der Text am häufigsten nennt, die Kategorie nach den meisten Regeltreffern, Briefkopf und Betreff doppelt gewichtet. Nennt ein Dokument mehrere Objekte, wird je weiterem Objekt eine Kopie in derselben Kategorie angeboten – vorausgewählt, wenn der Inhalt sie gleich oft nennt oder der Dateiname mehrere nennt. Für nichtssagende Scannernamen (CCO_000011.pdf, Scan 3.pdf) wird ein Name aus Briefdatum und Betreff vorgeschlagen („2026-09-22 Auszahlungsanweisung … .pdf“). Das Ergebnis der Texterkennung liegt je Dateiinhalt in .cache/texterkennung (dazu index.json mit den Prüfsummen je Pfad); ein zweiter Lauf ist dadurch sofort fertig. Ohne Texterkennung bleibt es bei der Zuordnung nach Dateinamen.

Volltext der ganzen Ablage. Der Volltext-Lauf (src/volltext.js) liest jedes PDF und Bild der Ablage außer Fotos, dessen Text noch nicht bekannt ist – auf dem Server von selbst (30 Sekunden nach dem Start, nach jedem Hochladen und stündlich, damit auch Dateien aus dem Cloud-Abgleich dazukommen), lokal auf Knopfdruck unter „Einstellungen → Dokumentenablage → Texterkennung und Volltext“; dort steht auch, wie viel gelesen ist. IMMO_VOLLTEXT=an|aus überschreibt die Vorgabe. Die Suche (Kopfzeile, Seite „Dokumente“) findet dann auch Treffer im Text: alle Suchwörter müssen vorkommen, ein Auszug zeigt die Fundstelle; Treffer im Dateinamen stehen vorn.

Ablage prüfen. Derselbe Abgleich für schon abgelegte Dokumente – die ganze Ablage (Seite „Dokumente“) oder ein Objekt (Dokumente-Reiter). Der Dialog zeigt nur Abweichungen: anderer Ordner laut Regelwerk, Namensvorschlag für Scannernamen, fehlende Kopie in einem weiteren Objekt. Ordnerwechsel sind nicht vorausgewählt, Umbenennungen und Kopien schon. Ohne greifende Regel bleibt eine Datei, wo sie ist; eigene Unterordner gelten als passend. Gelesen wird nur, wo der Dateiname nichts entscheidet; die Prüfung läuft im Hintergrund (/api/ablage/pruefen) mit Fortschritt am Knopf.

Unterlagen am Inhalt erkennen. Die Vollständigkeitsprüfung („Unterlagen“) erkennt eine Unterlage zuerst am Dateinamen. Verrät der nichts, zählt die Überschrift des Dokuments laut Texterkennung – die ersten 600 Zeichen, geprüft mit eigenen, engen Mustern je Unterlage (inhalt in src/checkliste.js), etwa „Kostenrechnung gem. § 19 GNotKG“ für die Notarrechnung. So zählt ein Brief, der den Wirtschaftsplan nur erwähnt, nicht selbst als Wirtschaftsplan. Gelesen wird dabei nichts; noch ungelesene Dateien liest der Knopf „Inhalte lesen“ im Hintergrund (/api/unterlagen/lesen).

Auf einem Server (Docker, mehrere Instanzen)

Jede Instanz ist ein eigener Container mit eigenen Daten, eigener Ablage, eigenem Erscheinungsbild und eigener Subdomain. Alle laufen aus demselben Image hinter einem gemeinsamen Caddy, der HTTPS (Let's Encrypt) macht. Die App-Container sind nur im internen Docker-Netz immo-netz erreichbar.

Je Instanz gibt es zwei Arten der Anmeldung:

  • entra – die App meldet über Microsoft Entra ID an (OpenID Connect mit PKCE); hinein darf nur, wer in einer der freigegebenen Entra-Gruppen ist.
  • passwort – Caddy fragt Benutzer und Passwort ab, die App selbst bleibt ohne Anmeldung.
Was Wo auf dem Server (/opt/immobilien)
Code, wird zum Image immobilien-app gebaut app/
Je Instanz: Einstellungen, Stammdaten, Ablage instanzen/<name>/instanz.env, daten/, ablage/
Proxy und je Instanz ein Eintrag proxy/Caddyfile, proxy/instanzen/<name>.caddy
Vorlage für einen Instanz-Container instanz.compose.yml
Verwaltung immo.sh

Im Repo liegen die Vorlagen unter deploy/.

./immo.sh liste
./immo.sh anlegen kunde immo-kunde.example.de entra "Kunde Immobilien"
./immo.sh anlegen test immo-test.example.de passwort "Test" test < passwort.txt
./immo.sh start kunde              # nach Änderungen an der instanz.env
./immo.sh umstellen daniel entra   # Anmeldeart wechseln
./immo.sh passwort test            # neues Passwort (über stdin)
./immo.sh entfernen test           # Container weg, Daten bleiben

Neuen Code einspielen – alle Instanzen werden neu gestartet, Daten bleiben unberührt:

tar -cf - --exclude=werkzeuge/startdaten.js Dockerfile .dockerignore package.json package-lock.json CHANGELOG.md server.js src public werkzeuge \
  | ssh root@<server> 'tar -xf - -C /opt/immobilien/app && /opt/immobilien/immo.sh aktualisieren'

Node.js und Pakete aktuell halten

Drei Ebenen, drei Wege:

Was Wie es auf den Server kommt
npm-Pakete (package-lock.json) mit dem Code: das Update aus der App führt danach npm install aus
Node-Patches, Alpine-Pakete (Sicherheitsupdates von Node 24, poppler, tesseract …) Image neu bauen: ./immo.sh auffrischen, am besten nach Zeitplan
Caddy-Proxy (caddy:2-alpine, Patches und Unterversionen) ebenfalls ./immo.sh auffrischen
neue Node-Hauptversion (24 → 26) bewusst: FROM im Dockerfile und das Image in .forgejo/workflows/ ändern, Tests abwarten, dann ./immo.sh aktualisieren

Das Dockerfile legt nur die Hauptversion fest (node:24-alpine, LTS bis April 2028); immo.sh baut immer mit --pull, holt also den neuesten Patch. auffrischen baut neu und startet die Instanzen nur, wenn sich das Image geändert hat. Antwortet danach eine Instanz nicht auf /gesund, geht es zurück auf das vorige Image (immobilien-app:vorher), und der Befehl endet mit Fehlercode.

Danach dasselbe für den Proxy: neuestes Caddy 2 holen, nur bei neuem Image neu starten, dann je Instanz https://<domain>/gesund über den Proxy abrufen (auf diesem Rechner, 2xx/3xx/401 gelten als erreichbar). Klappt das nicht, läuft wieder das vorige Image (immo-caddy:vorher); proxy/.env hält es fest, bis ein späterer Lauf erfolgreich ist. Dafür muss proxy/docker-compose.yml die neue Fassung mit image: ${CADDY_IMAGE:-caddy:2-alpine} sein – mit einer festen Version überspringt auffrischen den Proxy und meldet das.

./immo.sh auffrischen                  # einmal von Hand
./immo.sh zeitplan an                  # wöchentlich, sonntags 04:30 (/etc/cron.d/immobilien-auffrischen)
./immo.sh zeitplan an 0 3 '*'          # oder: täglich 03:00 (Minute Stunde Wochentag)
./immo.sh zeitplan aus
tail /var/log/immo-auffrischen.log     # was zuletzt passiert ist

Der Workflow .forgejo/workflows/tests.yml führt bei jedem Push und montags früh die Tests auf Node 24 aus und prüft die Pakete mit npm audit (ab „high“). Ein roter Montagslauf heißt: Es gibt eine neue Sicherheitslücke – npm audit fix, testen, committen; das Update aus der App bringt es auf den Server.

Anmeldung, Benutzer und Rollen

Anmeldung, Sitzungen, Benutzer und Rollen kommen aus tool-defaults (src/defaults/auth.js, roles.js, audit.js). Drei Rollen: Admin darf alles einschließlich Einstellungen, Benutzer und Updates, Member liest und bearbeitet, Viewer liest nur. Eingestellt wird alles unter „Einstellungen → Benutzer & Anmeldung“:

  • Benutzername und Passwort – lokale Benutzer (scrypt), optional mit Zwei-Faktor-Code (Authenticator-App, zehn Wiederherstellungscodes), Sperre nach fünf Fehlversuchen. Zwei-Faktor und Sperre sind Ergänzungen dieses Werkzeugs.
  • Microsoft Entra ID – Rolle aus den Gruppen (je Rolle Gruppen-Objekt-IDs). App-Registrierung mit den Umleitungs-URIs https://<domain>/auth/rueckruf (bzw. /auth/entra/callback) und https://<domain>/auth/abgemeldet, geheimer Clientschlüssel, Gruppenanspruch im ID-Token.
  • Google Workspace – Ergänzung dieses Werkzeugs. OAuth-Client „Webanwendung“ mit der Weiterleitungs-URI https://<domain>/auth/google/rueckruf, Zustimmungsbildschirm „Intern“. Zugang hat, wer eine bestätigte Adresse aus einer freigegebenen Domain hat oder einzeln eingetragen ist; neue Konten bekommen die eingestellte Rolle (nie Admin).
  • Demo – nur ansehen, ohne Konto.

Jeder Angemeldete pflegt unter „Mein Konto“ sein Passwort und seine Zwei-Faktor-Anmeldung. Was geschieht, steht im Protokoll (Admin). Eine Anmeldeart, über die man selbst angemeldet ist, lässt sich nicht abschalten; der letzte aktive Administrator lässt sich weder sperren noch herabstufen.

Ausgesperrt? ./immo.sh notfall <instanz> [benutzer] (Server) bzw. node werkzeuge/zugang.js <benutzer> (lokal) legt einen lokalen Administrator an oder setzt ihn zurück (Zwei-Faktor aus) und schaltet die lokale Anmeldung ein. Das Passwort kommt über stdin.

Update aus der App

Unter „Einstellungen → Update“ steht das Repository dieses Werkzeugs. Der Knopf „Update einspielen“ holt, was neu ist, und startet neu. Weil alle Instanzen den Code-Ordner app/ teilen, wirkt ein Update für alle – die übrigen starten von selbst nach, sobald bei ihnen nichts mehr läuft. Die gemeinsamen Funktionen aus tool-defaults kommen mit dem Code dieses Repositorys; ein eigenes tool-defaults-Repository lässt sich nicht mehr einstellen. Änderungen daran gehören nach tool-defaults und werden von dort in src/defaults/ bzw. public/assets/ übernommen.

Erscheinungsbild

Die Oberfläche nutzt Gerüst, Schriften und Bausteine des ho.Systeme-Design-Systems aus tool-defaults (public/assets/css/ho-base.css, Symbolsatz, Kopf- und Fußzeile aus public/assets/ho/kopf-fuss.js), aber nicht dessen Farben: public/assets/css/base.css ersetzt die Farb-Tokens durch eine neutrale Grundpalette (Grautöne, hell und dunkel) und schaltet die blaue Markengrafik ab. Darauf bauen die Bausteine dieses Werkzeugs auf. Unter „Einstellungen → Erscheinungsbild“ stehen Name und Zusatz der Marke, Fußzeile und Rechtliches, Standardmodus, Seitenbreite und die Marken-Farben je für hell und dunkel; sie wirken über /theme.css (tool-defaults branding.js) und gewinnen gegen die Grundpalette. Das Feld „Farbschema“ füllt die Farben mit einer Vorlage: Neutral (Grautöne, Standard – speichert nichts, es gilt die Grundpalette) oder Neutral (Schiefer). Die Werte der Grundpalette stehen zweimal: in base.css und als FARBEN_NEUTRAL in public/js/app.js – bei Änderungen beide anpassen. Eigenes CSS kommt als /anpassung.css zuletzt.

Der Seitenhintergrund ist ebenfalls einstellbar: Verlauf aus Leit- und Zweitfarbe (Vorgabe), einfarbig oder eigenes Bild, dazu die Abdeckung (wie stark die Hintergrundfarbe über dem Bild liegt). Das steht mit dem eigenen CSS in daten.einstellungen.anpassung; frühere Werte „auto“ und „marke“ gelten als Verlauf. Ein eigenes Bild liegt als hintergrund.bild neben der Datendatei und wird unter /hintergrund-bild ausgeliefert (auch ohne Anmeldung, für die Anmeldeseite). /anpassung.css legt den Hintergrund in eine fixierte Ebene statt auf <html>, weil iOS background-attachment: fixed nicht kann.

Die Texterkennung kommt auf dem Server über das Image (poppler, tesseract, deutsche Sprachdaten). Ein Update aus der App bringt nur neuen Code, kein neues Image – nach einem Update, das das Dockerfile ändert (etwa auf 0.32.0), einmal ./immo.sh aktualisieren ausführen. Bis dahin zeigt „Einstellungen → Dokumentenablage“, was fehlt. Die Ablage dort ist eine eigene Kopie; mit Dropbox oder Google Drive bleibt sie über den Cloud-Abgleich in Gleichstand.

Zum Ausprobieren

npm run demo        # erzeugt .demo\ mit erfundenen Objekten und einer Beispielablage
npm run demo:start  # startet den Server damit auf Port 4799

Die Screenshots in diesem README stammen aus diesem Datensatz – echte Zahlen stehen ausschließlich lokal in data\daten.json und werden nicht versioniert.

Tests

npm test            # Anmeldung, Update, Mail, Cloud-Abgleich, Objekt-Assistent, Unterlagen, Texterkennung

Der Texterkennungs-Test braucht Linux mit poppler, tesseract, ImageMagick und DejaVu-Schrift und überspringt sich sonst. Er läuft im Workflow .forgejo/workflows/texterkennung.yml in einem Alpine-Container mit denselben Paketen wie das Image.

Aufbau

server.js               HTTP-Server und API (Express)
src/config.js           Pfade (Dropbox-Erkennung), Port
src/store.js            JSON-Datenspeicher
src/struktur.js         Ordnerstruktur-Standard
src/dokumente.js        Dateiablage mit Pfadprüfung
src/kennzahlen.js       Cashflow, Rendite, Restschuld, Kostenstände, Jahresrechnung, Fristen
src/zuordnung.js        Zielordner-Ermittlung für Altbestand und Posteingang, Namensvorschlag
src/texterkennung.*     Inhalt von PDFs und Bildern lesen (Windows-OCR oder poppler + tesseract), Zwischenspeicher
src/volltext.js         Volltext-Lauf über die ganze Ablage im Hintergrund
src/migration.js        Datenmigrationen beim Serverstart
src/assistent.js        Objekt-Assistent: Pflichtangaben, Anlegen in einem Zug
src/abgleich.js         Cloud-Abgleich in beide Richtungen (Konflikte, Papierkorb, Sicherheitsstopp)
src/anbieter/           Dropbox und Google Drive (OAuth mit PKCE, API-Aufrufe)
src/regeln.js           Einsortier-Regelwerk
public/                 Oberfläche (Vanilla JS, kein Build)
werkzeuge/              Startdaten, Einsortieren, Hinweis-Mails, Demodaten, Notfallzugang
deploy/                 Server: immo.sh, Compose-Vorlage, Caddy
test/                   Tests (node, ohne Testframework)