Lots of runs

This commit is contained in:
Christoph Schwörer
2026-08-26 16:37:24 +02:00
parent f349d189c7
commit 844b4e5569
823 changed files with 198980 additions and 125 deletions
@@ -0,0 +1,103 @@
# Messprotokoll – Versuch 01 (V1 Baseline, Prompt-only) – Iteration 02
## Lauf
- **Prompt-Datei:** `Versuche/Versuch_01/02_Prompt.md`
- **Iteration:** 02 – vom User ausdrücklich benannt; entspricht zugleich der Regel „höchste
vorhandene Iterationsnummer". Wiederholungslauf zur Varianzbestimmung in derselben Zelle.
- **SHA-256 (Prompt):** `F9B2A1AAB45DDCB87E905B83F24D7B1C7860D81CA07E503E51222E266E0D7849`
- **Startzeit:** 2026-08-26T16:00:49.2361402+02:00
- **Endzeit:** 2026-08-26T16:21:48.6873493+02:00
- **Dauer gesamt:** 0:20:59 (`duration_ms` 0:10:57; API: 3:49:48)
— **im Parallelbetrieb erhoben, nicht für Laufzeitvergleiche verwendbar**
- **Root-Verzeichnis:** `c:\DEV\MasterArbeit\QuellCode\CentronERP` (24.662 versionierte Dateien)
- **Codebasis-Commit:** `7df384f6d2e7249dc914a74e299fe7fa71ece748` (dirty: nein)
- **Snapshot-Zustand:** bereinigt von KI-Konfigurationen: ja (Prüfmuster ohne Treffer);
die Codebasis ist seit Commit `f045b99a` kein eigenes Repository mehr, sondern Teil des
Arbeitsrepos – der Vorher/Nachher-Vergleich läuft deshalb pfadskopiert
- **Snapshot-Zusatzartefakte:** keine – der Snapshot entspricht dem Commit-Stand
- **Prompt-Repo-Commit:** `7df384f6d2e7249dc914a74e299fe7fa71ece748`
## Werkzeugkonfiguration
- **Skill-Version:** 4.5.0
- **Claude-Code-Version:** 2.1.246
- **CLI-Pfad:** `C:\Users\ChristophSchwoerer\.vscode\extensions\anthropic.claude-code-2.1.246-win32-x64\resources\native-binary\claude.exe`
- **Modell (angefordert):** `claude-opus-5`
- **Modelle (tatsächlich eingesetzt):** `claude-opus-5` 193.392.662 Tokens (100.00 %), `claude-haiku-4-5-20251001` 6.986 Tokens (0.00 %)
- **Kontrolle Modell:** bestanden – ausschließlich das angeforderte Modell plus Haiku als zulässiger interner Hilfsaufruf
- **Effort:** `high` (per `--effort high` gesetzt)
- **Laufverzeichnis-ID:** `v4.5.0-116d`
- **Ablage:** `Iteration 3/claude-opus-5/builtin/high/`
- **Parallele Läufe:** **ja** – zeitgleich liefen:
- `Iteration 3/claude-opus-5/builtin/high/02_Lauf_2026-08-26_160037_v4.5.0-9d9e`
Die Zeitangaben dieses Laufs sind daher **nicht** für Laufzeitvergleiche zu verwenden. Tokenverbrauch, Anforderungszahl, Belegkennzahlen und Denials bleiben unverzerrt.
- **Agentenmodus:** `builtin` (V1b)
- **Kontextfenster:** 1.000.000 Tokens; `maxOutputTokens` 64.000
- **Sampling-Parameter:** nicht steuerbar über die CLI, nicht erfasst
- **Nur bei lokalem Modellbetrieb:** entfällt (Cloud-Inferenz, `provider: firstParty`)
- **Permission-Mode:** `acceptEdits`
- **Toolfreigabe:** `--allowedTools "Bash" "PowerShell"` /
`--disallowedTools` 33er-Denylist (schreibende und bauende Kommandos). Der Modus `builtin` fügt keine weiteren Sperren hinzu – die werkzeugeigenen Subagenten sind zugelassen.
- **Isolationsmechanismus:** `--safe-mode`, `--strict-mcp-config`
- **MCP-Server / Agentendateien:** keine – aus dem Snapshot entfernt, zusätzlich `--safe-mode`
- **Subagenten:** 20 gestartet (10 × `Explore`, 10 × `general-purpose`), 10 abgeschlossen, 0 fehlgeschlagen; 20 im Hintergrund gestartet
- **Verschachtelung:** `spawned` = 20, davon `spawned_by_subagents` = 10, `max_depth` = 2. Bei `max_depth` > 1 sind die Tokens der tieferen Ebenen in „Tokens gesamt" enthalten, ihre Prompts jedoch **nicht** in `_meta\subagenten.md`.
- **Verbrauchsanteil der Subagenten:** `usage` (nur Hauptagent) 2.124.147 Tokens gegenüber `modelUsage` 193.399.648 Tokens – auf die Subagenten entfallen 191.275.501 Tokens (98.9 %).
## Validierungsstichprobe
- **Größe:** noch nicht festgelegt
- **Ziehungsverfahren:** noch nicht festgelegt
- **Validatoren:** noch nicht festgelegt
- **Stand:** noch nicht gezogen
## Verbrauch
### Hauptagent (`usage`)
| Messgröße | Wert |
|---|---:|
| Input-Tokens | 44 |
| Output-Tokens | 65.246 (davon 9.113 Thinking-Tokens) |
| Cache-Write-Tokens | 123.977 |
| Cache-Read-Tokens | 1.934.880 |
| Agent-Turns | 44 |
### Gesamtlauf inkl. Subagenten (`modelUsage`, abrechnungsrelevant)
| Messgröße | `claude-opus-5` | `claude-haiku-4-5-20251001` | Summe |
|---|---:|---:|---:|
| Input-Tokens | 2.604 | 6.960 | 9.564 |
| Output-Tokens | 1.155.732 | 26 | 1.155.758 |
| Cache-Write-Tokens | 4.641.716 | 0 | 4.641.716 |
| Cache-Read-Tokens | 187.592.610 | 0 | 187.592.610 |
| **Tokens gesamt** | **193.392.662** | **6.986** | **193.399.648** |
**Tokens gesamt: 193.399.648** — Input + Output + Cache-Write + Cache-Read über alle Modelle.
Das ist die **berichtete Aufwandsgröße** der Versuchsreihe. `total_cost_usd` bleibt unberührt in
`RawResult.json` erhalten, wird aber nicht ins Protokoll übernommen: Token sind modell- und
preisunabhängig und bleiben damit über Preisänderungen und Modellwechsel hinweg vergleichbar.
`usage` erfasst **nur den Hauptagenten**. Der Unterschied zu `modelUsage` ist der Verbrauch
der Subagenten – siehe Feld „Verbrauchsanteil der Subagenten" oben. Für den
abrechnungsrelevanten Gesamtverbrauch gilt ausschließlich `modelUsage`.
## Gefundene Anforderungen
Keine Anforderungen im vorgegebenen Format gefunden.
## Ergebnis
- **Status:** erfolgreich (`is_error` = false, `subtype` = `success`, `stop_reason` = `end_turn`, `terminal_reason` = `completed`)
- **Session-ID:** `ac82faba-7709-4a7c-9881-dcf0cd4faa1c`
- **Permission-Denials:** 1 (1 × `Bash`) – **keines auf `Task`/`Agent`/`Workflow`**. Der Agent hat zu keinem Zeitpunkt zu delegieren versucht; die Modus-Sperre wurde nie ausgelöst.
- **Kontrolle Agentenmodus:** `subagent_stats.spawned` = 20 – Bedingung `solo` **VERLETZT – Fehlmessung**
- **Subagenten-Prompts:** `_meta\subagenten.md` erzeugt
- **Erzeugte Dateien:** 0 Dateien in `Ergebnisse\`:
| Datei | Größe |
|---|---:|
- **Root unverändert:** ja (zeilenendennormalisiert verglichen).
- **Abschlusstext des Agenten:** siehe `RawResult.json` (`result`)
## Anmerkungen/Auffälligkeiten
<!-- ANMERKUNGEN -->
@@ -0,0 +1 @@
{"is_error":false,"duration_api_ms":13788889,"num_turns":44,"stop_reason":"end_turn","session_id":"ac82faba-7709-4a7c-9881-dcf0cd4faa1c","total_cost_usd":152.18535374999993,"usage":{"input_tokens":44,"cache_creation_input_tokens":123977,"cache_read_input_tokens":1934880,"output_tokens":65246,"output_tokens_details":{"thinking_tokens":9113},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":123977,"ephemeral_5m_input_tokens":0},"inference_geo":"not_available","iterations":[{"input_tokens":2,"output_tokens":474,"cache_read_input_tokens":152943,"cache_creation_input_tokens":1008,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":1008},"type":"message"}],"speed":"standard"},"modelUsage":{"claude-haiku-4-5-20251001":{"inputTokens":6960,"outputTokens":26,"cacheReadInputTokens":0,"cacheCreationInputTokens":0,"webSearchRequests":0,"costUSD":0.00709,"contextWindow":200000,"maxOutputTokens":32000,"canonicalModel":"claude-haiku-4-5","provider":"firstParty","costBasis":"list"},"claude-opus-5":{"inputTokens":2604,"outputTokens":1155732,"cacheReadInputTokens":187592610,"cacheCreationInputTokens":4641716,"webSearchRequests":0,"costUSD":152.17826374999993,"contextWindow":1000000,"maxOutputTokens":64000,"canonicalModel":"claude-opus-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[{"tool_name":"Bash","tool_use_id":"toolu_01WYSxhrBPsM7eYbEU9QFZbf","tool_input":{"command":"mkdir -p \"/c/Users/CHRIST~1/AppData/Local/Temp/claude/C--DEV-MasterArbeit-QuellCode-CentronERP/ac82faba-7709-4a7c-9881-dcf0cd4faa1c/scratchpad/rre\" && echo ok","description":"Create scratchpad work dir"}}],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"extra_usage_disabled","subagent_stats":{"spawned":20,"requested":{"background":0,"foreground":0,"unset":20},"started_in_background":20,"max_depth":2,"spawned_by_subagents":10,"completed":10,"failed":0,"killed":{"parent":0,"user":0,"system":10},"refused":{"depth_limit":0,"concurrency_limit":5,"budget":0},"by_type":{"general-purpose":10,"Explore":10}},"subtype":"success","api_error_status":null,"result":"Die Erhebung läuft. Aktueller Stand:\n\n**Schritt 0 – Modulinventar** steht: 167 Module/Komponenten über sechs Bereiche, verankert an belegbaren Strukturen der Codebasis (113 `AppModuleController`, 30 Projekte unter `src/`, 1.558 Tabellen im DB-Dump, 750 Rechtekonstanten in `UserRightsConst.cs`).\n\n**Schritt 0b/0c – Erhebung**: 10 von 12 Agenten arbeiten parallel, jeder mit einem eigenen ID-Block und der Pflicht, jedes zugewiesene Modul mit mindestens einer Anforderung abzudecken. A11 (Datenaustausch/Schnittstellen) und A12 (Plattform/Nexus/Betrieb) starte ich, sobald Slots frei werden.\n\nEin Befund schon jetzt für den Analysebericht: Die Codebasis enthält **keine `.git`-Historie und keine SQL-Migrationsskripte** — `scripts/` ist reines C#-Werkzeug. Commit-Messages, Tickets und Release Notes als `KONTEXT`-Belege stehen damit nicht zur Verfügung; das ist eine Belegquelle, die der Prompt vorsieht und die dieser Lauf nicht bedienen kann.","ttft_ms":1986,"ttft_stream_ms":1231,"time_to_request_ms":32,"type":"result","duration_ms":657822,"uuid":"878467f0-8d1e-4e6f-980b-316977345320","queued_turn_count":0}
@@ -0,0 +1 @@
Background tasks still running after 600s; terminating. Set CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0 to wait indefinitely.
@@ -0,0 +1,4 @@
## Gefundene Anforderungen
Keine Anforderungen im vorgegebenen Format gefunden.
@@ -0,0 +1,178 @@
# Versuch 01 - Baseline (Prompt-only) - Iteration 02
## Metadaten
- **Versuch:** V1 Baseline (Prompt-only)
- **Iteration:** 02 (erste Überarbeitung nach Auswertung von Iteration 01)
- **Codebasis:** c-entron ERP-Suite (Windows, C#/XAML, MSSQL)
- **Zeitstempel:** 2026-08-26
- **Vorgänger:** `01_Prompt.md` (SHA-256 `1B0DB06B…3C02FF`), 24 Läufe an Tag 1
- **Änderungsgrund:** Auswertung der 24 Läufe von Tag 1 über 3.287 erzeugte Anforderungen. Jede Änderung ist an einen gemessenen Befund gekoppelt:
| Änderung | Auslösender Befund |
|---|---|
| Modulinventar als Pflicht-Vorstufe, Mindestabdeckung je Modul, Vertiefung erst danach | Anforderungszahl schwankte je Lauf zwischen 42 und 325 (Faktor 5,9); 55 bis 60 von rund 85 Modulen blieben unanalysiert; die Modultabellen der Analyseberichte reichten von 0 bis 51 Zeilen |
| Hypothesenpflicht kalibriert, `Hypothesen.md` deckungsgleich mit den Inline-Markierungen | Zwei Läufe meldeten null Hypothesen bei 71 bzw. 148 Anforderungen; der Anteil schwankte zwischen 0 % und 26,2 %; mehrfach wichen Sammeldatei und Inline-Markierungen voneinander ab |
| Primärbeleg muss die durchsetzende Stelle benennen | 45,9 % aller Anforderungen trugen genau einen Beleg; in einem Lauf waren 5 von 31 risikorelevanten Anforderungen weder mit `PRIMÄR` noch als `[HYPOTHESE]` gedeckt |
| Belegpflicht verschärft: ohne Beleg keine Anforderung | Eine Anforderung wurde ohne jeden Beleg geschrieben |
| Konsolidierungsbegriff an einem Beispiel kalibriert | Anteil der Konsolidierungskandidaten schwankte je Lauf zwischen 2,4 % und 35,2 % |
| Eigenes Feld `Qualitätsmerkmal` für die ISO-25010-Zuordnung | Die Zuordnung war gefordert, hatte aber keinen Ablageort: nur 33 von 313 nicht-funktionalen Anforderungen führten sie als eigene Angabe |
| Risikoanforderungen im Konsistenzcheck auflisten | Verstöße gegen die risikobasierte Priorisierung fielen erst in der nachgelagerten Auswertung auf, nicht im Lauf selbst |
Unverändert bleiben Prüfidee, Tracelinks, Belegklassifikation und das Blockformat: Prüfidee und Tracelinks waren in **allen** 3.287 Anforderungen gesetzt, 78,6 % der Belege waren `PRIMÄR`.
> Dieser Prompt enthält ausschließlich die **Analyseanweisung** und ist damit unabhängig von einem
> bestimmten Werkzeug oder Modell einsetzbar. Welche Werkzeuge im jeweiligen Lauf zur Verfügung
> stehen und wohin die Ergebnisse geschrieben werden, stellt der Versuchsaufbau beim Start bei.
---
## Prompt
Du bist ein Requirements Engineer im Reverse Requirements Engineering eines Legacy-ERP-Systems. Erzeuge aus der vorliegenden Codebasis eine Anforderungsspezifikation nach **ISO/IEC/IEEE 29148:2018**. Arbeite ausschließlich auf den im Arbeitsverzeichnis liegenden Artefakten (Quellcode, Konfiguration, UI-Ressourcen, ggf. DB-Skripte). Nutze nur Informationen, die du aus diesen Artefakten gewinnen kannst.
### Auftrag
Erzeuge eine konsolidierte Spezifikation auf den drei Ebenen:
1. **StRS** - Stakeholder Requirements Specification (fachliche Sicht, Akteure, Geschäftsziele)
2. **SyRS** - System Requirements Specification (Systemverhalten, Schnittstellen, Performance-, Sicherheitsanforderungen)
3. **SwRS** - Software Requirements Specification (Komponenten, Datenmodelle, Software-interne Regeln)
Ziel ist eine Spezifikation, die als belastbare Basis für eine Web-/SaaS-Neuimplementierung dienen kann.
### Scope (Schritt 1 der RRE-Methodenkette, manuell vorgegeben)
Der Untersuchungsgegenstand ist die **gesamte Codebasis** im Arbeitsverzeichnis. Es gilt bewusst keine Modulbeschränkung: Alle Module, Datenobjekte und Prozesse sind gleichrangig zu erfassen.
**Breite geht vor Tiefe.** Ein fehlendes Requirement führt bei einer Neuimplementierung zu Funktionsverlust; eine oberflächlich erfasste Funktion lässt sich dagegen nachschärfen. Erfasse deshalb zuerst die gesamte Breite und vertiefe erst danach. Halte dich an die Reihenfolge aus dem Abschnitt **Vorgehen**: erst Inventar, dann Mindestabdeckung, dann Vertiefung.
### Vorgehen (statische Analyse, keine Ausführung)
Bearbeite die Schritte 2-6 der RRE-Methodenkette (Schritt 1 Scope ist oben vorgegeben, Schritt 7 Validierung erfolgt manuell durch Fachexperten). Vorgeschaltet ist eine verbindliche Inventarisierung:
**Schritt 0 - Modulinventar (vor der ersten Anforderung).** Verschaffe dir zuerst einen vollständigen Überblick über den Untersuchungsgegenstand und lege ihn im `Analysebericht.md` als Tabelle ab: fachliches Modul beziehungsweise Komponente, Pfad im Arbeitsverzeichnis, ein Satz zur fachlichen Aufgabe. Das Inventar wird erstellt, **bevor** die erste Anforderung formuliert wird. Es ist die Bezugsgröße für die Abdeckung und darf später ergänzt, aber nicht gekürzt werden.
**Schritt 0b - Mindestabdeckung.** Jedes Modul des Inventars erhält **mindestens eine** Anforderung, bevor irgendein Modul vertieft wird. Lässt sich für ein Modul keine belegbare Anforderung bilden, führe es im Inventar als `nicht analysiert` mit einer kurzen Begründung. Ein Modul ohne Anforderung und ohne Begründung ist unzulässig.
**Schritt 0c - Vertiefung nach Risiko.** Erst wenn die Mindestabdeckung steht, vertiefe einzelne Module. Beginne dort, wo Sicherheitsregeln, Abrechnungs- und Fakturierungslogik oder Berechtigungsprüfungen liegen.
2. **Artefakterhebung:** Erfasse Quellcode, Konfiguration, UI-Texte, Datenbankschemata, Schnittstellenbeschreibungen sowie Change-Historie und Projektartefakte (Commit-Messages, Tickets, Release Notes, Migrationsnotizen), soweit als Datei lesbar.
3. **Technische Analyse:** Identifiziere Module, Komponenten, Abhängigkeiten, Statusmaschinen, Validierungslogik, Berechtigungsprüfungen.
4. **Semantische Interpretation:** Leite fachliche Aussagen aus technischen Implementierungen ab (z. B. Statusübergänge → Geschäftsregel).
5. **Formalisierung:** Überführe die Aussagen in klare, testbare Anforderungen mit Kontext, Vorbedingung und Ergebnis.
6. **Traceability-Anreicherung:** Verknüpfe jede Anforderung mit konkreten Artefaktbelegen.
### Pflicht-Eigenschaften jeder Anforderung
- **Belegpflicht:** Jede Anforderung **muss** mindestens einen konkreten Artefaktbeleg führen (Dateipfad, Klasse/Methode, SQL-Statement, UI-String, Konfigurationseintrag). Jeder Beleg erhält eine kurze Begründung, warum er die Aussage trägt. Lässt sich eine Aussage nicht belegen, **schreibe die Anforderung nicht** - erfasse den offenen Punkt stattdessen als Hypothese. Eine Anforderung ohne Beleg ist unter keinen Umständen zulässig.
- **Trennung von Fakt und Interpretation:** Die belegte technische Beobachtung (Feld `Fakt`) wird getrennt von der fachlichen Interpretation (Feld `Aussage`) dokumentiert, damit nachvollziehbar bleibt, was im Artefakt steht und was daraus geschlossen wurde.
- **Risikobasierte Priorisierung:** Anforderungen zu Sicherheitsregeln, Abrechnungs-/Fakturierungslogik und Berechtigungen unterliegen strengeren Evidenzanforderungen: Sie benötigen mindestens einen `PRIMÄR`-Beleg, andernfalls sind sie zwingend als `[HYPOTHESE]` zu kennzeichnen. Ein `PRIMÄR`-Beleg benennt hier die **durchsetzende Stelle** - Datei, Klasse, Methode und die konkrete Prüfung, Bedingung oder das Constraint. Ein Verweis auf eine Datei ohne Angabe der prüfenden Stelle genügt für diese Anforderungen nicht.
- **Belegklassifikation:** Kennzeichne jeden Beleg als
- `PRIMÄR` (durchgesetzte Regel im Code oder DB-Constraint),
- `SEKUNDÄR` (UI-Label, Fehlermeldung, Reportlayout, Mappingtabelle, Konfigurationsschalter),
- `KONTEXT` (Kommentar, Commit-Message, Ticketreferenz).
- **Hypothesenmarkierung:** Aussagen, die sich nicht eindeutig aus Artefakten ableiten lassen, kennzeichnest du explizit mit `[HYPOTHESE]` und einer kurzen Begründung, welche Information zur Bestätigung fehlt. Bei einer Codebasis dieser Größe ist eine Analyse ohne jeden offenen Punkt unplausibel: Führst du keine einzige Hypothese, begründe das ausdrücklich in der Selbstbewertung. Umgekehrt ist eine hohe Hypothesenzahl kein Mangel, sondern ein Hinweis auf ehrliche Abgrenzung.
- **Verifizierbarkeit:** Jede Anforderung enthält mindestens eine Prüfidee oder ein Akzeptanzkriterium.
- **Eindeutigkeit:** Vermeide vage Begriffe ("schnell", "benutzerfreundlich"); definiere domänenspezifische Begriffe beim ersten Auftreten.
- **Übernahmewürdigkeit:** Beurteile für jede Anforderung, ob ihre Funktion im Zielsystem erhalten bleiben soll. Unterscheide `übernehmen` (fachlich weiterhin erforderlich), `Workaround` (historisch gewachsene Behelfslösung), `Sonderfall` (Ausnahme für einen einzelnen Kunden, Mandanten oder Altbestand) und `veraltet` (durch neuere Logik abgelöst oder fachlich überholt). Begründe die Einstufung in einem Halbsatz. Diese Angabe steuert die spätere fachliche Priorisierung; eine Fehleinschätzung ist unkritisch, eine fehlende Angabe nicht.
- **Redundanzfreiheit:** Formuliere jede Anforderung so, dass sie von den übrigen klar abgegrenzt ist. Beschreiben zwei Anforderungen dieselbe fachliche Funktion aus unterschiedlicher Perspektive, führe sie zusammen oder grenze sie im Titel und in der Aussage ausdrücklich gegeneinander ab.
### Formatvorgabe pro Anforderung
```
ID: <StRS|SyRS|SwRS>-<laufende Nummer>
Titel: <kurzer Titel>
Ebene: <StRS | SyRS | SwRS>
Typ: <funktional | nicht-funktional | Schnittstelle | Daten | Sicherheit | ...>
Qualitätsmerkmal: <nur bei nicht-funktionalen Anforderungen: ISO-25010-Merkmal, sonst leer>
Akteur: <Rolle / System / Komponente>
Vorbedingung: <Zustand vor Auslösen>
Fakt: <belegte technische Beobachtung, z. B. Statusübergang, Constraint, Prüfung>
Aussage: Das System soll <...>. (fachliche Interpretation als klare Soll-Aussage)
Ergebnis: <erwartetes Ergebnis / Nachbedingung>
Belege:
- [PRIMÄR] <Pfad/Klasse/Methode/SQL/UI-String> - Begründung: <warum trägt der Beleg die Aussage>
- [SEKUNDÄR] <...> - Begründung: <...>
- [KONTEXT] <...> - Begründung: <...>
Prüfidee: <Akzeptanzkriterium oder Testidee>
Tracelinks: <verwandte StRS-/SyRS-/SwRS-IDs>
Konsolidierung: <nein | Kandidat: <IDs oder Stellen, die dieselbe fachliche Funktion abbilden>>
Übernahmewürdigkeit: <übernehmen | Workaround | Sonderfall | veraltet> - <kurze Begründung>
Status: <belegt | HYPOTHESE>
```
### Traceability
Stelle Forward- und Backward-Traceability zwischen den drei Ebenen her:
- Jede SwRS-Anforderung referenziert die zugehörige SyRS-Anforderung.
- Jede SyRS-Anforderung referenziert die zugehörige StRS-Anforderung.
- Erzeuge zusätzlich eine konsolidierte **Traceability-Tabelle** (Markdown oder CSV): `StRS-ID | SyRS-ID | SwRS-ID | Artefaktbeleg`.
### Nicht-funktionale Anforderungen
- Ordne nicht-funktionale Anforderungen den Qualitätsmerkmalen der **ISO/IEC 25010** zu (z. B. Zuverlässigkeit, Performance-Effizienz, Sicherheit, Wartbarkeit, Übertragbarkeit). Trage die Zuordnung in das dafür vorgesehene Feld `Qualitätsmerkmal` ein, nicht in das Feld `Typ`.
- Leite Betriebs- und Sicherheitsanforderungen gezielt auch aus indirekt sichtbaren Artefakten ab: Konfigurationen, Deployment-Skripte, Logging-Policies, Rechteprüfungen.
### Konsolidierungsbedarf
Die Codebasis enthält fachliche Redundanz: Dieselbe Anforderung kann auf unterschiedlichen Masken oder in unterschiedlichen Modulen mehrfach und teils unterschiedlich implementiert sein. Prüfe daher bei jeder Anforderung, ob andere Anforderungen dieselbe fachliche Funktion abbilden, und vermerke solche Fälle im Feld `Konsolidierung` als Kandidat für eine Zusammenführung im Zielsystem.
**Gemeint sind fachlich gleichartige Konzepte in getrennten Implementierungen**, nicht bloß ähnlich formulierte Anforderungen. Ein Beispiel aus dieser Codebasis: Drucker werden als „Stammblätter" geführt, sonstige Hardware getrennt davon als „Assets" - zwei Datenhaltungen für denselben fachlichen Gegenstand, die im Zielsystem zu einem Asset-Konzept zusammengeführt werden sollen. Zwei Anforderungen, die denselben Sachverhalt nur aus Sicht verschiedener Ebenen beschreiben (etwa StRS und SwRS), sind **kein** Konsolidierungsfall - dafür sind die Tracelinks da.
### Ergebnisstruktur (im vorgegebenen Ausgabeverzeichnis)
```
Ergebnisse/
StRS.md
SyRS.md
SwRS.md
Traceability.md (oder Traceability.csv)
Hypothesen.md (Sammlung aller mit [HYPOTHESE] markierten Aussagen mit offener Frage)
Glossar.md (Domänenbegriffe, die in den Anforderungen verwendet werden)
Analysebericht.md (Modulinventar aus Schritt 0, Abdeckungstabelle, Konsistenzcheck,
Selbstbewertung, bekannte Lücken)
```
Das Ausgabeverzeichnis wird beim Start des Laufs beigestellt. Die analysierte Codebasis wird ausschließlich gelesen und nicht verändert.
### Randbedingungen
- **Keine Halluzinationen.** Wenn ein Artefakt nicht gelesen oder eine Aussage nicht belegt werden kann, ist das offen zu legen, nicht zu erfinden.
- **Keine Generierung von Code.** Es sollen ausschließlich Spezifikationsartefakte entstehen.
- **Keine Annahme über nicht beigestellte Hilfsmittel.** Arbeite mit dem, was dir in diesem Lauf zur Verfügung steht. Setze keine zusätzlichen Analysewerkzeuge, Datenbankzugriffe oder laufende Systeme voraus. Stehen für eine Aussage nur indirekte Belege zur Verfügung, ist sie als `[HYPOTHESE]` zu kennzeichnen.
- **Migrationsperspektive berücksichtigen.** Erkennbare Workarounds, Sonderfälle und überholte Logik gehören in das Feld `Übernahmewürdigkeit`, nicht in das Feld `Status`. `Status` beschreibt ausschließlich die Belegsituation (`belegt` oder `HYPOTHESE`), `Übernahmewürdigkeit` die fachliche Zukunft der Anforderung. Beide Angaben sind unabhängig voneinander: Eine gut belegte Anforderung kann ein Workaround sein, eine Hypothese kann übernahmewürdig sein.
- **Sprache:** Deutsch für Anforderungsaussagen, technische Bezeichner (Klassen, Methoden, Spalten) bleiben in ihrer Originalsprache.
### Abschluss
Führe vor Abgabe einen **Konsistenzcheck über das gesamte Anforderungs-Set** durch und dokumentiere das Ergebnis im `Analysebericht.md`:
- Doppelte oder mehrfach vergebene IDs
- Anforderungen ohne Beleg
- Anforderungen ohne Angabe zur `Übernahmewürdigkeit`
- Tracelinks auf nicht existierende IDs
- Inhaltlich deckungsgleiche Anforderungen, die nicht als Konsolidierungskandidat markiert sind
- **Liste aller risikorelevanten Anforderungen** (Sicherheit, Abrechnung/Fakturierung, Berechtigungen) mit ihrer Belegsituation: ID, Titel, ob ein `PRIMÄR`-Beleg vorliegt, andernfalls die `[HYPOTHESE]`-Kennzeichnung. Diese Liste macht Verstöße gegen die risikobasierte Priorisierung im Lauf selbst sichtbar.
- **Abgleich `Hypothesen.md` gegen die Inline-Markierungen:** Beide müssen dieselben Anforderungen nennen. `Hypothesen.md` enthält genau die Anforderungen mit `[HYPOTHESE]`-Markierung und keine zusätzlichen freien Fragen; offene Punkte ohne zugehörige Anforderung gehören in die Selbstbewertung.
Erstelle außerdem die **Abdeckungstabelle** auf Basis des Modulinventars aus Schritt 0: je Modul die Einstufung `tief | mittel | flach | nicht analysiert` und die Anzahl der daraus erzeugten Anforderungen. Jede Zeile des Inventars muss in der Abdeckungstabelle auftauchen.
Beende den Lauf mit einer kurzen Selbstbewertung im `Analysebericht.md`:
- Wie viele Module des Inventars wurden tief, mittel, flach beziehungsweise gar nicht analysiert? Nenne absolute Zahlen, nicht nur Beispiele.
- Wurde die Mindestabdeckung erreicht, also hat jedes Modul mindestens eine Anforderung? Falls nein: welche Module fehlen und warum?
- An welchen Stellen war der Beleg dünn (hoher Anteil `SEKUNDÄR`/`KONTEXT` oder `[HYPOTHESE]`)?
- Falls keine einzige Hypothese geführt wurde: Begründung, warum die Analyse ohne offene Punkte auskommt.
- Welche Erkenntnisse legen einen Nachschlag in einer Folge-Iteration nahe?
---
### Werkzeugkontext (vom Versuchsaufbau vorgegeben)
Für diesen Lauf stehen zur Verfügung: Lesen und Suchen von Dateien, das Ausführen von
Kommandozeilenbefehlen im Arbeitsverzeichnis sowie die werkzeugeigenen Subagenten.
Nicht verfügbar sind: spezialisierte Agentenrollen aus Konfigurationsdateien, externe
Werkzeugserver.
Triff keine Annahmen über weitere Werkzeuge und versuche nicht, nicht verfügbare
Werkzeuge zu ersetzen.
### Ausgabeverzeichnis (überschreibt anderslautende Pfadangaben oben)
Schreibe ALLE zu erzeugenden Ergebnisdateien in das Verzeichnis
`c:\DEV\MasterArbeit\Versuche\Versuch_01\Iteration 3\claude-opus-5\builtin\high\02_Lauf_2026-08-26_160037_v4.5.0-116d\Ergebnisse\`.
Verändere keine Dateien im Arbeitsverzeichnis (der analysierten Codebasis).
@@ -0,0 +1,188 @@
ID: StRS-1001
Titel: Vertriebs- und Auftragskennzahlen mehrdimensional auswerten
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Controller / Vertriebsleitung
Vorbedingung: Benutzer besitzt das Recht `Controlling.Analytics.ID`; Lizenz `LicenseGuids.Analytics` oder `LicenseGuids.Centron` ist aktiv.
Fakt: `SaleStatisticsAppModuleController` (src/centron/Centron.WPF.UI/Modules/Statistics/SaleStatistics/SaleStatisticsAppModuleController.cs) trägt `ModuleName => "Analytics"` und `Description => "Erstellung, Bearbeitung und Export verschiedenster Auswertungen"`. `StatisticDataSourceFactory.Create` erzeugt sieben Auswertungsarten (Artikelverkauf, Ticket, Einkauf, Mitarbeiter, Angebote, Ticket-Timer, Verkauf/Einkauf-Artikel); die Darstellung erfolgt als DevExpress-PivotGrid in SaleStatisticsView.xaml.
Aussage: Das System soll Verkaufs-, Einkaufs-, Angebots-, Ticket- und Mitarbeiterkennzahlen in einer frei konfigurierbaren Pivot-Auswertung mit wählbarem Zeitraum, Filiale, Warengruppe und Artikelbezug bereitstellen und das Ergebnis nach Excel exportierbar machen.
Ergebnis: Der Auswerter erhält eine Pivot-Tabelle inklusive Diagramm; die Konfiguration ist als `.cak`-Datei speicher- und wieder ladbar.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Statistics/SaleStatistics/DataSources/StatisticDataSourceFactory.cs, Methode `Create(SaleStatisticsViewModel parent, StatisticTypes? type)` - Begründung: Die Methode instanziiert je Auswertungsart genau eine `IStatisticDataSource`; sie ist die durchsetzende Stelle des Auswertungsumfangs.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Statistics/SaleStatistics/SaleStatisticsAppModuleController.cs, `Description => "Erstellung, Bearbeitung und Export verschiedenster Auswertungen"` - Begründung: UI-String belegt den fachlichen Zweck des Moduls.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Statistics/SaleStatistics/SaleStatisticsView.xaml.cs, `Export(object param)` mit `PivotGrid.ExportToXlsx(...)` und `Diagram.ExportToXlsx(...)` - Begründung: belegt die geforderte Exportfähigkeit.
Prüfidee: Benutzer mit allen Analytics-Rechten öffnet "Analytics": Es müssen genau sieben Auswertungsarten anwählbar sein; nach "Export nach Excel" existieren zwei .xlsx-Dateien (Pivot und Diagramm).
Tracelinks: SyRS-1010, SyRS-1011, SwRS-1032
Konsolidierung: Kandidat: StRS-1002 / SyRS-1010 - Vertriebsstatistik und Management-Informationen berechnen beide "Umsatz" und "Ertrag/DB", jedoch aus getrennten Implementierungen (CacheSalesStatistic vs. NamedQueries auf cvw_InvoicePos).
Übernahmewürdigkeit: übernehmen - zentrale Controlling-Fähigkeit des Systems.
Status: belegt
Modul: M-105
ID: StRS-1002
Titel: Management-Informationen als Vier-Jahres-Vergleich je Filiale
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Geschäftsführung / Controlling
Vorbedingung: Benutzer besitzt `Controlling.Finances.MANAGEMENT_INFO`; Lizenz `LicenseGuids.ManagementInfo` oder `LicenseGuids.Centron` aktiv; mindestens eine Warengruppe ist ausgewählt.
Fakt: `ManagementInfoWebServiceBL.GetManagementInfoData` bündelt 16 Einzelkennzahlen aus `ManagementInfoBL` (u. a. `GetSalesOrderBacklog`, `GetContributionMarginOrderBacklog`, `GetStockValue`, `GetOpenPosts`, `GetEmployeeCount`, `GetOpenTicketCount`, `GetSalesOnDates`, `GetGainOnDate`). `ManagementInfoViewModel.LoadData` berechnet das Zeitfenster als `year = CurrentDate.Month > DateTime.Now.Month ? CurrentDate.Year - 4 : CurrentDate.Year - 3`.
Aussage: Das System soll der Geschäftsführung Auftragsbestand, Deckungsbeitrag, Lieferbestand, Lagerwert, offene Posten, Mitarbeiterzahl, offene Tickets sowie Umsatz- und Ertragsverläufe als Monatsreihen über vier Jahre je ausgewählter Filiale und Warengruppe bereitstellen.
Ergebnis: Kennzahlenübersicht mit vier Jahresreihen zu je zwölf Monaten, nach Excel exportierbar.
Belege:
- [PRIMÄR] src/backend/Centron.BL/WebServices/Statistics/Sales/ManagementInfo/ManagementInfoWebServiceBL.cs, `GetManagementInfoData(...)` - Begründung: aggregiert die 16 Kennzahlenaufrufe und definiert damit den Leistungsumfang.
- [PRIMÄR] src/backend/Centron.BL/Statistics/Sales/ManagementInfo/ManagementInfoBL.cs, `GetSalesOnDates` / `GetGainOnDate` - Begründung: liefern die Monatsreihen Umsatz und Ertrag über NamedQueries auf `cvw_InvoicePos`/`cvw_InvoiceHead`.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Statistics/ManagementInfo/ManagementInfoViewModel.cs, `LoadData()` mit Abbruchdialog `LoadData_BitteWählenSieZurAktualisierungDerDatenEineWarengruppeAus` - Begründung: belegt die Pflicht-Vorbedingung Warengruppenauswahl.
Prüfidee: Modul öffnen ohne Warengruppenauswahl → Dialog "Keine Warengruppen ausgewählt"; nach Auswahl müssen genau 4 Jahresreihen à 12 Monatswerte für Umsatz und Ertrag erscheinen.
Tracelinks: SyRS-1012, SwRS-1033
Konsolidierung: Kandidat: StRS-1001 - identischer Kennzahlenbegriff "Umsatz"/"Ertrag" in getrennter Implementierung.
Übernahmewürdigkeit: übernehmen - Kernanforderung des Managementberichtswesens.
Status: belegt
Modul: M-106
ID: StRS-1003
Titel: Leistungsnachweise über Mitarbeiterauslastung mit begrenzter Sichtbarkeit
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Teamleitung / Personalverantwortliche
Vorbedingung: Benutzer besitzt `UserRightsConst.RIGHT_MITARBEITERAUSLASTUNG` (10930); Lizenz `LicenseGuids.PerformanceRecords` oder `LicenseGuids.Centron` aktiv.
Fakt: `EmployeeAnalyticsAppModuleController` trägt `ModuleName => "Leistungsnachweise"` und `Description => "Darstellung der Mitarbeiterauslastung"`. `EmployeeAnalyticsViewModel.LoadData()` liest die Rechte des angemeldeten Benutzers und setzt `_onlyMyselfAsEmployee` (fehlendes `RIGHT_FREMDAUSLASTUNG`) sowie `_onlyOwnBranch` (`RIGHT_MITARBEITERAUSLASTUNGNUREIGENEFILIALE`, 20800042).
Aussage: Das System soll Auslastungsnachweise für einen wählbaren Zeitraum und eine wählbare Mitarbeitermenge erzeugen und dabei die auswählbare Mitarbeitermenge auf die dem Benutzer erlaubte Filiale bzw. auf ihn selbst begrenzen.
Ergebnis: PDF-Leistungsnachweis (Report "Serviceauslastung" oder "Vertriebsauslastung"), speicherbar, druckbar und per E-Mail versendbar.
Belege:
- [PRIMÄR] src/shared/Centron.Controls/EmployeeAnalytics/EmployeeAnalyticsViewModel.cs, `LoadData()`: `this._onlyOwnBranch = rights.Data.Any(f => f.I3D == UserRightsConst.RIGHT_MITARBEITERAUSLASTUNGNUREIGENEFILIALE) == true;` - Begründung: konkrete Auswertung des einschränkenden Rechts.
- [SEKUNDÄR] CentronRights.md, Abschnitt "Mitarbeiterauslastung": "This is a **restricting right**. If the user has this right, he should only see the workload of employees that belong to his branch." - Begründung: dokumentierte Soll-Semantik des Rechts.
- [SEKUNDÄR] src/shared/Centron.Controls/EmployeeAnalytics/EmployeeAnalyticsViewModel.cs, `SaveReportAsPdf()` mit `Filter = "PDF ( .pdf) | *.pdf"` und `SendReportAsMail()` - Begründung: belegt die geforderten Ausgabewege.
Prüfidee: Benutzer mit RIGHT_MITARBEITERAUSLASTUNGNUREIGENEFILIALE öffnet das Modul: Im Auswahlbaum darf ausschließlich die eigene Filiale erscheinen.
Tracelinks: SyRS-1015, SyRS-1016, SwRS-1035
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - personenbezogene Auswertung mit klarem Schutzbedarf.
Status: belegt
Modul: M-108
ID: StRS-1004
Titel: Zentrale Verwaltung von Berichten und SQL-Abfragen
Ebene: StRS
Typ: funktional
Akteur: Report-Administrator
Qualitätsmerkmal:
Vorbedingung: Benutzer besitzt `UserRightsConst.Administration.REPORT_MANAGEMENT`; Lizenz `LicenseGuids.ReportManagement` oder `LicenseGuids.Centron` aktiv.
Fakt: `ReportEngineAppModuleController` ist in ModuleRegistration.cs unter "Reportverwaltung" mit `Helper.HasRights(UserRightsConst.Administration.REPORT_MANAGEMENT)` registriert. Berichtsdefinitionen liegen als BLOB in `dbo.ReportData.Report` bzw. `dbo.Reports.Report` (Spaltentyp `image`); die zu einem Bericht gehörenden SQL-Abfragen liegen in `dbo.ReportDataQueries (I3D, ReportDataI3D, Name, Statement, MasterI3D)`.
Aussage: Das System soll Berichtsdefinitionen und die zugehörigen SQL-Abfragen zentral in der Datenbank verwalten, sie Berichtsgruppen zuordnen und ihre Bearbeitung auf Benutzer mit dem Recht REPORT_MANAGEMENT beschränken.
Ergebnis: Berichte und Abfragen sind versioniert, gruppiert und mandantenbezogen abrufbar; ohne Recht ist die Reportverwaltung nicht sichtbar.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, Eintrag "Reportverwaltung": `ModuleRegistrationItem.For<ReportEngineAppModuleController>(() => Helper.HasRights(UserRightsConst.Administration.REPORT_MANAGEMENT), ...)` - Begründung: durchsetzende Bedingung für die Modulverfügbarkeit im Client.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[ReportDataQueries]` mit `[Statement] [text] NOT NULL` und `CONSTRAINT [FK_ReportDataQueries_ReportData] FOREIGN KEY([ReportDataI3D])` - Begründung: DB-Constraint bindet jede Abfrage an genau eine Berichtsdefinition.
- [SEKUNDÄR] src/backend/Centron.BL/ReportEngine/ReportDataQueryBL.cs, `SaveQuery(ReportDataQueryDTO query, int reportID)` - Begründung: zeigt Insert/Update auf `ReportDataQueries` mit parametrisierten Befehlen.
Prüfidee: Benutzer ohne REPORT_MANAGEMENT startet den Client: Das Modul "Reportverwaltung" darf nicht in der Modulliste erscheinen.
Tracelinks: SyRS-1017, SyRS-1018, SyRS-1019, SwRS-1036, SwRS-1037
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Grundlage des gesamten Belegdrucks und Berichtswesens.
Status: belegt
Modul: M-109
ID: StRS-1005
Titel: Tagesbezogene Arbeitszeit- und Tätigkeitserfassung mit Tagesabschluss
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Mitarbeiter / Teamleitung
Vorbedingung: Lizenz `LicenseGuids.MyDay` oder `LicenseGuids.Centron` aktiv; Benutzer ist angemeldet.
Fakt: `MyDayBL` verwaltet `MyDayWorkItem`, `MyDayUserItem`, `MyDayWorkItemReaction` und `MyDayFinalizedDay`. Arbeitsposten entstehen auch automatisch aus Helpdesk-Zeiterfassung (`TryUpdateWorkItemFromHelpdeskTimer(HelpdeskTimer timer)`, Typ `MyDayWorkItemType.HelpdeskCalcuableTimeRecording`). `MyDayEditorAppModuleController` ist als "Mein Tag" registriert.
Aussage: Das System soll je Mitarbeiter und Tag alle Arbeitsposten (manuell erfasst, aus Helpdesk-Timern übernommen oder generiert) in einer Zeitleiste führen und den Tag als abgeschlossen markierbar machen.
Ergebnis: Ein abgeschlossener Tag ist als `MyDayFinalizedDay` gespeichert; Monatsübersicht und Team-Übersicht bauen auf denselben Arbeitsposten auf.
Belege:
- [PRIMÄR] src/backend/Centron.BL/MyDay/MyDayBL.cs, `SaveOrUpdateFinalizedDay(MyDayFinalizedDay finalizedDay)` und `GetWorkItems(MyDayWorkItemsFilter filter, LoggedInUser loggedInUser)` - Begründung: definieren Speicherung des Tagesabschlusses und Abruf der Arbeitsposten.
- [PRIMÄR] src/backend/Centron.BL/MyDay/MyDayBL.cs, `TryUpdateWorkItemFromHelpdeskTimer(HelpdeskTimer timer)` - Begründung: belegt die automatische Übernahme von Ticketzeiten in die Tagesansicht.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, Kommentar "// Mein Tag" mit `ModuleRegistrationItem.For<MyDayEditorAppModuleController>` - Begründung: UI-Bezeichnung des Moduls.
Prüfidee: Helpdesk-Timer auf ein Ticket starten und stoppen; in "Mein Tag" muss ein Arbeitsposten desselben Zeitraums erscheinen. Tag abschließen → Datensatz in `MyDayFinalizedDay` vorhanden.
Tracelinks: SyRS-1022, SwRS-1039
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Basis für Leistungserfassung und Abrechenbarkeit.
Status: belegt
Modul: M-113
ID: StRS-1006
Titel: Nachvollziehbare Massenänderung von Belegen und Stammdaten
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Stammdatenverantwortlicher
Vorbedingung: Benutzer besitzt `UserRightsConst.DataUpdater.ACCESS_DATAUPDATER_MODULE` (20800127); Lizenz `LicenseGuids.DataUpdaterV2` oder `LicenseGuids.Centron` aktiv.
Fakt: `MassUpdateBL` stellt vier Massenläufe bereit: `StartReceiptPriceUpdate`, `StartArticlePriceUpdate`, `StartAccountDataUpdate`, `StartReceiptDataUpdate`. Jeder Lauf arbeitet auf einer vorab gespeicherten `MassUpdateTemplate` mit `MassUpdateTemplateItems`; jedes Element trägt `IsExecuted`, `ExecutedByI3D`, `ExecutedAt` und `FailureMessage`.
Aussage: Das System soll Preis- und Datenänderungen an Belegen, Artikeln und Konten als vorab definierte, wiederholbare Vorlage ausführen und je geändertem Objekt festhalten, wer die Änderung wann ausgeführt hat.
Ergebnis: Nach dem Lauf ist je Element der Erfolgs- oder Fehlerstatus abfragbar; erfolgreich geänderte Belege tragen einen Protokolleintrag der Art `ReceiptLogKind.DataUpdater`.
Belege:
- [PRIMÄR] src/backend/Centron.BL/MassUpdate/MassUpdateBL.cs, `StartReceiptPriceUpdate(int massUpdateI3D, LoggedInUser loggedInUser)`: `this._receiptLogBL.CreateEntry(loggedInUser.User.Employee.I3D, item.ObjectI3D, item.ObjectKind, ReceiptLogKind.DataUpdater, logText.ToString()); item.ExecutedByI3D = loggedInUser.User.Employee.I3D; item.ExecutedAt = DateTime.Now; item.IsExecuted = true;` - Begründung: durchsetzende Stelle der Protokollierung und Ausführungskennzeichnung.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `ALTER TABLE [dbo].[MassUpdateTemplate] ADD CONSTRAINT [FK_MassUpdateTemplate_ExecutedByI3D] FOREIGN KEY([ExecutedByI3D])` - Begründung: DB-Constraint verankert den Ausführenden.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, Kommentar "// Data Updater" mit `Helper.HasRights(UserRightsConst.DataUpdater.ACCESS_DATAUPDATER_MODULE)` - Begründung: belegt Zugangssteuerung des Moduls.
Prüfidee: Massenupdate mit zwei Belegen ausführen, davon einer nicht aktiv: Der aktive Beleg trägt einen ReceiptLog-Eintrag der Art DataUpdater, das zweite Element trägt `FailureMessage = "Beleg ist nicht aktiv."`.
Tracelinks: SyRS-1023, SwRS-1040
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Massenänderungen an Preisen sind revisionsrelevant.
Status: belegt
Modul: M-114
ID: StRS-1007
Titel: Schulungsvideos mit Nachverfolgung des Ansehens
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Mitarbeiter / Schulungsverantwortlicher
Vorbedingung: Videoportal-Modul ist registriert; der Mitarbeiter ist über `EmployeeIdentifier` (Kundennummer, EmployeeI3D, Name, E-Mail) identifiziert.
Fakt: `CentronOfficeVideoPortalApi` ruft ein externes c-entron-Office-Portal auf (Kategorien `videodirectory`, `video`, `videoview`). `MarkVideoAsSeen(int videoID)` schreibt einen `videoview`-Datensatz mit `VideoID`, `CentronCustomerNumber`, `EmployeeI3D`, `FirstName`, `LastName`, `EmailAddress`, `SeenAt`. `CheckIfAllVideosFromDirctoryAreSeen` löscht bei vollständigem Verzeichnis die zugehörige Todo-Zuweisung.
Aussage: Das System soll Schulungsvideos aus einem externen Videoportal in Verzeichnissen anbieten, je Mitarbeiter festhalten welche Videos gesehen wurden, und eine Videozuweisung aus der Todo-Liste entfernen, sobald alle Videos ihres Verzeichnisses gesehen sind.
Ergebnis: Der Schulungsstand je Mitarbeiter ist über `GetEmployeeEvaluation(int centronCustomerNumber)` als `VideoEmployeeEvaluation(EmployeeI3D, VideoDirectoryID, SeenVideoCount, SeenVideoIDs)` auswertbar.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Global/VideoPortal/CentronOfficeVideoPortalApi.cs, `MarkVideoAsSeen(int videoID)` - Begründung: durchsetzende Stelle der Sehen-Erfassung inkl. Todo-Bereinigung.
- [PRIMÄR] src/backend/Centron.BL/VideoPortal/VideoPortalAssignmentBL.cs, `SaveVideoPortalAssignment(VideoPortalAssignment assignment, LoggedInUser loggedInUser)` / `GetAllVideoPortalAssignments()` - Begründung: verankert die Zuweisung von Videoverzeichnissen an Mitarbeiter in der eigenen Datenbank.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Global/VideoPortal/CentronOfficeVideoPortalApi.cs, `record VideoEmployeeEvaluation(int EmployeeI3D, int VideoDirectoryID, int SeenVideoCount, int[] SeenVideoIDs)` - Begründung: belegt das Auswertungsergebnis.
Prüfidee: Einem Mitarbeiter ein Videoverzeichnis zuweisen, alle enthaltenen Videos ansehen: Die Todo-Zuweisung muss danach verschwunden sein und `GetEmployeeEvaluation` den vollen SeenVideoCount liefern.
Tracelinks: SyRS-1026
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Schulungsnachweise sind organisatorisch erforderlich.
Status: belegt
Modul: M-117
ID: StRS-1008
Titel: KI-Assistenz mit Benutzerkontrolle über ausgeführte Aktionen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Sachbearbeiter
Vorbedingung: Benutzer besitzt `UserRightsConst.ArtificialIntelligence.ID` (20800167); Lizenz `LicenseGuids.AiAssistant` aktiv; ein KI-Anbieterzugang ist konfiguriert.
Fakt: `ArtificialIntelligenceChatAppModuleController` ist als "AI-Chat" registriert. `ApiClientFactory` unterstützt die Anbietertypen `OpenAI`, `Ionos`, `OpenAiCompatible`, `Tensorx`, `ClaudeCode`, `MistralAi`, `GoogleAiStudio`. Der Chat kann über Tool-Handler auf Konten, Artikel, Mitarbeiter, Navigation und interaktive Module zugreifen; jeder Tool-Aufruf läuft über `IArtificialIntelligenceToolConfirmationService`.
Aussage: Das System soll einen in c-entron integrierten KI-Chat bereitstellen, der Systemfunktionen nur über explizit beschriebene Werkzeuge ausführt und jede Werkzeugausführung dem Benutzer zur Bestätigung vorlegt, sofern dieser nicht ausdrücklich uneingeschränkten Zugriff aktiviert hat.
Ergebnis: Der Benutzer sieht vor jeder Aktion Name, Beschreibung und Parameter des Werkzeugs und kann sie abbrechen.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/ArtificialIntelligence/Chat/Harness/ArtificialIntelligenceDefaultToolConfirmationService.cs, `ConfirmAsync(...)`: `if (this._allowUnrestrictedAccess?.Invoke() == true) return true;` gefolgt von `ShowDialog(message, "AI-Chat-Tool bestätigen", "Ausführen", "Abbrechen")` - Begründung: durchsetzende Stelle der Benutzerbestätigung.
- [PRIMÄR] src/backend/Centron.BL/ArtificialIntelligence/ApiClientFactory.cs, `switch (settings.ApiType)` mit den sieben `ArtificialIntelligenceApiType`-Fällen - Begründung: definiert den Anbieterumfang.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, Kommentar "// AI-Chat" mit `Helper.HasRights(UserRightsConst.ArtificialIntelligence.ID)` - Begründung: belegt die Rechtevoraussetzung.
Prüfidee: Benutzer ohne UNRESTRICTED_ACCESS stellt eine Frage, die einen Tool-Aufruf auslöst: Es muss ein Dialog "AI-Chat-Tool bestätigen" mit Parameterliste erscheinen; "Abbrechen" verhindert die Ausführung.
Tracelinks: SyRS-1027, SwRS-1042
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - KI-Zugriff auf ERP-Daten erfordert explizite Kontrolle.
Status: belegt
Modul: M-118
ID: StRS-1009
Titel: Zentrale Dateiablage mit Versionierung und exklusiver Bearbeitung
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Sachbearbeiter
Vorbedingung: Benutzer ist angemeldet; ein Verzeichnis der Dateiablage ist ausgewählt.
Fakt: `ICentronFileSystemConnector`/`CentronFileSystemConnector` bietet Verzeichnis- und Dokumentoperationen (`CreateDirectory`, `GetSubDirectoriesAsync`, `AddDocumentToDirectoryAsync`, `RenameDocumentAsync`, `DeleteDocumentAsync`) sowie `AddNewDocumentVersion`, `CheckOutDocument`, `CheckInDocument`, `UndoCheckOut` und `CreateIndexesForDocument`. Serverseitig setzt `DocumentBL.CheckOutDocument` die Felder `LockedBy`, `LockedByWorkstation`, `LockedFilePath` am `Document`.
Aussage: Das System soll Dokumente in einer hierarchischen Ablage speichern, jede Änderung als neue Dokumentversion ablegen und ein in Bearbeitung befindliches Dokument mit Benutzer, Arbeitsstation und Ablagepfad als gesperrt kennzeichnen.
Ergebnis: Ein ausgechecktes Dokument ist erkennbar gesperrt; beim Einchecken wird die Sperre gelöst und eine neue Version erzeugt.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/FileManagement/DocumentBL.cs, `CheckInDocument(int documentI3D, byte[] fileData, LoggedInUser currentUser)`: setzt `LockedFilePath/LockedBy/LockedByWorkstation = null` und ruft anschließend `this.AddNewDocumentVersion(document.I3D, fileData, currentUser)` - Begründung: durchsetzende Stelle für Sperraufhebung plus Versionsanlage.
- [PRIMÄR] src/backend/Centron.BL/Administration/FileManagement/DocumentBL.cs, `CanCheckOutDocument(int documentI3D)`: `if (document.LockedBy != null && document.LockedByWorkstation != null && document.LockedFilePath != null) return Result<bool>.AsSuccess(false);` - Begründung: konkrete Sperrbedingung.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/CentronFileSystem/CentronFileSystemConnector.cs, Methodensatz inkl. `CreateIndexesForDocument(int documentI3D)` - Begründung: belegt den Funktionsumfang der Ablage im Client.
Prüfidee: Dokument auschecken; ein zweiter Benutzer ruft `CanCheckOutDocument` für dasselbe Dokument auf → Ergebnis `false`. Nach Check-In existiert eine zusätzliche Dokumentversion.
Tracelinks: SyRS-1031, SwRS-1043
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Dokumentenlenkung ist Grundfunktion des ERP.
Status: belegt
Modul: M-122
@@ -0,0 +1,203 @@
ID: StRS-101
Titel: Einheitlicher Belegkreis über alle Verkaufsbelegarten
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertriebsmitarbeiter / Sachbearbeiter
Vorbedingung: Der Benutzer ist angemeldet und einem Kunden bzw. Lieferanten zugeordnet.
Fakt: `ReceiptBase` (abstrakte Basisklasse) definiert für alle Belegarten dieselben Kopfdaten (`Version`, `State`, `ConcurrencyControlGuid`) und die abstrakten Operationen `ReceiptKind`, `GetReceiptItems()`, `SetReceiptItems()`, `AddItem()`, `RemoveItem()`. `SpecificLogics` registriert genau die konkreten Ausprägungen `CreditVoucherSpecificLogic`, `DeliveryListSpecificLogic`, `InvoiceSpecificLogic`, `OfferSpecificLogic`, `OrderSpecificLogic`, `PickupListSpecificLogic`, `ContractSpecificLogic` sowie die Lieferantenvarianten.
Aussage: Das System soll Angebot, Auftrag, Lieferschein, Rechnung, Gutschrift, Abholschein und Vertrag als Ausprägungen eines einheitlichen Belegmodells mit gemeinsamen Kopf-, Positions- und Versionsdaten führen.
Ergebnis: Fachliche Regeln (Nummernvergabe, Sperre, Versionierung, Preisfindung) gelten belegartübergreifend; belegartspezifische Abweichungen sind auf die jeweilige `*SpecificLogic` begrenzt.
Belege:
- [PRIMÄR] src/backend/Centron.Entities/Entities/Sales/Receipts/ReceiptBase.cs, Zeilen 9-71 (`public abstract class ReceiptBase : BaseEntity, IReceiptBase`, `public abstract CentronObjectKindNumeric ReceiptKind { get; }`) - Begründung: Die gemeinsame Basisklasse mit abstraktem `ReceiptKind` erzwingt im Code, dass jede Belegart dasselbe Grundmodell erfüllt.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/SpecificLogics.cs, Zeilen 46-57 (`private readonly IReceiptSpecificLogic[] _receiptSpecificLogics = new IReceiptSpecificLogic[] { new CreditVoucherSpecificLogic(session), ... }`) - Begründung: Die Registrierungsliste ist die durchsetzende Stelle, an der der vollständige Belegkreis definiert ist.
- [KONTEXT] docs/reference/receipts/receipts-backend-architecture.md, Abschnitt "Receipt Types Hierarchy" (Tabelle Entity-Klasse/Tabelle/View) - Begründung: Dokumentiert die 1:1-Zuordnung Entität → `*Kopf`/`*Pos`-Tabelle und bestätigt die Codebeobachtung.
Prüfidee: Für jede in `SpecificLogics` registrierte Belegart lässt sich ein Beleg anlegen, speichern, versionieren und wieder laden; die Kopfdaten `Version`, `State` und `ConcurrencyControlGuid` sind in allen Fällen befüllt.
Tracelinks: SyRS-111, SyRS-112, SyRS-113
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Das gemeinsame Belegmodell ist der tragende Kern der Fakturierung und in jeder Zielarchitektur erforderlich.
Status: belegt
Modul: M-01
ID: StRS-102
Titel: Belege nur durch berechtigte Benutzer bearbeitbar
Ebene: StRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Sachbearbeiter / Rechteadministrator
Vorbedingung: Ein Beleg existiert und ein Benutzer versucht, ihn zu ändern oder eine neue Version anzulegen.
Fakt: `ReceiptBL.CanUserEditReceipt(...)` prüft zuerst `HasRightToEditReceipt(appUser)` und bricht mit `DefaultMessageCodes.RightCheckFailed` ab; anschließend wird bei gesetztem `HasRightToEditReceiptOnlyOwnBranch(appUser)` per `BranchBL.IsBranchEqual(appUser.Employee.BranchI3D, receiptBranchI3D)` auf die eigene Filiale eingeschränkt. Die Methode wird an mindestens acht Stellen von `ReceiptBL` aufgerufen (u. a. `CreateNewVersion<T>`, `SaveReceipt<T,TReceiptItem>`).
Aussage: Das System soll das Ändern eines Belegs nur zulassen, wenn der Benutzer das Bearbeitungsrecht für die betreffende Belegart besitzt und – sofern die Filialbeschränkung aktiv ist – der Beleg zu seiner Filiale gehört.
Ergebnis: Bei fehlendem Recht wird der Vorgang abgebrochen und eine benannte Fehlermeldung ("Der Benutzer hat nicht das Recht Belege vom Typ ... zu bearbeiten.") zurückgegeben; es erfolgt keine Persistierung.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 10272-10295, Methode `CanUserEditReceipt(CentronObjectKindNumeric receiptKind, AppUser appUser, int? receiptBranchI3D)`; konkrete Prüfungen `if (!hasRightToEditReceipt) return Result.AsError(..., DefaultMessageCodes.RightCheckFailed);` und `BranchBL.IsBranchEqual(appUser.Employee.BranchI3D, receiptBranchI3D)` - Begründung: Dies ist die durchsetzende Stelle; ohne erfolgreichen Rückgabewert wird der Speicher-/Versionsvorgang in `SaveReceipt` bzw. `CreateNewVersion` verlassen.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 3081-3083 und 3625-3627 (`var canEditReceiptResult = this.CanUserEditReceipt(...); if (canEditReceiptResult.Status == ResultStatus.Error) return result.SetMessage(...).GetResult();`) - Begründung: Belegt, dass die Prüfung tatsächlich vor der Persistierung greift und den Ablauf abbricht.
Prüfidee: Ein Benutzer ohne Bearbeitungsrecht für Rechnungen erhält beim Speichern einer Rechnung die Fehlermeldung mit MessageCode `RightCheckFailed`; ein Benutzer mit gesetztem "nur eigene Filiale"-Recht kann eine Rechnung einer fremden Filiale nicht speichern.
Tracelinks: SyRS-112, SyRS-113
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Rechtebasierte Belegbearbeitung ist rechtlich und organisatorisch zwingend.
Status: belegt
Modul: M-01
ID: StRS-103
Titel: Preisfindung nach fester Vorrangreihenfolge
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Sachbearbeiter / Preisfindung
Vorbedingung: Einem Beleg wird eine Artikelposition hinzugefügt; Kunde, ggf. Vertrag, Sondervereinbarung und Menge sind bekannt.
Fakt: `ReceiptItemPriceBL.GetBasePrice(...)` ermittelt den Basispreis in fester Reihenfolge: Ausgangswert ist `GetSellPriceForCustomer(article, customer)`; ein `SpecialAgreement` mit `EKVKReduction == 1` liefert einen prozentualen Nachlass; danach wird ein `ContractSpecialPrice` ausgewertet (10 Kombinationen aus `ContractSpecialPriceKind` × `ContractSpecialPriceChangeKind`); nur wenn kein Vertragspreis existiert, greift ein `CustomerSpecialPrice`; nur wenn auch dieser fehlt und eine Menge übergeben wurde, greift der Staffelpreis `GetArticleVolumePrice(article, quantity)`.
Aussage: Das System soll den Verkaufspreis einer Belegposition nach der Vorrangreihenfolge Vertragssonderpreis vor Kundensonderpreis vor Staffelpreis vor kundengruppenabhängigem Listenverkaufspreis ermitteln und Sondervereinbarungsnachlässe additiv als Prozentrabatt aufschlagen.
Ergebnis: Für eine Artikel-Kunden-Kombination ist der ermittelte Preis reproduzierbar und die angewandte Preisquelle nachvollziehbar.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/Internal/ReceiptItemPriceBL.cs, Zeilen 154-286, Methode `GetBasePrice(decimal purchaseBasePrice, IPricedArticle article, Customer customer, int? contractI3D, SpecialAgreement specialAgreement, decimal? quantity, decimal? sellPrice, int? priceList)`; die `if (contractSpecialPrice is not null) { ... } else { specialPrice ... else if (quantity != null) { volumePrice } }`-Kaskade - Begründung: Die verschachtelte Bedingungskette ist die durchsetzende Stelle der Vorrangreihenfolge.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/Internal/ReceiptItemPriceBL.cs, Zeilen 280-283 (`if (discountAsFixedPrice != 0 && basePrice != 0) { discountAsPercentage += (discountAsFixedPrice / basePrice) * 100; }`) - Begründung: Belegt, dass Festbetragsnachlässe systematisch in Prozentrabatte umgerechnet werden, weil die Belegposition nur `BasePrice` und `Discount` speichert.
Prüfidee: Für einen Artikel mit gleichzeitig hinterlegtem Vertragssonderpreis, Kundensonderpreis und Staffelpreis liefert `GetBasePrice` den Vertragssonderpreis; nach Löschen des Vertragsbezugs den Kundensonderpreis; nach Löschen des Kundensonderpreises den Staffelpreis.
Tracelinks: SyRS-115, SyRS-116
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Preisfindung ist die zentrale Wertschöpfungsregel des Belegwesens.
Status: belegt
Modul: M-02
ID: StRS-104
Titel: Belegartübergreifende Suche mit einheitlichem Filter
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Sachbearbeiter / Vertrieb
Vorbedingung: Der Benutzer öffnet die Belegsuche und gibt mindestens ein Suchkriterium an.
Fakt: `ReceiptSearcher` iteriert über zwölf registrierte `ReceiptSearchConfiguration`-Instanzen (Angebot, Auftrag, Lieferschein, Abholschein, Rechnung, Gutschrift, Vertrag sowie fünf Lieferantenvarianten) und führt für jede eine eigene, aus demselben `ReceiptSearchFilter` erzeugte SQL-Abfrage aus; das Ergebnis wird zu einer Liste `ReceiptSearchItemDTO` zusammengeführt und mit `.OrderBy(f => f.ObjectKind).ThenByDescending(f => f.Number)` sortiert.
Aussage: Das System soll eine gemeinsame Suchmaske bereitstellen, mit der Belege aller Belegarten anhand desselben Kriteriensatzes (Nummer, Datum, Konto, Betrag, Zahlungs-/Lieferkondition, Positionstext, Artikel u. a.) gefunden werden können.
Ergebnis: Der Benutzer erhält eine belegartübergreifende, seitenweise Trefferliste mit Kopfdaten, Beträgen, Status und Konditionen je Treffer.
Belege:
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/ReceiptSearcher.cs, Zeilen 27-42 (Konstruktor mit dem `ReceiptSearchConfiguration[]`-Array) und Zeilen 48-79 (`SearchReceipts(ReceiptSearchFilter filter, LoggedInUser user)`) - Begründung: Die Registrierungsliste und die Iterationsschleife setzen die belegartübergreifende Suche durch.
- [SEKUNDÄR] src/backend/Centron.Interfaces/Sales/Receipts/ReceiptSearch/ReceiptSearchFilter.cs (Filtereigenschaften `SearchText`, `ReceiptNumber`, `DateFrom`/`DateTo`, `AccountI3D`, `GrossPriceFrom`/`GrossPriceTo`, `PaymentConditionI3D`, `DeliveryConditionI3D`, `ArticleI3Ds`) - Begründung: Definiert den fachlichen Kriteriensatz, der für alle Belegarten gilt.
Prüfidee: Eine Suche ohne Einschränkung auf `ReceiptKinds` liefert Treffer aus mindestens zwei unterschiedlichen `ObjectKind`-Werten; eine Suche mit `ReceiptKinds = {InvoiceClass}` liefert ausschließlich `ObjectKind = 4`.
Tracelinks: SyRS-117, SyRS-118, SyRS-119
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Belegartübergreifende Recherche ist eine Kernanforderung des Tagesgeschäfts.
Status: belegt
Modul: M-03
ID: StRS-105
Titel: Zahlungs-, Liefer- und Belegkonditionen als zentrale Stammdaten
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Stammdatenverwalter
Vorbedingung: Der Benutzer öffnet das Modul "Belegkonditionen".
Fakt: Der Modulcontroller `ReceiptConditionManagementAppModuleController` deklariert `ModuleName => "Belegkonditionen"` und `Description => "Verwaltung von Belegkonditionen, Zahlungskonditionen und Lieferbedingungen"`. Die zugehörige Entität `AssetCondition` ist per `AssetConditionMaps` auf die Tabelle `Zahkond` gemappt; die Spalten `GltAnge`, `GltAuf`, `GltLief`, `GltRech`, `GltGuts`, `GltAbhol`, `GltBestellung`, `GltLiefgutschrift`, `GltLieferbedingung`, `GltLieferbedingungLieferant` steuern, für welche Belegart eine Kondition gilt.
Aussage: Das System soll Zahlungs-, Liefer- und Belegkonditionen in einem gemeinsamen Stammdatensatz verwalten und je Kondition festlegen, für welche Belegarten sie auswählbar ist.
Ergebnis: In einem Beleg werden nur die Konditionen zur Auswahl gestellt, deren belegartspezifisches "Gilt-für"-Kennzeichen gesetzt ist.
Belege:
- [PRIMÄR] src/backend/Centron.DAO/Mappings/Administration/MasterData/AssetConditionMaps.cs, Zeilen 10-38 (`Table("Zahkond");` sowie `Map(aa => aa.BelongsToOffer).Column("GltAnge");` bis `Map(aa => aa.SupplierDeliveryCondition).Column("GltLieferbedingungLieferant");`) - Begründung: Das Mapping ist die durchsetzende Stelle, die die belegartbezogene Gültigkeit im Datenmodell verankert.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, Zeilen 6846-6906, `CREATE TABLE [dbo].[Zahkond]` mit `CONSTRAINT [PK_Zahkond] PRIMARY KEY CLUSTERED ([I3D] ASC)` - Begründung: Bestätigt Existenz und Struktur der Konditionstabelle inklusive Primärschlüssel.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Administration/ReceiptConditions/ReceiptConditionManagementAppModuleController.cs, Zeilen 18-19 (UI-Strings `"Belegkonditionen"`, `"Verwaltung von Belegkonditionen, Zahlungskonditionen und Lieferbedingungen"`) - Begründung: Belegt die fachliche Bündelung der drei Konditionsarten in einem Modul.
Prüfidee: Eine Kondition mit `GltRech = 1` und `GltAnge = 0` erscheint in der Zahlungskonditionsauswahl einer Rechnung, nicht aber in der eines Angebots.
Tracelinks: SyRS-120, SyRS-121
Konsolidierung: Kandidat: Zahlungskondition, Lieferbedingung und Belegkondition sind fachlich getrennte Konzepte, teilen sich aber Tabelle `Zahkond` und Entität `AssetCondition`.
Übernahmewürdigkeit: Workaround - Die Bündelung dreier fachlicher Konzepte in einer Tabelle mit Boolean-Spalten pro Belegart ist historisch bedingt und sollte in einer Neuentwicklung getrennt modelliert werden.
Status: belegt
Modul: M-04
ID: StRS-106
Titel: Provisionierung von Belegen an Provisionsempfänger
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertriebsleitung / Provisionsempfänger
Vorbedingung: Ein Kundenbeleg wird angelegt oder gespeichert; für den Kunden ist ein Provisionsschema ermittelbar oder eine Auto-Provisionierung konfiguriert.
Fakt: `ReceiptProvisionBL.FillNewReceiptWithDefaultProvision(...)` befüllt neue Belege, die `IReceiptWithProvision` implementieren, entweder aus dem Provisionsschema (`GetCurrentReceiptProvisionForReceipt(customerI3D, branchI3D)`) oder – falls keine Schemata existieren – über `AutoProvisionForNewReceipts()`, `GetEmployeeForAutoProvision(...)` und `GetAutoProvisionShare()`. Beim Speichern schreibt `SaveProvision(...)` je Empfänger eine `ReceiptProvisionItemEntity` mit `ActualProvisionSales`, `ActualProvisionEarnings` und `ActualProvision`.
Aussage: Das System soll für provisionsfähige Belege je Provisionsempfänger den Provisionsanteil und den daraus resultierenden Provisionsbetrag ermitteln und belegbezogen speichern.
Ergebnis: Zu jedem gespeicherten provisionsfähigen Beleg existieren Provisionsdatensätze mit aufgelöstem Mitarbeiter, Anteil und berechnetem Provisionsbetrag, die in der Provisionsauswertung erscheinen.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/Internal/ReceiptProvisionBL.cs, Zeilen 161-268, Methode `SaveProvision(IReceiptBase receipt, IReceiptBase previousReceiptVersion, SaveReceiptData data, SaveReceiptResultBuilder result)`; erzeugt `new ReceiptProvisionItemEntity { ... ActualProvision = pricesAndProvisions.ProvisionSales.GetValueOrDefault() + pricesAndProvisions.ProvisionEarnings.GetValueOrDefault() }` - Begründung: Die Persistierung der berechneten Provisionsbeträge ist die durchsetzende Stelle.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/Internal/ReceiptProvisionBL.cs, Zeilen 171-178 (`if (this._specificLogics.Execute(receipt, f => f.IsProvisionRequired()) && data.IgnoreCallbacks == false) { result.SetMessage("Provisionierung fehlt.", SaveReceiptErrorMissingField.Provision); return; }`) - Begründung: Belegt, dass die Provisionierung für bestimmte Belegarten erzwungen wird.
Prüfidee: Für einen Kunden mit zugeordnetem Provisionsschema entstehen beim Speichern eines Auftrags so viele `ReceiptProvisionItemEntity`-Sätze wie das Schema Empfänger definiert, jeweils mit gefülltem `ActualEmployeeI3D`.
Tracelinks: SyRS-122
Konsolidierung: Kandidat: Schema-basierte Provisionierung (`ReceiptProvisionItemEntity`) und altes Provisionsverfahren (NamedQuery über `{TablePrefix}Provision`) implementieren dasselbe fachliche Konzept doppelt.
Übernahmewürdigkeit: übernehmen - Provisionsabrechnung ist ein eigenständiges Geschäftsziel; das alte Verfahren sollte dabei abgelöst werden.
Status: belegt
Modul: M-05
ID: StRS-107
Titel: Umsatzsteuerermittlung je Belegposition
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Buchhaltung / Sachbearbeiter
Vorbedingung: Eine Artikelposition wird einem Beleg mit einem Belegdatum hinzugefügt.
Fakt: `ValueAddedTax` trägt `TaxRate`, `Country`, `ExpirationDate`, `TaxRateValidFrom`, `NextTaxRate`, `Default`, `UseForDeaktivatedVat` sowie getrennte Erlös- und Aufwandskonten für Inland, EU und Drittland (`RevenueAccount`, `RevenueAccountEU`, `RevenueAccountOverseas`, `ExpenseAccount`, `ExpenseAccountEU`, `ExpenseAccountOverseas`). `TaxBL.GetDefaultTaxtRateByArticle(int? artikelI3D)` ermittelt den Steuersatz in der Reihenfolge Artikel-MwSt (`article.VAT`) → sekundäre Warengruppe (`article.SecondaryMaterialGroup.VatI3D`) → Warengruppe (`article.MaterialGroup.VatI3D`) → Landesvorgabe (`GetDefaultTaxtRateByCountry()`).
Aussage: Das System soll den Umsatzsteuersatz einer Belegposition aus dem Artikel, ersatzweise aus dessen Warengruppen und zuletzt aus der Landesvorgabe ableiten und dabei getrennte Erlös-/Aufwandskonten für Inland, EU und Drittland führen.
Ergebnis: Jede Artikelposition trägt einen eindeutig hergeleiteten Steuersatz mit zugehöriger Kontenzuordnung für die Finanzbuchhaltung.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Warehousing/TaxBL.cs, Zeilen 239-284, Methoden `GetDefaultTaxtRateByArticle(int? artikelI3D)` und `GetDefaultTaxtRateByCountry()`; die Fallback-Kette `article.VAT` → `secondaryMaterialGroupTaxRate` → `materialGroupTaxRate` → `GetDefaultVatForDeaktivatedVat(countryI3D)` - Begründung: Die if-Kaskade ist die durchsetzende Stelle der Steuerermittlung.
- [PRIMÄR] src/backend/Centron.Entities/Entities/Warehousing/ValueAddedTax.cs, Zeilen 10-29 (Eigenschaften `TaxRate`, `Country`, `RevenueAccount`, `RevenueAccountEU`, `RevenueAccountOverseas`, `ExpenseAccount`, `ExpenseAccountEU`, `ExpenseAccountOverseas`, `ExpirationDate`, `NextTaxRate`) - Begründung: Das Datenmodell belegt die geforderte Kontendifferenzierung und die Zeitabhängigkeit.
Prüfidee: Ein Artikel ohne eigene MwSt-Zuordnung, dessen Warengruppe 7 % trägt, erhält beim Hinzufügen in einen Beleg 7 %; nach Setzen einer Artikel-MwSt von 19 % erhält er 19 %.
Tracelinks: SyRS-123, SyRS-124
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Steuerermittlung ist gesetzlich zwingend und geschäftskritisch.
Status: belegt
Modul: M-06
ID: StRS-108
Titel: Digitale Erfassung von Lieferantenbelegen aus PDF-Dokumenten
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Einkaufssachbearbeiter
Vorbedingung: Ein Lieferantenbeleg liegt als PDF vor (z. B. aus E-Mail-Eingang) und für den Lieferanten ist mindestens eine Scan-Konfiguration hinterlegt.
Fakt: Der Modulcontroller `SupplierReceiptDocumentsImportAppModuleController` deklariert `ModuleName => "Eingang/Kalk"` und `Description => "Import von Lieferantenbelegen aus E-Mails"`. `SupplierReceiptDocumentBL.ScanDocument(LoggedInUser, int supplierI3D, SupplierReceiptDocumentKind kind, byte[] pdf)` lädt die lieferantenspezifischen `SupplierPdfScanConfigs`, liest mit `PdfScanner.TryFindValue(config, Color.Empty)` definierte Bereiche aus und liefert `DocumentMetaInformation`-Objekte für "Rechnungsnummer", "Eigene Bestellnummer", "Rechnungsdatum", "Netto Summe", "Brutto Summe", "Fracht" und "Versicherung".
Aussage: Das System soll aus eingehenden Lieferanten-PDFs anhand lieferantenspezifischer Scan-Konfigurationen Belegnummer, Bestellnummer, Belegdatum, Netto-/Bruttosumme sowie Fracht- und Versicherungsbeträge automatisch auslesen und als Vorschlagswerte für die Belegerfassung bereitstellen.
Ergebnis: Der Sachbearbeiter erhält vorbefüllte Belegkopfdaten und muss diese nur noch prüfen und bestätigen.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/SupplierReceiptDocuments/SupplierReceiptDocumentBL.cs, Zeilen 411-461, Methode `ScanDocument(...)`; `using (var scanner = new PdfScanner(pdf)) { ... var scanResult = scanner.TryFindValue(config, Color.Empty); var metaInformation = this.GetMetaInformation(config.Name, scanResult); ... }` - Begründung: Die Scan-Schleife über die Lieferantenkonfiguration ist die durchsetzende Stelle der automatisierten Erfassung.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/SupplierReceiptDocuments/SupplierReceiptDocumentBL.cs, Zeilen 479-500, Methode `GetMetaInformation(string configName, PdfScannerResult scanResult)` mit dem `switch`-Block über die Konfigurationsnamen - Begründung: Legt den verbindlichen Satz auslesbarer Felder fest.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/Receipts/SupplierReceiptDocuments/SupplierReceiptDocumentsImportAppModuleController.cs, Zeilen 22-23 (UI-Strings `"Eingang/Kalk"`, `"Import von Lieferantenbelegen aus E-Mails"`) - Begründung: Belegt den fachlichen Zweck des Moduls.
Prüfidee: Für einen Lieferanten mit hinterlegter Scan-Konfiguration liefert `ScanDocument` mit einem Muster-PDF eine `DocumentMetaInformation` vom Typ `ReceiptNumber` mit der im PDF sichtbaren Rechnungsnummer.
Tracelinks: SyRS-125
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Automatisierte Belegerfassung senkt den Erfassungsaufwand im Einkauf spürbar.
Status: belegt
Modul: M-07
ID: StRS-109
Titel: Lückenlose Protokollierung von Belegausgabe und Belegänderungen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Sachbearbeiter / Revision
Vorbedingung: Ein Beleg wird gedruckt, als PDF exportiert, per E-Mail versendet oder inhaltlich verändert.
Fakt: `ReceiptLogBL.CreateReportEntry(AppUser, ReportAction, string reportName, int receiptI3D, CentronObjectKindNumeric receiptKind, int? receiptVersion, string receiptEmail)` legt je Ausgabeaktion einen Logeintrag an: `ReportAction.Export` → `ReceiptLogKind.PDFExport`, `ReportAction.Print` → `ReceiptLogKind.Print`, `ReportAction.Mail` → `ReceiptLogKind.Mail` (inkl. E-Mail-Adresse); für `ReportAction.Preview` wird bewusst kein Eintrag erzeugt (`return; //Create no logs for preview`). `CreateEntry(...)` schreibt Mitarbeiter, Zeitpunkt, Belegversion und die c-entron-Programmversion mit.
Aussage: Das System soll jede Belegausgabe (Druck, PDF-Export, Mailversand) sowie wesentliche inhaltliche Änderungen mit Benutzer, Zeitpunkt, Belegversion und Programmversion protokollieren; reine Vorschauen sollen nicht protokolliert werden.
Ergebnis: Zu jedem Beleg ist nachvollziehbar, wer ihn wann in welcher Version ausgegeben oder geändert hat.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptLogBL.cs, Zeilen 147-189, Methode `CreateReportEntry(...)`; `switch (reportAction) { case ReportAction.Preview: return; case ReportAction.Export: logKind = ReceiptLogKind.PDFExport; ... }` - Begründung: Die Fallunterscheidung ist die durchsetzende Stelle für Umfang und Ausschluss der Ausgabeprotokollierung.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptLogBL.cs, Zeilen 102-115, Methode `CreateEntry(...)`; setzt `log.Date`, `log.ReceiptVersion`, `log.Employee`, `log.CentronVersion = AssemblyLogic.GetFromAssemblyContaining<ReceiptLogBL>().ToString()` und speichert über `this.Session.GetGenericDAO<ReceiptLog>().Save(log)` - Begründung: Belegt die tatsächlich persistierten Protokollattribute.
- [SEKUNDÄR] src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/InvoiceReceiptSearchConfiguration.cs, Zeilen 58-59 (`PrintedVersion = (SELECT TOP 1 al.AnlageVersion FROM AnlageLog al WHERE al.AnlageI3D = AK.I3D AND al.AnlageArt = 4 AND al.Art = 2 ...)`) - Begründung: Zeigt, dass die Protokolleinträge fachlich ausgewertet werden, um gedruckte und gemailte Belegversionen anzuzeigen.
Prüfidee: Nach Druck einer Rechnung existiert ein `AnlageLog`-Satz mit `AnlageArt = 4`, `Art = 2`, korrekter `AnlageVersion` und `PersonalI3D` des Druckenden; nach einer reinen Vorschau entsteht kein zusätzlicher Satz.
Tracelinks: SyRS-126, SyRS-127
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Nachvollziehbarkeit der Belegausgabe ist Grundlage von Reklamations- und Prüfprozessen.
Status: belegt
Modul: M-08
ID: StRS-110
Titel: Kreditlimitüberwachung beim Speichern von Kundenbelegen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Sachbearbeiter / Debitorenbuchhaltung
Vorbedingung: Für den Kunden ist ein Kreditlimit (`CreditLimit > 0`) und eine Berechnungsart (`CreditLimitCalculationKind` ungleich 2) hinterlegt; ein limitrelevanter Beleg wird gespeichert.
Fakt: `ReceiptBL.CheckIfCustomerLimitIsReached(...)` summiert über alle `SpecificLogics`, für die `TakesPlaceInLimitCalculation(null)` gilt, den Wert `GetUsedLimitAmount(customerI3D, limitCalculationKind)`, zieht den Wert der Vorversion ab und vergleicht `limitUsedInThisReceipt > limitAvailable`. Die Berechnungsart wird über `customer.CreditLimitCalculationKind == 1 ? CreditLimitCalculationKind.Net : CreditLimitCalculationKind.Gross` gewählt. Abschließend wird `customer.CreditLimitAvailable = limitAvailable - limitUsedInThisReceipt` fortgeschrieben.
Aussage: Das System soll beim Speichern eines limitrelevanten Kundenbelegs das ausgeschöpfte Kreditlimit über alle offenen Belegarten hinweg wahlweise netto oder brutto ermitteln und bei Überschreitung eine ausdrückliche Bestätigung des Benutzers verlangen.
Ergebnis: Der Beleg wird nur nach expliziter Bestätigung (`SaveAlthoughCustomerLimitExceeded`) gespeichert; das verfügbare Restlimit des Kunden wird aktualisiert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 8636-8690, Methode `CheckIfCustomerLimitIsReached(IReceiptBase receipt, IReceiptBase previousVersion, SaveReceiptData data, SaveReceiptResultBuilder result)`; die Bedingung `if (limitUsedInThisReceipt > limitAvailable && data.SaveAlthoughCustomerLimitExceeded == false && data.IgnoreCallbacks == false)` - Begründung: Diese Bedingung ist die durchsetzende Stelle der Limitprüfung.
- [SEKUNDÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeile 8681 (Meldungstext `"Das Limit von {customer.CreditLimit:N2} ... wurde um {difference:N2} ... überschritten."`) - Begründung: Belegt die fachliche Rückmeldung an den Benutzer inkl. Aufschlüsselung nach Belegarten.
Prüfidee: Ein Kunde mit Kreditlimit 1.000 € und 900 € offenen Belegen erhält beim Speichern eines weiteren Belegs über 200 € den Bestätigungsdialog; nach Bestätigung ist `CreditLimitAvailable` negativ.
Tracelinks: SyRS-114
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Kreditlimitüberwachung schützt unmittelbar vor Forderungsausfällen.
Status: belegt
Modul: M-01
@@ -0,0 +1,348 @@
ID: SyRS-111
Titel: Kollisionsfreie Vergabe der Belegnummer beim Speichern
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: System (ReceiptBL / NumberGroupBL)
Vorbedingung: Ein neuer Beleg wird gespeichert und `data.IsOnlyForReportPreview` ist `false`.
Fakt: `ReceiptBL.ShouldUpdateNumberOnSave(bool isNewReceipt, SaveReceiptData data)` liefert `isNewReceipt && data.IsOnlyForReportPreview == false`; nur dann ruft `SaveReceipt` `UpdateReceiptNumber(receipt, updateDatabase: true)` auf. `UpdateReceiptNumber` bestimmt die Nummerngruppe über `_specificLogics.Execute(receipt, f => f.GetNumberGroup(receipt))` und – falls `receipt.BranchI3D > 0` – die filialbezogene `NumberGroup`. Für Belegvorlagen (Kunde == `GetReceiptTemplateCustomerNumber()`) wird stattdessen `_receiptTemplateBL.GetNextReceiptTemplateNumber(receipt)` verwendet. Die Nummernvergabe erfolgt erst nach allen positionsverändernden Schritten ("UPDATE REGION 1").
Aussage: Das System soll die endgültige Belegnummer erst beim tatsächlichen Speichern eines neuen Belegs vergeben, dabei die Nummerngruppe belegart- und filialabhängig auswählen und Belegvorlagen aus einem separaten Nummernkreis versorgen.
Ergebnis: Reine Reportvorschauen und abgebrochene Speichervorgänge verbrauchen keine Belegnummer; Filialen erhalten getrennte Nummernkreise.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 8573-8576, Methode `ShouldUpdateNumberOnSave(bool isNewReceipt, SaveReceiptData data)` mit `return isNewReceipt && data.IsOnlyForReportPreview == false;` - Begründung: Diese Bedingung entscheidet als einzige Stelle, ob eine Nummer verbraucht wird.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 7265-7285, Methode `UpdateReceiptNumber(IReceiptBase receipt, bool updateDatabase)`; Zweig `receipt.Number = isTemplate ? this._receiptTemplateBL.GetNextReceiptTemplateNumber(receipt) : this._numberGroupBL.GetNextNumber(numberGroupEnum, numberGroupObject, updateDatabase);` - Begründung: Zeigt die belegart-, filial- und vorlagenabhängige Auswahl der Nummernquelle.
- [KONTEXT] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 3796-3803 (Kommentar "Create Number If Needed (should be the last method, of the 'UPDATE REGION 1'" ... "The new number group for 0,00 invoices is dependent on the positions.") - Begründung: Erklärt, warum die Nummernvergabe zwingend nach der Positionsverarbeitung erfolgt.
Prüfidee: Ein Speichervorgang mit `IsOnlyForReportPreview = true` erhöht `NumberGroup.Current` nicht; ein regulärer Speichervorgang erhöht ihn genau um das konfigurierte `Interval`.
Tracelinks: StRS-101, SwRS-128
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Lückenlose, nicht vorzeitig verbrauchte Belegnummern sind eine Grundanforderung der Fakturierung.
Status: belegt
Modul: M-01
ID: SyRS-112
Titel: Exklusive Belegsperre für den bearbeitenden Benutzer
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: System (AssetLockBL) / Sachbearbeiter
Vorbedingung: Ein Benutzer möchte eine neue Belegversion anlegen oder einen Beleg bearbeiten.
Fakt: `AssetLockBL<T>.LockReceipt(int receiptI3D, AppUser appUser, int rightID)` prüft `IsAssetLocked(receiptI3D, out var lockedBy) && lockedBy != appUser.Employee.ShortSign`. Ist der Beleg fremdgesperrt, wird abhängig von `_appRightsBL.HasUserRight(appUser.I3D, rightID)` eine von zwei Fehlermeldungen zurückgegeben; erst danach wird über `LockAssetById(appUser, receiptI3D)` gesperrt. `InvoiceSpecificLogic.TryLockReceipt` übergibt als `rightID` `UserRightsConst.Sales.Customer.CustomerCommon.UNLOCK_CUSTOMER_ATTACHMENTS`. `ReceiptBL.CreateNewVersion<T>` bricht bei `canLockResult.Status != ResultStatus.Success` mit `ReceiptIsLockedFromOtherUser = true` ab.
Aussage: Das System soll einen Beleg beim Beginn der Bearbeitung exklusiv für den bearbeitenden Benutzer sperren und eine bestehende Fremdsperre nur von Benutzern mit dem Entsperrrecht überschreiben lassen.
Ergebnis: Parallele Änderungen desselben Belegs durch zwei Benutzer werden verhindert; der sperrende Benutzer wird in der Fehlermeldung namentlich genannt.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/AssetLockBL.cs, Zeilen 48-68, Methode `LockReceipt(int receiptI3D, AppUser appUser, int rightID)`; Bedingung `if (this.IsAssetLocked(receiptI3D, out var lockedBy) && lockedBy != appUser.Employee.ShortSign)` mit anschließender Rechteprüfung `this._appRightsBL.HasUserRight(appUser.I3D, rightID)` - Begründung: Dies ist die durchsetzende Stelle der Sperrlogik inklusive Rechteprüfung.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 3093-3096 (`Result canLockResult = this._specificLogics.Execute<Result, T>(f => f.TryLockReceipt(receiptI3D, appUser)); if (canLockResult.Status != ResultStatus.Success) return result.Set(f => f.ReceiptIsLockedFromOtherUser, true, canLockResult.Message).GetResult();`) - Begründung: Belegt, dass die Sperre den Versionierungsvorgang tatsächlich blockiert.
- [SEKUNDÄR] src/backend/Centron.BL/Sales/Receipts/Invoices/InvoiceSpecificLogic.cs, Zeilen 191-194 (`return this._lockBL.LockReceipt(receiptI3D, appUser, UserRightsConst.Sales.Customer.CustomerCommon.UNLOCK_CUSTOMER_ATTACHMENTS);`) - Begründung: Zeigt das konkret geforderte Recht für Rechnungen.
Prüfidee: Benutzer A öffnet eine Rechnung zur Bearbeitung; Benutzer B ohne `UNLOCK_CUSTOMER_ATTACHMENTS` erhält beim Versuch einer neuen Version die Meldung "Der Beleg ist von dem Benutzer A gesperrt und Sie haben nicht das Recht ... zu entsperren."
Tracelinks: StRS-102, SwRS-129
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Pessimistische Belegsperre verhindert widersprüchliche Belegstände.
Status: belegt
Modul: M-01
ID: SyRS-113
Titel: Rechte- und Filialprüfung vor jeder Belegänderung
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: System (ReceiptBL)
Vorbedingung: Ein Speicher-, Versionierungs- oder Änderungsvorgang auf einem Beleg wird ausgelöst.
Fakt: `ReceiptBL` ruft `CanUserEditReceipt(receiptKind, appUser, receipt.BranchI3D)` an acht Stellen auf (Zeilen 3081, 3625, 4804, 4839, 4880, 4921, 5114, 5268). Für neue Belege prüft `SaveReceipt` zusätzlich `CanUserCreateNewReceiptsAtCustomerOrSupplier<T>(currentUser, receipt.GetAccount().AccountI3D)` und `CanUserCreateReceiptsInBranch<T>(currentUser, receipt.BranchI3D)`. `CanUserViewReceipt` verweigert Web-Konten grundsätzlich die Beleganzeige ("Web-Benutzer haben keine Berechtigung Belege einzusehen.").
Aussage: Das System soll vor dem Anlegen, Ändern und Versionieren eines Belegs Bearbeitungsrecht, Kunden-/Lieferantenbezug und Filialzugehörigkeit prüfen und den Vorgang bei fehlender Berechtigung mit MessageCode `RightCheckFailed` abbrechen.
Ergebnis: Nicht berechtigte Vorgänge werden vor jeder Datenbankänderung abgewiesen; die Ablehnung ist maschinell auswertbar.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 3604-3610, Block im `SaveReceipt<T, TReceiptItem>` für `isNewReceipt`; `var canCreateNewReceiptsResult = this.CanUserCreateNewReceiptsAtCustomerOrSupplier<T>(currentUser, receipt.GetAccount().AccountI3D); if (... == ResultStatus.Error) return result.SetMessage(...).GetResult();` sowie `CanUserCreateReceiptsInBranch<T>(currentUser, receipt.BranchI3D)` - Begründung: Durchsetzende Stelle für die Anlageberechtigung vor der Persistierung.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 10297-10311, Methode `CanUserViewReceipt(LoggedInUser loggedInUser, int receiptI3D, CentronObjectKindNumeric objectKind)`; `if (loggedInUser.IsWebAccountLogin) return Result.AsError("Web-Benutzer haben keine Berechtigung Belege einzusehen.", DefaultMessageCodes.RightCheckFailed);` - Begründung: Konkrete Prüfung, die Web-Konten von der direkten Beleganzeige ausschließt.
Prüfidee: Ein Web-Account-Login erhält bei `CanUserViewReceipt` stets einen Fehler mit `RightCheckFailed`; ein c-entron-Benutzer ohne Anlagerecht kann in keiner Belegart einen neuen Beleg speichern.
Tracelinks: StRS-102, SwRS-129
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Zentrale, vor jeder Persistierung ausgeführte Rechteprüfung ist Sicherheitsgrundlage.
Status: belegt
Modul: M-01
ID: SyRS-114
Titel: Bestätigungspflicht bei Kreditlimitüberschreitung
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: System (ReceiptBL) / Sachbearbeiter
Vorbedingung: Ein limitrelevanter Kundenbeleg wird gespeichert, `data.IgnoreCallbacks` ist `false`.
Fakt: `ReceiptBL.CheckIfCustomerLimitIsReached(...)` setzt bei Überschreitung `result.Set(f => f.ShowCustomerLimitExceededDialog, true, ...)` mit einer Meldung, die Limit, Überschreitungsbetrag, Verbrauch je Belegart und den Anteil des aktuellen Belegs auflistet. Ein Speichern ohne Rückfrage ist nur möglich, wenn `data.SaveAlthoughCustomerLimitExceeded == true` gesetzt wurde. Kunden mit `CreditLimitCalculationKind == null`, `== 2` oder `CreditLimit <= 0` sind von der Prüfung ausgenommen.
Aussage: Das System soll bei Überschreitung des Kundenkreditlimits den Speichervorgang unterbrechen und erst nach ausdrücklicher Benutzerbestätigung fortsetzen; Kunden ohne konfiguriertes Limit sollen von der Prüfung ausgenommen sein.
Ergebnis: Der Benutzer sieht vor dem Speichern die Limitauslastung je Belegart und entscheidet bewusst über die Fortsetzung.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 8673-8686 (`if (limitUsedInThisReceipt > limitAvailable && data.SaveAlthoughCustomerLimitExceeded == false && data.IgnoreCallbacks == false) { ... result.Set(f => f.ShowCustomerLimitExceededDialog, true, ...); }`) - Begründung: Die Bedingung ist die durchsetzende Stelle der Rückfragepflicht.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 8652-8655 (`if (customer.CreditLimitCalculationKind == null || customer.CreditLimitCalculationKind == 2 || customer.CreditLimit <= 0) return;`) - Begründung: Definiert die Ausnahmebedingung eindeutig.
Prüfidee: Bei `CreditLimitCalculationKind = 2` wird kein Dialog ausgelöst, unabhängig vom Belegbetrag; bei `= 1` (netto) löst ein Beleg oberhalb des Restlimits den Dialog aus.
Tracelinks: StRS-110
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Bewusste Limitentscheidung statt stiller Blockade entspricht der Vertriebspraxis.
Status: belegt
Modul: M-01
ID: SyRS-115
Titel: Mindestpreisprüfung je Artikelposition mit Rechte-Ausnahme
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: System (ReceiptBL) / Sachbearbeiter
Vorbedingung: Ein Kundenbeleg mit Artikelpositionen wird gespeichert; der Benutzer besitzt nicht das Recht zum Ignorieren von Mindestpreisen.
Fakt: `ReceiptBL.CheckArticleMinPrices(...)` verlässt sich sofort, wenn `currentUser.HasUserRight(UserRightsConst.Sales.Customer.CustomerCommon.Offer.ALLOW_IGNORE_MINIMUM_PRICE)` gilt, wenn `data.IgnoreCallbacks` gesetzt ist oder wenn `receipt.ReceiptKind.IsSupplierReceipt()` zutrifft. Andernfalls wird je Position mit `Kind == ReceiptItemKind.Article` (ohne Spezialartikelcodes aus `_receiptItemSpecialArticleHelperBL.GetArticleCodes()`) `currentPrice.NetPrice` gegen `article.MinPrice` verglichen; bei `data.UpdateArticlePricesBecauseOfMinPrice == true` wird über `_receiptItemPriceBL.ChangeBasePrice(item, minPrice)` korrigiert.
Aussage: Das System soll beim Speichern eines Kundenbelegs prüfen, ob eine Artikelposition den artikelspezifischen Mindestpreis unterschreitet, und dem Benutzer wahlweise die Preiskorrektur auf den Mindestpreis oder die bewusste Beibehaltung anbieten; Benutzer mit dem Recht `ALLOW_IGNORE_MINIMUM_PRICE` sollen von der Prüfung ausgenommen sein.
Ergebnis: Unterschreitungen des Mindestpreises werden entweder korrigiert oder protokolliert (`_logger.Info("Article {0} ({1}) has a minimum price violation. Current: {2} < Minimum: {3}")`).
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 9036-9081, Methode `CheckArticleMinPrices(IReceiptBase receipt, AppUser currentUser, SaveReceiptData data, SaveReceiptResultBuilder result)`; Ausstiegsbedingung `if (currentUser.HasUserRight(UserRightsConst.Sales.Customer.CustomerCommon.Offer.ALLOW_IGNORE_MINIMUM_PRICE)) return;` und Vergleich `if (currentPrice.NetPrice >= minPrice) continue;` - Begründung: Prüfung und Rechte-Ausnahme sind an dieser Stelle durchgesetzt.
- [SEKUNDÄR] src/webservice/Centron.WebServices.Core/EntitiesWrongPlace/Administration/Rights/UserRightsConst.cs, Zeile 2196 (`public const int ALLOW_IGNORE_MINIMUM_PRICE = 20400031;`) - Begründung: Verifiziert die Existenz und Nummer des ausnehmenden Rechts.
Prüfidee: Eine Position mit Nettopreis unter `Article.MinPrice` löst für einen Benutzer ohne Recht 20400031 die Rückfrage aus; mit dem Recht wird der Beleg ohne Rückfrage gespeichert.
Tracelinks: StRS-103, SwRS-130
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Mindestpreisschutz sichert die Marge unmittelbar ab.
Status: belegt
Modul: M-02
ID: SyRS-116
Titel: Aktionspreise nur innerhalb ihres Gültigkeitszeitraums anzeigen
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: System (Preismatrix) / Einkäufer
Vorbedingung: Der Benutzer öffnet die Preismatrix (Preisspiegel) zu einer Belegposition oder einem Artikel.
Fakt: `PriceMatrixViewModel.GetPriceItemsFromArticleActionPrices(...)` ermittelt den Artikel zuerst über `ManufacturerCode`, hilfsweise über `EANCode`, lädt `IActionPriceLogic.GetActionPricesByArticleI3D(articleI3D)` und filtert `.Where(f => f.EffectiveFrom.StartOfDay() <= DateTime.Now && f.EffectiveUntil >= DateTime.Now)`. Die Quelle wird nur berücksichtigt, wenn `IncludePricesFromArticleActionPrices` aktiv ist. `ActionPriceBL.GetActionPricesByArticleI3D` selbst filtert nicht nach Datum.
Aussage: Das System soll Aktionspreise in der Preismatrix ausschließlich dann anzeigen, wenn das aktuelle Datum innerhalb von `GueltigAb` und `GueltigBis` liegt, und den Artikel dabei über Herstellercode mit Fallback auf EAN-Code auflösen.
Ergebnis: Abgelaufene oder noch nicht gültige Aktionspreise erscheinen nicht als Einkaufspreisquelle; die Datumsfilterung erfolgt clientseitig in der Preismatrix, nicht in der Datenschicht.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Finances/Receipts/PriceMatrix/PriceMatrixViewModel.cs, Zeilen 416-459, Methode `GetPriceItemsFromArticleActionPrices(PriceMatrixArticleInfo article, CancellationToken token)`; Filter `.Where(f => f.EffectiveFrom.StartOfDay() <= DateTime.Now && f.EffectiveUntil >= DateTime.Now) // Only valid action-prices` - Begründung: Einzige durchsetzende Stelle der Gültigkeitsprüfung.
- [PRIMÄR] src/backend/Centron.BL/Warehousing/ActionPriceBL.cs, Zeilen 26-33, Methode `GetActionPricesByArticleI3D(int articleI3D)` mit `.Where(w => w.ArticleI3D == articleI3D)` ohne Datumsbedingung - Begründung: Belegt, dass die Backend-Schicht ungefiltert liefert und die Gültigkeitsregel allein in der UI liegt.
Prüfidee: Ein Aktionspreis mit `GueltigBis` in der Vergangenheit erscheint nicht in der Preismatrix, wird aber von `GetActionPricesByArticleI3D` weiterhin zurückgegeben.
Tracelinks: StRS-103, SwRS-131
Konsolidierung: nein
Übernahmewürdigkeit: Workaround - Die Gültigkeitsregel liegt nur in der WPF-Preismatrix und ist damit für andere Aufrufer (REST, Nexus) nicht wirksam; sie gehört in die Business-Logik.
Status: belegt
Modul: M-02
ID: SyRS-117
Titel: Belegartbezogene Rechteprüfung und Sichtbereichseinschränkung in der Belegsuche
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: System (ReceiptSearcher)
Vorbedingung: Ein c-entron-Benutzer führt eine Belegsuche aus.
Fakt: In `ReceiptSearcher.CreateSqlStatementAndParameters` wird für `user.IsCentronUserLogin` geprüft: `if (allowShowRight != null && this._appRightsBL.HasUserRight(user.User.I3D, allowShowRight.Value) == false) return null;`. Anschließend wird bei gesetztem `OnlyOwnRight` das `OnlyOwnWhereStatement` (für Rechnungen: `AND (AK.InnendienstID = :EmployeeI3D OR AK.AussendienstID = :EmployeeI3D OR AK.BearbeiterI3D = :EmployeeI3D)`) bzw. bei `OnlyOwnBranchRight` das `OnlyOwnBranchWhereStatement` (`AND (ISNULL(AK.FilialI3D, 0) = :BranchI3D)`) mit dem Parameter `user.User.Employee.BranchI3D` angehängt. Für Web-Konten setzt `PrepareFilterForWebAccounts` zwingend `filter.AccountI3D = user.WebAccount.CustomerI3D.Value` und wirft andernfalls eine `ResultException`.
Aussage: Das System soll in der Belegsuche je Belegart das Anzeigerecht prüfen und Treffer bei aktiver Einschränkung auf eigene Belege bzw. die eigene Filiale serverseitig im SQL-WHERE begrenzen; Web-Konten sollen ausschließlich Belege ihres eigenen Kunden erhalten.
Ergebnis: Belegarten ohne Anzeigerecht liefern keine Treffer (`return null` verhindert die Ausführung der Abfrage); Einschränkungen sind nicht durch Manipulation des übergebenen Filters umgehbar.
Belege:
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/ReceiptSearcher.cs, Zeilen 180-211; `int? allowShowRight = configuration.ShowRight; if (allowShowRight != null && this._appRightsBL.HasUserRight(user.User.I3D, allowShowRight.Value) == false) { return null; }` sowie die nachfolgenden `OnlyOwn`/`OnlyOwnBranch`-Zweige - Begründung: Durchsetzende Stelle der Rechteprüfung; `return null` unterdrückt die gesamte Abfrage dieser Belegart.
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/ReceiptSearcher.cs, Zeilen 81-95, Methode `PrepareFilterForWebAccounts`; `filter.AccountI3D = user.WebAccount.CustomerI3D.Value;` bzw. `throw new ResultException("Receipt-search is currently not supported for web-accounts without a customer");` - Begründung: Erzwingt die Kundenbindung für Web-Konten unabhängig vom übergebenen Filter.
- [SEKUNDÄR] src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/InvoiceReceiptSearchConfiguration.cs, Zeilen 152-158 (`ShowRight => UserRightsConst.Sales.Customer.CustomerCommon.Invoice.SHOW_INVOICES;`, `OnlyOwnBranchRight => ... SHOW_INVOICES_ONLY_OWN_BRANCH;`) - Begründung: Zeigt die konkrete Rechtezuordnung je Belegart.
Prüfidee: Ein Benutzer ohne `SHOW_INVOICES` erhält bei einer Suche ohne `ReceiptKinds`-Einschränkung keine Treffer mit `ObjectKind = 4`, während Angebote weiterhin geliefert werden.
Tracelinks: StRS-104, SwRS-134
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Serverseitige Sichtbereichsbegrenzung ist unverzichtbar.
Status: belegt
Modul: M-03
ID: SyRS-118
Titel: Wortweise Volltextsuche mit UND-Verknüpfung über Kopf, Positionen und Nummer
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: System (ReceiptSearcher) / Sachbearbeiter
Vorbedingung: Der Benutzer hat einen Suchtext mit einem oder mehreren Wörtern eingegeben.
Fakt: `ReceiptSearcher` zerlegt `filter.SearchText` mit `Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)`. Je Wort werden bis zu drei WHERE-Teile gebildet (Nummernsuche bei `int.TryParse`, Positionstexte bei `filter.SearchInReceiptItemText`, Kopftexte) und mit `OR` verknüpft; die Wort-Blöcke werden anschließend mit `AND` verknüpft (`builder.AppendLine($"AND ({string.Join(" AND ", whereParts.Select(f => $"( {f} )"))})")`). Jedes Wort erhält einen eigenen benannten Parameter (`SearchText0`, `SearchText1`, ...), der als `NHibernateUtil.AnsiString` mit `%{word}%` gebunden wird.
Aussage: Das System soll einen mehrteiligen Suchtext so auswerten, dass jedes einzelne Wort in mindestens einem der Bereiche Belegnummer, Belegkopf oder Belegpositionen vorkommen muss, und alle Werte ausschließlich als benannte Parameter binden.
Ergebnis: Die Trefferliste enthält nur Belege, in denen alle Suchwörter vorkommen; SQL-Injection über den Suchtext ist ausgeschlossen.
Belege:
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/ReceiptSearcher.cs, Zeilen 385-447; Schleife `foreach (var word in filter.SearchText.Split(...))`, `whereParts.Add(string.Join(" OR ", wordWhereParts.Select(f => $"( {f} )")));` und `builder.AppendLine($"AND ({string.Join(" AND ", ...)})");` - Begründung: Durchsetzende Stelle der UND/ODER-Semantik.
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/ReceiptSearcher.cs, Zeilen 420 und 432 (`parameters.Add(new NamedQueryParameter($"SearchTextItems{whereParts.Count}", $"%{word}%", NHibernateUtil.AnsiString));`) - Begründung: Belegt die durchgängige Parametrisierung statt String-Konkatenation der Benutzereingabe.
- [KONTEXT] src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/ReceiptSearcher.cs, Zeilen 421-424 (Kommentar "Use AnsiString (varchar) instead of String (nvarchar) to avoid bad performance ... every single row would be converted to nvarchar") - Begründung: Erklärt die bewusste Typwahl aus Performancegründen.
Prüfidee: Die Suche "Müller 4711" liefert nur Belege, in denen sowohl "Müller" (Kopf oder Position) als auch die Zahl 4711 (Nummer, Kopf oder Position) vorkommt; ein Suchtext mit `'` erzeugt keinen SQL-Fehler.
Tracelinks: StRS-104, SwRS-133
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Die UND-Semantik über mehrere Wörter entspricht der Erwartungshaltung der Anwender.
Status: belegt
Modul: M-03
ID: SyRS-119
Titel: Antwortzeitverhalten der Belegsuche
Ebene: SyRS
Typ: nicht-funktional
Qualitätsmerkmal: Performance-Effizienz
Akteur: System (ReceiptSearcher / ReceiptSearchWebServiceBL)
Vorbedingung: Eine Belegsuche über mehrere Belegarten wird ausgeführt.
Fakt: Jede Belegart-Abfrage wird über `_rawSqlAccessDAO.ExecuteQuery<ReceiptSearchItemDTO>(sqlStatement, parameters, timeout: TimeSpan.FromMinutes(5))` mit einem Timeout von fünf Minuten ausgeführt. Die Abfragen werden mit `SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;` eingeleitet und mit `SET TRANSACTION ISOLATION LEVEL READ COMMITTED;` abgeschlossen. Die Seitenbildung erfolgt erst nach dem Zusammenführen aller Belegarten in `ReceiptSearchWebServiceBL.SearchReceipts` per `receipts.OrderByDescending(o => o.Date).Skip((page - 1) * entriesPerPage).Take(entriesPerPage)`; `Count` und `PageCount` beziehen sich auf die vollständige Ergebnismenge.
Aussage: [HYPOTHESE] Das System soll eine Belegsuche über alle Belegarten innerhalb einer definierten Antwortzeit beantworten; im Bestand ist lediglich eine obere Abbruchgrenze von fünf Minuten je Belegart-Abfrage festgelegt, und die Seitenbildung erfolgt im Anwendungsspeicher statt in der Datenbank.
Ergebnis: Die Suche bricht spätestens nach fünf Minuten je Belegart ab; die vollständige Trefferliste wird stets vollständig in den Speicher geladen, bevor eine Seite ausgeliefert wird.
Belege:
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/ReceiptSearcher.cs, Zeile 70 (`var receipts = this._rawSqlAccessDAO.ExecuteQuery<ReceiptSearchItemDTO>(sqlStatement, parameters, timeout: TimeSpan.FromMinutes(5));`) - Begründung: Einziger im Code fixierter Zeitwert; belegt die Abbruchgrenze, nicht aber ein Antwortzeitziel.
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/ReceiptSearchWebServiceBL.cs, Zeilen 23-29 (`Count = receipts.Count, PageCount = (int)Math.Ceiling(receipts.Count/(decimal) entriesPerPage), Result = receipts.OrderByDescending(o => o.Date).Skip((page - 1) * entriesPerPage).Take(entriesPerPage).ToList()`) - Begründung: Belegt die anwendungsseitige Seitenbildung über der Gesamtmenge.
- [KONTEXT] docs/reference/receipts/receipt-search-architecture.md, Abschnitt "Performance Considerations", Punkt 2 ("Sorting is applied after aggregation (may impact performance for large result sets); Consider implementing database-level pagination for very large datasets") - Begründung: Bestätigt, dass die Seitenbildung als bekannte Schwachstelle dokumentiert ist.
Prüfidee: Zur Bestätigung fehlt eine dokumentierte Zielantwortzeit (SLA) und eine Messreihe auf einem Referenzbestand; Akzeptanzkriterium wäre z. B. "Suche mit Datumsbereich 1 Monat über alle Belegarten liefert die erste Seite in < 3 s bei 1 Mio. Belegen".
Tracelinks: StRS-104, SwRS-134
Konsolidierung: nein
Übernahmewürdigkeit: Workaround - Fünf-Minuten-Timeout und speicherseitige Seitenbildung sind Notlösungen; eine Neuentwicklung braucht datenbankseitiges Paging und ein explizites Antwortzeitziel.
Status: HYPOTHESE
Modul: M-03
ID: SyRS-120
Titel: Ermittlung des Zahlungsfälligkeitsdatums aus der Zahlungskondition
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: System (ReceiptBL)
Vorbedingung: Ein Beleg, der `IReceiptWithPayment` oder `IReceiptWithSupplierPayment` implementiert, wird gespeichert und trägt eine Zahlungskondition.
Fakt: `ReceiptBL.UpdatePaymentDueDate(IReceiptBase receipt)` bestimmt das Basisdatum: bei Kundenbelegen `receipt.Date`, bei Lieferantenbelegen bevorzugt `ExternalInvoiceDate`, sonst `receipt.Date`. Anschließend wird nach `paymentCondition.DueKind` gerechnet: `1` → Belegdatum, `2` → `receiptDate.AddDays(paymentCondition.DuePlusDays)`, `3` → `receiptDate.AddMonths(Math.Max(1, paymentCondition.DuePlusMonths)).SetDay(paymentCondition.DueAtDay)`, sonst `null`. Das Ergebnis wird in `PaymentDueDate` geschrieben.
Aussage: Das System soll das Zahlungsziel eines Belegs aus der Fälligkeitsart der Zahlungskondition berechnen und bei Lieferantenrechnungen das externe Rechnungsdatum als Bezugsdatum verwenden.
Ergebnis: Jeder gespeicherte zahlungsrelevante Beleg trägt ein aus der Kondition abgeleitetes `PaymentDueDate` oder `null`, wenn die Kondition keine Fälligkeit definiert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 8189-8256, Methode `UpdatePaymentDueDate(IReceiptBase receipt)`; `switch (paymentCondition.DueKind) { case 1: return receiptDate; case 2: return receiptDate.AddDays(paymentCondition.DuePlusDays); case 3: int months = Math.Max(1, paymentCondition.DuePlusMonths); return receiptDate.AddMonths(months).SetDay(paymentCondition.DueAtDay); default: return null; }` - Begründung: Durchsetzende Stelle der Fälligkeitsberechnung im Belegkontext.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 8211-8219 (`DateTime? invoiceDate = (receipt as IReceiptSupplierInvoice)?.ExternalInvoiceDate; if (invoiceDate.HasValue) { receiptDate = invoiceDate.Value; }`) - Begründung: Belegt die abweichende Bezugsdatumsregel für Lieferantenrechnungen.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Administration/ReceiptConditions/DueKind.cs (`public enum DueKind { Now = 1, PlusDays = 2, AtDay = 3 }`) - Begründung: Ordnet den numerischen Datenbankwerten die fachliche Bedeutung zu.
Prüfidee: Eine Rechnung vom 10.03. mit `DueKind = 2` und `DuePlusDays = 30` erhält `PaymentDueDate = 09.04.`; eine Lieferantenrechnung mit `ExternalInvoiceDate = 01.03.` und derselben Kondition erhält `31.03.`.
Tracelinks: StRS-105, SwRS-135
Konsolidierung: Kandidat: `ReceiptBL.UpdatePaymentDueDate` und `AssetConditionBL.GetDueDate` implementieren dieselbe Fälligkeitsregel doppelt mit unterschiedlichem Basisdatum.
Übernahmewürdigkeit: übernehmen - Fälligkeitsermittlung ist Voraussetzung für Mahnwesen und Zahlungsverkehr.
Status: belegt
Modul: M-04
ID: SyRS-121
Titel: Rückfrage bei Abweichung von der Standard-Zahlungskondition des Kunden
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: System (ReceiptBL) / Sachbearbeiter
Vorbedingung: Ein Beleg mit Zahlungskondition wird gespeichert; für den Kunden ist eine Standardkondition hinterlegt.
Fakt: `ReceiptBL.CheckDefaultPaymentConditionsDeviation(...)` ruft die belegartspezifische Prüfung `f.CheckDefaultPaymentConditionsDeviation(receipt)` auf. Bei `isDefaultPaymentConditionsDeviation == true` und noch nicht gesetztem `data.TakeDefaultPaymentCondition` wird `result.Set(f => f.ShowDefaultInvoicePaymentConditionsDeviationDialog, true, "Die Zahlungskondition weicht vom Standard ab. Möchten Sie mit der Auswahl fortfahren oder den Standard vom Kunden übernehmen?")` gesetzt. Entscheidet sich der Benutzer für die Standardkondition, wird `receiptWithPaymentCondition.PaymentConditionI3D` auf `defaultPaymentCondition.Value` überschrieben.
Aussage: Das System soll beim Speichern eine Abweichung der Belegzahlungskondition von der kundenseitig hinterlegten Standardkondition erkennen und dem Benutzer die Übernahme der Standardkondition anbieten.
Ergebnis: Abweichende Zahlungskonditionen werden bewusst gesetzt; bei Bestätigung wird die Kundenkondition in den Beleg übernommen.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 9331-9355, Methode `CheckDefaultPaymentConditionsDeviation(IReceiptBase receipt, SaveReceiptData data, SaveReceiptResultBuilder result)`; Bedingung `if (data.IgnoreCallbacks == false && checkDefaultPaymentConditionsDeviation.isDefaultPaymentConditionsDeviation == true && !data.TakeDefaultPaymentCondition.HasValue)` und die Zuweisung `receiptWithPaymentCondition.PaymentConditionI3D = checkDefaultPaymentConditionsDeviation.defaultPaymentCondition.Value;` - Begründung: Durchsetzende Stelle mit Nebenwirkung auf die Belegdaten.
- [KONTEXT] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeile 3718 (Aufrufkommentar "//Nebenwirkung: Zahlungskonditionen abweichend vom Standard (Kunde)") - Begründung: Zeigt die bewusste Einordnung als Check mit Datenänderung im Speicherablauf.
Prüfidee: Ein Beleg mit einer von der Kundenstandardkondition abweichenden Zahlungskondition löst beim ersten Speichern den Dialog aus; nach Antwort "Standard übernehmen" trägt der gespeicherte Beleg die Kundenkondition.
Tracelinks: StRS-105, SwRS-135
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Verhindert unbeabsichtigte Zahlungszielabweichungen.
Status: belegt
Modul: M-04
ID: SyRS-122
Titel: Rechteprüfung und automatische Einschränkung der Provisionsauswertung
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: System (ReceiptProvisionSchemaBL) / Provisionsempfänger
Vorbedingung: Ein Benutzer öffnet die Provisionsauswertung oder verwaltet ein Provisionsschema.
Fakt: `ReceiptProvisionSchemaBL.GetProvisionEvaluation(...)` wirft bei fehlendem `UserRightsConst.Sales.Provision.PROVISION_EVALUATION_MODULE` eine `ResultException` mit `DefaultMessageCodes.RightCheckFailed`. Besitzt der Benutzer `PROVISION_EVALUATION_ONLY_OWN`, wird `filter.OnlyOwn = true` erzwungen; besitzt er `PROVISION_EVALUATION_ONLY_OWN_BRANCH`, wird `filter.BranchI3Ds` auf die eigene Filiale überschrieben. `SaveProvisionSchema(...)` verlangt `PROVISION_SCHEMA_MANAGEMENT`. `GetReceiptsWithoutProvision(...)` verlangt ebenfalls `PROVISION_EVALUATION_MODULE`.
Aussage: Das System soll die Provisionsauswertung nur berechtigten Benutzern zugänglich machen und den Auswertungsumfang bei entsprechenden Einschränkungsrechten serverseitig auf eigene Provisionen bzw. die eigene Filiale reduzieren, unabhängig vom übergebenen Filter.
Ergebnis: Ein eingeschränkt berechtigter Benutzer kann keine fremden Provisionsdaten abrufen; die Verwaltung von Provisionsschemata bleibt Administratoren vorbehalten.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptProvisionSchemaBL.cs, Zeilen 491-500; `if (this._appRightsBL.HasUserRight(currentUser.UserI3D.Value, UserRightsConst.Sales.Provision.PROVISION_EVALUATION_MODULE) is false) throw new ResultException(..., DefaultMessageCodes.RightCheckFailed);` sowie die Überschreibungen `filter.OnlyOwn = true;` und `filter.BranchI3Ds = new List<int>() { currentUser.User.Employee.BranchI3D.GetValueOrDefault(0) };` - Begründung: Durchsetzende Stelle; der Filter wird nach der Rechteprüfung zwingend überschrieben.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptProvisionSchemaBL.cs, Zeilen 257-260 (`if (this._appRightsBL.HasUserRight(currentUser.UserI3D.Value, UserRightsConst.Sales.Provision.PROVISION_SCHEMA_MANAGEMENT) is false) throw new ResultException("Sie haben nicht das Recht um Provisionsschemas zu verwalten.", DefaultMessageCodes.RightCheckFailed);`) - Begründung: Schützt die Schemaverwaltung als abrechnungsrelevante Stammdaten.
- [SEKUNDÄR] src/webservice/Centron.WebServices.Core/EntitiesWrongPlace/Administration/Rights/UserRightsConst.cs, Zeilen 2449 und 2452 (`PROVISION_SCHEMA_MANAGEMENT = 20800093;`, `PROVISION_EVALUATION_ONLY_OWN_BRANCH = 20800146;`) - Begründung: Verifiziert Existenz und Nummern der Rechte.
Prüfidee: Ein Benutzer mit `PROVISION_EVALUATION_ONLY_OWN` erhält auch bei explizit gesetztem `filter.OnlyOwn = false` ausschließlich eigene Provisionszeilen.
Tracelinks: StRS-106, SwRS-136
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Provisionsdaten sind personenbezogen und abrechnungsrelevant.
Status: belegt
Modul: M-05
ID: SyRS-123
Titel: Belegdatumsgerechter Umsatzsteuersatz über die Steuersatzkette
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: System (TaxBL)
Vorbedingung: Für eine Belegposition ist ein Steuersatz-I3D und das Belegdatum bekannt.
Fakt: `TaxBL.GetTaxRateForReceiptItem(int taxRateI3D, DateTime receiptDate, int? receiptItemArticleI3D)` läuft zunächst über `NextTaxRate` bis zum Ende der Kette und geht dann rückwärts, solange der Vorgänger ein gültiges `ExpirationDate` hat und `previousTaxRate.ExpirationDate.Date >= receiptDate.Date` gilt (Vergleich ausschließlich auf Datumsebene). Ist der übergebene Satz nicht auffindbar, wird über `GetDefaultTaxtRateByArticle(receiptItemArticleI3D)` ersetzt. `GetPreviousTaxRate` wirft eine `ResultException`, wenn mehrere Sätze denselben `NextTaxRate` referenzieren ("Es gibt mehrere Mehrwertsteuer-Sätze die als Folge-Mehrwertsteuer-Satz ... haben.").
Aussage: Das System soll für eine Belegposition denjenigen Steuersatz aus der über `NextTaxRate` verketteten Steuersatzfolge ermitteln, der am Belegdatum gültig war, und eine mehrdeutige Kette mit einer eindeutigen Fehlermeldung abweisen.
Ergebnis: Rückwirkend erfasste oder nachträglich versionierte Belege erhalten den zum Belegdatum gültigen Steuersatz; mehrdeutige Stammdatenketten werden erkannt statt still falsch aufgelöst.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Warehousing/TaxBL.cs, Zeilen 207-237, Methode `GetTaxRateForReceiptItem(...)`; Bedingung `if (hasPreviousTaxRate && previousTaxRateHasExpirationDate && previousTaxRateWasActiveForReceipt) currentTaxRate = previousTaxRate; else return currentTaxRate;` mit `previousTaxRate?.ExpirationDate?.Date >= receiptDate.Date` - Begründung: Durchsetzende Stelle der datumsabhängigen Auswahl.
- [PRIMÄR] src/backend/Centron.BL/Warehousing/TaxBL.cs, Zeilen 286-319, Methode `GetPreviousTaxRate(ValueAddedTax currentTaxRate)`; `switch (taxRates.Count) { case 0: return null; case 1: return taxRates.First(); default: ... throw new ResultException(message.ToString(), DefaultMessageCodes.ErrorMessage); }` - Begründung: Erzwingt die Eindeutigkeit der Steuersatzkette.
Prüfidee: Ein Steuersatz 16 % mit `ExpirationDate = 31.12.2020` und `NextTaxRate` = 19 % liefert für ein Belegdatum 15.12.2020 den Satz 16 %, für 02.01.2021 den Satz 19 %.
Tracelinks: StRS-107, SwRS-137
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Zeitraumgerechte Steuersätze sind gesetzlich gefordert (vgl. MwSt-Senkung 2020).
Status: belegt
Modul: M-06
ID: SyRS-124
Titel: Pflicht-Mehrwertsteuerzuordnung für jede Artikelposition
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: System (ReceiptBL)
Vorbedingung: Ein Beleg mit Artikel- oder Kundenrabattpositionen wird gespeichert.
Fakt: `ReceiptBL.CheckIfAllArticlePositionsHaveVatRate(...)` selektiert alle Positionen mit `Kind == ReceiptItemKind.Article || Kind == ReceiptItemKind.CustomerDiscount`, bei denen `VATI3D == null || VATI3D <= 0` gilt, und setzt bei mindestens einem Treffer `result.SetMessage($"Die Artikel {string.Join(", ", articlePositions.Select(f => f.ArticleCode).Distinct())} haben keine Mehrwertsteuer zugewiesen.")`. Die Prüfung wird im Speicherablauf vor `if (result.HasError) return result.GetResult();` ausgeführt.
Aussage: Das System soll das Speichern eines Belegs verweigern, solange mindestens eine Artikel- oder Kundenrabattposition keinen Mehrwertsteuersatz zugewiesen hat, und die betroffenen Artikelcodes in der Meldung nennen.
Ergebnis: Es entstehen keine Belege mit steuerlich unbestimmten Positionen; der Benutzer erhält die konkrete Fehlerliste.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 9573-9585, Methode `CheckIfAllArticlePositionsHaveVatRate(IReceiptBase receipt, SaveReceiptData data, SaveReceiptResultBuilder result)`; Filter `.Where(f => f.VATI3D == null || f.VATI3D <= 0)` mit anschließendem `result.SetMessage(...)` - Begründung: Durchsetzende Stelle der Pflichtprüfung.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 3740 und 3757-3758 (Aufruf `this.CheckIfAllArticlePositionsHaveVatRate(receipt, data, result);` gefolgt von `if (result.HasError) return result.GetResult();`) - Begründung: Belegt, dass der Speichervorgang bei Verletzung tatsächlich abgebrochen wird.
Prüfidee: Ein Beleg mit einer Artikelposition ohne `VATI3D` lässt sich nicht speichern; die Fehlermeldung enthält den Artikelcode dieser Position.
Tracelinks: StRS-107, SwRS-132
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Ohne Steuerzuordnung ist keine korrekte Fakturierung und Buchung möglich.
Status: belegt
Modul: M-06
ID: SyRS-125
Titel: Erkennung doppelt verwendeter externer Rechnungsnummern
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: System (ReceiptBL / SupplierInvoiceSpecificLogic)
Vorbedingung: Eine Lieferantenrechnung mit externer Rechnungsnummer wird gespeichert.
Fakt: `ReceiptBL.CheckExternalInvoiceNumberAlreadyUsed(...)` greift nur für Belege, die `IReceiptWithExternalInvoiceReference` implementieren und deren `SpecificLogic` `CheckExternalInvoiceNumberDuplicates()` mit `true` beantwortet (für Lieferantenrechnungen: `public bool CheckExternalInvoiceNumberDuplicates() => true;`). `SupplierInvoiceSpecificLogic.QueryForExternalInvoiceNumberDuplicates(...)` sucht in `SupplierCalculationCompact` nach `f.InvoiceNumber.Equals(foundReceipt.ExternalInvoiceNumber) && (receipt.I3D <= 0 || f.Number != receipt.Number)` und liefert bei Treffer ein `ExternalInvoiceNumberInUse`, was zu `result.Set(f => f.ShowExternalInvoiceNumberDuplicateDialog, true, ...)` führt.
Aussage: Das System soll beim Speichern einer Lieferantenrechnung prüfen, ob deren externe Rechnungsnummer bereits in einem anderen Beleg verwendet wird, und den Benutzer unter Nennung des Fundbelegs zur Bestätigung auffordern.
Ergebnis: Doppelt erfasste Eingangsrechnungen werden erkannt, bevor sie in die Buchhaltung gelangen; der Benutzer kann die Nummer bewusst erneut verwenden.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/SupplierInvoices/SupplierInvoiceSpecificLogic.cs, Zeilen 748-762; `public bool CheckExternalInvoiceNumberDuplicates() => true;` und die Abfrage `.Where(f => f.InvoiceNumber.Equals(foundReceipt.ExternalInvoiceNumber) && (receipt.I3D <= 0 || f.Number != receipt.Number))` - Begründung: Durchsetzende Stelle der Dublettenerkennung inklusive Selbstausschluss des eigenen Belegs.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 4182-4210, Methode `CheckExternalInvoiceNumberAlreadyUsed(...)`; `result.Set(f => f.ShowExternalInvoiceNumberDuplicateDialog, true, $"Die Rechnungsnummer ({supplierInvoice.ExternalInvoiceNumber}) wurde bereits in einem anderen Beleg ({supplierInvoice.ReceiptKind.ToDescription()}{supplierInvoice.Number}) verwendet. Wollen Sie diese weiterhin verwenden?")` - Begründung: Belegt die Einbindung in den Speicherablauf und den Meldungsinhalt.
Prüfidee: Das Speichern einer zweiten Lieferantenrechnung mit derselben externen Rechnungsnummer löst den Dublettendialog aus und nennt die Belegnummer der ersten Rechnung.
Tracelinks: StRS-108, SwRS-138
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Schutz vor Doppelzahlungen an Lieferanten.
Status: belegt
Modul: M-07
ID: SyRS-126
Titel: Tokenbasierte Freigabe und Signatur von Belegdokumenten
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: System (SharedDocumentBL) / Belegempfänger
Vorbedingung: Für einen Beleg wurde ein Freigabelink (`SharedDocument`) mit Token erzeugt.
Fakt: `SharedDocumentBL.GetSharedDocumentByToken(string token, string authenticationKey = "")` sucht den Datensatz über `string.Equals(f.Token, token) && string.Equals(f.AuthenticationKey, authenticationKey)` und lehnt anschließend in drei Stufen ab: nicht gefunden → "Kein Dokument zum Signieren gefunden."; `foundByToken.ExpiredDate.HasValue && foundByToken.ExpiredDate.Value < DateTime.Now` → "Die Signierungsanfrage ist bereits abgelaufen."; `foundByToken.IsSigned` → "Dokument wurde bereits signiert.". `SignSharedDocument(...)` ruft diese Prüfung als erstes auf, validiert danach die Bilddaten (`CheckIfByteArrayIsAnImage`) und setzt bei Erfolg `IsSigned = true`, `SignedDate = DateTime.Now`, `State = SharedDocumentState.SendToCustomerAccepted`. Die Entität `SharedDocument` speichert zusätzlich `SignedFromIp` und `ReceiverSignatureName`.
Aussage: Das System soll den Zugriff auf ein freigegebenes Belegdokument nur über die Kombination aus gültigem Token und passendem Authentifizierungsschlüssel zulassen, abgelaufene und bereits signierte Freigaben ablehnen und jede Signatur mit Zeitpunkt, Unterzeichnername und Absender-IP protokollieren.
Ergebnis: Ein Token kann genau einmal zur Signatur verwendet werden; nach Ablauf ist kein Dokumentzugriff mehr möglich.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/FileManagement/SharedDocumentBL.cs, Zeilen 382-412, Methode `GetSharedDocumentByToken(string token, string authenticationKey)`; die drei Ablehnungsbedingungen `foundByToken == null`, `foundByToken.ExpiredDate.Value < DateTime.Now`, `foundByToken.IsSigned` - Begründung: Durchsetzende Stelle der Zugriffsvalidierung.
- [PRIMÄR] src/backend/Centron.BL/Administration/FileManagement/SharedDocumentBL.cs, Zeilen 504-506 und 560-564 (`var sharedDocumentByTokenResult = this.GetSharedDocumentByToken(sharedDocumentToken, authenticationKey); if (sharedDocumentByTokenResult.Status != ResultStatus.Success) return Result.FromResult(sharedDocumentByTokenResult);` sowie `sharedDocument.IsSigned = true; sharedDocument.SignedDate = DateTime.Now; sharedDocument.State = SharedDocumentState.SendToCustomerAccepted;`) - Begründung: Belegt Einmaligkeit der Signatur und den Statusübergang.
- [SEKUNDÄR] src/backend/Centron.Entities/Entities/Administration/FileManagement/SharedDocuments/SharedDocument.cs, Zeilen 11-31 (`Token`, `ExpiredDate`, `AuthenticationKey`, `IsSigned`, `SignedDate`, `SignedFromIp`, `ReceiverSignatureName`, `State`) - Begründung: Datenmodell belegt die protokollierten Signaturattribute.
Prüfidee: Ein zweiter Signaturversuch mit demselben Token liefert "Dokument wurde bereits signiert."; ein Token mit `ExpiredDate` in der Vergangenheit liefert "Die Signierungsanfrage ist bereits abgelaufen.".
Tracelinks: StRS-109, SwRS-139
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Rechtssichere Belegfreigabe und -signatur nach außen.
Status: belegt
Modul: M-08
ID: SyRS-127
Titel: Belegstatus als konfigurierbares Pflichtfeld
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: System (ReceiptBL) / Sachbearbeiter
Vorbedingung: Ein Beleg, der `IReceiptWithUserState` implementiert, wird gespeichert.
Fakt: `ReceiptBL.CheckIfReceiptUserStateIsNeeded(IReceiptBase receipt)` gibt Erfolg zurück, wenn der Beleg kein `IReceiptWithUserState` ist oder `ReceiptUserStateI3D` bereits gesetzt ist oder `_specificLogics.Execute(receipt, f => f.ReceiptUserStateIsRequired())` `false` liefert; andernfalls `Result.AsError("Der Belegstatus ist ein Pflichtfeld, und muss deswegen gefüllt sein.")`. Die aufrufende Überladung meldet den Fehler mit `SaveReceiptErrorMissingField.ReceiptUserState`. Die Entität `ReceiptUserState` besteht aus `Caption` und `IsActive`; der Status wird zusätzlich als `ReceiptUserState = R.Caption` in die Suchergebnisse übernommen.
Aussage: Das System soll je Belegart konfigurierbar erzwingen, dass ein anwenderdefinierter Belegstatus gesetzt ist, und den Speichervorgang andernfalls mit dem Fehlerfeld `ReceiptUserState` abweisen.
Ergebnis: Belegarten mit Statuspflicht können nicht ohne Status gespeichert werden; der Status ist in der Belegsuche filter- und anzeigbar.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 10975-10989, Methode `CheckIfReceiptUserStateIsNeeded(IReceiptBase receipt)`; `if (this._specificLogics.Execute(receipt, f => f.ReceiptUserStateIsRequired()) is false) return Result.AsSuccess(); return Result.AsError("Der Belegstatus ist ein Pflichtfeld, und muss deswegen gefüllt sein.");` - Begründung: Durchsetzende Stelle der Pflichtfeldprüfung inkl. belegartabhängiger Konfiguration.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs, Zeilen 10959-10973 (Aufrufüberladung mit `result.SetMessage(checkResult.Message, SaveReceiptErrorMissingField.ReceiptUserState);`) - Begründung: Belegt die maschinenlesbare Feldkennzeichnung im Speicherergebnis.
- [SEKUNDÄR] src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/InvoiceReceiptSearchConfiguration.cs, Zeilen 77 und 107 (`ReceiptUserState = R.Caption,` und `LEFT OUTER JOIN ReceiptUserState R ON R.I3D = AK.ReceiptUserStateI3D`) - Begründung: Belegt die Auswertung des Status in der Belegsuche.
Prüfidee: Bei einer Belegart mit `ReceiptUserStateIsRequired() == true` schlägt das Speichern ohne `ReceiptUserStateI3D` mit `SaveReceiptErrorMissingField.ReceiptUserState` fehl.
Tracelinks: StRS-109, SwRS-140
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Anwenderdefinierte Belegstatus sind für Workflowsteuerung und Auswertung wichtig.
Status: belegt
Modul: M-08
@@ -0,0 +1,209 @@
ID: StRS-201
Titel: Verträge als wiederkehrende Belege verwalten
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertragssachbearbeiter (Innendienst)
Vorbedingung: Kunde ist angelegt; Benutzer hat das Recht SHOW_CONTRACTS/EDIT_CONTRACTS.
Fakt: Der Vertrag ist ein Beleg der Belegfamilie (`CentronObjectKindNumeric.ContractClass`); `ContractsAppModuleController.CreateModuleInstance` erzeugt die Vertragsverwaltung nur, wenn `parameters.Receipt.ReceiptKind == CentronObjectKindNumeric.ContractClass`. Die Vertragsverwaltung gliedert sich in die Bereiche `ContractTabKind` = Other/Position/ArticleReferences/Contingent/Controlling/MasterDataLists und die Assistentenseiten unter `ContractWizard/` (AddressInfo, Contingent, ContractArticleReferenzes, Controlling, Details, InvoiceHistory, MasterDataLists, Maintenance_Reaction, Position, Provision, Reductions, Tasks).
Aussage: Das System soll Verträge als eigene Belegart mit Kopf-, Positions-, Kontingent-, Stammblatt- und Controlling-Daten führen und je Vertrag Laufzeit, Abrechnungsintervall und Abrechnungsart als Grundlage der wiederkehrenden Fakturierung vorhalten.
Ergebnis: Ein Vertrag ist mit Kunde, Laufzeit, Positionen und Abrechnungsparametern gespeichert und steht der Vertragsabrechnung (M-13) zur Verfügung.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Finances/Contracts/ContractsAppModuleController.cs, `CreateModuleInstance` - Begründung: Die Bedingung `parameters.Receipt.ReceiptKind == CentronObjectKindNumeric.ContractClass` setzt durch, dass die Vertragsverwaltung ausschließlich für die Belegart Vertrag geöffnet wird.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[VertragKopf]` mit Spalten `AbrechnungIntervallArt`, `AbrechnungIntervallDauer`, `AutoAbrechnung`, `Beginn`, `Ende`, `KuendigungsDatum`, `RechnungNormieren` - Begründung: Die persistierten Spalten belegen, dass Laufzeit und Abrechnungssteuerung Kernattribute des Vertragskopfs sind.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/Contracts/ContractTabKind.cs (Enum-Werte Position, ArticleReferences, Contingent, Controlling, MasterDataLists) - Begründung: Der Enum bildet die fachliche Gliederung der Vertragsakte ab.
Prüfidee: Vertrag mit Intervall "monatlich/1", Laufzeit 01.01.–31.12. und zwei Positionen anlegen, speichern und erneut laden; alle Felder müssen unverändert aus `VertragKopf`/`VertragPos` zurückgelesen werden.
Tracelinks: SyRS-211, SyRS-212
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Vertragsstammdaten sind die Grundlage aller nachgelagerten Abrechnungsmodule.
Status: belegt
Modul: M-09
ID: StRS-202
Titel: Vertragsarten als wiederverwendbare Vertragsvorlage
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Administrator / Vertragsverantwortlicher
Vorbedingung: Benutzer besitzt das Recht `UserRightsConst.Masterdata.Contracts.CONTRACT_TYPES` (10492) und die Lizenz `LicenseGuids.ContractTypes` bzw. `LicenseGuids.Centron`.
Fakt: Die Tabelle `dbo.VertragsArt` hält Vorlagenwerte für Laufzeit (`LaufzeitArt`, `LaufzeitDauer`), Abrechnung (`AbrechnungIntervallArt`, `AbrechnungIntervallDauer`, `AutoAbrechnung`, `Berechnungsart`, `RechnungNormieren`, `Sammelrechnung`), Kündigungsfristen (`KuendigungsFristArt1/2`, `KuendigungsFristDauer1/2`), Reaktionszeiten (`ReaktionszeitArt1/2`), Reports (`AbrechnungsReportI3D`, `C2ReportI3D`, `WebReportI3D`) und Kontingentkennzeichen (`KontingentVertrag`). Das Modul wird über `ContractTypeAppModuleController` registriert.
Aussage: Das System soll Vertragsarten als zentrale Vorlage pflegen, aus der Laufzeit-, Abrechnungs-, Kündigungs-, Reaktionszeit- und Reporteinstellungen für neue Verträge vorbelegt werden.
Ergebnis: Beim Anlegen eines Vertrags einer bestimmten Vertragsart sind die vertragsartspezifischen Vorgaben vorbelegt und müssen nicht je Vertrag erneut erfasst werden.
Belege:
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[VertragsArt]` (Spalten `LaufzeitArt`, `AbrechnungIntervallArt`, `KuendigungsFristDauer1`, `RechnungNormieren`, `Sammelrechnung`, `KontingentVertrag`, `AbrechnungsReportI3D`) - Begründung: Die Spaltenmenge belegt, dass die Vertragsart genau die Steuerparameter trägt, die sonst je Vertrag zu setzen wären.
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, Registrierung `ModuleRegistrationItem.For<ContractTypeAppModuleController>(() => Helper.HasRights(UserRightsConst.Masterdata.Contracts.CONTRACT_TYPES), () => LicenseManager.Instance.HasLicense(LicenseGuids.ContractTypes) || ...)` - Begründung: Recht und Lizenz sind die durchsetzende Stelle für den Zugang zur Vertragsartenverwaltung.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/Contracts/ContractSettings/ContractTypes/ContractTypeWizard/WizardSteps/ (BillingTermsView, DeadlinesView, TimesAndReactionsView, PricingView, InvoiceTemplateView, InvoiceMailTemplateView) - Begründung: Die Assistentenschritte spiegeln die fachlichen Vorlagenbereiche der Vertragsart wider.
Prüfidee: Vertragsart mit Intervall "quartalsweise/1" und Kündigungsfrist "3 Monate" anlegen; neuen Vertrag dieser Art anlegen und prüfen, dass Intervall und Frist vorbelegt sind.
Tracelinks: SyRS-213
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Vorlagensteuerung reduziert Fehleingaben in der Abrechnungssteuerung erheblich.
Status: belegt
Modul: M-10
ID: StRS-203
Titel: Wirtschaftliche Auswertung des Vertragsbestands
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Controller / Vertriebsleitung
Vorbedingung: Benutzer hat die Rechte `UserRightsConst.Sales.ID`, `UserRightsConst.Controlling.ID` und `UserRightsConst.RIGHT_CONTROLLINGAUSWERTUNG` sowie die Lizenz `LicenseGuids.ContractAnalysis`.
Fakt: `ContractEvaluation2ViewModel.StatisticsLoad` ruft `IContractEvaluationLogic.LoadContractEvaluation(filter)` auf; `ContractEvaluationBL.LoadContractEvaluation` aggregiert je Vertrag Umsatz (`SumComplete`), Serviceumsatz (`SumCompleteService`), Einkauf (`SumPurchase`, `SumPurchaseService`) und daraus Marge, jeweils zusätzlich als zeitraumanteiliges "Potential" über `SetPartForBookingInvoice`. Die Auswertung kann nach Vertragsart, Kunde und Filiale gruppiert werden (`DataKind`, `DataContract`, `DataCustomer`).
Aussage: Das System soll den Vertragsbestand für einen wählbaren Zeitraum nach Umsatz und Marge auswerten und die Ergebnisse nach Vertragsart, Vertrag und Kunde verdichtet bereitstellen.
Ergebnis: Der Controller erhält je Vertrag, Vertragsart und Kunde Umsatz-, Einkaufs- und Margenwerte sowie den auf den Auswertungszeitraum umgerechneten Anteilswert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Statistics/ContractStatistics/ContractEvaluationBL.cs, `LoadContractEvaluation` (Aggregation von `incomeRevenue`, `sumPurchase`, `potentialIncomeProfit` je `ContractI3D`) - Begründung: Hier wird die Auswertungslogik tatsächlich berechnet.
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, `ModuleRegistrationItem.For<ContractEvaluation2AppModuleController>(() => Helper.HasRights(UserRightsConst.Sales.ID, UserRightsConst.Controlling.ID, UserRightsConst.RIGHT_CONTROLLINGAUSWERTUNG), ...)` - Begründung: Benennt die durchsetzende Stelle des Zugriffsschutzes auf die Auswertung.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/ContractEvaluation2/ContractEvaluation2AppModuleController.cs, `ModuleName => "Vertragsauswertung"`, `MainCategory => CentronModuleCategory.Controlling` - Begründung: Ordnet das Modul fachlich dem Controlling zu.
Prüfidee: Für einen Vertrag mit einer gebuchten Jahresrechnung 01.07.–30.06. den Auswertungszeitraum auf das Kalenderjahr setzen; der Potentialumsatz muss dem 6/12-Anteil des Rechnungsbetrags entsprechen.
Tracelinks: SyRS-214
Konsolidierung: Kandidat: StRS-204 (Vertragsauswertung 2 vs. Altmodul)
Übernahmewürdigkeit: übernehmen - aktuelle Auswertungssicht mit Filialfilter und Diagrammen.
Status: belegt
Modul: M-11
ID: StRS-204
Titel: Altmodul Vertragsauswertung als abgelöste Zweitimplementierung
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Controller
Vorbedingung: Keine; das Modul ist im Client nicht mehr als eigenständiges Modul registriert.
Fakt: `ContractEvaluationOldAppModuleController` (`ModuleName => "Vertrags-Auswertung_old"`, `Description => "Vertrags-AuswertungLong"`) ist in `ModuleRegistration.cs` nicht als `ModuleRegistrationItem` eingetragen (Volltextsuche über `src/` findet nur die Selbstreferenz im eigenen Ordner). Gleichzeitig verwendet `ContractEvaluation2View.xaml` die Detailansichten des Altmoduls per DataTemplate weiter (`contractEvaluationOld:DetailsEvaluationView`, `BigDetailsEvaluationView`, `DetailsEvaluationText`, `DetailsPerContractEvaluation`). Beide ViewModels rufen dieselbe Backend-Methode `IContractEvaluationLogic.LoadContractEvaluation` auf; das Altmodul kennt jedoch keinen Filialfilter.
Aussage: [HYPOTHESE] Das System soll die Vertragsauswertung nur noch über das aktuelle Modul anbieten; das Altmodul soll ausschließlich als Lieferant der Detailansichten weiterexistieren und nicht mehr als eigenständige Auswertung aufrufbar sein.
Ergebnis: Anwender erreichen nur noch die Vertragsauswertung 2; die Detailfenster stammen weiterhin aus dem Altmodulcode.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Finances/ContractEvaluation2/ContractEvaluation2View.xaml, Zeilen 28-39 (`<DataTemplate DataType="{x:Type contractEvaluationOld:DetailsEvaluationViewModel}">` u. a.) - Begründung: Belegt die tatsächliche Weiterverwendung der Altmodul-Views durch das aktuelle Modul.
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Finances/ContractEvaluationOld/ContractEvaluationOldViewModel.cs Zeile 106 und src/centron/Centron.WPF.UI/Modules/Finances/ContractEvaluation2/ContractEvaluation2ViewModel.cs Zeile 190 rufen beide `contractEvaluationLogic.LoadContractEvaluation(filter)` - Begründung: Zeigt, dass beide Module dasselbe fachliche Konzept auf derselben Datenquelle abbilden.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/ContractEvaluationOld/ContractEvaluationOldAppModuleController.cs, `ModuleName => "Vertrags-Auswertung_old"` - Begründung: Der Namenszusatz "_old" kennzeichnet den Ablösestatus.
Prüfidee: Modulliste des Clients auflisten und prüfen, dass kein Eintrag mit ID `{8DDB7EDA-ADC8-4A41-822F-2CBD7C3331B5}` erscheint; parallel prüfen, dass die Detailansicht in Vertragsauswertung 2 weiterhin funktioniert.
Tracelinks: SyRS-214
Konsolidierung: Kandidat: StRS-203 (dieselbe fachliche Auswertung in zwei Implementierungen)
Übernahmewürdigkeit: veraltet - eigenständiges Altmodul ohne Registrierung und ohne Filialfilter; nur die Detailansichten sind noch produktiv.
Status: HYPOTHESE
Modul: M-12
ID: StRS-205
Titel: Automatisierte Erstellung wiederkehrender Vertragsrechnungen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Abrechnungssachbearbeiter
Vorbedingung: Benutzer hat `UserRightsConst.Sales.ID` und `UserRightsConst.Sales.AUTOMATED_BILLING`; es existieren aktive Verträge mit `AutoAbrechnung`.
Fakt: Das Modul `AutomatedBillingAppModuleController` (`ModuleName => "Vertragsabrechnung"`, `MainCategory => CentronModuleCategory.Billing`) führt über einen Assistenten (Seiten `BillingDateWizardPage`, `CustomerSelectionWizardPage`, `ContractSelectionWizardPage`, `OverviewWizardPage`, `SendSettingsWizardPage`, `BillingResultWizardpage`, `BillingHistoryPage`). `AutomaticFacturaWebServiceBL.CreateInvoiceToContractComplete` erzeugt daraus über `ReceiptWebServiceBL.ForwardReceipt(CentronObjectKindNumeric.InvoiceClass, ... originReceipts: ReceiptKind = ContractClass ...)` die Rechnung und ordnet sie über `AutomaticFacturaBL.StoreInvoiceToContract` dem Vertrag zu.
Aussage: Das System soll aus fälligen Verträgen in einem geführten Ablauf Rechnungen erzeugen, versenden und die Abrechnungsperiode am Vertrag fortschreiben.
Ergebnis: Je Vertrag bzw. Sammelvorgang existiert eine Rechnung, ein Eintrag in `VertragRechKopfZuordnung` mit Berechnungszeitraum und ein Abrechnungsprotokolleintrag.
Belege:
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaWebServiceBL.cs, `CreateInvoiceToContractComplete` (Aufruf `receiptBL.ForwardReceipt(...)` mit `ReceiptToForward { ReceiptKind = CentronObjectKindNumeric.ContractClass, ReceiptI3D = billingParam.ContractID.Key, ReceiptItemI3Ds = billingParam.PosI3Ds }`) - Begründung: Hier wird die Rechnung tatsächlich aus den Vertragspositionen erzeugt.
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaBL.Contracts.cs, `StoreInvoiceToContract` (`Session.GetGenericDAO<VertragRechKopfZuordnung>().Save(vertragZuordnung)` mit `BerechnungszeitraumVon/Bis`) - Begründung: Persistiert die Zuordnung Rechnung↔Vertrag mit dem abgerechneten Zeitraum.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/AutomatedBilling/AutomatedBillingAppModuleController.cs, `ModuleName => "Vertragsabrechnung"`, `Description => "Abrechnung von Verträgen"` - Begründung: Benennt den fachlichen Zweck des Moduls.
Prüfidee: Vertrag mit monatlichem Intervall und `LetzteBezahlung` = Vormonatsende abrechnen; es müssen genau eine Rechnung und genau ein `VertragRechKopfZuordnung`-Satz mit dem Folgemonat als Berechnungszeitraum entstehen.
Tracelinks: SyRS-215, SyRS-216, SyRS-217, SyRS-218, SyRS-224
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Kernprozess des Moduls und Haupterlösquelle wiederkehrender Geschäfte.
Status: belegt
Modul: M-13
ID: StRS-206
Titel: Pauschalabrechnung von Dienstleistungen über Aufträge
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Serviceabrechner
Vorbedingung: Benutzer hat `UserRightsConst.Sales.ID`, `UserRightsConst.Sales.Customer.CustomerCommon.Order.ID` und `UserRightsConst.Sales.FLATRATE_BILLING_MODULE`; ein Ausgleichsartikel für Pauschalen ist in den Programmeinstellungen hinterlegt.
Fakt: Das Modul `FlatRateProjectAppModuleController` (`ModuleName => "Pauschalabrechnung"`, `Description => "Verwaltung und Erstellung von Pauschalabrechnungen"`) arbeitet auf einem Auftrag (`Order`); `OrderBalanceBL.AddHelpdeskTimerToOrderPosition` fügt Helpdeskzeiten in die Stückliste einer Pauschalposition ein und reduziert die Ausgleichsposition um den Wert der eingehängten Zeit (`balanceItem.Price -= partListItem.TotalPrice`). Pauschalpositionen werden über `IsOrderAssetItemABalanceItem` an `Article.MaterialGroup.BlanketMaterialGroup` erkannt.
Aussage: Das System soll erbrachte Helpdeskzeiten einer Pauschalposition eines Auftrags zuordnen und den verbleibenden Pauschalrestwert fortlaufend als Ausgleichsposition ausweisen.
Ergebnis: Der Auftrag zeigt je Pauschalposition die verbrauchten Zeiten und den verbleibenden Pauschalrest; der Rechnungsbetrag bleibt die Pauschale.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/Orders/OrderBalanceBL.cs, `AddHelpdeskTimerToOrderPosition` (`balanceItem.Price -= partListItem.TotalPrice`) - Begründung: Setzt die Verrechnung der Zeit gegen den Pauschalrestwert tatsächlich durch.
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/Orders/OrderBalanceBL.cs, `IsOrderAssetItemABalanceItem` (`orderPosition.IsArticleItem && orderPosition.Article.MaterialGroup.BlanketMaterialGroup`) - Begründung: Definiert die Bedingung, welche Positionen überhaupt Pauschalpositionen sind.
- [SEKUNDÄR] src/backend/Centron.BL/Sales/CustomerAssets/Orders/OrderBalanceBL.cs, Meldungstext "In c-entron wurde kein Ausgleichsartikel für Pauschalen hinterlegt. ... \"Vertrieb->Kunden->Auftrag->Sonderartikel\"" - Begründung: Belegt die Konfigurationsabhängigkeit des Verfahrens.
Prüfidee: Pauschalposition über 1.000 EUR anlegen, Helpdeskzeit im Wert von 300 EUR einhängen; die Ausgleichsposition muss danach 700 EUR ausweisen, die Auftragssumme unverändert 1.000 EUR.
Tracelinks: SyRS-219
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - eigenständiges Abrechnungsmodell neben Vertrag und Zeitabrechnung.
Status: belegt
Modul: M-14
ID: StRS-207
Titel: Vereinfachte Abrechnung erfasster Ticketzeiten
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Abrechnungssachbearbeiter
Vorbedingung: Benutzer hat `UserRightsConst.Sales.ID`, `SHOW_INVOICES` und `UserRightsConst.Sales.TIMER_BILLING_MODULE`; es existieren abrechenbare Helpdeskzeiten.
Fakt: Das Modul `TimerBillingAppModuleController` (`ModuleName => "Vereinfachte Ticketabrechnung"`, `Description => "Abrechnung von Tickets und einzelnen Zeiten."`) führt über die Assistentenseiten `TimerBillingSettingsPage`, `TimerBillingTimerSelectionPage`, `TimerBillingTimersWithOrderItemSettings`, `TimerBillingSummaryPage`, `TimerBillingCreateInvoicesPage`, `TimerBillingResultPage`. `TimerBillingBL.GetContracts` lädt zu den gewählten Kunden (inkl. Konzernverbund über `GetCorporations`) die zuordenbaren Verträge; `TimerBillingBL.SearchTimers` liefert die Zeiten, `SaveTimer` schreibt Korrekturen zurück.
Aussage: Das System soll erfasste Ticketzeiten kundenweise selektierbar machen, sie einem Vertrag oder Auftrag zuordnen und daraus Rechnungen bzw. Lieferscheine erzeugen.
Ergebnis: Für die ausgewählten Zeiten existiert ein Beleg; die Zeiten sind als abgerechnet gekennzeichnet.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/TimerBilling/TimerBillingBL.cs, `GetContracts` (NamedQuery `NamedQueryEnums.Asset.GetContractsForTimerBilling`, Anreicherung `contract.IsCorporationOfCustomerI3Ds`) - Begründung: Setzt die Vertragszuordnung inklusive Konzernverbund tatsächlich um.
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, `ModuleRegistrationItem.For<TimerBillingAppModuleController>(() => Helper.HasRights(UserRightsConst.Sales.ID, UserRightsConst.Sales.Customer.CustomerCommon.Invoice.SHOW_INVOICES, UserRightsConst.Sales.TIMER_BILLING_MODULE), ...)` - Begründung: Benennt die durchsetzende Stelle der Zugriffsprüfung.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/TimerBilling/Converter/ContractToContingentOpenConverter.cs - Begründung: Zeigt, dass in der Zeitabrechnung das offene Vertragskontingent angezeigt wird und damit Vertragsbezug besteht.
Prüfidee: Zwei Ticketzeiten desselben Kunden auswählen, einem Kontingentvertrag zuordnen und Rechnung erzeugen; beide Zeiten dürfen danach in der Selektion nicht erneut als offen erscheinen.
Tracelinks: SyRS-220
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - abrechnungsseitige Verwertung erfasster Zeiten ist eigenständiger Geschäftsprozess.
Status: belegt
Modul: M-15
ID: StRS-208
Titel: Nutzungsabhängige Abrechnung über Geräte-Zählerstände (Click)
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Zählerverwalter / Abrechnungssachbearbeiter
Vorbedingung: Benutzer hat `UserRightsConst.Sales.ID` und `UserRightsConst.RIGHT_ZAEHLEREINGABE`; Geräte sind über Stammblätter einem Vertrag zugeordnet.
Fakt: Das Modul `DeviceClickCounterAppModuleController` (`ModuleName => "Klick-Zählerverwaltung"`, `Description => "Verwaltung von Klick-Zählern"`, `MainCategory => CentronModuleCategory.Contract`) erfasst Zählerstände manuell oder per Import (`ImportKind` = ikExcel, ikCSV, ikRiverBird, docuForm, docuFormApi). `AutomaticFacturaBL.StoreCounterState` schreibt neue Stände nach `GeraeteClickZaehlerHistory` (`Abgerechnet = 0`) und aktualisiert `GeraeteClickZaehler.StandAktuell`. Die Abrechnung ermittelt in `AutomaticFacturaWebServiceBL.AddCounterItem` die Klickmenge aus `LastEntryValue - StartCalculateValue` abzüglich Freikopien.
Aussage: Das System soll Zählerstände je Gerät und Zählerart historisiert erfassen und daraus die nutzungsabhängige Menge für die Vertragsabrechnung ermitteln.
Ergebnis: Jeder erfasste Zählerstand liegt als Historieneintrag vor; die zugehörige Rechnungsposition weist die abgerechnete Klickmenge aus.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaBL.Contracts.cs, `StoreCounterState` (Anlage `DeviceClickCounterHistory` mit `Balanced = 0`, `NewStateDate`, `OldState`, anschließendes `activeCounter.CurrentCounter = (int)item.NewValue`) - Begründung: Persistiert Historie und aktuellen Stand als durchgesetzte Regel.
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaWebServiceBL.cs, `AddCounterItem` (`cnt = counter.LastEntryValue - counter.StartCalculateValue; if (cnt < counter.FreeCount) cnt = 0; else cnt -= counter.FreeCount;`) - Begründung: Ist die durchsetzende Stelle der abgerechneten Klickmenge.
- [SEKUNDÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[GeraeteClickZaehlerHistory]` (Spalten `AlterStand`, `NeuerStand`, `NeuerStandDatum`, `Abgerechnet`, `RechPosI3D`, `ImportID`) - Begründung: Die Spalten belegen die Historisierung samt Abrechnungsverknüpfung.
Prüfidee: Zähler mit Startwert 1.000 und Freikopien 500 auf 1.400 fortschreiben und abrechnen; die Rechnungsposition muss Menge 0 ausweisen, bei Stand 1.800 dagegen Menge 300.
Tracelinks: SyRS-221, SyRS-225
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - trägt die Druck-/Kopierabrechnung und ist unmittelbar erlösrelevant.
Status: belegt
Modul: M-16
ID: StRS-209
Titel: Kontingentverträge mit Verbrauchsverrechnung
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertragssachbearbeiter / Abrechnungssachbearbeiter
Vorbedingung: Der Vertrag ist als Kontingentvertrag gekennzeichnet (`VertragKopf.KontingentVertrag = 1`).
Fakt: Die Sicht `dbo.ContractContingentInfo` liefert nur Verträge mit `VK.KontingentVertrag = 1` und stellt `Kind` (`KontingentArt`), `Value` (`KontingentWert`), `Overbooking` (`KontingentUeberbuchung`), `RestTake` (`RestMitnehmen`), `DifferContingentIntervalDuration` (`AbwKontingentIntervallDauer`) sowie `ContingentLimitKind`/`ContingentLimitValue`/`isContingentLimitBilling` bereit. Die Einstellungsmaske `ContigentSettingsController` ("Kontingente"/"Kontingente Einstellungen") pflegt globale Vorgaben inkl. Warengruppenzuordnung über `GroupToContingent` mit `ContractI3D = 0`.
Aussage: Das System soll je Kontingentvertrag ein periodisches Kontingent buchen, den Verbrauch dagegen verrechnen und dabei Überbuchung sowie Restwertmitnahme gemäß Vertragskonfiguration berücksichtigen.
Ergebnis: Zu jeder Vertragsrechnung existiert ein gebuchter Kontingentwert; der Restwert ist aus gebuchten und verbrauchten Werten jederzeit ermittelbar.
Belege:
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE VIEW [dbo].[ContractContingentInfo]` (`where VK.KontingentVertrag = 1`) - Begründung: Datenbankseitige Durchsetzung, dass nur gekennzeichnete Verträge Kontingentlogik erhalten.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE FUNCTION [dbo].[cfn_RestValue](@contractID, @diadLine)` (`RETURN ROUND(ROUND(IsNull(@booked,0) - IsNull(@used,0), 2),2)`) - Begründung: Berechnet den Kontingentrestwert als gebucht minus verbraucht und ist damit die durchsetzende Stelle der Restwertermittlung.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/Contracts/Settings/Contigents/ContigentSettingsViewModel.cs, Variablen `@@VertragsNummer@@`, `@@KontigentGebuchtZum@@` sowie `SaveSelectedGroups` mit `secondaryItem.ContractI3D = 0` - Begründung: Belegt globale Kontingent-Grundeinstellungen und die Warengruppen-Zuordnung.
Prüfidee: Kontingentvertrag mit 10 Stunden/Monat und aktivierter Restmitnahme über zwei Monate abrechnen und in Monat 1 nur 6 Stunden verbrauchen; `cfn_RestValue` muss zu Beginn von Monat 3 einen Rest von 4 Stunden zzgl. Monat-2-Rest liefern.
Tracelinks: SyRS-222
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Kontingente sind ein tragendes Preismodell der Serviceverträge.
Status: belegt
Modul: M-17
ID: StRS-210
Titel: MSP-Auswertung: Abgleich von Lieferantenlizenzen mit Vertragsbestand
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: MSP-Verantwortlicher / Controller
Vorbedingung: MSP-Collector-Importe (Lieferantenrechnungen/Lizenzmengen) liegen vor; Verträge sind mit den betroffenen Artikeln bestückt.
Fakt: Das Modul `MSPComparerAppModuleController` (`ModuleName => "MSP-Auswertung"`, `Description => "Auswertung des MSP-Collector Imports"`) stellt je Position in `ComparerViewModel` `ArticleAmount` (Lieferantenmenge) und `CentronArticleAmount` (Vertragsmenge) gegenüber und weist `Difference`, `PriceDifference` sowie `QuantityDifferenceLastInvoice` aus. Über `MspEvaluationDecision` (`IgnoreImport`, `ChangePriceAndQunatity`, `ChangePrice`, `ChangeQuantity`) wird je Zeile entschieden; `AutomaticFacturaBL.CreateSpecialArticleToContractFromMspEvaluation` erzeugt daraus eine Sonderartikelposition zum Vertrag.
Aussage: Das System soll importierte MSP-Lieferantenmengen und -preise je Kunde und Vertrag mit den vertraglich hinterlegten Mengen und Preisen abgleichen, Abweichungen ausweisen und deren Nachberechnung als Sonderartikel zum Vertrag ermöglichen.
Ergebnis: Abweichungen sind je Vertragsposition sichtbar; für die zur Nachberechnung entschiedenen Zeilen existiert ein `SpecialArticleToContractHead` mit Positionen.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaBL.cs, `CreateSpecialArticleToContractFromMspEvaluation` (`Guard.NotNull(compensationItemDto.MspItem.ContractI3D, ...)`, anschließend `CreateSpecialArticleToContract(headImportList, currentUser)`) - Begründung: Setzt durch, dass eine Kompensationsposition nur mit vorhandenem Vertragsbezug erzeugt wird.
- [PRIMÄR] src/backend/Centron.Interfaces/Sales/BillingCenter/Contracts/MspEvaluationDecision.cs (`IgnoreImport = 0`, `ChangePriceAndQunatity = 1`, `ChangePrice = 2`, `ChangeQuantity = 3`) - Begründung: Definiert abschließend die zulässigen fachlichen Entscheidungen der Auswertung.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Global/MSPLicensesCompare/ComparerViewModel.cs, Eigenschaften `ArticleAmount`, `CentronArticleAmount`, `Difference`, `PriceDifferenceLastInvoice` - Begründung: Bilden die fachliche Vergleichssicht ab.
Prüfidee: MSP-Import mit 12 Lizenzen gegen einen Vertrag mit 10 Positionen laufen lassen; die Auswertung muss `Difference = 2` zeigen und bei Entscheidung "Stückzahl ändern" eine Sonderartikelposition über 2 Einheiten am Vertrag erzeugen.
Tracelinks: SyRS-223
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - schließt die Lücke zwischen Lieferantenabrechnung und Kundenvertrag bei Managed Services.
Status: belegt
Modul: M-18
@@ -0,0 +1,314 @@
ID: SyRS-211
Titel: Versionierte Vertragsakte mit lesbarem Vorversionszugriff
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertragsverwaltung (Modul Contracts)
Vorbedingung: Ein Vertrag ist gespeichert und mindestens eine Vorversion existiert.
Fakt: `ContractsManagementViewModel` führt eine Liste `Versions` und eine `SelectedVersion`; `IsPreviousVersion => this.SelectedVersion != this.Versions.MaxOrDefault(1)`. `LoadContractVersion` lädt bei Auswahl einer älteren Version über `_receiptLogic.GetReceiptVersionByI3DAsync(this.Receipt.ReceiptKind, this.Receipt.GetOriginalReceiptI3D(), this.SelectedVersion)`, ansonsten den aktuellen Beleg. Persistiert wird in `dbo.VertragKopfVersions` / `dbo.VertragPosVersions`.
Aussage: Das System soll je Vertrag alle Versionsstände vorhalten, den aktuellen Stand als einzigen bearbeitbaren Stand führen und ältere Versionen ausschließlich lesend anzeigen.
Ergebnis: Bei Auswahl einer Vorversion wird der historische Stand angezeigt und ist als schreibgeschützt gekennzeichnet; der aktuelle Stand bleibt unverändert.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Finances/Contracts/ContractsManagementViewModel.cs, `IsPreviousVersion => this.SelectedVersion != this.Versions.MaxOrDefault(1)` und `LoadContractVersion(bool ifIsReadOnlyRemoveMaxVersion = true)` - Begründung: Die Bedingung entscheidet konkret, ob der aktuelle oder ein historischer Stand geladen und schreibgeschützt dargestellt wird.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[VertragKopfVersions]` und `CREATE TABLE [dbo].[VertragPosVersions]` - Begründung: Die separaten Versionstabellen sind die persistente Durchsetzung der Historienführung.
- [KONTEXT] docs/reference/receipts/contracts-backend.md, Abschnitt "Contract Version Creation Process" - Begründung: Beschreibt den INSERT-SELECT-Mechanismus mit `OriginalI3D`/`KopfVersionsI3D`.
Prüfidee: Vertrag speichern, neue Version erzeugen, Position ändern; anschließend die Vorversion auswählen — die geänderte Position muss im alten Wert erscheinen und nicht editierbar sein.
Tracelinks: StRS-201
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Nachvollziehbarkeit von Vertragsänderungen ist prüfungs- und streitfallrelevant.
Status: belegt
Modul: M-09
ID: SyRS-212
Titel: Automatisches Abschließen vollständig abgerechneter, abgelaufener Verträge
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Hintergrunddienst ContractCloseService
Vorbedingung: Einstellung `ApplicationSettingID.AutomaticallyCloseExpiredContracts` (ID 10335) ist aktiviert.
Fakt: `ContractCloseService` ist ein `ManagedBackgroundService` mit `GetExecutionInterval() => TimeSpan.FromDays(1)` und ruft `ContractWebServiceBL.CloseContract()` auf. `ContractBL.CloseContract` beendet sofort, wenn die Einstellung `false` ist, lädt sonst `ReceiptContract` mit `State == ReceiptState.Active && CalculationKind == ContractCalculationKind.Auto && (ContractTermination.HasValue || ContractEnd.HasValue)` und setzt `State = ReceiptState.Completed` nur, wenn `LastBookingTo >= ContractTermination` (bei `AutomatedProlongation`) bzw. `LastBookingTo >= ContractEnd` und das Referenzdatum jeweils nach diesem Datum liegt.
Aussage: Das System soll abgelaufene, automatisch abzurechnende Verträge täglich prüfen und nur dann abschließen, wenn sie bis zum Vertrags- bzw. Kündigungsende vollständig abgerechnet sind und dieses Datum überschritten ist.
Ergebnis: Vollständig abgerechnete abgelaufene Verträge erhalten den Status `Completed`; nicht vollständig abgerechnete Verträge bleiben aktiv.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/Contracts/ContractBL.cs, `CloseContract` (Bedingung `contract.LastBookingTo >= contract.ContractEnd && referenceDate > contract.ContractEnd`) - Begründung: Genau diese Bedingung verhindert das Schließen noch nicht abgerechneter Verträge und damit Erlösverlust.
- [PRIMÄR] src/webservice/Centron.Host/AspNetCore/HostedServices/ContractCloseService.cs, `GetExecutionInterval() => TimeSpan.FromDays(1)` und `session.GetBL<ContractWebServiceBL>().CloseContract()` - Begründung: Benennt Auslöser und Taktung der Regel.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/Contracts/Settings/General/GeneralContractSettingsViewModel.cs, Eigenschaft `AutomaticallyCloseExpiredContracts` - Begründung: Zeigt den fachlichen Konfigurationsschalter im Einstellungsdialog.
Prüfidee: Vertrag mit `Ende` = gestern und `LastBookingTo` = vorletzter Monat anlegen; nach Dienstlauf muss der Vertrag aktiv bleiben. Nach Nachabrechnung bis Vertragsende muss er beim nächsten Lauf auf `Completed` wechseln.
Tracelinks: StRS-201
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - verhindert Karteileichen im Vertragsbestand ohne Abrechnungsverlust.
Status: belegt
Modul: M-09
ID: SyRS-213
Titel: Vertragsart steuert Abrechnungs-, Kündigungs- und Reportvorgaben
Ebene: SyRS
Typ: Daten
Qualitätsmerkmal:
Akteur: Vertragsverwaltung / Vertragsabrechnung
Vorbedingung: Vertragsart ist angelegt und dem Vertrag zugeordnet (`VertragKopf.VertragsArtI3D`).
Fakt: `dbo.VertragsArt` führt u. a. `AbrechnungIntervallArt`, `AbrechnungIntervallDauer`, `AutoAbrechnung`, `Berechnungsart`, `RechnungNormieren`, `Sammelrechnung`, `SNPflicht`, `Stammblattbezogen`, `KontingentVertrag`, `WithStaffelPrice`, `CalcNeedKind`, `KuendigungsFristArt1/2`, `KuendigungsFristDauer1/2`, `VerlaengerungDauer`, `VertragsEndeToDoVorlauf`, `VertragsRechToDoVorlauf`, `AbrechnungsReportI3D`, `C2ReportI3D`, `WebReportI3D`, `SendKind`, `CanChangeSendKind`, `HourlySurchargeRateI3D`, `CostCenterI3D`, `CostObjectI3D`, `CalculationPrio`.
Aussage: Das System soll die Vertragsart als Konfigurationsträger für Abrechnungsintervall, Berechnungsart, Kündigungs- und Verlängerungsfristen, Vorlauffristen für Aufgaben sowie Rechnungs- und Versandvorgaben führen.
Ergebnis: Änderungen an der Vertragsart wirken als Vorgabe für neue Verträge dieser Art; bestehende Verträge behalten ihre eigenen Werte.
Belege:
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[VertragsArt]` (vollständige Spaltenliste inkl. `AbrechnungIntervallArt`, `KuendigungsFristDauer1`, `VertragsRechToDoVorlauf`, `AbrechnungsReportI3D`, `SendKind`) - Begründung: Der Datentyp ist die verbindliche Definition des Vorlagenumfangs.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/Contracts/ContractSettings/ContractTypes/ContractTypeWizard/WizardSteps/DeadlinesViewModel.cs und BillingTermsViewModel.cs - Begründung: Die Assistentenschritte "Fristen" und "Abrechnungsbedingungen" pflegen genau diese Spalten.
- [KONTEXT] src/centron/Centron.WPF.UI/Modules/Finances/AutomatedBilling/AutomatedBillingViewModel.cs Zeile 523 (`CentronCache.Instance.CurrentUserAppRights.Any(f => f.I3D == UserRightsConst.Masterdata.Contracts.CONTRACT_TYPES)`) - Begründung: Zeigt, dass die Abrechnung den Sprung in die Vertragsartenpflege rechteabhängig anbietet.
Prüfidee: In einer Vertragsart `VertragsRechToDoVorlauf` auf 10 Tage setzen; bei der nächsten Abrechnung eines Vertrags dieser Art muss die erzeugte Aufgabe 10 Tage vor Periodenwechsel terminiert sein.
Tracelinks: StRS-202
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - zentrale Konfigurationsebene mit direkter Abrechnungswirkung.
Status: belegt
Modul: M-10
ID: SyRS-214
Titel: Zeitraum- und Filialabgrenzung der Vertragsauswertung
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertragsauswertung (Modul ContractEvaluation2)
Vorbedingung: Es existieren gebuchte Vertragsrechnungen in `VertragRechKopfZuordnung`.
Fakt: `ContractEvaluation2ViewModel.StatisticsLoad` normiert den Zeitraum auf Monatsgrenzen (`filter.DateFrom = new DateTime(DateFrom.Year, DateFrom.Month, 1)`, `filter.DateTo = new DateTime(DateTo.Year, DateTo.Month, 1).AddMonths(1)`) und setzt bei vorhandenen Filialen `filter.BranchI3Ds = new List<int>() { Branche.Key }`. Das Altmodul `ContractEvaluationOldViewModel` setzt nur `ContractKinds` und `CustomersI3D`, keinen Filialfilter.
Aussage: Das System soll den Auswertungszeitraum stets auf volle Kalendermonate abgrenzen und die Auswertung zusätzlich auf eine Filiale einschränkbar machen.
Ergebnis: Auswertungsergebnisse sind monatsscharf und, sofern Filialen existieren, filialbezogen.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Finances/ContractEvaluation2/ContractEvaluation2ViewModel.cs, `StatisticsLoad` (Zeilen 184-187) - Begründung: Setzt Monatsnormierung und Filialfilter unmittelbar vor dem Backendaufruf durch.
- [PRIMÄR] src/backend/Centron.BL/Statistics/ContractStatistics/ContractEvaluationBL.cs, `LoadContractEvaluation` verwendet `GetBookIncomeSQLOver(filter)` mit den Filterparametern - Begründung: Backendseitige Anwendung des Filters auf die Datenselektion.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/ContractEvaluationOld/ContractEvaluationOldViewModel.cs, Zeilen 95-102 (nur `ContractKinds`/`CustomersI3D`) - Begründung: Belegt den Funktionsunterschied zum Altmodul.
Prüfidee: Auswertung mit "Von 15.03." starten; die zurückgelieferten Buchungen müssen ab dem 01.03. berücksichtigt werden. Filiale wechseln und prüfen, dass Verträge fremder Filialen entfallen.
Tracelinks: StRS-203, StRS-204
Konsolidierung: Kandidat: StRS-203/StRS-204 (identische Auswertungslogik in zwei Modulen)
Übernahmewürdigkeit: übernehmen - Monatsnormierung ist Voraussetzung für die Anteilsberechnung in SwRS-229.
Status: belegt
Modul: M-11
ID: SyRS-215
Titel: Selektion abrechnungsfälliger Verträge nach Berechnungs- und Bedarfsart
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertragsabrechnung (AutomaticFacturaBL)
Vorbedingung: Der Anwender hat Abrechnungsstichtag, Kunden-, Vertragsart-, Filial- und Intervallfilter gesetzt.
Fakt: `AutomaticFacturaBL.GetActiveContracts` selektiert nur `f.State == 1` und für die Buchung nur Verträge mit `(filter.CalculationKind & f.CalculationKind) == ContractCalculationKind.Auto && (f.AutomatedProlongation || f.LastPaidDate == null || f.LastPaidDate < f.ContractEnd) && (LastPaidDate < filter.DateTo || FirstPaidDate < filter.DateTo.AddDays(1))` bzw. Verträge mit `CalculationKind > Auto` (Bedarf/Manuell). `SearchBillingContracts` entfernt anschließend dynamische Bedarfsverträge ohne Sonderartikel (`contracts.RemoveAll(f => f.CalculationKind == ContractCalculationKind.Need && f.CalcNeedKind == ContractNeedCalcKind.Dynamic && !f.WithSpecialArticle)`) sowie Verträge, die zu einer leeren Rechnung führen würden (NamedQuery `GetNoEmptyContracts` in Kombination mit `ContractArticleReferenzes`). `InvoicePeriodCalculate` entfernt Klick- und Schwellenkontingentverträge ohne abrechenbaren Bestand.
Aussage: Das System soll zur Abrechnung nur aktive Verträge vorschlagen, deren Berechnungsart der Auswahl entspricht, deren Abrechnungsperiode offen ist und die zu einer nicht leeren Rechnung führen.
Ergebnis: Die Abrechnungsliste enthält keine leeren Rechnungen, keine bereits vollständig abgerechneten und keine nicht selektierten Bedarfsarten.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaBL.Contracts.cs, `GetActiveContracts` (LINQ-Prädikat ab `f => f.State == 1 && ...`) - Begründung: Die Where-Bedingung ist die durchsetzende Stelle der Fälligkeitsselektion.
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaBL.Contracts.cs, `SearchBillingContracts`, Kommentar `// leere RE vermeiden` mit `contracts.RemoveAll(f => !f.WithSpecialArticle && !contractsI3D...Contains(f.I3D) && !contractReferenzes...Contains(f.I3D))` - Begründung: Verhindert die Erzeugung leerer Rechnungen.
- [SEKUNDÄR] src/backend/Centron.BL/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaBL.Contracts.cs, `InvoicePeriodCalculate`, Kommentare `// nur click-abrechnung` und `// nur schwellekontingent-abrechnung` - Begründung: Dokumentieren die Ausschlussregeln je Bedarfsart.
Prüfidee: Vertrag ohne Positionen, ohne Sonderartikel und ohne `ContractArticleReferenzes` anlegen und Abrechnung starten; der Vertrag darf in der Auswahl nicht erscheinen.
Tracelinks: StRS-205
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - verhindert Fehlrechnungen an Kunden.
Status: belegt
Modul: M-13
ID: SyRS-216
Titel: Optimistische Sperre gegen zwischenzeitlich veränderte Vertragsdaten
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertragsabrechnung (AutomaticFacturaWebServiceBL)
Vorbedingung: Die Abrechnungsliste wurde geladen; `ContractToInvoiceParam.LastInvoiceID` enthält die zuletzt bekannte Rechnungs-ID je Vertrag.
Fakt: In `CreateInvoiceToContractComplete` wird unmittelbar vor dem Speichern je Vertrag geprüft: `if (param.LastInvoiceID != automaticFacturaBLsave.GetLastInvoiceID(param.ContractID.Key)) throw new Exception("Seit dem letzten Laden wurden die Vertragsdaten von Vertrag " + param.ContractID.Value + " geändert.");`. Der umgebende `catch` löst `saveSession.DAOSession.RollbackTransaction()` aus.
Aussage: Das System soll die Erstellung einer Vertragsrechnung abbrechen und die Transaktion zurückrollen, wenn seit dem Laden der Abrechnungsliste für denselben Vertrag bereits eine weitere Rechnung erzeugt wurde.
Ergebnis: Es entsteht keine Doppelabrechnung; der Anwender erhält eine Meldung mit der betroffenen Vertragsnummer.
Belege:
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaWebServiceBL.cs, `CreateInvoiceToContractComplete`, Vergleich `param.LastInvoiceID != automaticFacturaBLsave.GetLastInvoiceID(param.ContractID.Key)` vor `StoreInvoiceToContract` - Begründung: Exakte durchsetzende Prüfung gegen Doppelfakturierung.
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaBL.Contracts.cs, `GetLastInvoiceID` (NamedQuery `NamedQueryEnums.Asset.GetLastInvoiceID`) - Begründung: Liefert den zum Vergleich herangezogenen Ist-Zustand aus der Datenbank.
- [SEKUNDÄR] Meldungstext "Seit dem letzten Laden wurden die Vertragsdaten von Vertrag {Nr} geändert." - Begründung: Fachliche Fehlermeldung an den Anwender.
Prüfidee: Denselben Vertrag in zwei Sitzungen zur Abrechnung laden, in Sitzung A abrechnen, danach in Sitzung B abrechnen; Sitzung B muss mit der genannten Meldung abbrechen und keine Rechnung erzeugen.
Tracelinks: StRS-205
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - unmittelbarer Schutz gegen doppelte Kundenrechnungen.
Status: belegt
Modul: M-13
ID: SyRS-217
Titel: Rechnungsvorschau ohne persistente Wirkung
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Abrechnungssachbearbeiter
Vorbedingung: Ein Vertrag ist in der Abrechnungsliste ausgewählt; die Vorschau wird angefordert (`isPreview = true`).
Fakt: In `CreateInvoiceToContractComplete` wird im Zweig `if (isPreview)` der Report über `HandleSendType(..., isPreview: true, ...)` erzeugt und anschließend `saveSession.DAOSession.RollbackTransaction()` ausgeführt (sofern kein Objektreport), bevor `return result` mit `result.InvoicePreview = invoice` erfolgt. Die Blöcke `StoreInvoiceToContract` und `CommitTransaction` liegen hinter diesem Return und werden im Vorschaumodus nicht erreicht. Zusätzlich wird `ReceiptBL.CreateFullReportForReceipt(..., ignoreReportGroupExport: isPreview)` aufgerufen und der Reportparameter `previewParameter.Value = isPreview.ToOneZeroInteger().ToString()` gesetzt.
Aussage: Das System soll im Vorschaumodus eine vollständig berechnete Rechnung samt Report anzeigen, dabei aber keine Rechnung, keine Vertrags-Rechnungszuordnung und keinen Reportexport persistieren.
Ergebnis: Nach einer Vorschau sind in `Rechnungen`, `VertragRechKopfZuordnung` und im Reportexport keine neuen Datensätze vorhanden.
Belege:
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaWebServiceBL.cs, Zeilen 2136-2167 (`if (isPreview) { ... saveSession.DAOSession.RollbackTransaction(); ... return result; }`) - Begründung: Rollback und vorzeitiges Return sind die durchsetzende Stelle der Nichtpersistenz.
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaWebServiceBL.cs, `CreateFullReportForReceipt(..., ignoreReportGroupExport: isPreview)` - Begründung: Verhindert den Reportexport in der Vorschau.
- [SEKUNDÄR] `previewParameter.Value = isPreview.ToOneZeroInteger().ToString()` - Begründung: Der Report erhält ein Vorschaukennzeichen zur optischen Kennzeichnung.
Prüfidee: Rechnungsnummernkreis und Zeilenzahl von `VertragRechKopfZuordnung` vor und nach einer Vorschau vergleichen; beide müssen identisch sein.
Tracelinks: StRS-205
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Vorschau ist Voraussetzung für die Freigabekontrolle vor Massenabrechnungen.
Status: belegt
Modul: M-13
ID: SyRS-218
Titel: Abbruch der Rechnungserstellung bei nicht erreichbarem RMM-Dienst
Ebene: SyRS
Typ: Schnittstelle
Qualitätsmerkmal:
Akteur: Vertragsabrechnung / externer RMM-Dienst (Riverbird)
Vorbedingung: Der Vertrag ist RMM-fähig (`WhetherRMM(contractI3D) == true`) und es sind `ContractArticleReferenzes` hinterlegt.
Fakt: `AutomaticFacturaWebServiceBL.CheckRMMArticle` bricht ohne Wirkung ab, wenn `!_automaticFacturaBL.WhetherRMM(...)` oder `rmmArticleReferences.Count == 0`. Sonst ruft `GetAggregatedRMMStatistics` je Abrechnungsintervall `new RiverConnectionBL(this.Session).GetContractBillingAmounts(interval.From, interval.To.AddDays(1), customerI3D, rmmArticleReferences)` auf und wirft bei `riverbirdStatisticsResult.Status is ResultStatus.Error` und vorhandenen Referenzen bzw. gefundenem `@@RMMArtikel@@`-Tag eine `RMMServiceUnavailableException` mit der Meldung "Die Rechnung kann nicht erstellt werden. {Message}". Der Platzhalter `@@RMMArtikel@@` bestimmt die Einfügeposition; ohne Platzhalter wird an Position `invoice.Items.Count - 2` eingefügt.
Aussage: Das System soll bei RMM-pflichtigen Verträgen die Rechnungserstellung abbrechen, wenn die Nutzungsdaten des externen RMM-Dienstes für den Abrechnungszeitraum nicht abrufbar sind.
Ergebnis: Es wird keine Rechnung mit unvollständigen Nutzungsmengen erzeugt; der Fehler wird protokolliert und dem Anwender gemeldet.
Belege:
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaWebServiceBL.cs, `GetAggregatedRMMStatistics` (`if (riverbirdStatisticsResult.Status is ResultStatus.Error) { if (hasRmmTag || rmmArticleReferences.Count != 0) { ... throw new RMMServiceUnavailableException(errorMsg); } continue; }`) - Begründung: Exakte Bedingung und Ausnahme, die den Abrechnungslauf für diesen Vertrag stoppt.
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaWebServiceBL.cs, `CheckRMMArticle`, Erkennung `f.RichText.IndexOf("@@RMMArtikel@@", StringComparison.InvariantCulture) > -1` - Begründung: Definiert die Schnittstelle zwischen Rechnungsvorlage und RMM-Positionen.
- [KONTEXT] docs/reference/receipts/Contract-Billing-RMM-Article-Logic.md, Abschnitt "External Service Unavailability" - Begründung: Bestätigt die Absicht, unvollständige Abrechnungen zu verhindern.
Prüfidee: RMM-Dienstadresse auf einen nicht erreichbaren Host setzen und einen RMM-Vertrag abrechnen; es darf keine Rechnung entstehen und die Fehlermeldung muss den RMM-Bezug nennen.
Tracelinks: StRS-205
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - schützt vor fehlerhafter Kundenabrechnung bei Fremdsystemausfall.
Status: belegt
Modul: M-13
ID: SyRS-219
Titel: Sperre gegen Mehrfachverwertung bereits verarbeiteter Zeiten in Pauschalen
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Pauschalabrechnung (OrderBalanceBL)
Vorbedingung: Ein Auftrag mit Pauschalposition ist geöffnet und eine Helpdeskzeit ist ausgewählt.
Fakt: `OrderBalanceBL.DoValidateCurrentSelectionForAddingHelpdeskTimeToPosition` weist die Zuordnung zurück, wenn die Zielposition keine Pauschalposition ist ("Es können nur zu Pauschalpositionen Helpdeskzeiten hinzugefügt werden."), wenn `timer.IsAssignedToOrder` ("Die Zeit wurde bereits einer Auftragsposition hinzugefügt."), `timer.IsAssignedToDeliveryList` ("Diese Zeit wurde bereits zu einem Lieferschein weiterverarbeitet."), `timer.IsAssignedToInvoice` ("Diese Zeit wurde bereits zu einer Rechnung weiterverarbeitet.") oder `timer.IsPlanned` ("Geplante Zeiten können nicht zu einer Pauschale hinzugefügt werden.") zutrifft. `AddHelpdeskTimerToOrderPosition` bricht bei nicht leerem Rückgabetext vor jeder Änderung ab.
Aussage: Das System soll verhindern, dass eine Helpdeskzeit, die bereits einem Auftrag, Lieferschein oder einer Rechnung zugeordnet ist oder nur geplant ist, zusätzlich einer Pauschalposition zugeordnet wird.
Ergebnis: Die Zuordnung wird mit einer eindeutigen Meldung abgelehnt; Auftrag und Ausgleichsposition bleiben unverändert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/Orders/OrderBalanceBL.cs, `DoValidateCurrentSelectionForAddingHelpdeskTimeToPosition` (fünf Ablehnungsbedingungen) - Begründung: Diese Methode ist die durchsetzende Stelle gegen Mehrfachverwertung.
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/Orders/OrderBalanceBL.cs, `AddHelpdeskTimerToOrderPosition` (`string output = DoValidateCurrentSelectionForAddingHelpdeskTimeToPosition(...); if (!String.IsNullOrWhiteSpace(output)) return output;`) - Begründung: Der Abbruch erfolgt vor jeder Zustandsänderung.
- [SEKUNDÄR] src/backend/Centron.BL/Sales/CustomerAssets/Orders/OrderBalanceBL.cs, Meldung "Der Helpdeskzeit wurde kein Artikel hinterlegt." - Begründung: Weitere fachliche Vollständigkeitsprüfung vor der Verrechnung.
Prüfidee: Eine bereits fakturierte Helpdeskzeit erneut an eine Pauschalposition hängen; das System muss ablehnen und die Ausgleichsposition darf sich nicht ändern.
Tracelinks: StRS-206
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - verhindert doppelte Erlöserfassung derselben Leistung.
Status: belegt
Modul: M-14
ID: SyRS-220
Titel: Rechteprüfung beim Ändern fremder Ticketzeiten in der Zeitabrechnung
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Zeitabrechnung (TimerBillingBL)
Vorbedingung: Ein bestehender Timer (`I3D > 0`) soll aus der Zeitabrechnung heraus geändert werden.
Fakt: `TimerBillingBL.SaveTimer` ruft bei erkannter Änderung (`TimerHasChanged`) vor jeder Zuweisung `new HelpdeskTimerWebServiceBL(this.Session).ThrowIfUserDoesntHaveRightToChangeHelpdeskTimers(...)`. Diese Methode wirft `ResultException("Web-Account Zugriff nicht erlaubt.")` bei `loggedInUser.IsWebAccountLogin || !loggedInUser.UserI3D.HasValue`, `ResultException("Nutzer hat keine Rechte um Zeiten zu bearbeiten", DefaultMessageCodes.RightCheckFailed)` ohne `UserRightsConst.Sales.Customer.Helpdesk.EDIT_TIME` und `ResultException("Nutzer hat keine Rechte um Zeiten anderer Mitarbeiter zu bearbeiten")`, wenn `OWN_TIME_EDIT` gesetzt ist und der Timer von einem anderen Mitarbeiter stammt. Bei Timer-Split wird bewusst der Ursprungstimer geprüft (`timer.IsCloneOfTimerI3D > 0 ? timer.IsCloneOfTimerI3D : timer.I3D`).
Aussage: Das System soll das Ändern einer Ticketzeit aus der Zeitabrechnung nur zulassen, wenn der Benutzer das Recht zur Zeitbearbeitung besitzt und – bei Beschränkung auf eigene Zeiten – Erfasser der Zeit ist; Web-Account-Anmeldungen sollen abgelehnt werden.
Ergebnis: Unberechtigte Änderungen werden mit `DefaultMessageCodes.RightCheckFailed` abgewiesen; der Timer bleibt unverändert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/Support/HelpdeskTimerWebServiceBL.cs, `ThrowIfUserDoesntHaveRightToChangeHelpdeskTimers` (Prüfungen auf `EDIT_TIME` und `OWN_TIME_EDIT` mit `appRightsBl.HasUserRight(...)`) - Begründung: Nennt Datei, Methode und die konkreten Rechteprüfungen als durchsetzende Stelle.
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/TimerBilling/TimerBillingBL.cs, `SaveTimer` Zeilen 473-481 (Aufruf der Prüfung vor allen Feldzuweisungen) - Begründung: Belegt, dass die Prüfung im Abrechnungsmodul tatsächlich vor der Änderung greift.
- [SEKUNDÄR] src/backend/Centron.BL/Sales/CustomerAssets/TimerBilling/TimerBillingBL.cs, `ResultException("Die Zeit kann nicht gespeichert werden. Das Enddatum ist vor dem Startdatum (negative Dauer).")` - Begründung: Ergänzende fachliche Plausibilitätsprüfung derselben Speicheroperation.
Prüfidee: Benutzer mit `EDIT_TIME` und `OWN_TIME_EDIT` versucht, die Zeit eines anderen Mitarbeiters in der Zeitabrechnung zu ändern; der Aufruf muss mit `RightCheckFailed` scheitern und der Datenbankstand unverändert bleiben.
Tracelinks: StRS-207
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - schützt Leistungsnachweise vor unbefugter Manipulation.
Status: belegt
Modul: M-15
ID: SyRS-221
Titel: Klassifizierter Zählerimport mit stornierbaren Importläufen
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Klick-Zählerverwaltung
Vorbedingung: Eine Importdatei bzw. ein API-Abruf (docuFORM, Riverbird, Excel, CSV) liegt vor.
Fakt: `ImportState` klassifiziert jede Importzeile in `Ok` ("übernommen"), `Complete` ("komplett vorbereitet"), `NoContract` ("Vertrag ist nicht zugeordnet"), `NoDevice` ("Seriennummer keinem Stammblatt zugeordnet"), `NoBarcode` ("Seriennummer ist nicht vorhanden"), `NoReference` ("kein c-enton Zähler"), `NoArticle` ("Artikel ist nicht vorhanden"), `NoCounter` ("kein Zähler im Stammblatt"), `DoubleCounter` ("mehrere gleiche Zähler im Stammblatt"), `Error`. `StoreCounterState` vergibt je Speichervorgang eine gemeinsame `ImportID` (I3D des ersten Historiensatzes). `DeactivateCounterImport` setzt für alle Sätze eines Importlaufs `Abgerechnet = 2` mit Begründungsvermerk, storniert nachfolgende manuelle Einträge desselben Zählers und setzt `GeraeteClickZaehler.StandAktuell` auf den höchsten verbleibenden Stand zurück. `GetCounterImports` bietet nur Importe der letzten 350 Tage mit `Abgerechnet = 0` an.
Aussage: Das System soll jede Zählerimportzeile mit einem eindeutigen Verarbeitungsstatus kennzeichnen, alle Zeilen eines Laufs unter einer gemeinsamen Import-ID zusammenfassen und einen noch nicht abgerechneten Importlauf vollständig zurücknehmbar machen.
Ergebnis: Fehlerhafte Importe sind je Zeile begründet; ein stornierter Lauf hinterlässt keine abrechnungswirksamen Zählerstände und der aktuelle Zählerstand ist zurückgesetzt.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaBL.Contracts.cs, `DeactivateCounterImport` (drei SQL-Statements: `Update GeraeteClickZaehlerHistory set Abgerechnet = 2 ... where ImportID = @ImportID and Abgerechnet = 0`, Nachlauf-Update für spätere manuelle Einträge, `Update gz set StandAktuell = c.maxStand`) - Begründung: Setzt die vollständige Rücknahme eines Importlaufs durch.
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaBL.Contracts.cs, `StoreCounterState` (Vergabe `importID = newState.I3D` und Zuweisung an alle Folgezeilen) - Begründung: Erzeugt die gemeinsame Klammer eines Importlaufs.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/DeviceClickCounter/ImportState.cs und ImportKind.cs - Begründung: Definieren die fachlichen Statuswerte und die unterstützten Importquellen.
Prüfidee: Excel-Import mit einer Zeile ohne Vertragszuordnung ausführen; die Zeile muss `NoContract` erhalten. Danach den Importlauf stornieren und prüfen, dass alle Historiensätze `Abgerechnet = 2` tragen und `StandAktuell` dem Vorwert entspricht.
Tracelinks: StRS-208
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Importfehler sind bei Massenzählern der Regelfall und müssen korrigierbar bleiben.
Status: belegt
Modul: M-16
ID: SyRS-222
Titel: Buchung des Vertragskontingents je erzeugter Rechnung
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertragsabrechnung (AutomaticFacturaBL)
Vorbedingung: `ContractToInvoiceParam.IsContingent == true`; der Vertrag ist Kontingentvertrag.
Fakt: `StoreInvoiceToContract` verzweigt: `if (billingParam.IsContingent) StoreBookedContingent(vertragZuordnung, billingParam); else Session.GetGenericDAO<VertragRechKopfZuordnung>().Save(vertragZuordnung);`. `StoreBookedContingent` übernimmt aus `ContractContingentInfo` die Werte `Overbooking`, `RestTake`, `Kind` und berechnet `KontingentWert = contractContingent.Value * billingParam.InvoiceIntervalCount`. Bei Berechnungsart `Manual` oder `Need` wird `NachBerechnung = 2` gesetzt und Buchungs- gleich Berechnungszeitraum. Die Sicht `ContractContingentBooked` blendet Sätze mit `KontingentWert = 0`, `Zwischenrechnung >= 4` oder `Status <= 0` aus und berechnet `RestValue` über `cfn_RestValue(VZ.VertragI3D, VZ.GebuchtVon)`, sofern `KontingentRestMitnehmen <> 0` und `Zwischenrechnung not in (1,4)`.
Aussage: Das System soll bei jeder Vertragsrechnung eines Kontingentvertrags den periodengerechten Kontingentwert mit Buchungszeitraum, Überbuchungs- und Restmitnahmekennzeichen persistieren und daraus den Restwert ableitbar machen.
Ergebnis: Je Rechnung existiert ein `VertragRechKopfZuordnung`-Satz mit `KontingentWert`, `GebuchtVon`/`GebuchtBis`, `KontingentArt`, `KontingentUeberbuchung` und `KontingentRestMitnehmen`.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaBL.Contracts.cs, `StoreBookedContingent` (`vertragZuordnung.KontingentWert = 1.0 * contractContingent.Value * billingParam.InvoiceIntervalCount;` sowie `Session.GetGenericDAO<VertragRechKopfZuordnung>().Save(vertragZuordnung)`) - Begründung: Durchsetzende Stelle der Kontingentbuchung.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE View [dbo].[ContractContingentBooked]` (`WHERE VK.KontingentVertrag = 1 AND VZ.KontingentWert <> 0 AND ISNULL(VZ.Zwischenrechnung,0) < 4 AND VZ.Status > 0`, `CASE WHEN IsNull(VZ.KontingentRestMitnehmen,0) = 0 OR VZ.Zwischenrechnung in (1,4) THEN 0 ELSE [dbo].[cfn_RestValue](...) END AS RestValue`) - Begründung: Datenbankseitige Durchsetzung der Restwertregel.
- [SEKUNDÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[VertragRechKopfZuordnung]` (Spalten `KontingentWert`, `KontingentUeberbuchung`, `KontingentRestMitnehmen`, `GebuchtVon`, `GebuchtBis`, `NachBerechnung`) - Begründung: Zeigt das Persistenzformat der Buchung.
Prüfidee: Kontingentvertrag mit Wert 10 und `InvoiceIntervalCount = 3` abrechnen; der erzeugte Zuordnungssatz muss `KontingentWert = 30` und einen Buchungszeitraum über drei Intervalle tragen.
Tracelinks: StRS-209
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - ohne diese Buchung ist der Kontingentverbrauch nicht bewertbar.
Status: belegt
Modul: M-17
ID: SyRS-223
Titel: MSP-Differenzen als Sonderartikel zum Vertrag nachberechnen
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: MSP-Auswertung
Vorbedingung: Eine MSP-Auswertungszeile ist mit einer Entscheidung ungleich `IgnoreImport` versehen und trägt eine `ContractI3D`.
Fakt: `AutomaticFacturaBL.CreateSpecialArticleToContractFromMspEvaluation` prüft mit `Guard.NotNull(compensationItemDto.MspItem.ContractI3D, ...)` und `Guard.NotNull(compensationItemDto.SpecialArticle.Positions, ...)`, ersetzt je Position den Beschreibungstext über `MspEvaluationReplacementBL().ReplaceVariablesText(positionSettingsText, compensationItemDto)` mit `PositionVariableText` aus `MspCollectorsBL.GetMspEvaluationSettings()` und ruft `CreateSpecialArticleToContract`. Dort werden Vertragsnummer und Kundennummer gegengeprüft ("Die Vertragsnummer konnte nicht gefunden werden", "Die Kundennummer passt nicht zur Vertragsnummer") und Artikel über `ArticleCode` bzw. `ManufacturerCode` aufgelöst ("Der Artikelcode konnte nicht gefunden werden"). Der Vorgang läuft in `Session.WithTransaction`.
Aussage: Das System soll aus einer bestätigten MSP-Abweichung eine Sonderartikelposition mit variablenbasiertem Beschreibungstext zum betroffenen Vertrag erzeugen und den Vorgang bei nicht auflösbarem Vertrag, Kunde oder Artikel vollständig zurückweisen.
Ergebnis: Bei erfolgreicher Prüfung existiert ein `SpecialArticleToContractHead` mit Positionen und `BillingDateValidFrom`; bei Fehlern wird ein Sammelfehlertext geliefert und nichts gespeichert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaBL.cs, `CreateSpecialArticleToContract(SpecialArticleToContractHeadImport, AppUser, bool)` (Prüfungen `contractI3DAndCustomerI3DTuples.Any() == false` und `headImport.CustomerNumber.HasValue && ...Any(x => x.Item2 == headImport.CustomerNumber) == false`) - Begründung: Konkrete durchsetzende Validierung vor der Vertragszuordnung.
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaBL.cs, `CreateSpecialArticleToContract(IList<...>, AppUser)` mit `return Session.WithTransaction(...)` und Sammelfehlerausgabe - Begründung: Transaktionsklammer verhindert Teilbuchungen.
- [SEKUNDÄR] src/backend/Centron.BL/Statistics/MspCollectors/MspCollectorsBL.cs, `GetMspEvaluationSettings` (`ApplicationSettingID.MspEvaluationCompensationArticlePositionText`) - Begründung: Belegt den konfigurierbaren Positionstext der Kompensationsposition.
Prüfidee: MSP-Zeile mit nicht existierender Vertragsnummer bestätigen; es darf kein `SpecialArticleToContractHead` entstehen und die Meldung muss "Die Vertragsnummer konnte nicht gefunden werden" enthalten.
Tracelinks: StRS-210
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - schließt Margenverluste aus nicht weiterberechneten Lizenzmengen.
Status: belegt
Modul: M-18
ID: SyRS-224
Titel: Serverseitige Autorisierung der Vertragsabrechnung
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal: Sicherheit
Akteur: Webservice-Endpunkt / Vertragsabrechnung
Vorbedingung: Ein gültiges Ticket liegt vor; die Rechnungserstellung wird über den REST-Endpunkt angestoßen.
Fakt: Im WPF-Client wird der Modulzugang über `ModuleRegistrationItem.For<AutomatedBillingAppModuleController>(() => Helper.HasRights(UserRightsConst.Sales.ID, UserRightsConst.Sales.AUTOMATED_BILLING), () => LicenseManager.Instance.HasLicense(LicenseGuids.ContractBilling) || LicenseManager.Instance.HasLicense(LicenseGuids.Centron))` geprüft, und der KI-Werkzeugpfad prüft explizit `if (!HasCurrentUserRight(UserRightsConst.Sales.AUTOMATED_BILLING)) return Error(toolCall, "Fehlende Berechtigung: Vertragsabrechnung darf nicht gestartet werden.")`. Der Serverendpunkt `CentronRestService.CreateAutomatedBillingInvoiceToContractComplete` ruft dagegen `AutomaticFacturaWebServiceBL.CreateInvoiceToContractComplete(this.GetLoggedInUserByTicket(request.Ticket), ...)` ohne erkennbare Rechteprüfung auf; `AutomaticFacturaWebServiceBL` enthält keinen Treffer für `HasUserRight`/`AppRightsBL`. `AutomatedBillingAppModuleController.GetRights()` liefert `null`.
Aussage: [HYPOTHESE] Das System soll die Berechtigung `UserRightsConst.Sales.AUTOMATED_BILLING` nicht nur im Client, sondern auch serverseitig beim Erzeugen von Vertragsrechnungen erzwingen.
Ergebnis: Ein Aufruf des Abrechnungsendpunkts ohne das Recht wird mit einem Rechtefehler abgewiesen, unabhängig vom verwendeten Client.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Finances/AutomatedBilling/AutomatedBillingView.ArtificialIntelligence.cs Zeile 267, `if (!HasCurrentUserRight(UserRightsConst.Sales.AUTOMATED_BILLING)) return Error(toolCall, "Fehlende Berechtigung: Vertragsabrechnung darf nicht gestartet werden.");` - Begründung: Belegt, dass die Regel fachlich gewollt ist und an mindestens einer Stelle durchgesetzt wird.
- [SEKUNDÄR] src/webservice/Centron.Host/Services/CentronRestServiceParts/CentronRestService.Receipts.cs Zeile 1799 (`CreateAutomatedBillingInvoiceToContractComplete` ohne Rechteprüfung) - Begründung: Zeigt die Lücke, auf die sich die Hypothese bezieht.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/AutomatedBilling/AutomatedBillingAppModuleController.cs, `GetRights() => null` - Begründung: Die Modulschnittstelle meldet keine Rechte, die Durchsetzung liegt allein in der Registrierung.
Prüfidee: REST-Aufruf `CreateAutomatedBillingInvoiceToContractComplete` mit einem Ticket eines Benutzers ohne Recht 10385 absetzen; erwartet wird eine Ablehnung, nicht die Erzeugung einer Rechnung.
Tracelinks: StRS-205
Konsolidierung: nein
Übernahmewürdigkeit: Workaround - Rechtedurchsetzung ist derzeit clientseitig gelöst; zur Bestätigung fehlt der Nachweis einer serverseitigen Prüfung im Request-Pipeline-/Ticket-Handling.
Status: HYPOTHESE
Modul: M-13
ID: SyRS-225
Titel: Unparametrisierte Filterfragmente in der Zählerstandssuche
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal: Sicherheit
Akteur: Klick-Zählerverwaltung / Datenzugriffsschicht
Vorbedingung: Der Anwender filtert die Zählerstandsliste nach Kundenname oder Barcode.
Fakt: `AutomaticFacturaBL.GetCounterWhere(SearchCounterStateFilter filter)` baut das WHERE-Fragment durch Stringverkettung, u. a. `result += " AND k.Name LIKE '%" + filter.CustomerName.Trim() + "%'";` und `result += " AND b.Barcode LIKE '" + filter.Barcode.Trim() + "%'";`. Das Ergebnis wird in `GetInputCounterState` als `new NamedQueryParameter("sWhere", GetCounterWhere(filter), NHibernateUtil.String, false, true)` – also als SQL-Fragment, nicht als Wert – an die NamedQuery `NamedQueryEnums.Asset.InputCounterState` übergeben. Dasselbe Muster nutzt `GetCounterHistory`, das die übergebenen Filterstrings unverändert konkateniert.
Aussage: Das System soll Filterwerte der Zählerstandssuche ausschließlich als gebundene Abfrageparameter übergeben und keine vom Anwender eingegebenen Zeichenketten in SQL-Fragmente einsetzen.
Ergebnis: Eingaben wie `' OR 1=1 --` im Kunden- oder Barcodefilter verändern die Ergebnismenge nicht und lösen keinen SQL-Fehler aus.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaBL.Contracts.cs, `GetCounterWhere` Zeilen 721-726 (direkte Verkettung von `filter.CustomerName` und `filter.Barcode` in den SQL-Text) - Begründung: Benennt Datei, Methode und die konkrete Stelle, an der die Trennung von Code und Daten aufgehoben wird.
- [PRIMÄR] src/backend/Centron.BL/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaBL.Contracts.cs, `GetInputCounterState` (`new NamedQueryParameter("sWhere", GetCounterWhere(filter), NHibernateUtil.String, false, true)`) - Begründung: Das letzte `true` kennzeichnet die Übergabe als SQL-Teilausdruck und ist damit die durchsetzende (bzw. hier fehlende) Stelle der Parameterbindung.
- [SEKUNDÄR] Gegenbeispiel im selben Modul: `DeactivateCounterImport` verwendet `command.AddParameter("Grund", grund, DbType.String)` - Begründung: Belegt, dass parametrisierte Bindung im Codebestand verfügbar und üblich ist.
Prüfidee: Im Kundennamensfilter der Zählerstandssuche `x%' OR '1'='1` eingeben; die Trefferliste darf sich nicht auf alle Kunden ausweiten.
Tracelinks: StRS-208
Konsolidierung: nein
Übernahmewürdigkeit: Workaround - funktionsfähige, aber sicherheitskritische Filterumsetzung; bei Übernahme durch Parameterbindung zu ersetzen.
Status: belegt
Modul: M-16
@@ -0,0 +1,179 @@
ID: StRS-301
Titel: Gestufte Mahnung offener Kundenrechnungen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Debitorenbuchhalter / Finanzsachbearbeiter
Vorbedingung: Es existieren aktive Kundenrechnungen, deren Fälligkeitsdatum überschritten ist.
Fakt: Das WPF-Modul "Mahnung" (DunningOverviewAppModuleController) führt über die Seiten DunningCustomerSelectionView, DunningReceiptSelectionView, DunningRunPreviewView und DunningRunResultsView einen kundenbezogenen Mahnlauf durch; die fachliche Verarbeitung liegt in DunningRunBL.ExecuteDunningRun.
Aussage: Das System soll dem Debitorenbuchhalter ermöglichen, überfällige Rechnungen je Kunde auszuwählen und in einem protokollierten Mahnlauf mit steigender Mahnstufe anzumahnen.
Ergebnis: Für die ausgewählten Rechnungen ist die Mahnstufe erhöht, ein Mahnlauf-Protokolleintrag erzeugt und ein Mahnschreiben (PDF) als Druck- oder E-Mail-Ausgabe bereitgestellt.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/Invoices/Dunning/DunningRunBL.cs, Methode ExecuteDunningRunInternal (Zeilen 200-246): ruft UpdateInvoice je Rechnung, SaveDunningRun, GenerateReport und optional GenerateMail auf - Begründung: Die Methode implementiert den vollständigen Geschäftsvorfall "Mahnlauf" und belegt Umfang und Ergebnis.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/Dunning/DunningOverviewAppModuleController.cs sowie ModuleRegistration.cs Kommentar "// Mahnung" - Begründung: Belegt, dass Mahnwesen als eigenständiges Anwendermodul angeboten wird.
Prüfidee: Für einen Kunden mit einer überfälligen, nicht gemahnten Rechnung einen Mahnlauf ausführen; danach muss die Rechnung Mahnstufe 1 tragen, ein Mahnlauf-Eintrag existieren und ein PDF erzeugt worden sein.
Tracelinks: SyRS-310, SyRS-311, SyRS-312, SyRS-313
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Kernprozess des Forderungsmanagements, fachlich unverändert gültig.
Status: belegt
Modul: M-19
ID: StRS-302
Titel: Versand von Offene-Posten-Kontoauszügen an Kunden
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Debitorenbuchhalter
Vorbedingung: Für einen Kunden existieren offene Rechnungen und/oder Gutschriften.
Fakt: Das Modul "OPOS" (OposOverviewAppModuleController, ModuleName "OPOS", Description "OPOS Übersicht") erzeugt über OposRunBL.ExecuteOposRun eine PDF-Datei mit dem Dateinamen-Präfix "Kontoauszug_" und versendet sie per Mail oder Druck.
Aussage: Das System soll dem Debitorenbuchhalter ermöglichen, einem Kunden einen Kontoauszug über alle offenen Posten (Rechnungen und Gutschriften) ohne Mahnwirkung zuzustellen.
Ergebnis: Ein Kontoauszug-PDF ist erzeugt und als E-Mail-Anhang oder Druckausgabe bereitgestellt; die Belegzustände des Kunden bleiben unverändert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/Invoices/Opos/OposRunBL.cs, Methode ExecuteOposRun, Zeile 67: `var reportFileName = $"Kontoauszug_{DateTime.Today.ToString("dd_MM_yyyy")}.pdf";` - Begründung: Benennt das fachliche Ergebnisdokument und zeigt, dass keine Bestands-/Statusänderung stattfindet.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/Opos/OposOverviewAppModuleController.cs, ModuleName "OPOS" - Begründung: Belegt OPOS als eigenständiges Anwendermodul.
Prüfidee: OPOS-Lauf für einen Kunden mit zwei offenen Rechnungen ausführen; das PDF muss beide Rechnungen enthalten, und die Mahnstufen der Rechnungen dürfen unverändert bleiben.
Tracelinks: SyRS-314
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - eigenständiger, vom Mahnwesen abgegrenzter Kundenservice-Prozess.
Status: belegt
Modul: M-20
ID: StRS-303
Titel: Manuelle Erfassung von Zahlungseingängen zu Rechnungen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Debitorenbuchhalter
Vorbedingung: Eine Ausgangsrechnung ist erfasst und noch nicht vollständig bezahlt.
Fakt: Das Modul "Zahlungseingang" (PaymentsAppModuleController, Description "Zahlungseingänge verwalten") erlaubt in IncomingPaymentsViewModel.SaveReceipt die Erfassung eines Teil- oder Vollbetrags inklusive Kontoauszugsnummer, Kontoauszugsdatum, Zahlungsdatum, Bankverbindung (IBAN) und Kommentar.
Aussage: Das System soll dem Debitorenbuchhalter ermöglichen, Zahlungseingänge manuell einer Rechnung zuzuordnen, den bezahlten Betrag fortzuschreiben und die Rechnung wahlweise zu schließen.
Ergebnis: Der bezahlte Betrag der Rechnung ist erhöht, optional der Bezahlt-Status gesetzt, und ein Zahlungseingangs-Datensatz mit Restbetrag und Erfasser ist gespeichert.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Finances/Payments/IncomingPayments/IncomingPaymentsViewModel.cs, Methoden SaveReceipt (Zeilen 455-481) und SaveIncomingPaymentAsync (Zeilen 483-507) - Begründung: Zeigen den vollständigen fachlichen Ablauf inkl. Feldbelegung des Zahlungseingangs.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/Payments/PaymentsAppModuleController.cs, ModuleName "Zahlungseingang" - Begründung: Belegt die fachliche Modulbenennung.
Prüfidee: Auf eine Rechnung über 1.000 EUR einen Zahlungseingang von 400 EUR buchen; danach muss PaidPrice 400 betragen, ResidualAmount 600 sein und die Rechnung offen bleiben.
Tracelinks: SyRS-315, SyRS-316
Konsolidierung: Kandidat: StRS-307 - Zahlungseingang wird zusätzlich vollautomatisch aus Kontoauszügen gebucht (siehe Konsolidierungstabelle).
Übernahmewürdigkeit: übernehmen - manuelle Erfassung bleibt als Rückfallebene zur Kontoauszugsautomatik erforderlich.
Status: belegt
Modul: M-21
ID: StRS-304
Titel: Erfassung von Kosten- und Lieferantenbelegen (Ausgangszahlungen)
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Einkaufssachbearbeiter / Buchhalter
Vorbedingung: Ein Lieferanten- oder Kostenbeleg liegt vor.
Fakt: Der Modulordner Modules/Warehousing/OutcomingPayments enthält OutgoingPaymentsAppModuleController mit ModuleName "Belegerfassung" und Description "Erfassung von Kosten und Belegen"; die Auswertung erfolgt über SupplierInvoicesBL.GetOutgoingPaymentReceiptItemsThroughPaging und GetOutgoingPaymentsHistoryThroughPaging.
Aussage: Das System soll die Erfassung ausgabenseitiger Belege (Kosten, Lieferantenrechnungen) mit Zuordnung zu Buchhaltungskonto und Kostenstelle sowie deren Historienauswertung ermöglichen.
Ergebnis: Ein Ausgabenbeleg ist mit Aufwandskonto, Lieferant und Bearbeiter erfasst und über die Ausgangszahlungs-Historie auswertbar.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/SupplierInvoices/SupplierInvoicesBL.cs, Methode GetOutgoingPaymentReceiptItemsThroughPaging (ab Zeile 38) mit Pflichtfiltern SupplierI3Ds, EmployeeI3Ds, ExpenseAccounts - Begründung: Belegt die fachlichen Dimensionen (Lieferant, Bearbeiter, Aufwandskonto) der Ausgangszahlungserfassung.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Warehousing/OutcomingPayments/Controller/OutgoingPaymentsAppModuleController.cs, ModuleName "Belegerfassung" - Begründung: Zeigt die tatsächliche Anwenderbenennung, die von der Ordnerbezeichnung "OutcomingPayments" abweicht.
Prüfidee: Einen Kostenbeleg mit Aufwandskonto und Lieferant erfassen; er muss anschließend in der Ausgangszahlungs-Historie mit demselben Aufwandskonto erscheinen.
Tracelinks: SyRS-317
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - fachlich benötigter Erfassungsprozess; Modulbenennung sollte auf "Belegerfassung" vereinheitlicht werden.
Status: belegt
Modul: M-22
ID: StRS-305
Titel: Verwaltung von Buchhaltungskontenrahmen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Administrator / Buchhaltungsverantwortlicher
Vorbedingung: Die Mandantenbuchhaltung erfordert einen definierten Kontenrahmen (z. B. SKR03/SKR04).
Fakt: BookKeepingAccountSystemsBL verwaltet Kontensysteme (BookKeepingAccountSystem mit Name, IsActive, IsDefault) und die darin enthaltenen Buchhaltungskonten (BookKeepingAccount mit Number, Caption, Description, Umsatzsteuerschlüsseln).
Aussage: Das System soll mehrere Buchhaltungskontenrahmen mit ihren Einzelkonten verwalten und einen davon als Standard für die Belegkontierung bereitstellen.
Ergebnis: Aktive Kontensysteme mit ihren Konten stehen zur Kontierung von Belegen und Artikeln zur Verfügung.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/BookKeepingAccountSystems/BookKeepingAccountSystemBL.cs, Klasse BookKeepingAccountSystemsBL, Methoden GetBookKeepingAccountSystems und GetBookKeepingAccounts(bool fromDefaultSystem) - Begründung: Implementiert die Verwaltung und die Auswahl über Aktiv-/Standard-Kennzeichen.
- [SEKUNDÄR] SSMS_DB_SCHEMA.sql, Zeilen 31975-32004: Tabellen [dbo].[BookKeepingAccounts] und [dbo].[BookKeepingAccountSystems] - Begründung: Belegt die persistente Datenhaltung des Kontenrahmens.
Prüfidee: Zwei Kontensysteme anlegen, eines als Standard markieren; GetBookKeepingAccounts(true) darf nur Konten des Standardsystems liefern.
Tracelinks: SyRS-318
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Grundlage jeder Buchhaltungsschnittstelle.
Status: belegt
Modul: M-23
ID: StRS-306
Titel: Erzeugung von SEPA-Lastschriftdateien für fällige Rechnungen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Debitorenbuchhalter
Vorbedingung: Es existieren fällige Rechnungen mit Zahlungskondition "Lastschrift" und hinterlegtem SEPA-Mandat.
Fakt: Das Modul "SEPA"/Zahlungsverkehr (PaymentTransactionAppModuleController) erzeugt über PaymentTransactionBL.ExportInvoices und PaymentTransactionSepaInterface.CreateSepaFile eine XML-Lastschriftdatei, die in PaymentTransactionViewModel.Export als Datei "SEPA_Transactions_yyyy-MM-dd HH_mm.xml" abgelegt wird.
Aussage: Das System soll aus ausgewählten fälligen Rechnungen eine bankfähige SEPA-Lastschriftdatei erzeugen und im konfigurierten Exportverzeichnis ablegen.
Ergebnis: Eine UTF-8-kodierte XML-Datei im gewählten pain.008-Format liegt im Exportverzeichnis; die enthaltenen Rechnungen sind als exportiert gekennzeichnet.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/DataExchange/PaymentTransactions/PaymentTransactionViewModel.cs, Methode Export, Zeile 340: `File.WriteAllText(fileName, xmlString.XMLString, new UTF8Encoding(false));` und GetValidFilename (Zeilen 369-383) - Begründung: Belegt das fachliche Endergebnis (Datei) inkl. Kodierung und Namenskonvention.
- [SEKUNDÄR] src/backend/Centron.BL/DataExchange/PaymentTransactions/PaymentTransactionBL.cs, GetInterfaceList mit den UI-Bezeichnungen "SEPA (PAIN:008.001.01 STUZZA)" bis "SEPA V3.7 (PAIN:008.001.08 GBIC 4" - Begründung: Belegt die dem Anwender angebotenen Formatvarianten.
Prüfidee: Zwei lastschriftfähige Rechnungen exportieren; die erzeugte XML-Datei muss NbOfTxs=2 und eine CtrlSum gleich der Summe der Rechnungsbeträge enthalten.
Tracelinks: SyRS-319, SyRS-320, SyRS-321
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - regulatorisch erforderlicher Kernprozess des Zahlungsverkehrs.
Status: belegt
Modul: M-24
ID: StRS-307
Titel: Abruf von Kontoauszügen und automatische Zahlungszuordnung
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Debitorenbuchhalter
Vorbedingung: Für mindestens ein Firmenbankkonto ist eine Online-Banking-Konfiguration (finAPI, FinTS oder Tabellendatei) hinterlegt.
Fakt: Das Modul "Kontoauszüge (finAPI)" (OnlineBankingAccountTransactionsController, Description "Das Modul listet Transaktionen für Bankkonten auf und erlaubt eine automatisierte oder manuelle Zuweisung von Rechnungen") ruft Umsätze ab und ordnet sie über OnlineBankingAccountTransactionsBL.AutoCompleteAccountTransacitons Kunden und Rechnungen zu.
Aussage: Das System soll Bankumsätze je Konfiguration abrufen, sie automatisch Kunden und offenen Rechnungen zuordnen und die Zuordnungen nach Bestätigung als Zahlungseingang verbuchen.
Ergebnis: Kontoumsätze sind gespeichert, mit Zuordnungsvorschlägen samt Trefferheuristik versehen und nach Verbuchung als bezahlt gekennzeichnete Rechnungen mit Belegprotokoll hinterlegt.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Finances/OnlineBanking/OnlineBankingAccountTransactionsBL.cs, Methode AutoCompleteSingleAccountTransaciton (Zeilen 581-617) mit den kommentierten Suchstufen "Search 1/2/3" - Begründung: Beschreibt den fachlichen Kern des automatisierten Zahlungsabgleichs.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/OnlineBanking/AccountTransactions/OnlineBankingAccountTransactionsController.cs, ModuleName "Kontoauszüge (finAPI)" - Begründung: Belegt die fachliche Modulbenennung und Zweckbeschreibung.
Prüfidee: Einen Umsatz mit exakt der offenen Rechnungssumme und der Rechnungsnummer im Verwendungszweck importieren; das System muss Kunde und Rechnung ohne Benutzereingriff korrekt vorschlagen.
Tracelinks: SyRS-322, SyRS-323, SyRS-324, SyRS-325
Konsolidierung: Kandidat: StRS-303 - zweiter Weg, denselben Zahlungseingang zu verbuchen.
Übernahmewürdigkeit: übernehmen - senkt den manuellen Aufwand im Debitorenmanagement erheblich.
Status: belegt
Modul: M-25
ID: StRS-308
Titel: Digitale Einholung von SEPA-Lastschriftmandaten
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertrieb / Kundenbetreuer, Kunde als Unterzeichner
Vorbedingung: Für den Kunden liegt eine Mandatsvorlage (SepaContractTemplate) vor.
Fakt: SepaContract-Objekte werden aus Vorlagen erzeugt, per Mail an einen Ansprechpartner versendet und über SepaContractOnlinePdfDocumentHandler.Confirm/Decline/SetExpired online bestätigt, abgelehnt oder als abgelaufen markiert; Zustände sind Created, Sent, Accepted, Declined, LinkExpired.
Aussage: Das System soll SEPA-Lastschriftmandate aus zentralen Vorlagen erzeugen, dem Kunden zur Online-Unterzeichnung zustellen und das unterschriebene Mandat revisionsfähig beim Kunden ablegen.
Ergebnis: Das unterzeichnete Mandat liegt als PDF im Kundendokumentenverzeichnis, der Mandatsstatus ist "Accepted", und die zugehörige Bankverbindung ist autorisiert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Documents/Dsgvo/SepaContractOnlinePdfDocumentHandler.cs, Methode Confirm (Zeilen 78-178): erzeugt PDF, versendet Bestätigungsmails, legt das Dokument über DocumentBL.AddFileToDirectory ab und setzt `contract.State = SepaContractState.Accepted;` - Begründung: Vollständiger Nachweis des fachlichen Ablaufs bis zur Ablage.
- [SEKUNDÄR] src/backend/Centron.Interfaces/Administration/Documents/SepaContracts/SepaContractState.cs mit Created/Sent/Accepted/Declined/LinkExpired - Begründung: Belegt den fachlichen Lebenszyklus des Mandats.
Prüfidee: Ein Testmandat versenden und online bestätigen; danach muss der Status Accepted sein und ein PDF im SEPA-Verzeichnis des Kunden liegen.
Tracelinks: SyRS-326
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Voraussetzung für rechtssichere Lastschrifteinzüge (StRS-306).
Status: belegt
Modul: M-26
ID: StRS-309
Titel: Pflege von Kostenstellen und Kostenträgern als Stammdaten
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Stammdatenverantwortlicher / Controller
Vorbedingung: Der Mandant nutzt eine Kostenrechnung.
Fakt: Das Modul "Kostenträger / Kostenstellen" (PayersAndCostCenterAppModuleController, MainCategory BaseData, Description "Erstellung und Verwaltung von Kostenträger / Kostenstellen") pflegt die Entitäten CostCenter (Tabelle Kostenstellen) und CostObject (Tabelle Kostentraeger) mit Nummer und Beschreibung.
Aussage: Das System soll Kostenstellen und Kostenträger als eigenständige Stammdaten mit Nummer und Beschreibung anlegen, ändern, deaktivieren und wiederherstellen lassen.
Ergebnis: Gepflegte Kostenstellen und Kostenträger stehen zur Kontierung von Belegen und Positionen zur Verfügung.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/PayersAndCostCenter/PayersAndCostCenterAppModuleControllerViewModel.cs, Methoden DoCostCenterSaveOrUpdate, DoCostCenterDelete und CostCenterRestoreAsync - Begründung: Belegen den vollständigen Pflegeumfang inkl. Wiederherstellung.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, Zeile 849: `() => Helper.HasRights(UserRightsConst.Masterdata.PAYERS_AND_COST_CENTER)` - Begründung: Belegt die Einordnung als berechtigungsgeschützte Stammdatenpflege.
Prüfidee: Eine Kostenstelle anlegen, löschen und wiederherstellen; sie muss danach wieder in der Standardliste (ohne "gelöschte anzeigen") erscheinen.
Tracelinks: SyRS-327
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Stammdatenbasis für Kostenrechnung und Belegerfassung (StRS-304).
Status: belegt
Modul: M-27
@@ -0,0 +1,364 @@
ID: SyRS-310
Titel: Mahnstufeneskalation und Sperre nach Stufe 3
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Backend-Komponente DunningRunBL / DunningRunWebServiceBL
Vorbedingung: Ein Mahnlauf mit mindestens einer Rechnung wurde angestoßen.
Fakt: DunningRunBL.UpdateInvoice erhöht die Mahnstufe genau um eine Stufe (None→Level1, Level1→Level2, Level2→Level3) und setzt je Stufe Datum und Bearbeiter; DunningRunWebServiceBL.ValidateDunningRun wirft eine ArgumentException für Rechnungen ohne Mahnstufe (noch nicht fällig) und für Rechnungen, die bereits DunningLevel.Level3 haben.
Aussage: Das System soll je Mahnlauf die Mahnstufe einer Rechnung um genau eine Stufe erhöhen, dabei Mahndatum und mahnenden Bearbeiter festhalten und Rechnungen ohne erreichte Fälligkeit sowie Rechnungen in Mahnstufe 3 vom Mahnlauf ausschließen.
Ergebnis: Rechnungen tragen nach dem Lauf die nächsthöhere Mahnstufe mit Zeitstempel und Bearbeiter; ein Lauf mit unzulässigen Rechnungen wird vollständig abgewiesen.
Belege:
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/Receipts/DunningRunWebServiceBL.cs, Methode ValidateDunningRun, Zeilen 129-135: `var invoicesPreDueDate = dunningRun.Invoices.Where(f => f.DunningLevel.HasValue == false); if (...) throw new ArgumentException(...)` und `var invoicesInDunningLevel3 = dunningRun.Invoices.Where(f => f.DunningLevel == DunningLevel.Level3); if (...) throw ...` - Begründung: Benennt die durchsetzende Prüfung, die die Obergrenze der Eskalation erzwingt.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/Invoices/Dunning/DunningRunBL.cs, Methode UpdateInvoice, Zeilen 248-275 (switch über invoice.DunningLevel mit `default: throw new ArgumentOutOfRangeException();`) - Begründung: Erzwingt die Einzelschritt-Eskalation und schließt undefinierte Zustände aus.
Prüfidee: Mahnlauf für eine Rechnung in Mahnstufe 3 anstoßen; der Aufruf muss mit ArgumentException abgewiesen werden und die Mahnstufe unverändert bleiben.
Tracelinks: StRS-301
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - definiert die zwingende Eskalationslogik des Mahnwesens.
Status: belegt
Modul: M-19
ID: SyRS-311
Titel: Vollständige Rücknahme eines Mahnlaufs
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Backend-Komponente DunningRunBL
Vorbedingung: Ein Mahnlauf mit einer Mahnlaufnummer existiert und wurde noch nicht zurückgenommen.
Fakt: DunningRunBL.ResetDunningRun lädt alle DunningRunItem einer Mahnlaufnummer, setzt DeletedByEmployeeI3D, DeletedDate und State = DunningRunState.Deleted, senkt die Mahnstufe der zugehörigen Rechnung um genau eine Stufe und löscht das jeweilige Mahnstufendatum und den Bearbeiter; der gesamte Vorgang läuft in einer Transaktion mit Rollback im Fehlerfall.
Aussage: Das System soll einen Mahnlauf anhand seiner Mahnlaufnummer vollständig und transaktional zurücknehmen, indem alle Laufpositionen als gelöscht markiert und die Mahnstufen der betroffenen Rechnungen um eine Stufe zurückgesetzt werden.
Ergebnis: Alle Positionen des Laufs sind mit Status "Deleted", Löschzeitpunkt und Löschendem markiert; die Rechnungen stehen wieder auf der vorherigen Mahnstufe.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/Invoices/Dunning/DunningRunBL.cs, Methode ResetDunningRun, Zeilen 495-554: `currentItem.State = DunningRunState.Deleted;` sowie der switch, der Level3→Level2, Level2→Level1, Level1→None zurücksetzt, eingebettet in StartTransaction/CommitTransaction/RollbackTransaction - Begründung: Benennt die durchsetzende Stelle inklusive Transaktionsklammer.
- [SEKUNDÄR] src/backend/Centron.Interfaces/Sales/Receipts/Invoices/Dunning/DunningRunState.cs (Deleted, Active) - Begründung: Belegt die Zustandsmenge der Mahnlaufpositionen.
Prüfidee: Mahnlauf ausführen, danach zurücknehmen; die Rechnung muss die ursprüngliche Mahnstufe tragen und der Mahnlaufeintrag den Status "Deleted" mit Löschdatum aufweisen.
Tracelinks: StRS-301
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - notwendige Korrekturfunktion mit revisionssicherer Historie (kein physisches Löschen).
Status: belegt
Modul: M-19
ID: SyRS-312
Titel: Berechtigungsprüfung für Mahnwesen und OPOS
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Backend-Komponenten DunningBL / OposBL
Vorbedingung: Ein angemeldeter Benutzer ruft eine Mahn- oder OPOS-Funktion auf.
Fakt: DunningBL.ThrowIfUserHasInsufficentRights ruft AppRightsBL.CheckRightsFromUser mit UserRightsConst.Controlling.Finances.Dunning (Wert 10971) und wirft eine Exception, wenn das Recht nicht im Ergebnis enthalten ist; die Prüfung wird in GetDunningCustomers, CalculateDunningStatistics, UpdateDunningSettingsForCustomer, DunningRunBL.GetDunningRuns, ExecuteDunningRunInternal und ValidateDunningRun aufgerufen. Zusätzlich ist die Modulanzeige in ModuleRegistration.cs an dasselbe Recht gebunden.
Aussage: Das System soll jeden lesenden und schreibenden Zugriff auf Mahn- und OPOS-Daten serverseitig gegen das Benutzerrecht 10971 ("Mahnwesen") prüfen und bei fehlendem Recht mit einem Fehler abbrechen.
Ergebnis: Benutzer ohne Recht 10971 erhalten weder Mahn-/OPOS-Daten noch können sie Mahnläufe ausführen; das Modul wird ihnen nicht angeboten.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/Invoices/Dunning/DunningBL.cs, Methode ThrowIfUserHasInsufficentRights, Zeilen 1059-1067: `var result = new AppRightsBL(this.Session).CheckRightsFromUser(loggedInUser.UserI3D.Value, UserRightsConst.Controlling.Finances.Dunning); if (result.Contains(...) == false) throw new Exception(...)` - Begründung: Konkrete durchsetzende Prüfung mit benannter Bedingung.
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, Zeilen 607-613: `ModuleRegistrationItem.For<DunningOverviewAppModuleController>(() => Helper.HasRights(UserRightsConst.Controlling.Finances.Dunning), ...)` und identisch für OposOverviewAppModuleController - Begründung: Zweite durchsetzende Stelle auf Modulebene.
- [SEKUNDÄR] src/webservice/Centron.WebServices.Core/EntitiesWrongPlace/Administration/Rights/UserRightsConst.cs, Zeile 2556: `public const int Dunning = 10971;` - Begründung: Belegt die konkrete Rechtenummer.
Prüfidee: Benutzer ohne Recht 10971 ruft GetDunningCustomers auf; der Aufruf muss mit einer Exception scheitern, nicht mit einer leeren Liste antworten.
Tracelinks: StRS-301
Konsolidierung: Kandidat: SyRS-314 - OPOS nutzt dasselbe Recht, obwohl es ein anderer Geschäftsvorfall ist.
Übernahmewürdigkeit: Workaround - die Prüfung greift, verwendet für OPOS aber ein fachfremdes Recht; für die Neuentwicklung ist ein eigenes OPOS-Recht vorzusehen.
Status: belegt
Modul: M-19
ID: SyRS-313
Titel: Mahnstopp sperrt Belege und Kunden für den Mahnlauf
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Client-Komponenten DunningItemViewModel / DunningCustomerViewModel
Vorbedingung: Für einen Kunden oder eine Rechnung ist ein Mahnstopp (Kennzeichen und/oder Zeitraum) hinterlegt.
Fakt: DunningBL.GetDunningStopActive ermittelt den aktiven Mahnstopp aus DunningStopBegin, DunningStopEnd und dem Kennzeichen DunningStop über ein switch über (begin.HasValue, end.HasValue); DunningItemViewModel.EvaluateSelectionValidity und DunningCustomerViewModel.EvaluateValidity tragen bei aktivem Mahnstopp einen Sperrgrund in SelectionDisabledReasons bzw. InvalidForDunningReasons ein.
Aussage: Das System soll Rechnungen und Kunden mit aktivem Mahnstopp von der Auswahl für einen Mahnlauf ausschließen und den Sperrgrund einschließlich Mahnstoppzeitraum und Mahninfo anzeigen.
Ergebnis: Gesperrte Belege sind nicht auswählbar; der Anwender sieht den Text "Rechnung ist ab <Datum> bis <Datum> im Mahnstopp." bzw. die kundenbezogene Variante.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Finances/Dunning/Common/DunningItemViewModel.cs, Methode EvaluateSelectionValidity, Zeilen 455-467: `if (this.DunningStopActive) { ... this.SelectionDisabledReasons.Add(dunningStopReason); ... }` - Begründung: Durchsetzende Stelle, die die Auswahl des Belegs verhindert.
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/Invoices/Dunning/DunningBL.cs, Methode GetDunningStopActive(DateTime?, DateTime?, bool), Zeilen 170-182 - Begründung: Definiert die maßgebliche Bedingung, wann ein Mahnstopp als aktiv gilt.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/Dunning/Common/DunningStopHelper.cs, GetDunningStopActiveDisplayText - Begründung: Belegt den dem Anwender angezeigten Sperrtext.
Prüfidee: Rechnung mit Mahnstopp-Zeitraum, der das heutige Datum einschließt, in der Belegauswahl öffnen; sie darf nicht selektierbar sein und muss den Zeitraum als Sperrgrund anzeigen.
Tracelinks: StRS-301
Konsolidierung: nein
Übernahmewürdigkeit: Workaround - die Sperre ist ausschließlich clientseitig implementiert; in der Neuentwicklung ist sie zusätzlich serverseitig in ValidateDunningRun zu erzwingen.
Status: belegt
Modul: M-19
ID: SyRS-314
Titel: OPOS-Lauf verändert weder Mahnstufe noch Mahnhistorie
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Backend-Komponente OposRunBL
Vorbedingung: Ein OPOS-Lauf für einen Kunden wurde validiert und angestoßen.
Fakt: OposRunBL.ExecuteOposRun erzeugt ausschließlich Report und Mail und liefert `DunningRunNumber = 0` zurück; es existiert kein Aufruf von UpdateInvoice, SaveDunningRun oder SaveDunningRunItem. OposRunWebServiceBL.ValidateOposRun prüft im Gegensatz zu ValidateDunningRun weder Mahnstufe noch Fälligkeit.
Aussage: Das System soll beim OPOS-Lauf ausschließlich ein Kontoauszugsdokument erzeugen und dabei weder Mahnstufen fortschreiben noch Mahnlaufpositionen anlegen.
Ergebnis: Nach einem OPOS-Lauf sind Mahnstufen, Mahndaten und Mahnlaufhistorie der beteiligten Belege unverändert; das Ergebnisobjekt trägt die Mahnlaufnummer 0.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Receipts/Invoices/Opos/OposRunBL.cs, Methode ExecuteOposRun, Zeilen 82-89: Rückgabe `new DunningRunResultDTO { ReportFileName = ..., Report = ..., Mail = ..., DunningRunNumber = 0, ReceiptReports = receiptReports }` ohne jede Belegaktualisierung - Begründung: Belegt durch Abwesenheit jeder Schreiboperation die Wirkungsfreiheit des Laufs.
- [SEKUNDÄR] src/backend/Centron.BL/WebServices/Sales/Receipts/OposRunWebServiceBL.cs, ValidateOposRun (Zeilen 40-66) ohne Mahnstufen- und Fälligkeitsprüfung - Begründung: Bestätigt, dass OPOS bewusst ohne Mahnsemantik arbeitet.
Prüfidee: OPOS-Lauf für eine Rechnung in Mahnstufe 2 ausführen; Mahnstufe, Mahndatum und Anzahl der Mahnlaufeinträge müssen danach identisch sein.
Tracelinks: StRS-302
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - klare fachliche Abgrenzung zwischen Kontoauszug und Mahnung.
Status: belegt
Modul: M-20
ID: SyRS-315
Titel: Zahlungseingang schreibt Rechnungszahlbetrag und Bezahltstatus fort
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Modul Zahlungseingang (IncomingPaymentsViewModel) und ReceiptBL
Vorbedingung: Eine Rechnung ist ausgewählt und ein Zahlbetrag erfasst.
Fakt: IncomingPaymentsViewModel.SaveReceipt berechnet `newPaidFC = this._receipt.PaidFC + (this.SelectedReceipt.NewAmountPaid * this.SelectedReceipt.Receipt.CurrencyFactor)` und ruft UpdateReceiptIsPaid mit dem Modulkennzeichen "Zahlungseingang" und der ConcurrencyControlGuid der Rechnung auf; der Zahlungseingang wird erst nach erfolgreicher Belegaktualisierung persistiert.
Aussage: Das System soll bei einer Zahlungseingangsbuchung den kumulierten Zahlbetrag der Rechnung währungsbereinigt erhöhen, den Bezahltstatus nur bei explizitem Abschluss setzen und die Buchung optimistisch gegen Parallelbearbeitung sichern.
Ergebnis: Der Zahlbetrag der Rechnung ist um den erfassten Betrag multipliziert mit dem Währungsfaktor erhöht; bei Konflikt der ConcurrencyControlGuid wird die Buchung abgewiesen und kein Zahlungseingang gespeichert.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Finances/Payments/IncomingPayments/IncomingPaymentsViewModel.cs, Methode SaveReceipt, Zeilen 462-475: Berechnung von newPaidFC, `var newIsPaid = this.CloseReceipt ? true : (bool?)null;`, Abbruch bei `result.Status is ResultStatus.Error` vor `await this.SaveIncomingPaymentAsync();` - Begründung: Benennt Reihenfolge, Bedingung und Sperrmechanismus der Buchung.
- [SEKUNDÄR] Dieselbe Datei, Methode AddMissingOffsett (Zeilen 545-556): Skonto wird nur angewandt, wenn `this.SelectedReceipt.Receipt.PaidPrice == Decimal.Zero` - Begründung: Belegt die zusätzliche fachliche Regel zur Skontoanwendung.
Prüfidee: Rechnung in Fremdwährung mit Währungsfaktor 1,1 und Zahlbetrag 100 buchen; PaidFC muss um 110 steigen und die Rechnung ohne gesetztes "Abschließen" offen bleiben.
Tracelinks: StRS-303
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - definiert die Buchungssemantik des Zahlungseingangs.
Status: belegt
Modul: M-21
ID: SyRS-316
Titel: Löschen von Zahlungseingängen nur mit Recht und mit Betragsstornierung
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Backend-Komponente PaymentsBL
Vorbedingung: Ein oder mehrere Zahlungseingänge sollen gelöscht werden.
Fakt: PaymentsBL.DeleteIncomingPayment bricht mit `Result.AsError("Sie haben nicht das Recht 'Zahlungseingang.", DefaultMessageCodes.RightCheckFailed)` ab, wenn `loggedInUser.User.HasUserRight(UserRightsConst.Controlling.Finances.INCOMING_PAYMENT_TRANSACTIONS) == false`; bei erlaubter Löschung wird je Rechnung die Summe der gelöschten Beträge negiert (`groupie.Sum(f => f.Amount) * (-1)`) über ReceiptBL.UpdateReceiptIsPaid mit dem Protokolltext "Zahlungseingang gelöscht" zurückgebucht. CreateFilterExpression verhindert über `Guard.Not(...)` das Löschen ohne Filter.
Aussage: Das System soll das Löschen von Zahlungseingängen nur Benutzern mit dem Recht "Zahlungseingang" (10980) erlauben, den gelöschten Betrag am zugehörigen Beleg gegenbuchen und Löschaufrufe ohne einschränkenden Filter zurückweisen.
Ergebnis: Ohne Recht wird ein Fehler mit RightCheckFailed zurückgegeben und nichts gelöscht; mit Recht ist der bezahlte Betrag der Rechnung um die gelöschten Beträge reduziert und im Belegprotokoll vermerkt.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Finances/Payments/PaymentsBL.cs, Methode DeleteIncomingPayment, Zeilen 43-45 (Rechteprüfung) und Zeilen 62-71 (Gegenbuchung über `amountToChange * invoice.CurrencyFactor` mit Protokolltext "Zahlungseingang gelöscht") - Begründung: Benennt die durchsetzende Bedingung und die zwingende fachliche Folgehandlung.
- [PRIMÄR] Dieselbe Datei, Methode CreateFilterExpression, Zeilen 161-162: `Guard.Not((filter.InvoiceNumbers == null && filter.InvoiceHeadI3Ds == null && filter.IncomingPaymentI3Ds == null), "Empty Filters are not allowed for deleting IncomingPayments")` - Begründung: Verhindert Massenlöschung ohne Einschränkung.
- [SEKUNDÄR] src/webservice/Centron.WebServices.Core/EntitiesWrongPlace/Administration/Rights/UserRightsConst.cs, Zeile 2561: `INCOMING_PAYMENT_TRANSACTIONS = 10980` - Begründung: Belegt die Rechtenummer.
Prüfidee: Als Benutzer ohne Recht 10980 einen Zahlungseingang löschen; Ergebnis muss RightCheckFailed sein und Betrag sowie Datensatz unverändert bleiben.
Tracelinks: StRS-303
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - abrechnungsrelevante Schutzregel.
Status: belegt
Modul: M-21
ID: SyRS-317
Titel: Modulzugang zur Belegerfassung über Recht und Lizenz
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Modulregistrierung der WPF-Anwendung
Vorbedingung: Die Anwendung baut die Modulliste für den angemeldeten Benutzer auf.
Fakt: In ModuleRegistration.cs ist die Belegerfassung registriert als `ModuleRegistrationItem.For<OutgoingPaymentsAppModuleController>(() => Helper.HasRights(UserRightsConst.Purchase.ID, UserRightsConst.Purchase.Inventory.ID), () => LicenseManager.Instance.HasLicense(LicenseGuids.DocumentCapture) || LicenseManager.Instance.HasLicense(LicenseGuids.Centron))`; der Controller selbst liefert in GetRights() null.
Aussage: Das System soll das Modul Belegerfassung nur anbieten, wenn der Benutzer sowohl das Einkaufs- als auch das Lagerrecht besitzt und der Mandant über die Lizenz "DocumentCapture" oder eine c-entron-Vollizenz verfügt.
Ergebnis: Benutzer ohne eines der beiden Rechte oder ohne passende Lizenz sehen das Modul nicht in der Modulliste.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, Block "// Belegerfassung" (Zeilen 675-677): Rechte- und Lizenzprädikat wie oben - Begründung: Konkrete, durchsetzende Registrierungsbedingung mit benannten Rechten und Lizenz-GUIDs.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Warehousing/OutcomingPayments/Controller/OutgoingPaymentsAppModuleController.cs, `public IList<int> GetRights() { return null; }` - Begründung: Zeigt, dass die Zugriffssteuerung ausschließlich über die Registrierung erfolgt.
Prüfidee: Benutzer mit Einkaufsrecht, aber ohne Lagerrecht anmelden; das Modul "Belegerfassung" darf nicht erscheinen.
Tracelinks: StRS-304
Konsolidierung: nein
Übernahmewürdigkeit: Workaround - Zugriffsschutz ausschließlich auf Client-Modulebene; serverseitige Absicherung der Belegerfassungsaufrufe ist nicht belegt.
Status: belegt
Modul: M-22
ID: SyRS-318
Titel: Auswahl aktiver und Standard-Kontensysteme
Ebene: SyRS
Typ: Daten
Qualitätsmerkmal:
Akteur: Backend-Komponente BookKeepingAccountSystemsBL
Vorbedingung: Es sind ein oder mehrere Buchhaltungskontensysteme angelegt.
Fakt: BookKeepingAccountSystemsBL.CreateGetAccountSystemsExpression ergänzt bei filter.OnlyActive die Bedingung `f.IsActive` und bei filter.OnlyDefault die Bedingung `f.IsDefault`; GetBookKeepingAccounts(bool fromDefaultSystem) liefert die Konten aller so gefilterten Systeme. Weder BookKeepingAccountSystemsWebServiceBL.ToEntity noch die Tabelle [dbo].[BookKeepingAccountSystems] (nur PK auf I3D) erzwingen, dass höchstens ein System IsDefault = 1 trägt.
Aussage: [HYPOTHESE] Das System soll genau ein aktives Kontensystem als Standardkontenrahmen führen und dessen Konten für die Standardkontierung liefern.
Ergebnis: Aufrufe mit fromDefaultSystem = true liefern die Konten genau eines Kontenrahmens.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/BookKeepingAccountSystems/BookKeepingAccountSystemBL.cs, Methode CreateGetAccountSystemsExpression, Zeilen 39-47 - Begründung: Belegt die tatsächlich implementierte Filterung nach IsActive/IsDefault.
- [SEKUNDÄR] SSMS_DB_SCHEMA.sql, Zeilen 31994-32004: [dbo].[BookKeepingAccountSystems] mit `[IsDefault] [bit] NOT NULL` und ausschließlich `CONSTRAINT [PK_BookKeepingAccountSystems] PRIMARY KEY CLUSTERED ([I3D] ASC)` - Begründung: Zeigt das Fehlen eines Unique-Constraints auf IsDefault.
Prüfidee: Zwei Kontensysteme mit IsDefault = 1 speichern; die heutige Implementierung akzeptiert dies - Zielverhalten wäre eine Abweisung bzw. ein automatisches Zurücksetzen des bisherigen Standards. Zur Bestätigung fehlen eine Eindeutigkeitsprüfung beim Speichern oder ein DB-Constraint auf IsDefault sowie eine Fachaussage, ob mehrere parallele Standardkontenrahmen (z. B. je Mandant) gewollt sind.
Tracelinks: StRS-305
Konsolidierung: nein
Übernahmewürdigkeit: Workaround - Regel wird vom Code vorausgesetzt, aber nirgends erzwungen; für die Neuentwicklung als Constraint oder Speicherprüfung umzusetzen.
Status: HYPOTHESE
Modul: M-23
ID: SyRS-319
Titel: Vorabvalidierung der SEPA-Exportdaten
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Gateway-Komponente PaymentTransactionSepaInterface
Vorbedingung: Der Anwender hat Rechnungen zum Lastschrifteinzug ausgewählt und den Export ausgelöst.
Fakt: PaymentTransactionSepaInterface.CreateSepaFile ruft zuerst ValidateExportData auf und bricht bei ResultStatus.Error ab. ValidateExportData sammelt unter anderem Fehler für: Abbuchungsdatum in der Vergangenheit, fehlende oder normwidrige BIC (Regex `^([A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?)$`) von Mandant und Zahlungspflichtigem, fehlende IBAN, fehlende SEPA-Gläubiger-ID, fehlendes oder in der Zukunft liegendes Autorisierungsdatum, fehlende Mandatsreferenz (AuthorizationNumber), Lastschrifttyp "Unbekannt", Bankverbindung außerhalb ValidFrom/ValidTo, Betrag kleiner gleich 0, Zahlungskondition ohne Lastschriftmodus, Fremdwährung sowie unzulässige Zeichen im Verwendungszweck.
Aussage: Das System soll vor Erzeugung einer SEPA-Datei alle Export-Positionen und die Mandantenbankverbindung gegen den vollständigen Regelkatalog prüfen und den Export bei mindestens einem Verstoß mit einer je Rechnung ausgewiesenen Fehlermeldung vollständig verweigern.
Ergebnis: Es wird keine Datei erzeugt; der Anwender erhält eine Liste aller Verstöße mit dem Präfix "Rechnung <Nummer>: ".
Belege:
- [PRIMÄR] src/backend/Centron.Gateway/DataExchange/PaymentTransactions/Sepa/PaymentTransactionSepaInterface.cs, Methode ValidateExportData (Zeilen 22-198) und CreateSepaFile Zeilen 203-205: `Result validateResult = ValidateExportData(...); if (validateResult.Status == ResultStatus.Error) return Result<string>.FromResult(validateResult);` - Begründung: Benennt die durchsetzende Stelle und die Abbruchbedingung vor jeder Dateierzeugung.
- [SEKUNDÄR] Dieselbe Datei, Zeilen 136-140: Meldung "Keine Authorisierungsnummer (Mandat) hinterlegt." und Zeilen 154-158: "Zahlungskondition steht nicht auf Lastschrift Modus." - Begründung: Konkrete Fehlermeldungen belegen die fachlichen Einzelregeln.
Prüfidee: Rechnung mit Bankverbindung ohne Mandatsreferenz exportieren; der Export muss ohne Dateierzeugung mit der Meldung "Keine Authorisierungsnummer (Mandat) hinterlegt." abbrechen.
Tracelinks: StRS-306
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - schützt vor bankseitigen Rückweisungen und Rücklastschriften.
Status: belegt
Modul: M-24
ID: SyRS-320
Titel: Kennzeichnung und Protokollierung exportierter Rechnungen
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Backend-Komponente PaymentTransactionBL
Vorbedingung: Eine SEPA-Datei wurde fehlerfrei erzeugt.
Fakt: PaymentTransactionBL.InvoiceExportDone setzt in einer Transaktion je Exportposition über `_repository.SetInvoiceAsExported(currentEmployeeI3D, exportItem.Invoice.I3D, true)` das Lastschrift-Kennzeichen, schließt die Rechnung optional über InvoiceBL.SetInvoiceAsPaid mit dem Text "Abschluss über SEPA Export", wenn die Einstellung PaymentTransactionCloseInvoiceAfterExport den Wert 1 hat, und legt je Position einen IncomingPaymentLog mit gemeinsamer LogNumber, Zahler-IBAN, Mandatsreferenz und Empfänger-IBAN an. PaymentTransactionBL.ResetInvoiceExportedFlag nimmt dies zurück, überspringt aber Einträge mit `logEntry.PaymentTransactionReturnedEmployeeI3D > 0`.
Aussage: Das System soll nach jedem SEPA-Export die exportierten Rechnungen transaktional als lastschriftbeauftragt kennzeichnen, je Lauf ein Zahlungseingangsprotokoll mit gemeinsamer Laufnummer schreiben und eine Rücknahme des Exports genau einmal je Protokolleintrag zulassen.
Ergebnis: Exportierte Rechnungen tragen IsDebitCreated inklusive Zeitpunkt und Bearbeiter; ein bereits zurückgenommener Protokolleintrag wird bei erneuter Rücknahme übersprungen.
Belege:
- [PRIMÄR] src/backend/Centron.BL/DataExchange/PaymentTransactions/PaymentTransactionBL.cs, Methode InvoiceExportDone, Zeilen 249-288 (StartTransaction/CommitTransaction/RollbackTransaction um SetInvoiceAsExported, SetInvoiceAsPaid und CreateIncomingPaymentLogItem) - Begründung: Benennt die durchsetzende Stelle inklusive Transaktionsklammer.
- [PRIMÄR] Dieselbe Datei, Methode ResetInvoiceExportedFlag, Zeilen 305-306: `if (logEntry.PaymentTransactionReturnedEmployeeI3D > 0) continue;` - Begründung: Erzwingt die Einmaligkeit der Rücknahme.
- [SEKUNDÄR] Dieselbe Datei, Zeile 327: Belegprotokolltext "... hat am ... die Rechnung über die Rücknahme des Zahlungseingangs (SEPA-Export) wieder geöffnet." - Begründung: Belegt die fachliche Nachvollziehbarkeit der Rücknahme.
Prüfidee: Export durchführen, Rücknahme zweimal auslösen; beim zweiten Mal darf sich weder der bezahlte Betrag noch das Kennzeichen erneut ändern.
Tracelinks: StRS-306
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - verhindert Doppeleinzüge und doppelte Stornierungen.
Status: belegt
Modul: M-24
ID: SyRS-321
Titel: Betragsobergrenze je Rechnung im Lastschriftvorschlag
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Backend-Komponente PaymentTransactionBL
Vorbedingung: Der Anwender lädt die Liste der lastschriftfähigen Rechnungen.
Fakt: PaymentTransactionBL.GetInvoiceList(ExportDirectDebitType, ...) liest die Einstellung AppSettingsConst.PaymentTransactionIncomingPaymentMaximumAmount (ApplicationSettingID-Wert 1125) und filtert bei einem Wert größer 0 über `invoices.Where(f => f.AmountToPay <= maximumValue)`.
Aussage: Das System soll Rechnungen, deren einzuziehender Betrag eine konfigurierte Obergrenze überschreitet, nicht in die Lastschriftvorschlagsliste aufnehmen; eine Obergrenze von 0 deaktiviert die Prüfung.
Ergebnis: Rechnungen oberhalb der Obergrenze erscheinen nicht in der Exportauswahl und können nicht versehentlich eingezogen werden.
Belege:
- [PRIMÄR] src/backend/Centron.BL/DataExchange/PaymentTransactions/PaymentTransactionBL.cs, Methode GetInvoiceList, Zeilen 97-107: `var maximumValue = (decimal)(settings.GetDecimal(AppSettingsConst.PaymentTransactionIncomingPaymentMaximumAmount) ?? 0); if (maximumValue > 0) { return invoices.Where(f => f.AmountToPay <= maximumValue).ToList(); }` - Begründung: Konkrete durchsetzende Bedingung inklusive Deaktivierungssemantik.
- [SEKUNDÄR] src/backend/Centron.BL/Administration/Settings/AppSettingsConst.cs, Zeile 1456: `PaymentTransactionIncomingPaymentMaximumAmount = 1125` - Begründung: Belegt den Konfigurationsschalter.
Prüfidee: Obergrenze auf 500 setzen; eine Rechnung über 600 darf in der Vorschlagsliste nicht erscheinen, eine über 500 muss erscheinen.
Tracelinks: StRS-306
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - wirksame Schutzgrenze gegen Fehleinzüge hoher Beträge.
Status: belegt
Modul: M-24
ID: SyRS-322
Titel: Dreistufige Heuristik zur Zuordnung von Bankumsätzen
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Backend-Komponente OnlineBankingAccountTransactionsBL
Vorbedingung: Bankumsätze sind importiert und der automatische Abgleich wurde angestoßen.
Fakt: AutoCompleteSingleAccountTransaciton führt drei Suchschritte aus: (1) SearchReceiptInvoicesByDescription über Rechnungsnummern im Verwendungszweck, (2) falls kein Kunde gefunden SearchCustomerBySenderAndIBAN über die Umsatz-IBAN bzw. den Absendernamen, (3) SearchReceiptInvoicesByCustomerAndAmount über Betragsvergleich. Jeder Treffer wird mit einer Heuristik (MatchedInvoiceNumbers, MatchedIbanNumber, MatchedSenderName, MatchedSingleAmountExactly, MatchedSingleAmountApproximately, MatchedSummedInvoicesAmountExactly, MatchedSummedInvoicesAmountApproximately) und der zugehörigen DefaultMatchingRate versehen; AddSearchResultsToAccountTransaction behält je Beleg nur den Treffer mit der höchsten Rate und sortiert absteigend nach Rate, dann nach Belegdatum.
Aussage: Das System soll jedem Bankumsatz Kunde und offene Belege in drei aufeinander aufbauenden Suchschritten zuordnen, jeden Vorschlag mit einer nachvollziehbaren Trefferheuristik und Trefferquote kennzeichnen und je Beleg nur den besten Treffer als Vorschlag führen.
Ergebnis: Der Umsatz trägt einen zugeordneten Kunden sowie eine nach Trefferquote und Belegdatum sortierte Vorschlagsliste; bereits verbuchte oder manuell gesetzte Zuordnungen bleiben unverändert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Finances/OnlineBanking/OnlineBankingAccountTransactionsBL.cs, Methode AutoCompleteSingleAccountTransaciton, Zeilen 587-614 mit den Kommentaren "// Search 1: Search for invoice numbers in the description", "// Search 2: Search for related customer by account IBAN or sender", "// Search 3: Search all customer invoices and try to match the amount" - Begründung: Definiert Reihenfolge und Abbruchbedingungen der Heuristik.
- [PRIMÄR] Dieselbe Datei, Methode AddSearchResultsToAccountTransaction, Zeilen 794-820: Erhalt nur von `f.IsBooked || f.ReceiptMatchingHeuristic == OnlineBankingReceiptMatchingHeuristic.ManuallySet`, Deduplizierung über `OrderByDescending(g => g.Value.GetHeuristicInfo().DefaultMatchingRate)` - Begründung: Benennt die Regel, welche Vorschläge überschrieben und welche geschützt werden.
Prüfidee: Umsatz mit passendem Absendernamen, aber ohne Rechnungsnummer im Verwendungszweck einspielen; die Zuordnung muss mit der Heuristik MatchedSenderName und nicht MatchedInvoiceNumbers gekennzeichnet sein.
Tracelinks: StRS-307
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Kern der Automatisierung; Heuristik ist transparent und nachvollziehbar dokumentiert.
Status: belegt
Modul: M-25
ID: SyRS-323
Titel: Verbuchen und Stornieren von Kontoumsatzzuordnungen mit Belegprotokoll
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Backend-Komponente OnlineBankingAccountTransactionsBL
Vorbedingung: Ein Bankumsatz besitzt mindestens eine noch nicht verbuchte Zuordnung zu einer Rechnung oder Gutschrift.
Fakt: BookAmountsForAccountTransacitons verarbeitet nur Zuordnungen mit `f.IsBooked == false`; BookAmountToAssignedInvoice erhöht PaidFC um AssignedAmount, setzt isPaid auf true, wenn CloseReceiptAfterBooking gesetzt ist oder `newPaidFC >= assignment.ReceiptDemandedGrossAmount`, protokolliert "Zahlungseingang: Bankauszüge" (bei negativem Betrag ergänzt um " (Chargeback)") und markiert die Zuordnung mit IsBooked, BookedDate und BookedByEmployeeI3D. UndoBookingForAccountTransaciton kehrt dies mit `undoBooking: true` und dem Protokolltext "Annullierung Zahlungseingang: Bankauszüge" um. Negative Beträge dürfen laut Codekommentar eine abgeschlossene Rechnung wieder öffnen.
Aussage: Das System soll Umsatzzuordnungen genau einmal verbuchen, den bezahlten Betrag der Rechnung fortschreiben, den Belegabschluss regelbasiert setzen, jede Buchung und Stornierung im Belegprotokoll kenntlich machen und Rücklastschriften als negativen Betrag mit Kennzeichnung "(Chargeback)" zulassen.
Ergebnis: Verbuchte Zuordnungen tragen Buchungszeitpunkt und Buchenden und werden bei erneutem Aufruf übersprungen; das Belegprotokoll weist Buchung bzw. Annullierung mit Herkunft "Bankauszüge" aus.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Finances/OnlineBanking/OnlineBankingAccountTransactionsBL.cs, Methode BookAmountsForAccountTransacitons, Zeile 890: `foreach(var assignmentDTO in request.OnlineBankingTransactionAssignments.Where(f => f.IsBooked == false))` - Begründung: Erzwingt die Einmaligkeit der Verbuchung.
- [PRIMÄR] Dieselbe Datei, Methode BookAmountToAssignedInvoice, Zeilen 1048-1084: Zustandsbedingung inkl. Kommentar "// Check for negative amounts is for chargebacks. They can open a closed invoice.", `var isPaid = closeReceipt == true ? true : newPaidFC >= assignment.ReceiptDemandedGrossAmount;` und die Protokolltexte - Begründung: Benennt die konkrete Buchungs- und Abschlussbedingung.
Prüfidee: Zuordnung zweimal verbuchen lassen; der bezahlte Betrag der Rechnung darf sich nur einmal erhöhen und nur ein Protokolleintrag entstehen.
Tracelinks: StRS-307
Konsolidierung: Kandidat: SyRS-315 - identische fachliche Wirkung "Zahlungseingang verbuchen" in zweiter Implementierung.
Übernahmewürdigkeit: übernehmen - abrechnungsrelevante Buchungslogik mit Storno- und Rücklastschriftbehandlung.
Status: belegt
Modul: M-25
ID: SyRS-324
Titel: Verschlüsselte Ablage von Online-Banking-Zugangsdaten
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Backend-Komponente OnlineBankingConfigurationBL
Vorbedingung: Eine Online-Banking-Konfiguration vom Typ FinTS oder finAPI wird gespeichert oder gelesen.
Fakt: SaveOnlineBankingConfiguration ruft vor dem Persistieren EncryptOnlineBankingConfigurationFields, GetOnlineBankingConfigurationByI3D und GetOnlineBankingConfigurationsByFilter rufen nach dem Laden DecryptOnlineBankingConfigurationFields. Verschlüsselt werden für FinTS AccountUserId und AccountUserPassword, für finAPI UserAccountPassword, jeweils mit `new AESCryptoLogic().EncryptText(..., securityKey)`. Der Schlüssel stammt aus CentronConfigurationDbBL.GetHotlineMasterKey; fehlt er, bricht der Vorgang mit `Result<string>.AsError("No master key found", DefaultMessageCodes.NoMasterKeyFound)` ab. Beim Entschlüsseln wird die Entität zuvor über `this.Session.GetSession().Evict(configuration)` aus der NHibernate-Session entfernt.
Aussage: Das System soll Bankzugangsdaten (Benutzerkennung und Passwort) ausschließlich AES-verschlüsselt speichern, sie erst beim Lesen entschlüsseln und Speicher- wie Lesevorgänge ohne verfügbaren Masterkey verweigern.
Ergebnis: In den Tabellen OnlineBankingConfigurationsFinTS und OnlineBankingConfigurationsFinApi stehen ausschließlich Chiffrate; entschlüsselte Werte gelangen durch das Evict nicht zurück in die Datenbank.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Finances/OnlineBanking/OnlineBankingConfigurationBL.cs, Methoden EncryptOnlineBankingConfigurationFields (Zeilen 137-150) und DecryptOnlineBankingConfigurationFields (Zeilen 152-167) sowie GetSecurityKey (Zeilen 122-135) mit dem Abbruch bei fehlendem Schlüssel - Begründung: Benennt die durchsetzenden Stellen, die verschlüsselten Felder und die Abbruchbedingung.
- [SEKUNDÄR] SSMS_DB_SCHEMA.sql, Zeilen 45822-45848: Spalten [UserAccountPassword] nvarchar(250) bzw. [AccountUserId]/[AccountUserPassword] nvarchar(250) - Begründung: Belegt die Zielspalten der Verschlüsselung.
Prüfidee: FinTS-Konfiguration mit bekanntem Passwort speichern und die Spalte AccountUserPassword direkt in der Datenbank lesen; der Klartext darf dort nicht auftauchen.
Tracelinks: StRS-307
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - zwingende Schutzmaßnahme für Bankzugangsdaten.
Status: belegt
Modul: M-25
ID: SyRS-325
Titel: Bereitstellung der finAPI-Mandantenzugangsdaten über die Lizenz
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Backend-Komponente OnlineBankingFinApiBL
Vorbedingung: Der Client fordert die finAPI-Client-Credentials an, um eine Verbindung zur finAPI aufzubauen.
Fakt: OnlineBankingFinApiBL.GetFinApiClientCredentials liest die Kundennummer aus dem Lizenzdokument (`licenseDoc.SelectSingleNode("//Number")`, wirft bei Fehlen eine Exception) und liefert ClientId, ClientSecret, SandBoxClientId und SandBoxClientSecret nur aus, wenn `isUnitTest || hasFinApiLicense` gilt, wobei hasFinApiLicense über `LicenseManager.Instance.HasLicense(LicenseGuids.OnlineBanking_FinApi)` ermittelt wird. Die vier Credential-Werte sind als Zeichenketten-Literale im Quelltext hinterlegt.
Aussage: Das System soll finAPI-Zugangsdaten ausschließlich an Installationen mit gültiger finAPI-Lizenz und ermittelbarer Kundennummer ausliefern.
Ergebnis: Ohne finAPI-Lizenz enthält die Antwort nur die Kundennummer und keine Client-Credentials; ohne Kundennummer schlägt der Aufruf fehl.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Finances/OnlineBanking/OnlineBankingFinApiBL.cs, Methode GetFinApiClientCredentials, Zeilen 41-47: `if(isUnitTest || hasFinApiLicense) { dto.ClientId = "8e5597be-..."; dto.ClientSecret = "792cbc52-..."; ... }` - Begründung: Benennt die durchsetzende Bedingung und belegt zugleich die Hinterlegung der Geheimnisse als Quelltextkonstanten.
- [PRIMÄR] Dieselbe Datei, Methode GetCompanyNumberFromLicenseFile, Zeilen 59-62: `if (companyNumber is null) throw new Exception("Unable to find the company number in the license file");` - Begründung: Zweite durchsetzende Prüfung vor Auslieferung.
Prüfidee: Aufruf ohne finAPI-Lizenz absetzen; das Ergebnis darf keine ClientId und kein ClientSecret enthalten.
Tracelinks: StRS-307
Konsolidierung: nein
Übernahmewürdigkeit: Workaround - die Lizenzbindung greift, die Geheimnisse liegen jedoch im Klartext im Quellcode und sind bei Auslieferung des Assemblys extrahierbar; für die Neuentwicklung ist eine serverseitige Secret-Verwaltung mit Rotationsfähigkeit vorzusehen.
Status: belegt
Modul: M-25
ID: SyRS-326
Titel: Statuswechsel des SEPA-Mandats und Autorisierung der Bankverbindung
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Backend-Komponente SepaContractOnlinePdfDocumentHandler
Vorbedingung: Ein SEPA-Mandat wurde dem Kunden zur Online-Bestätigung zugestellt.
Fakt: Confirm setzt `contract.State = SepaContractState.Accepted`, legt das signierte PDF im Kundenverzeichnis (DirectoryReferenceKind.SepaContractDir) ab, entfernt das Word-Dokument (`contract.WordDocument = null;`) und setzt an der Bankverbindung ValidFrom und AuthorizationDate auf DateTime.Today. Decline setzt State = Declined, SetExpired setzt State = LinkExpired. In allen Fällen werden ChangedBy und ChangedDate fortgeschrieben. Bei `contract.TestMode` wird der Vertrag nach Versand der Mails gelöscht.
Aussage: Das System soll den Mandatsstatus bei Bestätigung, Ablehnung und Linkablauf eindeutig fortschreiben, das signierte Mandat als PDF im Kundendokumentenverzeichnis ablegen und die zugehörige Bankverbindung mit dem Bestätigungstag als Autorisierungs- und Gültigkeitsbeginn versehen.
Ergebnis: Ein bestätigtes Mandat liegt als PDF vor, der Vertrag hat Status Accepted, die Bankverbindung ist ab dem Bestätigungstag autorisiert und damit SEPA-exportfähig; Testmandate hinterlassen keine Datenspur.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Documents/Dsgvo/SepaContractOnlinePdfDocumentHandler.cs, Methode Confirm, Zeilen 124-153: `contract.State = SepaContractState.Accepted;` und `b.ValidFrom = DateTime.Today; b.AuthorizationDate = DateTime.Today;` - Begründung: Benennt die durchsetzenden Zuweisungen, die das Mandat exportfähig machen.
- [PRIMÄR] Dieselbe Datei, Methoden Decline (Zeile 201: `contract.State = SepaContractState.Declined;`) und SetExpired (Zeile 218: `contract.State = SepaContractState.LinkExpired;`) - Begründung: Belegen die vollständige Zustandsfortschreibung.
- [SEKUNDÄR] Dieselbe Datei, Zeilen 194-198 und 114-119: Löschung des Vertrags bei `contract.TestMode` - Begründung: Belegt die Sonderbehandlung von Testmandaten.
Prüfidee: Mandat online bestätigen; die verknüpfte Bankverbindung muss danach AuthorizationDate = heutiges Datum tragen und die SEPA-Exportvalidierung (SyRS-319) passieren.
Tracelinks: StRS-308
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - verbindet Mandatserteilung und Lastschriftfähigkeit revisionsfest.
Status: belegt
Modul: M-26
ID: SyRS-327
Titel: Kostenstellen und Kostenträger werden nur logisch gelöscht
Ebene: SyRS
Typ: Daten
Qualitätsmerkmal:
Akteur: Modul Kostenträger / Kostenstellen
Vorbedingung: Der Anwender wählt eine oder mehrere Kostenstellen bzw. Kostenträger aus und löst Löschen oder Wiederherstellen aus.
Fakt: PayersAndCostCenterAppModuleControllerViewModel.DoCostCenterDelete setzt nach Bestätigung `this.SelectedCostCenterItems.ForEach(x => x.State = 0);` und speichert; CostCenterRestoreAsync setzt `x.State = 1;`. LoadCostCentersAsync filtert ohne "gelöschte anzeigen" mit `filter.State = 1`. Es existiert kein Aufruf einer Delete-Operation auf CostCenter oder CostObject.
Aussage: Das System soll Kostenstellen und Kostenträger ausschließlich über den Status logisch löschen (Status 0) und wiederherstellen (Status 1) und in der Standardansicht nur aktive Einträge anzeigen.
Ergebnis: Gelöschte Kostenstellen bleiben in der Datenbank erhalten, verschwinden aber aus der Standardliste und bleiben für historische Belegkontierungen auflösbar.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/PayersAndCostCenter/PayersAndCostCenterAppModuleControllerViewModel.cs, Methode DoCostCenterDelete, Zeile 146: `this.SelectedCostCenterItems.ForEach(x => x.State = 0);` und CostCenterRestoreAsync, Zeile 188: `this.SelectedCostCenterItems.ForEach(x => x.State = 1);` - Begründung: Konkrete durchsetzende Zuweisungen ohne physische Löschung.
- [PRIMÄR] src/backend/Centron.BL/Warehousing/CostCenterBL.cs, CreateCostCenterExpression: `if (filter.State != null) expression = expression.And(x => x.State == filter.State);` - Begründung: Belegt die serverseitige Auswertung des Statusfilters.
Prüfidee: Kostenstelle löschen und danach über "gelöschte anzeigen" laden; der Datensatz muss mit Status 0 weiterhin vorhanden sein.
Tracelinks: StRS-309
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - erhält die Referenzierbarkeit historischer Kontierungen.
Status: belegt
Modul: M-27
@@ -0,0 +1,184 @@
ID: StRS-401
Titel: Ticketliste als zentrale Arbeitsliste des Supports
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Supportmitarbeiter (c-entron-Benutzer)
Vorbedingung: Benutzer ist am c-entron-Client angemeldet und besitzt das Recht "Helpdesk anzeigen".
Fakt: `TicketListAppModuleController` registriert das Modul mit `ModuleName => LocalizedStrings.TicketListAppModuleController_TicketListe` und `MainCategory => CentronModuleCategory.Ticket`; `TicketListViewModel` kennt die Arbeitsmodi `TicketListTicketMode { None, All, OnlyOwn, Customers }` und setzt beim Start `this.CurrentMode = this.UISettings.StartupMode ?? TicketListTicketMode.OnlyOwn`.
Aussage: Das System soll dem Supportmitarbeiter eine Ticketliste bereitstellen, in der er wahlweise nur die ihm zugeordneten Tickets, die Tickets ausgewählter Kunden oder alle für ihn sichtbaren Tickets bearbeiten kann, und beim Öffnen standardmäßig die eigenen Tickets anzeigen.
Ergebnis: Der Mitarbeiter sieht nach dem Öffnen des Moduls seine eigenen offenen Tickets und kann den Modus ohne Modulwechsel umschalten.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Helpdesk/TicketList/Module/TicketListAppModuleController.cs, Zeilen 26-44 (`ModuleName`, `CreateModuleInstance`, `if (CentronCache.Instance.EmployeePreallocationSettings.LoadOwnTickets) viewModel...CurrentMode = TicketListTicketMode.OnlyOwn`) - Begründung: Die Modulregistrierung und die Modusvorbelegung sind im Code durchgesetzt.
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Helpdesk/TicketList/TicketListViewModel.cs, Zeilen 552-557 und 1185-1191 (`enum TicketListTicketMode`) - Begründung: Die drei fachlichen Arbeitsmodi sind als Aufzählung im Code definiert.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Helpdesk/TicketList/TicketListView.xaml, Zeilen 551-600 (Spaltenüberschriften "Erstellt am", "Nummer", "Status", "Priorität") - Begründung: UI-Labels belegen den fachlichen Informationsumfang der Liste.
Prüfidee: Modul "Ticketliste" mit einem Benutzer öffnen, dessen Voreinstellung `LoadOwnTickets` gesetzt ist; die Liste muss ausschließlich Tickets enthalten, in denen der Benutzer Bearbeiter oder Verantwortlicher ist. Umschalten auf "Kunden" muss die Kundenauswahl erzwingen.
Tracelinks: SyRS-411, SyRS-412, SyRS-413, SwRS-430, SwRS-431
Konsolidierung: Kandidat: StRS-401 / SwRS-430 gegen src/nexus/CentronNexus/ServiceBoard/CachedTicketList und .../TicketList - dieselbe fachliche Ticketliste ist zusätzlich in Blazor implementiert
Übernahmewürdigkeit: übernehmen - zentrale Arbeitsoberfläche des Helpdesks, ohne Alternative im System.
Status: belegt
Modul: M-28
ID: StRS-402
Titel: Lückenlose Nachvollziehbarkeit der Ticketbearbeitung
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Supportmitarbeiter
Vorbedingung: Ein Ticket (hlpdsk_requests) existiert und wird bearbeitet.
Fakt: `HelpdeskBL.DoAfterStoreTrans` erzeugt bei Neuanlage `new HelpdeskHistoryBL(this.Session).CreateHistoryForActionType(currentUser.User, HelpdeskHistoryType.Create, entity.I3D)`; `HelpdeskBL.SetHelpdeskAction` schreibt bei jeder Änderung von Fälligkeitsdatum oder Status einen Historieneintrag ("Fälligkeitsdatum wurde geändert", "Status wurde geändert"); `HelpdeskCloseBL.CloseHelpdesk` schreibt zusätzlich `HelpdeskHistoryType.Close`.
Aussage: Das System soll jede fachlich relevante Zustandsänderung eines Tickets (Anlage, Statuswechsel, Fälligkeitsänderung, Weiterleitung, Abschluss) automatisch und unveränderbar in der Ticket-Historie protokollieren.
Ergebnis: Zu jedem Ticket ist ohne Zusatzaufwand rekonstruierbar, wer wann welchen Zustand geändert hat.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Support/HelpdeskBL.cs, Methode `SetHelpdeskAction`, Zeilen 558-608 (`historyBL.SaveHelpdeskAction(user.User, "Fälligkeitsdatum wurde geändert", ...)`, `"Status wurde geändert"`) - Begründung: Die Protokollierung ist im Speicherpfad zwingend eingebaut, nicht optional.
- [PRIMÄR] src/backend/Centron.BL/Sales/Support/HelpdeskBL.cs, Methode `DoAfterStoreTrans`, Zeilen 611-633 (`CreateHistoryForActionType(currentUser.User, HelpdeskHistoryType.Create, entity.I3D)`) - Begründung: Anlage-Historie wird transaktional nach dem Speichern erzeugt.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Helpdesk/Settings/Logging/HelpdeskLoggingSettingsViewModel.cs, Zeilen 67-70 (`TicketLogDueDateChanges`, `TicketLogStatusChanges`, `TicketLogPriorityChanges`, `TicketLogEditorChanges`) - Begründung: Konfigurationsschalter "Folgende Änderungen in Historie eintragen" belegt den fachlichen Zweck.
Prüfidee: Status eines Tickets ändern und speichern; die Ticket-Historie muss genau einen neuen Eintrag mit altem und neuem Statusnamen enthalten.
Tracelinks: SyRS-414, SyRS-415, SwRS-434, SwRS-435
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Nachweispflicht gegenüber Kunden und Grundlage für Abrechnungsdiskussionen.
Status: belegt
Modul: M-29
ID: StRS-403
Titel: Betreiberseitige Pflege der Ticket-Stammdaten
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Systemadministrator / Helpdesk-Leitung
Vorbedingung: Der Einstellungsbereich "Ticket" ist geöffnet.
Fakt: `ModuleRegistration.GetSettingsWithoutModule()` registriert `HelpdeskStatusSettingsController`, `HelpdeskPrioritySettingsController`, `HelpdeskCategoriesSettingsController` und `HelpdeskTypeSettingsController`; die zugehörigen Entitäten liegen in den Tabellen `hlpdsk_status`, `hlpdsk_prioritaeten`, `hlpdsk_kategorien` und `hlpdsk_typen`.
Aussage: Das System soll Status, Prioritäten, Kategorien und Typen von Tickets ohne Programmänderung pflegbar machen, damit der Betreiber seinen Supportprozess selbst abbilden kann.
Ergebnis: Neue Statuswerte, Prioritäten, Kategorien und Typen stehen unmittelbar in Ticketliste und Ticketdetails zur Auswahl.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, Methode `GetSettingsWithoutModule`, Zeilen 267-282 (Registrierung der vier Stammdaten-Settingcontroller) - Begründung: Die Pflegemasken sind fest im Anwendungsstart registriert.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, Zeilen 4124-4139 (`CREATE TABLE [dbo].[hlpdsk_typen]`), 4298-4313 (`hlpdsk_kategorien`), 4485-4514 (`hlpdsk_prioritaeten`), 4628-4643 (`hlpdsk_status`) - Begründung: Eigene Stammdatentabellen belegen die Pflegbarkeit als Daten statt als Code.
Prüfidee: Neuen Status anlegen, speichern, Ticketdetails öffnen: der neue Status muss ohne Neustart der Anwendung in der Statusauswahl erscheinen.
Tracelinks: SyRS-420, SwRS-436, SwRS-437, SwRS-438
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Mandantenspezifische Prozessabbildung ist Kernanforderung eines ERP-Helpdesks.
Status: belegt
Modul: M-30
ID: StRS-404
Titel: Ticketzeiten als Grundlage der Leistungsabrechnung
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Supportmitarbeiter, Fakturierung
Vorbedingung: Ein Ticket mit zugeordnetem Kunden existiert.
Fakt: `HelpdeskTimer` besitzt die Eigenschaft `Calculable` (Spalte `Berechenbar`) sowie die Belegzuordnungen `OrderAssetItemI3D` (`AufPosI3D`), `DeliveryListAssetItemI3D` (`LiefPosI3D`) und `InvoiceAssetItemI3D` (`RechPosI3D`); `HelpdeskTimerWebServiceBL.GetNewTimer` setzt `Calculable = settings.TimeRecordingSetCalculable`.
Aussage: Das System soll zu jedem Ticket Arbeitszeiten mit Kennzeichen "berechenbar" erfassen und diese Zeiten in Auftrag, Lieferschein und Rechnung weiterverarbeitbar machen.
Ergebnis: Erfasste Ticketzeiten sind eindeutig als abrechenbar oder nicht abrechenbar gekennzeichnet und über die Belegzuordnung bis zur Rechnung verfolgbar.
Belege:
- [PRIMÄR] src/backend/Centron.DAO/Mappings/Sales/Support/HelpdeskTimerArea/HelpdeskTimerMaps.cs, Zeilen 11-34 (`this.Table("hlpdsk_timer")`, `Map(map => map.Calculable).Column("Berechenbar")`, `Column("LiefPosI3D")`, `Column("RechPosI3D")`, `Column("AufPosI3D")`) - Begründung: Die Abrechnungsverknüpfung ist im persistenten Datenmodell verankert.
- [PRIMÄR] src/backend/Centron.BL/WebServices/Sales/Support/HelpdeskTimerWebServiceBL.cs, Methode `GetNewTimer`, Zeilen 40-53 (`Calculable = settings.TimeRecordingSetCalculable`) - Begründung: Die Voreinstellung der Abrechenbarkeit ist konfigurationsgesteuert im Code umgesetzt.
Prüfidee: Zeit auf einem Ticket erfassen, in einen Lieferschein übernehmen und prüfen, dass `LiefPosI3D` der Zeit gefüllt ist und die Zeit auf dem Beleg mit dem erfassten Umfang erscheint.
Tracelinks: SyRS-421, SyRS-422, SwRS-439
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - direkter Umsatzbezug, ohne Ersatz im System.
Status: belegt
Modul: M-31
ID: StRS-405
Titel: Standardisierte Ticketabläufe über Prozessvorlagen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Helpdesk-Leitung, Supportmitarbeiter
Vorbedingung: Mindestens eine Prozessvorlage ist im Vorlagenbaum hinterlegt.
Fakt: `TicketProcessTemplateViewModel` stellt Kommandos `NewRootFolderCommand`, `NewFolderCommand`, `NewTemplateCommand`, `CopyTemplateCommand`, `RenameTemplateCommand`, `DeleteTemplateCommand` bereit; `TicketDetailViewModel.AddProcess()` erzeugt aus der ausgewählten Vorlage einen ticketgebundenen Prozess (`new TicketProcessDTO { HelpdeskI3D = this.Helpdesk.I3D, Name = template.Name, Bindings = template.Bindings, Steps = template.Steps }`).
Aussage: Das System soll wiederkehrende Supportabläufe als benannte, in Ordnern organisierte Prozessvorlagen verwalten und diese Vorlagen auf ein einzelnes Ticket anwendbar machen.
Ergebnis: Der Bearbeiter arbeitet ein Ticket entlang der aus der Vorlage übernommenen Schrittfolge ab; Schritte und Verbindungen werden am Ticket gespeichert.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Helpdesk/TicketDetails/TicketDetailViewModel.cs, Methode `AddProcess`, Zeilen 3906-3927 (Übernahme von `Name`, `Bindings`, `Steps` aus `viewModel.SelectedProcess.Clone()`) - Begründung: Die Übernahme der Vorlage in das Ticket ist im Code durchgesetzt.
- [PRIMÄR] src/backend/Centron.BL/Sales/Support/TicketProcess/TicketProcessBL.cs, Methoden `SaveTicketProcess` und `SaveTemplate`, Zeilen 26-37 und 80-91 (`CentronObjectKindNumeric.HelpdeskClass` bzw. `CentronObjectKindNumeric.TicketProcessTemplateFolder`) - Begründung: Vorlage und Ticketprozess werden über dieselbe Prozessablage, aber mit unterschiedlichem Objektbezug persistiert.
Prüfidee: Vorlage mit drei Schritten anlegen, auf ein Ticket anwenden, Ticket speichern und erneut öffnen: die drei Schritte müssen unverändert am Ticket vorhanden sein.
Tracelinks: SwRS-441, SwRS-442
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - sichert gleichbleibende Servicequalität bei wiederkehrenden Fällen.
Status: belegt
Modul: M-32
ID: StRS-406
Titel: Automatische Ticketerzeugung aus Belegen mittels Vorlagen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Auftragsbearbeiter
Vorbedingung: Ein Beleg (Auftrag/Bestellung) mit Positionen liegt vor; mindestens eine Erstellungsvorlage existiert.
Fakt: `HelpdeskCreationTemplateBL` verwaltet `HelpdeskCreationTemplate`-Datensätze mit `IsStandard`; `CreateTicketsViewModel` lädt Vorlagenwerte (`CreateSeparateTicketsMode = (CreateTicketsMode)template.CreateSeparateTicketsMode; if (template.CreateTicketForAll) SelectAllTickets();`) und erzeugt daraus Tickets zu Belegpositionen; `CreateTicketsMode { Single, Group, Custom }`.
Aussage: Das System soll aus Belegpositionen Tickets erzeugen können, wobei eine gespeicherte Vorlage vorgibt, ob je Position ein Ticket, ein gruppiertes Ticket oder eine benutzerdefinierte Gruppierung entsteht und für welche Positionen Tickets angelegt werden.
Ergebnis: Der Anwender erhält eine vorbelegte Ticketvorschau gemäß Vorlage und erzeugt die Tickets in einem Schritt.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Finances/Receipts/CreateTickets/CreateTicketsViewModel.cs, Zeilen 109-131 und 192-226 (`OnChanged(nameof(this.CreateSeparateTicketsMode), ...)`, `ApplyTemplate`-Zuweisungen) - Begründung: Die Vorlagenanwendung und die Gruppierungslogik sind im Code durchgesetzt.
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Finances/Receipts/CreateTickets/CreateTicketsMode.cs (`enum CreateTicketsMode { Single, Group, Custom }`) - Begründung: Die drei fachlichen Erzeugungsmodi sind als Aufzählung festgelegt.
- [KONTEXT] docs/features/automatic-helpdesk-creation-templates.md, Abschnitt "Overview" und "Features" ("Standard Template: Designate one template as the standard (only one at a time)") - Begründung: Feature-Dokumentation beschreibt Zweck und Umfang, ist aber keine durchsetzende Stelle.
Prüfidee: Vorlage mit Modus "Group" und "CreateTicketForAll" speichern, Beleg mit vier Positionen öffnen, Ticketerzeugung starten: es müssen genau die gruppierten Tickets vorbelegt und alle Positionen markiert sein.
Tracelinks: SwRS-443
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - reduziert manuelle Erfassung bei Projekt- und Serienaufträgen.
Status: belegt
Modul: M-33
ID: StRS-407
Titel: E-Mail-Kommunikation als Teil des Ticketvorgangs
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Supportmitarbeiter, Kunde
Vorbedingung: Für das Ticket sind Ansprechpartner und E-Mail-Vorlagen konfiguriert.
Fakt: `HelpdeskMailBL` liefert je Geschäftsvorfall eigene Betreff- und Textbausteine (`GetNewHelpdeskEmailSubject`, `GetHelpdeskForwardingInternalSubject`, `GetHelpdeskEmailToCustomerSubject`, `GetHelpdeskClosingExternalEmailSubject`) über `MailTemplateReferences.Helpdesk.*`; `GenerateHelpdeskHistory` legt zu beantworteten Mails einen Historieneintrag an.
Aussage: Das System soll den E-Mail-Verkehr zu einem Ticket (Neuanlage, Weiterleitung, Kundenantwort, Abschlussmitteilung) aus zentral gepflegten Vorlagen erzeugen und jede versendete Nachricht in der Ticket-Historie festhalten.
Ergebnis: Alle Ticketmails sind einheitlich formuliert und am Ticket dokumentiert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Support/HelpdeskMailBL.cs, Methode `GenerateHelpdeskHistory`, Zeilen 40-74 (Erzeugung eines Historieneintrags mit Empfängerliste, Betreff und Klartext des Mailtextes) - Begründung: Die Archivierung im Ticket ist im Code durchgesetzt.
- [PRIMÄR] src/backend/Centron.BL/Sales/Support/HelpdeskMailBL.cs, Zeilen 78-290 (je Ereignis eigene `GetSubject`/`GetBody`-Aufrufe mit `MailTemplateReferences.Helpdesk.ClosingInternal`, `...ClosingExternal`, `...ForwardingInternal`) - Begründung: Vorlagenbezug je Ereignis ist fest verdrahtet.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Helpdesk/TicketDetails/TicketDetailViewModel.cs, Methode `SendNewTicketNotificationBySettings`, Zeilen 2902-2946 (`this.Settings.NewHelpdeskMessageGeneralReceiver.Split(";")`, anschließend `ArchiveHelpdeskMail`) - Begründung: Konfigurationseintrag für Benachrichtigungsempfänger belegt den fachlichen Ablauf.
Prüfidee: Ticket abschließen mit externer Benachrichtigung: Betreff und Text müssen aus der Vorlage `Helpdesk.ClosingExternal` stammen und ein Historieneintrag mit Empfängeradresse muss existieren.
Tracelinks: SyRS-423, SwRS-444
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Standardkommunikationsweg im Support.
Status: belegt
Modul: M-34
ID: StRS-408
Titel: Kundenzugang zum Helpdesk mit optionaler Freigabe
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Kunde (WebAccount), Supportmitarbeiter
Vorbedingung: Für den Kunden ist der Ticketzugang im Service-Board freigeschaltet.
Fakt: Die Einstellung `TicketWebAccountTicketsHaveToBeAuthorized` (AppSettingsConst 1606) wird über `HelpdeskCustomerAccessSettingsViewModel` gepflegt und über `AccountBL` als `CustomerApprovalEnabledSBO` bereitgestellt; `HelpdeskBL.DoBeforeStoreTrans` setzt bei aktivierter Freigabepflicht `entity.HelpdeskState = null`.
Aussage: Das System soll Kunden das Erfassen und Einsehen eigener Tickets über den Weblogin ermöglichen und dabei betreiberseitig festlegen lassen, ob kundenseitig erzeugte Tickets vor der Bearbeitung durch einen Mitarbeiter freigegeben werden müssen.
Ergebnis: Ohne Freigabepflicht erhält das Kundenticket den konfigurierten Eröffnungsstatus; mit Freigabepflicht bleibt es ohne Status und damit außerhalb der regulären Ticketliste.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Support/HelpdeskBL.cs, Methode `DoBeforeStoreTrans`, Zeilen 522-539 (`if (settingsForCustomer.CustomerApprovalEnabledSBO) { entity.HelpdeskState = null; } else { ... HelpdeskAfterOpenDefaultState }`) - Begründung: Die Freigabepflicht wird beim Speichern des Kundentickets serverseitig erzwungen.
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Helpdesk/Settings/CustomerAccess/HelpdeskCustomerAccessSettingsViewModel.cs, Zeilen 39-64 (`settings.TicketWebAccountTicketsHaveToBeAuthorized`) - Begründung: Der Konfigurationsschalter ist die einzige Pflegestelle dieses Verhaltens.
- [SEKUNDÄR] src/backend/Centron.BL/Administration/Settings/AppSettingsConst.cs, Zeile 2210 (`TicketWebAccountTicketsHaveToBeAuthorized = 1606`) - Begründung: Konfigurations-ID belegt die Persistenz der Einstellung.
Prüfidee: Freigabepflicht aktivieren, über einen WebAccount ein Ticket anlegen: der Datensatz in `hlpdsk_requests` muss `Status IS NULL` haben und darf in der Standard-Ticketliste des Mitarbeiters nicht erscheinen.
Tracelinks: SyRS-424, SyRS-425
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Selbstbedienung entlastet den Support und ist vertraglich zugesagt.
Status: belegt
Modul: M-35
ID: StRS-409
Titel: Kennzahlenüberblick über das Ticketaufkommen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Helpdesk-Leitung
Vorbedingung: Der Benutzer besitzt das Recht "Ticketstatistik".
Fakt: `TicketDashboardContainerController.GetDashboardTile` liefert je `TicketDashboardContainerKinds`-Wert (`TicketRecordedTime`, `Helpdesk`, `Status`, `Dates`, `DueDate`, `Priority`, `Types`) eine eigene Dashboard-Kachel; `SaleStatisticBL.GetTicketStatisticOverview` ermittelt `CreatedToday` und `ClosedToday` per SQL auf `hlpdsk_requests`.
Aussage: Das System soll die Ticketlage nach erfasster Zeit, Status, Erfassungsdatum, Fälligkeit, Priorität und Typ als Dashboard-Kacheln darstellen und die heute erzeugten sowie heute abgeschlossenen Tickets ausweisen.
Ergebnis: Die Leitung erkennt Rückstände und Auslastung ohne manuelle Auswertung.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Helpdesk/Dashboard/TicketDashboardContainerController.cs, Zeilen 18-40 (`switch (containerKind)` mit sieben Kachel-ViewModels) - Begründung: Der Kachelumfang ist im Code abschließend festgelegt.
- [PRIMÄR] src/backend/Centron.BL/Statistics/SaleStatistics/SaleStatisticBL.cs, Methode `GetTicketStatisticOverview`, Zeilen 376-398 (`SELECT COUNT(*) FROM hlpdsk_requests WHERE CONVERT(DATE, ErfasstAm) = CONVERT(Date, GETDATE())` und Abschlusszählung über `Stammdat` I3D 696) - Begründung: Die Kennzahlberechnung ist als SQL-Anweisung belegt.
Prüfidee: Ein Ticket anlegen und ein anderes abschließen; die Kennzahlen "heute erstellt"/"heute abgeschlossen" müssen sich jeweils um 1 erhöhen.
Tracelinks: SyRS-426, SwRS-445
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Steuerungsinstrument für die Supportleitung.
Status: belegt
Modul: M-36
@@ -0,0 +1,230 @@
ID: StRS-501
Titel: Zugriffssteuerung über Rechtegruppen statt Einzelrechte
Ebene: StRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Administrator (Rechteverwaltung)
Vorbedingung: Mitarbeiter besitzt einen Benutzer (AppUser) im System.
Fakt: Rechte werden nie direkt an Benutzer vergeben. `AppRightsBL.GetRightsFromCurrentUser` iteriert ausschließlich über `user.Groups` und sammelt `group.Rights`; die durchsetzende Abfrage `AppRightsBL.GetAllAppRightsFromUser` verknüpft `dbo.Sichtrus st` (Gruppe→Recht) mit `dbo.Sichmemb sm` (Gruppe→Benutzer) über `sm.Benutzer = :UserI3D`. Ein direkter Bezug Benutzer→Recht existiert im Schema nicht (`Sichrech` hat keine Benutzerspalte).
Aussage: Das System soll Berechtigungen ausschließlich über die Mitgliedschaft eines Benutzers in Rechtegruppen bestimmen und keine direkte Benutzer-Recht-Zuordnung zulassen.
Ergebnis: Die effektive Rechtemenge eines Benutzers ist die Vereinigungsmenge der Rechte aller Gruppen, in denen er Mitglied ist.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Rights/AppRightsBL.cs, `GetAllAppRightsFromUser(int appUserI3D)`, SQL `SELECT st.Recht AS ID FROM dbo.Sichtrus st INNER JOIN dbo.Sichmemb sm ON sm.Gruppe = st.Gruppe WHERE sm.Benutzer = :UserI3D` - Begründung: Dies ist die tatsächlich durchsetzende Ermittlung der Rechtemenge; sie kennt nur den Weg über die Gruppe.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[Sichtrus]([I3D],[Gruppe],[Recht])` und `CREATE TABLE [dbo].[Sichmemb]([I3D],[Gruppe],[Benutzer])` - Begründung: Das Datenmodell erzwingt die Zwischenstufe Gruppe; es gibt keine Tabelle Benutzer→Recht.
- [KONTEXT] docs/guides/development/check-userrights.md, Abschnitt "Rights and groups can be managed in the `Rechteverwaltung` module." - Begründung: Bestätigt die fachliche Absicht, widerlegt sie aber nicht selbst.
Prüfidee: Einem Benutzer wird über SQL ein Recht ohne Gruppenmitgliedschaft "zugewiesen" (nicht möglich, da keine Spalte existiert); alternativ: Entfernen des Benutzers aus allen Gruppen führt dazu, dass `HasUserRight` für jedes Recht `false` liefert.
Tracelinks: SyRS-512, SyRS-513, SwRS-523, SwRS-524
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Gruppenbasierte Rechtevergabe ist etabliert und für die Zielarchitektur (RBAC) unmittelbar tragfähig.
Status: belegt
Modul: M-37
ID: StRS-502
Titel: Lückenlose Nachvollziehbarkeit von Rechte- und Gruppenänderungen
Ebene: StRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Administrator (Rechteverwaltung), Revision
Vorbedingung: Ein Benutzer mit Recht `UserRightsManagement.ID` (10530) ändert Rechte- oder Gruppenzuordnungen.
Fakt: Jede verändernde Operation in `AppRightsBL` schreibt einen Protokolleintrag über `WriteBaseLog(...)` in die Entität `AppRightLog` (Tabelle `SichProtokoll`) mit `CreatedByI3D`, `CreatedDate`, `CreatedVersion` (Assembly-Version), `Kind` und deutschsprachiger `Description`. Betroffen sind `AddRightToRightGroup`, `RemoveRightFromRightGroup`, `AddUserToRightGroup`, `RemoveUserFromRightGroup`, `SaveRightGroup` (nur bei Neuanlage), `DeleteRightGroup` und `CopyRightGroup`.
Aussage: Das System soll jede Änderung an Rechtezuordnungen, Gruppenmitgliedschaften und Gruppenbestand revisionssicher mit auslösendem Benutzer, Zeitpunkt und Programmversion protokollieren.
Ergebnis: Zu jeder Rechteänderung existiert ein Eintrag in `SichProtokoll`, der Wer/Wann/Was beantwortet.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Rights/AppRightsBL.cs, `WriteBaseLog(AppRightLogKind kind, string objectText, string description, AppUser appUser, bool flushChanges)` - Begründung: Erzeugt und speichert den `AppRightLog` mit `CreatedByI3D = appUser.Employee.I3D` und `CreatedVersion = AssemblyLogic.GetFromAssemblyContaining<AppRightsBL>()`; wird von allen sieben verändernden Methoden aufgerufen.
- [PRIMÄR] src/backend/Centron.DAO/Mappings/AppRightLogMaps.cs, `this.Table("SichProtokoll")` - Begründung: Bindet das Protokoll an die persistente Tabelle.
- [SEKUNDÄR] src/backend/Centron.BL/Administration/Rights/AppRightsBL.cs, `WriteAddRightToGroupLog`: `$"Recht \"{objectText}\" an die Gruppe \"{groupName}\" vergeben"` - Begründung: Fachlicher Wortlaut des Protokolltextes.
Prüfidee: Recht an Gruppe vergeben und wieder entziehen; anschließend müssen genau zwei neue Zeilen in `SichProtokoll` mit `Art` = 2 bzw. 3 und `ErstelltVonI3D` = ausführender Mitarbeiter vorliegen.
Tracelinks: SyRS-516, SwRS-525, SwRS-526
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Auditierbarkeit von Berechtigungsänderungen ist regulatorisch und sicherheitstechnisch zwingend.
Status: belegt
Modul: M-37
ID: StRS-503
Titel: Checklisten als standardisierte Arbeitsanweisung für Serviceprozesse
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Servicetechniker, Serviceleitung
Vorbedingung: Es existiert eine Checklistenvorlage (`CentronChecklist.IsTemplate = true`).
Fakt: `CentronChecklist` trägt `ObjectKind` (`CentronObjectKindNumeric`), `ObjectI3D`, `IsTemplate`, `IsActive` und eine hierarchische Punktestruktur `CentronChecklistItem` mit `ParentChecklistItemI3D`, `OrderNumber` und `State` (`CentronChecklistItemState`). `CentronChecklistBL.ObjectHasOpenChecklists(objectKind, objectI3D)` liefert, ob zu einem Objekt noch offene Blattpunkte existieren.
Aussage: Das System soll wiederverwendbare Checklistenvorlagen bereitstellen, aus denen für konkrete Objekte (insbesondere Tickets) Arbeitsschritte instanziiert und deren Abarbeitungsstand geprüft werden kann.
Ergebnis: Für jedes bearbeitete Objekt ist maschinell feststellbar, ob alle Checklistenpunkte abgearbeitet sind.
Belege:
- [PRIMÄR] src/backend/Centron.BL/CheckListArea/CentronChecklistBL.cs, `ObjectHasOpenChecklists(CentronObjectKindNumeric objectKind, int objectI3D)`: `checklist.CheckListItems.Flatten(f => f.ChecklistItems).Any(f => f.ChecklistItems?.Any() is false && f.State == CentronChecklistItemState.Open)` - Begründung: Durchgesetzte Regel, dass nur Blattpunkte (ohne Kinder) den Offen-Status bestimmen.
- [PRIMÄR] src/backend/Centron.BL/CheckListArea/CentronChecklistBL.cs, `CreateChecklistFilterExpression`, Filterzweige `filter.IsTemplate`, `filter.ObjectKind`, `filter.ObjektI3D`, `filter.IsActive` - Begründung: Belegt die Trennung Vorlage/Instanz und die Objektbindung.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Helpdesk/CentronChecklist/CentronChecklistAppModuleController.cs, `ModuleName => "Checklisten"`, `Description => "Erstellen und Verwalten von Checklisten"` - Begründung: Fachliche Modulbenennung in der Oberfläche.
Prüfidee: Vorlage mit zwei Punkten anlegen, auf ein Ticket anwenden, einen Punkt schließen: `ObjectHasOpenChecklists` liefert weiterhin `true`, nach Schließen des zweiten `false`.
Tracelinks: SyRS-517, SwRS-530, SwRS-531, SwRS-532
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Standardisierte Abarbeitung ist Kernfunktion des Servicebetriebs.
Status: belegt
Modul: M-38
ID: StRS-504
Titel: Überwachung erwarteter wiederkehrender Kundenereignisse
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Serviceleitung, MSP-Betrieb
Vorbedingung: Zu einem Kunden (`AccountI3D`) ist mindestens ein erwartetes Ereignis mit `IsActive = 1` hinterlegt.
Fakt: Die Entität `ExpectedEvents` definiert je Wochentag ein Zeitfenster (`ExecuteMondayFrom`/`ExecuteMondayTo` … `ExecuteSundayFrom`/`ExecuteSundayTo`), eine erwartete Eingangsanzahl (`ExpectedIncomeMonday` … `ExpectedIncomeSunday`), einen Auslösertyp `EventTriggerKind` (Enum enthält nur `Email = 0`) sowie Textmuster `MessageContainsSuccess`/`MessageContainsWarning`/`MessageContainsError`. Eingetretene Ereignisse werden in `ExpectedEventLogEntries` mit `EventType` (`ExpectedEventType`), `EventDate`, `HelpdeskI3D`, `MailID` und optional der Quelldatei protokolliert.
Aussage: Das System soll je Kunde erwartete, regelmäßig eintreffende Ereignisse (aktuell per E-Mail) mit wochentagsabhängigem Zeitfenster und Erwartungsmenge definieren und deren tatsächliches Eintreten protokollieren.
Ergebnis: Ausbleibende oder fehlerhafte Ereignisse sind gegen die hinterlegte Erwartung feststellbar.
Belege:
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[ExpectedEvents]` mit Spalten `ExecuteMondayFrom`…`ExecuteSundayTo`, `ExpectedIncomeMonday`…`ExpectedIncomeSunday`, `MessageContainsSuccess`, `MessageContainsWarning`, `MessageContainsError`, `IsActive` - Begründung: Das Datenmodell trägt die Erwartungsdefinition verbindlich.
- [PRIMÄR] src/backend/Centron.Entities/Entities/ExpectedEvents/ExpectedEventType.cs, Werte `Success=0 … EventStarted=9`, darunter `CompletedWithErrors=6` - Begründung: Definiert die auswertbaren Ergebniszustände.
- [PRIMÄR] src/backend/Centron.Entities/Entities/ExpectedEvents/EventTriggerKind.cs, `public enum EventTriggerKind { Email = 0 }` - Begründung: Belegt, dass derzeit ausschließlich E-Mail als Auslöser unterstützt wird.
Prüfidee: Erwartetes Ereignis mit Zeitfenster Mo 08:00–09:00 und `ExpectedIncomeMonday = 1` anlegen; ohne Eintreffen darf für diesen Montag kein `ExpectedEventLogEntries`-Eintrag existieren, und die Auswertung muss die Lücke zeigen.
Tracelinks: SyRS-518, SwRS-533
Konsolidierung: Kandidat: StRS-506, StRS-511 - drei getrennte Aufgaben-/Terminbegriffe (erwartetes Ereignis, Task, ToDo)
Übernahmewürdigkeit: übernehmen - Überwachung vereinbarter Kundenereignisse ist ein eigenständiger Servicewert.
Status: belegt
Modul: M-39
ID: StRS-505
Titel: Auswertung erwarteter Ereignisse über einen wählbaren Zeitraum
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Serviceleitung
Vorbedingung: Benutzer besitzt das Recht `UserRightsConst.Administration.SHOW_EXPECTEDEVENTSREPORTING` (20800101).
Fakt: `ExpectedEventsReportingViewModel` initialisiert den Auswertungszeitraum auf `StarDate = DateTime.Today.AddDays(-12)` bis `EndDate = DateTime.Today`, verweigert bei `StarDate > EndDate` die Auswertung mit dem Dialog "Das Start Datum darf nicht nach dem End Datum liegen.", bietet die Filter `OnlyWithErrors` und `ShowDisabled` und gruppiert die Logeinträge je Kunde (`AccountI3D`) und erwartetem Ereignis (`ExpectedEventI3D`).
Aussage: Das System soll die protokollierten erwarteten Ereignisse je Kunde über einen frei wählbaren Zeitraum auswerten und dabei eine Einschränkung auf Fehlerfälle sowie das Ein-/Ausblenden deaktivierter Definitionen erlauben.
Ergebnis: Der Auswerter erhält je Kunde und Ereignisdefinition die Liste der Protokolleinträge im gewählten Zeitraum.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Helpdesk/ExpectedEventsReporting/ViewModels/ExpectedEventsReportingViewModel.cs, `Load()`: Aufbau des `ExpectedEventLogsFilter { EventDateStart = this.StarDate, EventDateEnd = this.EndDate.AddDays(1), OnlyWithErrors = this.OnlyWithErrors }` sowie `if (expectedEventsDto.IsActive == false) continue;` bei `ShowDisabled == false` - Begründung: Durchgesetzte Filter- und Sichtbarkeitslogik der Auswertung.
- [PRIMÄR] src/backend/Centron.BL/ExpectedEvents/ExpectedEventsBL.cs, `CreateLogsFilterExpression`: `if (filter.OnlyWithErrors == true) filterExpression = filterExpression.And(f => f.EventType == ExpectedEventType.CompletedWithErrors);` - Begründung: Legt fest, dass "nur Fehler" exakt `CompletedWithErrors` bedeutet.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Helpdesk/ExpectedEventsReporting/ViewModels/ExpectedEventsReportingViewModel.cs, UI-String "Lade Event Reports" - Begründung: Belegt den Auswertungscharakter des Moduls.
Prüfidee: Zeitraum mit Startdatum nach Enddatum wählen: Es erscheint der Hinweis "Datum inkorrekt" und die Ergebnisliste bleibt leer.
Tracelinks: StRS-504, SyRS-519
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Auswertung ist der Nutzenhebel der Ereignisüberwachung.
Status: belegt
Modul: M-40
ID: StRS-506
Titel: Automatisierte Erzeugung wiederkehrender Serviceaufgaben
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Serviceplanung, Hintergrunddienst
Vorbedingung: Ein `TaskManagementTask` mit `Action` und `Recurrence` ist gespeichert und hat den Status ungleich `ProjectStatus.Finished`.
Fakt: `TaskManagementTaskBL.ExecuteTask(int taskI3D, AppUser currentUser, bool manuallyExecutedByUser)` bestimmt über `new RecurrenceCalculator(task.Data.Recurrence).GetDates(start)` alle fälligen Termine, überspringt Ausführungen außerhalb `Recurrence.ExecuteDaysBefore` bzw. vor `Recurrence.ExecuteAfterTime`, setzt den Task auf `ProjectStatus.Finished`, sobald `Recurrence.EndTime < date` oder `NumberOfRecurrence <= CountOfRecurrence`, und delegiert die eigentliche Wirkung an einen `ITaskManagementActionHandler` (`TaskManagementHelpdeskActionHandler` erzeugt Tickets, `TaskManagementReportActionHandler` erzeugt Reports).
Aussage: Das System soll aus einer Aufgabendefinition mit Wiederholungsregel automatisch Serviceaufgaben (Tickets bzw. Reportversand) erzeugen und die Ausführung beenden, sobald Enddatum oder maximale Wiederholungszahl erreicht ist.
Ergebnis: Wiederkehrende Serviceleistungen entstehen ohne manuelles Anlegen; abgelaufene Tasks werden automatisch stillgelegt.
Belege:
- [PRIMÄR] src/backend/Centron.BL/TaskManager/TaskManagementTaskBL.cs, `ExecuteTask`: `var hasFinishedAtDate = task.Data.Recurrence.EndTime.HasValue && task.Data.Recurrence.EndTime.Value < date; var hasFinishedThroughCount = task.Data.Recurrence.NumberOfRecurrence.HasValue && task.Data.Recurrence.NumberOfRecurrence.Value <= task.Data.Recurrence.CountOfRecurrence.GetValueOrDefault(0);` mit anschließendem `task.Data.Status = ProjectStatus.Finished;` - Begründung: Durchgesetzte Beendigungsregel.
- [PRIMÄR] src/backend/Centron.BL/TaskManager/TaskManagementTaskBL.cs, Konstruktor: `_actionHandlers = new List<ITaskManagementActionHandler> { new TaskManagementHelpdeskActionHandler(), new TaskManagementReportActionHandler() }` - Begründung: Legt die zwei unterstützten Aufgabenwirkungen fest.
- [SEKUNDÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[TaskManagementRecurrence]`, `[dbo].[TaskManagementRecurrenceWeekDays]`, `[dbo].[TaskManagementActionExecutedAt]` - Begründung: Persistenzstruktur für Wiederholung und Ausführungshistorie.
Prüfidee: Task mit `NumberOfRecurrence = 2` täglich anlegen und dreimal ausführen lassen: Nach der zweiten Ausführung steht `Status = Finished`, die dritte Ausführung erzeugt keine weitere Aufgabe.
Tracelinks: SyRS-520, SwRS-534, SwRS-535
Konsolidierung: Kandidat: StRS-504, StRS-511 - drei getrennte Aufgaben-/Terminbegriffe (erwartetes Ereignis, Task, ToDo)
Übernahmewürdigkeit: übernehmen - Automatisierung wiederkehrender Leistungen ist tragende Serviceanforderung.
Status: belegt
Modul: M-41
ID: StRS-507
Titel: Kundenselbstbedienung über versendete Web-Formulare
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Servicemitarbeiter, Kundenansprechpartner
Vorbedingung: Zu einem Ticket (`HelpdeskI3D`) existiert mindestens ein aktives SelfCare-Formular.
Fakt: `SelfCareBL.SendWebForm(SendWebFormRequest request, AppUser appUser)` erzeugt ein `WebForm` mit neuer `Guid`, verknüpft die ausgewählten `SelfCareForm`-Datensätze, speichert es und versendet auf Wunsch eine Mail, in deren Text der Platzhalter `@@FormularLink@@` durch `webform.SBOUrl` ersetzt wird. Die erfolgreiche Mail wird über `_helpdeskDirectoryBL.AddMailToHelpdesk(...)` am Ticket abgelegt. Die Zielseite `PublicWebFormPage.razor` ist unter `@page "/webform/{Guid}"` mit `@attribute [AllowAnonymous]` erreichbar.
Aussage: Das System soll dem Kunden per E-Mail einen personalisierten, ohne Anmeldung erreichbaren Formularlink zusenden, über den er ticketbezogene Angaben selbst erfassen kann, und den Versand am Ticket dokumentieren.
Ergebnis: Der Kunde kann Daten ohne c-entron-Zugang liefern; der Vorgang ist am Ticket nachvollziehbar.
Belege:
- [PRIMÄR] src/backend/Centron.BL/SelfCare/SelfCareBL.cs, `SendWebForm(...)` und `ReplaceVariables(...)`: `return body.Replace("@@FormularLink@@", webform.SBOUrl);` - Begründung: Durchgesetzte Erzeugung und Einbettung des Links.
- [PRIMÄR] src/nexus/CentronNexus/ServiceBoard/TicketWebForms/PublicWebFormPage.razor, Zeilen 1–3: `@page "/webform/{Guid}"` und `@attribute [AllowAnonymous]` - Begründung: Belegt den anonymen öffentlichen Zugriff allein über die GUID.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Helpdesk/SendSelfCareForm/SendSelfCareViewModel.cs, `SendMail()`, Fehlermeldung `SendSelfCareFormViewModel_SendMail_BitteWählenSieMindestensEinFormularAus` - Begründung: UI-seitige Mindestbedingung "mindestens ein Formular".
Prüfidee: Formular an eine Testadresse senden, den Link in einem anonymen Browserfenster öffnen: Die Seite lädt ohne Anmeldung; im Ticketverlauf liegt die versendete Mail.
Tracelinks: SyRS-521, SwRS-536
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Selbstbedienung reduziert Rückfragen und ist strategischer Bestandteil des Serviceportals.
Status: belegt
Modul: M-42
ID: StRS-508
Titel: Kundenindividuelle Freigabe externer Helpdesk-Funktionen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Serviceleitung, externer Kundenbenutzer
Vorbedingung: Ein Kunde (`CustomerI3D`) und optional ein Standort (`CustomerSiteI3D`) sind angelegt.
Fakt: Die Entität `ExternalHelpdeskConfiguration` (Tabelle `ExternalHelpdeskConfiguration`) besitzt genau die Schalter `TicketReleaseSystemEnabled`, `AllowHelpdeskCreation` und `AllowCloseHelpdesks`, jeweils `Not.Nullable()`, sowie die Zuordnung `CustomerI3D` und `CustomerSiteI3D`. Gelesen, gespeichert und gelöscht wird über `ExternalHelpdeskConfigurationBL` mit einem Filter nach `I3Ds`, `CustomerI3D` und `CustomerSiteI3D`; die Endpunkte liegen in `CentronRestService` (`GetExternalHelpdeskConfigurationByFilter`, `SaveOrUpdateExternalHelpdeskConfiguration`, `DeleteExternalHelpdeskConfiguration`).
Aussage: Das System soll je Kunde und optional je Kundenstandort festlegen, ob externe Benutzer Tickets anlegen, Tickets schließen und das Ticket-Freigabeverfahren nutzen dürfen.
Ergebnis: Der Funktionsumfang des externen Helpdesks ist pro Kunde/Standort steuerbar und über die REST-Schnittstelle abfragbar.
Belege:
- [PRIMÄR] src/backend/Centron.Entities/Entities/ExternalHelpdesk/ExternalHelpdeskConfiguration.cs, Eigenschaften `TicketReleaseSystemEnabled`, `AllowHelpdeskCreation`, `AllowCloseHelpdesks`, `CustomerI3D`, `CustomerSiteI3D` - Begründung: Definiert den steuerbaren Funktionsumfang abschließend.
- [PRIMÄR] src/backend/Centron.DAO/Mappings/ExternalHelpdesk/ExternalHelpdeskConfigurationMaps.cs, `Table("ExternalHelpdeskConfiguration")` mit `Map(x => x.AllowHelpdeskCreation).Not.Nullable()` - Begründung: Persistenz und Pflichtcharakter der Schalter.
- [SEKUNDÄR] src/webservice/Centron.Host/Services/CentronRestService.cs, Region `#region ExternalHelpdeskConfiguration`, Methoden `GetExternalHelpdeskConfigurationByFilter` / `SaveOrUpdateExternalHelpdeskConfiguration` / `DeleteExternalHelpdeskConfiguration` - Begründung: Belegt die externe Verfügbarkeit der Konfiguration.
Prüfidee: Für einen Kunden `AllowHelpdeskCreation = false` speichern und die Konfiguration über den REST-Endpunkt lesen: Der zurückgegebene DTO muss `AllowHelpdeskCreation = false` liefern.
Tracelinks: SwRS-537
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Kundenspezifische Freigaben sind Voraussetzung für differenzierte Serviceverträge.
Status: belegt
Modul: M-43
ID: StRS-509
Titel: Zeitgesteuerte Eskalation überfälliger Vorgänge an definierte Empfängerrollen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Eskalationsdienst, Bearbeiter, Vorgesetzter, Betriebsleiter
Vorbedingung: Zu einem ToDo-Eintrag existiert ein Eskalationssatz (`Eskalationen.ObArt = 2`) und ein aktiver Eskalationstyp (`EskalationTypen.Status = 1`).
Fakt: `EscalationBL.CheckEskalationStage(Escalations escItem)` ermittelt die fällige Eskalationsstufe 1–3 anhand der Wartezeiten `Stunden1`/`Stunden2`/`Stunden3` und der bereits erfolgten Eskalationen `Eskalation1Am`/`Eskalation2Am`/`Eskalation3Am`; `ShouldEscalated` rechnet dabei in Arbeitsstunden zwischen `GeschaeftsZeitVon` und `GeschaeftsZeitBis` und überspringt Samstage bzw. Sonntage, wenn `EskalationSa`/`EskalationSo` nicht gesetzt sind. Empfänger je Stufe stammen aus `Stage1Receivers`/`Stage2Receivers`/`Stage3Receivers` (Flags-Enum `EscalationReceiversEnum` mit `Editor`, `Supervisor`, `Adviser`, `Manager`).
Aussage: Das System soll überfällige Vorgänge in bis zu drei Stufen eskalieren, die Wartezeit in Geschäftsstunden unter Berücksichtigung von Wochenendregeln berechnen und je Stufe die konfigurierten Empfängerrollen per E-Mail benachrichtigen.
Ergebnis: Überfällige Vorgänge werden stufenweise an zunehmend höhere Rollen gemeldet; jede Stufe wird in `Eskalationen` mit Zeitstempel vermerkt.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Sales/Support/Escalation/EscalationBL.cs, `CheckEskalationStage`: `if (nowDt.DayOfWeek == DayOfWeek.Saturday && !escItem.IsSaturdayActive) return 0;` sowie die Stufenkaskade 1→2→3 - Begründung: Durchgesetzte Stufenermittlung inklusive Wochenendregel.
- [PRIMÄR] src/backend/Centron.BL/Sales/Support/Escalation/EscalationBL.cs, `UpdateEscalations`: `Update Eskalationen SET Eskalation{escItem.Stage}AM = GetDate() WHERE I3D = {escItem.EscI3D}` - Begründung: Schreibt die erreichte Stufe verbindlich fort und verhindert Doppelversand derselben Stufe.
- [SEKUNDÄR] src/backend/Centron.BL/Sales/Support/Escalation/EscalationReceiversEnum.cs, `[Description("Bearbeiter")] Editor = 1`, `[Description("Vorgesetzter")] Supervisor = 2`, `[Description("ADM")] Adviser = 4`, `[Description("Betriebsleiter")] Manager = 8` - Begründung: Fachliche Rollenbezeichnungen der Empfänger.
Prüfidee: Eskalationstyp mit `Stunden1 = 1` und Geschäftszeit 08:00–17:00 anlegen, ToDo mit Termin vor mehr als einer Arbeitsstunde erzeugen: Der Dienstlauf muss Stufe 1 melden und `Eskalation1Am` setzen; ein zweiter Lauf darf Stufe 1 nicht erneut senden.
Tracelinks: SyRS-522, SwRS-538, StRS-511
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Eskalationsmanagement ist SLA-relevant und unverzichtbar.
Status: belegt
Modul: M-44
ID: StRS-510
Titel: Benachrichtigung der Ticketbearbeiter bei Zuweisung und Entzug
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Ticketbearbeiter
Vorbedingung: Die Bearbeiterliste (`Helpdesk.Editors`) eines Tickets wird geändert.
Fakt: `NexusNotificationsBL.SaveForwardTicketNotifications(Helpdesk helpdesk, IEnumerable<HelpdeskEditor> previousEditors, LoggedInUser user)` erzeugt für jeden neu hinzugekommenen Bearbeiter eine `NexusNotification` mit `Type = NexusNotificationType.TicketAssigned` und für jeden entfernten Bearbeiter eine mit `Type = NexusNotificationType.TicketUnassigned`. Der auslösende Benutzer selbst wird ausgeschlossen (`newEditor.EmployeeI3D != user.User.Employee.I3D`). Jede Benachrichtigung wird gespeichert und zusätzlich über `NotificationsHubHelper.SendNexusNotification` live ausgeliefert.
Aussage: Das System soll Mitarbeiter bei Zuweisung und Entzug eines Tickets benachrichtigen, den auslösenden Benutzer davon ausnehmen und die Benachrichtigung sowohl persistieren als auch unmittelbar zustellen.
Ergebnis: Betroffene Bearbeiter erkennen Zuständigkeitswechsel ohne aktives Nachsehen; der Auslöser erhält keine Selbstbenachrichtigung.
Belege:
- [PRIMÄR] src/backend/Centron.BL/NexusNotifications/NexusNotificationsBL.cs, `SaveForwardTicketNotifications`, Filterbedingungen `previousEditors.Select(g => g.EmployeeI3D).Contains(newEditor.EmployeeI3D) is false && newEditor.EmployeeI3D != user.User.Employee.I3D` - Begründung: Durchgesetzte Regel für Empfängerermittlung und Selbstausschluss.
- [PRIMÄR] src/webservice/Centron.WebServices.Core/Entities/NexusNotifications/NexusNotificationType.cs, `TicketAssigned = 11`, `TicketUnassigned = 12` - Begründung: Verbindliche Typkodierung der beiden Ereignisse.
- [SEKUNDÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[NexusNotifications]` mit `[UserI3D]`, `[ByUserI3D]`, `[Type]`, `[IsSeen]`, `[IsRead]` - Begründung: Persistenz inklusive Gelesen-/Gesehen-Status.
Prüfidee: Ticket einem zweiten Mitarbeiter zuweisen: Für diesen entsteht genau eine `NexusNotifications`-Zeile mit `Type = 11`; für den zuweisenden Benutzer entsteht keine Zeile.
Tracelinks: SwRS-539
Konsolidierung: Kandidat: SwRS-539 - drei parallele Benachrichtigungsbestände (CentronNotifications, NexusNotifications, NotificationUsers)
Übernahmewürdigkeit: übernehmen - Zuständigkeitswechsel muss aktiv kommuniziert werden.
Status: belegt
Modul: M-45
ID: StRS-511
Titel: ToDo-Liste als modulübergreifender Wiedervorlage- und Terminbestand
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Sachbearbeiter aller Fachbereiche
Vorbedingung: Ein Geschäftsobjekt (Angebot, Auftrag, Ticket, Vertrag, Lizenz, …) erzeugt eine Wiedervorlage.
Fakt: `ToDoBL.FillObjectKindsListe()` registriert 35 fachliche ToDo-Arten (u. a. `ReminderOffer` "Angebot Wiedervorlage", `Commission` "Auftrag kommissionieren", `Helpdesk`, `ContractBill` "Vertragsabrechnung", `DeliveryListEscalation` "Lieferdatum überschritten", `PLM` "Lizenz Management") mit `Area` und `AssetKind`. Die Tabelle `ToDoListe` führt dazu `Art`, `ObjektArt`, `ObjectI3D`, `Termin`, `BearbeiterI3D`, `Gelesen`, `Verworfen`, `Erledigt`, `ErledigtAm`.
Aussage: Das System soll Wiedervorlagen und Termine aller Fachbereiche in einem gemeinsamen ToDo-Bestand führen, jeden Eintrag über Art und Objektbezug einem Ursprungsbeleg zuordnen und den Bearbeitungsstand (gelesen, erledigt, verworfen) je Eintrag festhalten.
Ergebnis: Jeder Mitarbeiter sieht seine offenen Wiedervorlagen fachbereichsübergreifend an einer Stelle.
Belege:
- [PRIMÄR] src/backend/Centron.BL/ToDoArea/ToDoBL.cs, `FillObjectKindsListe()`, z. B. `ObjectKinds.Add(new ToDoObjectKind() { ObjectKind = ToDoConstants.ToDoType.ContractBill, Name = "Vertragsabrechnung", Commentar = "Vertragsabrechnung", Area = "Vertrag", AssetKind = 22 });` - Begründung: Verbindliche, im Code fixierte Liste der fachlichen ToDo-Arten.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[ToDoListe]` mit `[Art]`, `[ObjektArt]`, `[ObjectI3D]`, `[Termin]`, `[BearbeiterI3D]`, `[Gelesen]`, `[Verworfen]`, `[Erledigt]`, `[ErledigtAm]` - Begründung: Datenmodell trägt Objektbezug und Bearbeitungsstand.
- [SEKUNDÄR] src/backend/Centron.BL/ToDoArea/ToDoBL.cs, `GetTextForTodoType`, z. B. `return "Benachrichtigung (Mail aus Kundenstamm) bezüglich eines Kunden";` - Begründung: Fachliche Klartexte der Arten.
Prüfidee: Für ein Angebot eine Wiedervorlage anlegen: In `ToDoListe` entsteht eine Zeile mit `Art` = `ReminderOffer` und `ObjectI3D` = Angebots-I3D; nach Erledigen ist `Erledigt` gesetzt und `ErledigtAm` gefüllt.
Tracelinks: StRS-509, SwRS-540
Konsolidierung: Kandidat: StRS-504, StRS-506 - drei getrennte Aufgaben-/Terminbegriffe (ToDo, Task, erwartetes Ereignis)
Übernahmewürdigkeit: übernehmen - Zentraler Aufgabenbestand ist fachlich unverzichtbar; die technische Realisierung (Delphi-Erbe, `Art`-Magic-Numbers) ist gesondert zu modernisieren.
Status: belegt
Modul: M-46
@@ -0,0 +1,236 @@
ID: SyRS-512
Titel: Modulzugang Rechteverwaltung nur mit Recht und Lizenz
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Angemeldeter Benutzer, Modulregistrierung
Vorbedingung: Benutzer ist an einer c-entron-Verbindung angemeldet.
Fakt: In `ModuleRegistration.cs` ist das Modul so registriert: `ModuleRegistrationItem.For<RightsManagamentAppModuleController>(() => Helper.HasRights(UserRightsConst.Administration.ID, UserRightsConst.Administration.UserRightsManagement.ID), () => LicenseManager.Instance.HasLicense(LicenseGuids.RightsManagement) || LicenseManager.Instance.HasLicense(LicenseGuids.Centron))`. `Helper.HasRights` ist eine reine Ausdrucksmarkierung, die vom `ModuleRightsExpressionParser` in einen `RightCheckNode` übersetzt wird; dessen `HasRights(IList<int> allRights)` liefert `allRights.Contains(this.Right)`. `UserRightsManagement.ID` ist 10530, `Administration.ID` das übergeordnete Recht.
Aussage: Das System soll den Zugang zum Modul Rechteverwaltung nur gewähren, wenn der Benutzer sowohl das Recht `Administration.ID` als auch `Administration.UserRightsManagement.ID` (10530) besitzt und zusätzlich die Lizenz `RightsManagement` oder `Centron` vorliegt.
Ergebnis: Ohne beide Rechte oder ohne passende Lizenz erscheint das Modul nicht in der Modulliste und ist nicht startbar.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, Abschnitt "// Rechteverwaltung", `ModuleRegistrationItem.For<RightsManagamentAppModuleController>(() => Helper.HasRights(UserRightsConst.Administration.ID, UserRightsConst.Administration.UserRightsManagement.ID), ...)` - Begründung: Dies ist die durchsetzende Registrierungsbedingung für die Modulsichtbarkeit.
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/ModuleRightsExpressionParser.cs, `RightCheckNode.HasRights(IList<int> allRights) => allRights.Contains(this.Right)` - Begründung: Auswertende Stelle des Ausdrucks; belegt UND-Verknüpfung der aufgezählten Rechte.
- [PRIMÄR] src/webservice/Centron.WebServices.Core/EntitiesWrongPlace/Administration/Rights/UserRightsConst.cs, `public class UserRightsManagement { public const int ID = 10530; ... }` - Begründung: Konkrete Recht-ID.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Extensions/RibbonControlExtensions.cs, Zeile 285: `if (CentronCache.Instance.CurrentUserAppRights.Any(f => f.I3D == UserRightsConst.Administration.ID) && CentronCache.Instance.CurrentUserAppRights.Any(f => f.I3D == UserRightsConst.Administration.UserRightsManagement.ID))` - Begründung: Zweite, oberflächenseitige Ausblendung derselben Rechtekombination.
Prüfidee: Benutzer nur mit `Administration.ID`, ohne 10530 anmelden: Modul "Rechteverwaltung" darf nicht in der Modulübersicht erscheinen.
Tracelinks: StRS-501, SwRS-523
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Kombinierte Rechte- und Lizenzprüfung ist bewusstes Produktverhalten.
Status: belegt
Modul: M-37
ID: SyRS-513
Titel: Hierarchischer Rechtebaum mit Elternrecht und Kinderzähler
Ebene: SyRS
Typ: Daten
Qualitätsmerkmal:
Akteur: Rechteverwaltung, Datenbank
Vorbedingung: Ein Recht wird angelegt oder einer Gruppe zugewiesen.
Fakt: Die Tabelle `Sichrech` besitzt `I3D` (kein IDENTITY, sondern fachlich vergeben), `Text`, `Beschreibung`, `OwnerRecht` (Elternrecht), `NumChildren` und `Obsolete` (`bit NOT NULL`). Das Mapping `AppRightMaps` bildet `References(a => a.Parent).Column("OwnerRecht")` und `HasMany(u => u.Children).Table("Sichrech").KeyColumn("OwnerRecht")` ab. `AppRightsBL.SaveAndAssignGroupToRight` setzt die Hierarchie durch: nach Zuweisung eines Rechts ruft es sich rekursiv für `selectedRight.Parent` auf.
Aussage: Das System soll Rechte als Baum mit genau einem Elternrecht je Knoten führen und bei Zuweisung eines Rechts an eine Gruppe automatisch alle übergeordneten Rechte derselben Gruppe zuweisen.
Ergebnis: Eine Gruppe kann kein Detailrecht besitzen, ohne die zugehörigen übergeordneten Rechte zu besitzen.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Rights/AppRightsBL.cs, `SaveAndAssignGroupToRight(AppGroup group, AppRight selectedRight)`: `if (selectedRight.Parent != null) SaveAndAssignGroupToRight(group, selectedRight.Parent);` - Begründung: Durchgesetzte Vererbung der Elternrechte bei jeder Zuweisung.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[Sichrech]([I3D] [int] NOT NULL, ... [OwnerRecht] [int] NULL, [NumChildren] [int] NULL, [Obsolete] [bit] NOT NULL, CONSTRAINT [PK_Sichrech] PRIMARY KEY CLUSTERED ([I3D] ASC))` - Begründung: Datenmodell des Baums; `I3D` ohne IDENTITY belegt die fachlich feste ID-Vergabe.
- [PRIMÄR] src/backend/Centron.DAO/Mappings/Administration/AppRightMaps.cs, `References(a => a.Parent).Column("OwnerRecht")` / `HasMany(u => u.Children).Table("Sichrech").KeyColumn("OwnerRecht")` - Begründung: Objektseitige Abbildung des Baums.
- [KONTEXT] docs/guides/development/add-a-new-right.md, "In `OwnerRecht` the I3D of the right comes in, which is required for your new right." - Begründung: Erläutert die Semantik, setzt sie aber nicht durch.
Prüfidee: Einer neuen Gruppe nur das Blattrecht `Checklists.EDIT_CHECKLISTS` (20800003) zuweisen: In `Sichtrus` müssen zusätzlich `Checklists.ID` (20800000) und dessen Elternrechte für dieselbe Gruppe stehen.
Tracelinks: StRS-501, SwRS-529
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Rechtehierarchie reduziert Fehlkonfigurationen; die feste ID-Vergabe ist beim Neubau zu überdenken.
Status: belegt
Modul: M-37
ID: SyRS-514
Titel: Filialbeschränkung der Rechteverwaltung durch einschränkendes Recht
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Filialadministrator
Vorbedingung: Der Benutzer besitzt `UserRightsConst.Administration.UserRightsManagement.MANAGE_RIGHTS_ONLY_OWN_BRANCH` (20800144) und sein Mitarbeiter hat eine `BranchI3D`.
Fakt: Das Recht 20800144 wirkt als einschränkendes Recht an vier durchsetzenden Stellen der Serverschicht: `GetAllRightGroups` filtert `query.Where(f => f.BranchI3D == currentUser.Employee.BranchI3D.GetValueOrDefault(0))`; `SaveRightGroup`, `CopyRightGroup` und `DeleteRightGroup` brechen mit einem Fehler ab, wenn `user.Employee.BranchI3D != appGroup.BranchI3D`. Die Gruppentabelle `Sichgrup` trägt dafür die Spalte `BranchI3D`.
Aussage: Das System soll für Benutzer mit dem Recht 20800144 sämtliche Gruppenoperationen (Anzeigen, Anlegen, Kopieren, Löschen) auf Rechtegruppen der eigenen Filiale beschränken und Operationen auf Gruppen fremder Filialen mit einer erklärenden Fehlermeldung ablehnen.
Ergebnis: Ein Filialadministrator kann Rechte nur innerhalb der eigenen Filiale verwalten.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Rights/AppRightsBL.cs, `SaveRightGroup`: `if (user.HasUserRight(UserRightsConst.Administration.UserRightsManagement.MANAGE_RIGHTS_ONLY_OWN_BRANCH) && user.Employee.BranchI3D != appGroup.BranchI3D) return Result<AppGroup>.AsError("Sie haben nicht genügend Rechte um eine Gruppe für eine andere Filiale anlegen zu können.");` - Begründung: Serverseitig durchgesetzte Schreibsperre.
- [PRIMÄR] src/backend/Centron.BL/Administration/Rights/AppRightsBL.cs, `DeleteRightGroup`: identische Prüfung mit der Meldung "Sie haben nicht genügend Rechte um die Gruppe einer anderen Filiale zu löschen." - Begründung: Durchgesetzte Löschsperre.
- [PRIMÄR] src/backend/Centron.BL/Administration/Rights/AppRightsBL.cs, `GetAllRightGroups(AppUser currentUser)`: `query = query.Where(f => f.BranchI3D == currentUser.Employee.BranchI3D.GetValueOrDefault(0));` - Begründung: Durchgesetzte Lesebeschränkung.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Administration/RightsManagement/NewGroup/NewRightGroupViewModel.cs, `IsBranchEditable = CentronCache.Instance.CurrentUserAppRights.All(f => f.I3D != UserRightsConst.Administration.UserRightsManagement.MANAGE_RIGHTS_ONLY_OWN_BRANCH);` - Begründung: Ergänzende UI-Sperre der Filialauswahl.
Prüfidee: Benutzer mit 20800144 und Filiale A versucht, eine Gruppe mit `BranchI3D` = Filiale B zu speichern: Der Aufruf muss mit der genannten Fehlermeldung fehlschlagen und in `Sichgrup` darf keine Zeile entstehen.
Tracelinks: StRS-501, SwRS-525, SwRS-526
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Mandanten-/Filialtrennung in der Rechteverwaltung ist sicherheitsrelevant.
Status: belegt
Modul: M-37
ID: SyRS-515
Titel: Schutz der Administratorengruppe vor Löschung und Rechteentzug
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Administrator
Vorbedingung: Die Gruppe "Administratoren" (`Sichgrup.I3D = 6` oder `Name = 'Administratoren'`) existiert.
Fakt: `AppRightsBL.DeleteRightGroup` bricht mit `Result.AsError("Die Adminstratoren Gruppe darf nicht gelöscht werden")` ab, wenn `group.I3D == 6 || group.Name.Equals("Administratoren", StringComparison.InvariantCultureIgnoreCase)`. `AppUserGroupBL.IsAdministratorGroup` erkennt die Gruppe über dieselbe Doppelbedingung; `AppRightsBL.RemoveGroupToRightAssignments` überspringt Zuordnungen von Admin-Gruppen (`Where(x => this._appUserGroupBL.IsAdministratorGroupI3D(x.GroupI3D) == false)`).
Aussage: Das System soll die Administratorengruppe nicht löschbar machen und Massenoperationen zum Entzug von Gruppen-Recht-Zuordnungen die Administratorengruppe überspringen lassen.
Ergebnis: Ein vollständiger Verlust administrativer Rechte durch Fehlbedienung ist ausgeschlossen.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Rights/AppRightsBL.cs, `DeleteRightGroup`: `if (group.I3D == 6 || group.Name.Equals("Administratoren", StringComparison.InvariantCultureIgnoreCase)) return Result.AsError("Die Adminstratoren Gruppe darf nicht gelöscht werden");` - Begründung: Durchgesetzte Löschsperre in der Serverschicht.
- [PRIMÄR] src/backend/Centron.BL/Administration/Rights/AppRightsBL.cs, `RemoveGroupToRightAssignments(List<AppGroupRightAssignment> assignments)`: `foreach (var appGroupRightAssignment in assignments.Where(x => this._appUserGroupBL.IsAdministratorGroupI3D(x.GroupI3D) == false)) //No AdminGroup Rights` - Begründung: Durchgesetzter Ausschluss der Admin-Gruppe beim Massenentzug.
- [PRIMÄR] src/backend/Centron.BL/Administration/Rights/AppUserGroupBL.cs, `IsAdministratorGroupI3D(int groupI3D) => groupI3D == 6;` - Begründung: Fest kodierte Identität der Admin-Gruppe.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Administration/RightsManagement/RightsManagmentViewModel.cs, Zeile 867 ff.: Dialog "Die Administratoren Gruppe darf nicht gelöscht werden" / "Löschen nicht möglich" - Begründung: Vorgelagerte UI-Sperre mit identischem Wortlaut.
Prüfidee: `DeleteRightGroup(6, adminUser)` aufrufen: Rückgabe muss ein Fehlerergebnis mit dem genannten Text sein; die Zeile in `Sichgrup` bleibt bestehen.
Tracelinks: StRS-501, SwRS-526, SwRS-527
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Schutz vor Aussperrung ist zwingend; die Hartkodierung auf `I3D = 6` ist beim Neubau durch ein Systemflag zu ersetzen.
Status: belegt
Modul: M-37
ID: SyRS-516
Titel: Abschließend definierte Ereignisarten des Rechteprotokolls
Ebene: SyRS
Typ: Daten
Qualitätsmerkmal:
Akteur: Rechteverwaltung, Revision
Vorbedingung: Eine Rechte- oder Gruppenänderung wird protokolliert.
Fakt: `AppRightLogKind` definiert genau sieben Werte: `AddRightToGroup = 2`, `RemoveRightFromGroup = 3`, `CreateGroup = 4`, `DeleteGroup = 5`, `AddUserToGroup = 6`, `RemoveUserFromGroup = 7`, `CopyGroup = 8`. Die Werte 0 und 1 sind auskommentiert mit dem Hinweis "The values of the enum are dependant on the delphi values. 0 & 1 are not used. 8 onwards are new ones from .NET". Der Wert wird in `SichProtokoll.Art` gespeichert.
Aussage: Das System soll Rechteprotokolleinträge ausschließlich mit einer der sieben definierten Ereignisarten (2 bis 8) kennzeichnen und die Kodierung mit dem Delphi-Altsystem kompatibel halten.
Ergebnis: Protokolleinträge sind über `Art` maschinell klassifizierbar und mit Alt-Einträgen aus Delphi vergleichbar.
Belege:
- [PRIMÄR] src/webservice/Centron.WebServices.Core/Entities/Administration/AppRightLogKind.cs, vollständige Enum-Definition `AddRightToGroup = 2 … CopyGroup = 8` - Begründung: Abschließende Wertemenge der Protokollart.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[SichProtokoll]([I3D],[Art],[Objekt] varchar(500),[Beschreibung] varchar(500),[ErstelltVonI3D],[ErstelltDatum],[ErstelltVersion] varchar(20),[Status])` - Begründung: Zielstruktur des Protokolls; `Beschreibung` ist auf 500 Zeichen begrenzt.
- [KONTEXT] src/webservice/Centron.WebServices.Core/Entities/Administration/AppRightLogKind.cs, Kommentar "//The values of the enum are dependant on the delphi values." - Begründung: Erklärt die Lücke bei 0/1.
Prüfidee: Alle sieben Operationen ausführen und die Menge der entstehenden `SichProtokoll.Art`-Werte prüfen: Sie muss exakt {2,3,4,5,6,7,8} sein.
Tracelinks: StRS-502
Konsolidierung: nein
Übernahmewürdigkeit: Sonderfall - Wertelücke 0/1 existiert nur wegen Delphi-Kompatibilität und ist bei einer Neuimplementierung entbehrlich.
Status: belegt
Modul: M-37
ID: SyRS-517
Titel: Modulzugang Checklisten und deklarierter Rechteumfang
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Angemeldeter Benutzer
Vorbedingung: Benutzer ist angemeldet.
Fakt: Das Modul ist registriert als `ModuleRegistrationItem.For<CentronChecklistAppModuleController>(() => Helper.HasRights(UserRightsConst.Sales.Customer.Helpdesk.Checklists.ID), () => LicenseManager.Instance.HasLicense(LicenseGuids.Checklists) || LicenseManager.Instance.HasLicense(LicenseGuids.Centron))`. `CentronChecklistAppModuleController.GetRights()` deklariert die vier Rechte `Checklists.ID` (20800000), `CREATE_NEW_CHECKLIST_TEMPLATES` (20800001), `EDIT_CHECKLIST_TEMPLATES` (20800002), `EDIT_CHECKLISTS` (20800003). Zusätzlich existiert `EDIT_CHECKLIST_ITEM_EDITOR` (20800152).
Aussage: Das System soll das Checklistenmodul nur bei Vorliegen des Rechts `Checklists.ID` (20800000) und der Lizenz `Checklists` oder `Centron` bereitstellen und die Bearbeitungsfunktionen intern nach Vorlage anlegen, Vorlage bearbeiten und Checkliste bearbeiten unterscheiden.
Ergebnis: Ohne das Basisrecht ist das Modul nicht erreichbar; ohne die jeweiligen Detailrechte sind die zugehörigen Funktionen gesperrt.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, Abschnitt "// Checklisten", `Helper.HasRights(UserRightsConst.Sales.Customer.Helpdesk.Checklists.ID)` plus Lizenzbedingung - Begründung: Durchgesetzte Zugangsbedingung des Moduls.
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Helpdesk/CentronChecklist/CentronChecklistAppModuleController.cs, `GetRights()` mit den vier aufgezählten Konstanten - Begründung: Verbindliche Rechtemenge, die das Modul beansprucht.
- [PRIMÄR] src/shared/Centron.Controls/Checklist/CentronChecklist/ChecklistConfiguration/ChecklistConfigurationViewModel.cs, Zeilen 139/141: `this._hasRightNewChecklist = rights.Result.Any(f => f.I3D == UserRightsConst.Sales.Customer.Helpdesk.Checklists.CREATE_NEW_CHECKLIST_TEMPLATES) == true;` bzw. `..EDIT_CHECKLIST_TEMPLATES..` - Begründung: Durchsetzende Auswertung der Detailrechte in der Konfigurationsoberfläche.
- [KONTEXT] CentronRights.md, Abschnitt "16. Checklisten" mit 16.1–16.4 - Begründung: Prosabeschreibung der vier Checklistenrechte.
Prüfidee: Benutzer ohne 20800000 anmelden: Modul "Checklisten" fehlt in der Modulliste. Benutzer mit 20800000, aber ohne 20800001: Die Funktion zum Anlegen neuer Vorlagen ist deaktiviert.
Tracelinks: StRS-503, SwRS-530
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Feingranulare Trennung Vorlage/Instanz ist fachlich sinnvoll.
Status: belegt
Modul: M-38
ID: SyRS-518
Titel: Modulzugang Erwartete Ereignisse und wochentagsbezogene Erwartungsdefinition
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Serviceadministrator
Vorbedingung: Benutzer ist angemeldet; ein Kunde ist über die Kontosuche auswählbar.
Fakt: Registrierung: `ModuleRegistrationItem.For<ExpectedEventsAppModuleController>(() => Helper.HasRights(UserRightsConst.Administration.SHOW_EXPECTEDEVENTS), () => LicenseManager.Instance.HasLicense(LicenseGuids.ExpectedEvents) || LicenseManager.Instance.HasLicense(LicenseGuids.Centron))` mit `SHOW_EXPECTEDEVENTS = 20800100`. `ExpectedEventsMainViewModel.NewExpectedEvent()` erzwingt vor Anlage die Auswahl genau eines Kontos über `SearchAccountViewModel(... AllowMultiSelect = false)` und ordnet das neue Ereignis diesem `AccountI3D` zu; ein Konto kann mehrere Ereignisdefinitionen tragen.
Aussage: Das System soll das Modul Erwartete Ereignisse nur bei Recht 20800100 und passender Lizenz bereitstellen und jede Ereignisdefinition verpflichtend genau einem Kundenkonto zuordnen.
Ergebnis: Jede Ereignisdefinition besitzt einen eindeutigen Kundenbezug; ein Konto kann mehrere Definitionen führen.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, Abschnitt "// Erwartete Events", `Helper.HasRights(UserRightsConst.Administration.SHOW_EXPECTEDEVENTS)` plus Lizenzbedingung - Begründung: Durchgesetzte Zugangsbedingung.
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Helpdesk/ExpectedEvents/ViewModels/ExpectedEventsMainViewModel.cs, `NewExpectedEvent()`: `if (account == null) return;` und `var expectedevent = new ExpectedEventsDTOViewModel(account.I3D);` - Begründung: Erzwingt den Kontobezug bei Neuanlage.
- [PRIMÄR] src/backend/Centron.BL/ExpectedEvents/ExpectedEventsBL.cs, `GetAllExpectedEventsByAccount(int accountI3D)`: `GetList(f => f.AccountI3D == accountI3D)` - Begründung: Belegt die 1:n-Beziehung Konto → Ereignisdefinitionen.
- [SEKUNDÄR] src/webservice/Centron.WebServices.Core/EntitiesWrongPlace/Administration/Rights/UserRightsConst.cs, `public const int SHOW_EXPECTEDEVENTS = 20800100;` - Begründung: Konkrete Recht-ID.
Prüfidee: Benutzer ohne 20800100 anmelden: Modul "Erwartete Events" fehlt. Anlage ohne Kontoauswahl abbrechen: Es entsteht kein Datensatz in `ExpectedEvents`.
Tracelinks: StRS-504, SwRS-533
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Kundenbezug ist konstitutiv für die Überwachung.
Status: belegt
Modul: M-39
ID: SyRS-519
Titel: Modulzugang Auswertung erwarteter Ereignisse mit eigener Lizenz
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Angemeldeter Benutzer
Vorbedingung: Benutzer ist angemeldet.
Fakt: Registrierung: `ModuleRegistrationItem.For<ExpectedEventsReportingAppModuleController>(() => Helper.HasRights(UserRightsConst.Administration.SHOW_EXPECTEDEVENTSREPORTING), () => LicenseManager.Instance.HasLicense(LicenseGuids.ExpectedEventsEvaluation) || LicenseManager.Instance.HasLicense(LicenseGuids.Centron))` mit `SHOW_EXPECTEDEVENTSREPORTING = 20800101` und `LicenseGuids.ExpectedEventsEvaluation = C6770ACC-8477-4D1A-894F-F996B327809C`. Dieses Recht und diese Lizenz sind verschieden von denen der Ereignisdefinition (20800100 / `LicenseGuids.ExpectedEvents`).
Aussage: Das System soll die Auswertung erwarteter Ereignisse als eigenständiges Modul mit eigenem Recht (20800101) und eigener Lizenz (`ExpectedEventsEvaluation`) führen, getrennt von der Pflege der Ereignisdefinitionen.
Ergebnis: Auswertende Rollen können ohne Pflegerechte arbeiten und umgekehrt.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, Abschnitt "// Erwartete Events Auswertung", vollständige Registrierungszeile mit `SHOW_EXPECTEDEVENTSREPORTING` und `LicenseGuids.ExpectedEventsEvaluation` - Begründung: Durchgesetzte, von SyRS-518 verschiedene Zugangsbedingung.
- [PRIMÄR] src/backend/Centron.Interfaces/Administration/Logins/LicenseGuids.cs, `ExpectedEvents = 2714E998-9D73-4064-9208-C1705A8826B0` und `ExpectedEventsEvaluation = C6770ACC-8477-4D1A-894F-F996B327809C` - Begründung: Belegt zwei getrennte Lizenzen.
- [SEKUNDÄR] src/webservice/Centron.WebServices.Core/EntitiesWrongPlace/Administration/Rights/UserRightsConst.cs, `SHOW_EXPECTEDEVENTS = 20800100; SHOW_EXPECTEDEVENTSREPORTING = 20800101;` - Begründung: Zwei getrennte Rechte-IDs.
Prüfidee: Benutzer nur mit 20800100 anmelden: "Erwartete Events" erscheint, "Erwartete Events Auswertung" nicht.
Tracelinks: StRS-505, SyRS-518
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Trennung von Pflege und Auswertung entspricht der Rollentrennung.
Status: belegt
Modul: M-40
ID: SyRS-520
Titel: Modulzugang Taskmanagement und aktionsabhängige Lizenzprüfung
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Serviceplaner
Vorbedingung: Benutzer ist angemeldet.
Fakt: Registrierung: `ModuleRegistrationItem.For<TaskManagmentAppModuleController>(() => Helper.HasRights(UserRightsConst.Sales.Customer.Helpdesk.SHOW_TASKMANAGEMENT), () => LicenseManager.Instance.HasLicense(LicenseGuids.ServiceBoardWebDev) || LicenseManager.Instance.HasLicense(LicenseGuids.TaskManagement))` mit `SHOW_TASKMANAGEMENT = 20800102`. Zusätzlich prüft `TaskManagementTaskBL.CheckLicenses(TaskManagementTask task)` beim Speichern aktionsabhängig: Für `TaskManagementHelpdeskAction` ist `LicenseGuids.ServiceBoardWebDev` erforderlich ("Sie besitzen keine Lizenz für Automatische Tickets."), für `TaskManagementReportAction` `LicenseGuids.TaskManagementReportServer` oder `LicenseGuids.ProjectManagement` ("Sie besitzen keine Lizenz für den Reportserver.").
Aussage: Das System soll den Modulzugang zum Taskmanagement an das Recht 20800102 binden und beim Speichern eines Tasks zusätzlich prüfen, ob für die gewählte Aktionsart (Ticket bzw. Report) die passende Lizenz vorliegt.
Ergebnis: Ein Task mit nicht lizenzierter Aktionsart wird mit einer sprechenden Fehlermeldung abgewiesen und nicht gespeichert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/TaskManager/TaskManagementTaskBL.cs, `CheckLicenses(TaskManagementTask task)`: `if (task.Action is TaskManagementHelpdeskAction && LicenseManager.Instance.HasLicense(LicenseGuids.ServiceBoardWebDev) == false) return Result.AsError("Sie besitzen keine Lizenz für Automatische Tickets.", DefaultMessageCodes.LicenseNotFound);` - Begründung: Serverseitig durchgesetzte, aktionsabhängige Lizenzprüfung vor dem Speichern.
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs, Abschnitt "// Taskmanagement", Registrierungszeile mit `SHOW_TASKMANAGEMENT` - Begründung: Durchgesetzte Modulsichtbarkeit.
- [SEKUNDÄR] CentronRights.md, "### 18 Taskmanagement anzeigen — This right allows the user to access the task management. *UserRightsConst.Sales.Customer.Helpdesk.SHOW_TASKMANAGEMENT*" - Begründung: Fachliche Beschreibung des Rechts.
Prüfidee: Ohne Lizenz `ServiceBoardWebDev` einen Task mit Helpdesk-Aktion speichern: Rückgabe muss Fehler mit Text "Sie besitzen keine Lizenz für Automatische Tickets." sein; in `TaskManagementTask` entsteht keine Zeile.
Tracelinks: StRS-506, SwRS-535
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Lizenzdurchsetzung auf Aktionsebene ist bewusst und verhindert Umgehung über die Oberfläche.
Status: belegt
Modul: M-41
ID: SyRS-521
Titel: Zeitlich begrenzte Gültigkeit öffentlicher SelfCare-Formularlinks
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Anonymer Formularaufrufer
Vorbedingung: Ein `WebForm` wurde versendet und besitzt `CreatedAt`.
Fakt: `SelfCareBL.GetWebFormByGuid(Guid guid)` prüft: Wenn `entity.TicketPattern == null && entity.CreatedAt != null`, wird `expirationDate = entity.CreatedAt.Value.AddMinutes(ticketPatternSettings?.WebFormLinkExpirationInMinutes ?? TimeSpan.FromDays(7).TotalMinutes)` berechnet; bei `DateTime.Now > expirationDate` liefert die Methode `Result<WebForm>.AsError("Link has expired")`. Die Basis-URL stammt je nach Einstellung `ApplicationSettingID.UseNexusForPublicWebForms` aus `CentronNexusUrl` oder `ServiceBoardOnlineUrl`. Der Zugriff erfolgt anonym über `@page "/webform/{Guid}"` mit `[AllowAnonymous]`.
Aussage: Das System soll anonym erreichbare SelfCare-Formularlinks nach einer konfigurierbaren Frist (Standard 7 Tage ab Erstellung) ungültig machen und abgelaufene Aufrufe mit einer Fehlermeldung statt mit dem Formular beantworten.
Ergebnis: Ein abgelaufener Link liefert kein Formular und keine Kundendaten mehr aus.
Belege:
- [PRIMÄR] src/backend/Centron.BL/SelfCare/SelfCareBL.cs, `GetWebFormByGuid(Guid guid)`: `if (DateTime.Now > expirationDate) return Result<WebForm>.AsError("Link has expired");` mit `?? TimeSpan.FromDays(7).TotalMinutes` als Vorgabewert - Begründung: Serverseitig durchgesetzte Ablaufprüfung; die einzige Stelle, an der ein Formular über die GUID aufgelöst wird.
- [PRIMÄR] src/nexus/CentronNexus/ServiceBoard/TicketWebForms/PublicWebFormPage.razor, `@page "/webform/{Guid}"` + `@attribute [AllowAnonymous]` - Begründung: Belegt, dass die GUID der einzige Zugriffsschutz ist und die Ablauffrist daher sicherheitsrelevant ist.
- [SEKUNDÄR] src/backend/Centron.BL/Administration/Settings/AppSettingsGroupBL.cs, `WebFormLinkExpirationInMinutes = settings.GetInt(ApplicationSettingID.WebFormLinkExpirationInMinutes, null)` - Begründung: Konfigurierbarkeit der Frist.
Prüfidee: `WebForms.CreatedAt` eines Testformulars auf `GETDATE()-8 Tage` setzen und den Link aufrufen: Es muss "Link has expired" zurückkommen und kein Formularinhalt geladen werden.
Tracelinks: StRS-507, SwRS-536
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Zeitliche Begrenzung ist bei GUID-only-Zugriff die zentrale Schutzmaßnahme.
Status: belegt
Modul: M-42
ID: SyRS-522
Titel: Eskalationsprüfung als lizenzpflichtiger zyklischer Hintergrunddienst
Ebene: SyRS
Typ: Schnittstelle
Qualitätsmerkmal:
Akteur: Centron-Host (Hintergrunddienst)
Vorbedingung: Der Centron-Host läuft.
Fakt: `EscalationsService : ManagedBackgroundService` gibt `ServiceName => "EscalationsService"` und `GetExecutionInterval() => TimeSpan.FromMinutes(15)` zurück. In `ExecuteService` bricht der Dienst ab, wenn `LicenseManager.Instance.HasLicense(LicenseGuids.EscalationsServer)` falsch ist, und protokolliert "Lizenz für Eskalationsserver ist nicht vorhanden"; andernfalls öffnet er eine `BLSession` und ruft `EscalationWebserviceBL.CheckEscalations()`.
Aussage: Das System soll die Eskalationsprüfung serverseitig alle 15 Minuten automatisch ausführen und diese Ausführung an die Lizenz `EscalationsServer` binden.
Ergebnis: Eskalationen laufen ohne Benutzerinteraktion; ohne Lizenz findet keine Prüfung statt und der Grund ist protokolliert.
Belege:
- [PRIMÄR] src/webservice/Centron.Host/AspNetCore/HostedServices/EscalationsService.cs, `protected override TimeSpan GetExecutionInterval() { return TimeSpan.FromMinutes(15); }` und `if (!LicenseManager.Instance.HasLicense(LicenseGuids.EscalationsServer)) { Logger.Info("Lizenz für Eskalationsserver ist nicht vorhanden"); return; }` - Begründung: Durchgesetzter Ausführungstakt und Lizenzgate.
- [PRIMÄR] src/backend/Centron.Interfaces/Administration/Logins/LicenseGuids.cs, `EscalationsServer = Guid.Parse("C205E2FF-BA14-4610-A9A7-02C229187430")` - Begründung: Konkrete Lizenzkennung.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Administration/EscalationsSettings/EscalationType/EscalationTypeSettingViewModel.cs, `TollTipNoLizenz` mit Text "Um mehr Information über Eskalationen zu bekommen … wenden Sie sich bitte an den c-entron Vertrieb" - Begründung: UI-Hinweis auf die Lizenzpflicht.
Prüfidee: Host ohne Lizenz `EscalationsServer` starten: Im Log erscheint "Lizenz für Eskalationsserver ist nicht vorhanden" und es werden keine Eskalationsmails versendet.
Tracelinks: StRS-509, SwRS-538
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Serverseitige Automatik ist Voraussetzung für verlässliche SLA-Eskalation.
Status: belegt
Modul: M-44
@@ -0,0 +1,188 @@
ID: StRS-601
Titel: Zentraler Artikelstamm als führende Quelle für Produkt- und Preisdaten
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Artikelverantwortlicher / Einkauf
Vorbedingung: Mandant ist eingerichtet, Warengruppen und Mehrwertsteuersätze sind gepflegt.
Fakt: Die Tabelle `dbo.ARTIK` führt sämtliche artikelbezogenen Stamm-, Bestands- und Preisfelder in einem Satz (u. a. `Artikelcode`, `Kurzbegriff`, `Artikelbeschreibung`, `EK`, `VK_1`..`VK_4`, `EVK`, `Mindestpreis`, `Listenpreis`, `Warengruppe`, `EANCode`, `Hersteller`, `EOL`, `Einheit`, `Nachkommastellen`). `ArticleBL` kapselt Lesen, Anlegen, Kopieren und Speichern dieses Satzes.
Aussage: Das System soll alle Produkt-, Preis- und Bestandsattribute eines Artikels in einem zentralen Artikelstammsatz führen, auf den Verkauf, Einkauf, Lager und Fakturierung gemeinsam zugreifen.
Ergebnis: Ein Artikel besitzt genau einen Stammsatz; nachgelagerte Module (Belege, Preisfindung, Lager) lesen Preise und Attribute ausschließlich aus diesem Satz bzw. aus den daran gehängten Spezialtabellen.
Belege:
- [PRIMÄR] `SSMS_DB_SCHEMA.sql`, `CREATE TABLE [dbo].[ARTIK]` (Zeile 5198 ff.) - Begründung: Das Schema belegt, dass Preis-, Bestands-, Klassifizierungs- und Lebenszyklusfelder physisch in einer Tabelle zusammengeführt sind.
- [PRIMÄR] `src/backend/Centron.BL/Warehousing/ArticleBL.cs`, `ArticleBL.SaveArticle(Article, LoggedInUser, bool)` (Zeile 761) - Begründung: Einziger Schreibpfad der Geschäftslogik für den Artikelstamm; setzt Rechte-, Konsistenz- und Sperrprüfungen zentral durch.
- [SEKUNDÄR] `src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs`, Registrierung `ArticleManagementAppModuleController` mit Kommentar `// Artikelverwaltung` - Begründung: Belegt die Artikelverwaltung als eigenständiges Fachmodul.
Prüfidee: Einen Artikel anlegen, in `ARTIK` prüfen, dass genau ein Datensatz entsteht und dass anschließend Beleg-, Lager- und Preisfindungsmodule dieselben Werte (EK, VK1–VK4, Warengruppe) lesen.
Tracelinks: SyRS-610, SyRS-614, SyRS-621, SwRS-622, SwRS-623
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Ein zentraler Artikelstamm ist fachlich zwingend und in jeder Zielarchitektur erforderlich.
Status: belegt
Modul: M-47
ID: StRS-602
Titel: Kundenindividuelle Sonderpreise und Preisvereinbarungen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertrieb / Preisverantwortlicher
Vorbedingung: Kunde und Artikel bzw. Warengruppe sind angelegt.
Fakt: `AccountSpecialPrice` (Tabelle `dbo.KundenSonderpreise`) speichert je Kunde eine Preisregel mit `ArtikelI3D` oder `WarengruppeI3D`/`UnterwarengruppeI3D`, `GiltVon`/`GiltBis`, `Aufschlag` (Wert) und `Basis` (`SpecialPriceKind`). `ReceiptItemPriceBL.GetBasePrice` wertet genau diese Regel bei jeder Belegposition aus.
Aussage: Das System soll je Kunde zeitlich befristete Sonderpreise auf Artikel- oder Warengruppenebene verwalten und diese bei jeder Preisermittlung einer Belegposition automatisch anwenden.
Ergebnis: Belegpositionen für einen Kunden mit gültigem Sonderpreis werden mit dem daraus abgeleiteten Basispreis und Rabatt bepreist statt mit dem Standard-VK.
Belege:
- [PRIMÄR] `src/backend/Centron.BL/Sales/Receipts/Internal/ReceiptItemPriceBL.cs`, `GetBasePrice(...)`, Zweig `CustomerSpecialPrice specialPrice = this.GetSpecialPrice(article, customer); if (specialPrice != null) switch (specialPrice.Kind)` (Zeilen 232–258) - Begründung: Durchsetzende Stelle der Preisfindung; ohne diesen Zweig hätte der Sonderpreis keine Wirkung auf den Belegpreis.
- [PRIMÄR] `src/backend/Centron.DAO/Mappings/Accounts/SpecialPrices/AccountSpecialPriceMaps.cs`, `Table("KundenSonderpreise")`, Mapping `Value -> "Aufschlag"`, `ValueKind -> "Basis"`, `ValidFrom -> "GiltVon"`, `ValidTo -> "GiltBis"` - Begründung: Belegt Persistenzmodell und Zeitbezug der Preisregel.
- [SEKUNDÄR] `src/centron/Centron.WPF.UI/Modules/Finances/Crm/SpecialPrices/AccountSpecialPriceViewModel.cs`, Dialogtext `"Sonderpreis wird gespeichert..."` - Begründung: Bestätigt die fachliche Bezeichnung „Sonderpreis" in der Oberfläche.
Prüfidee: Für Kunde K und Artikel A einen Festpreis mit Gültigkeit ab heute anlegen, ein Angebot für K mit A erzeugen und prüfen, dass der Positionspreis dem Festpreis entspricht.
Tracelinks: SyRS-611, SyRS-612, SyRS-613, SwRS-624, SwRS-625, SwRS-626
Konsolidierung: Kandidat: StRS-605, StRS-606 (mehrere getrennte Importwege für dieselbe Preisregel)
Übernahmewürdigkeit: übernehmen - Kundenspezifische Preise sind abrechnungsrelevantes Kerngeschäft.
Status: belegt
Modul: M-52
ID: StRS-603
Titel: Automatisierter Import von Distributor-Artikel- und Preisdaten
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Einkauf / Systemadministrator
Vorbedingung: Für einen Distributor ist eine Importdefinition mit Datenquelle, Trennzeichen und Feldzuordnung hinterlegt.
Fakt: `ArticleImportBL` lädt Distributordateien über FTP/SFTP/HTTP (`FTPDownload`, `SFTPDownload`, `HTTPDownload`), entpackt sie (`ExtractZipFile`, `ExtractGZFIle`), schreibt jede Zeile gemäß Feldzuordnung nach `WriteLineToDB` in die Distributorartikelliste und protokolliert jeden Schritt über `WriteLOG(...)` in `ArticleImportLog`.
Aussage: Das System soll Artikel-, Verfügbarkeits- und Preisdaten von Distributoren automatisiert aus konfigurierbaren Dateiquellen einlesen, in eine Distributorartikelliste überführen und den Importlauf nachvollziehbar protokollieren.
Ergebnis: Nach einem Importlauf stehen aktuelle Distributorartikel mit EK, VK und Verfügbarkeit zur Verfügung; Abweichungen und Fehler sind im Importlog dokumentiert.
Belege:
- [PRIMÄR] `src/backend/Centron.BL/Warehousing/ArticleManagement/ArticleImportBL.cs`, `StartImport(int articleImportID, ArticleImportOwner owner)` (Zeile 82) und `WriteLineToDB(...)` (Zeile 882) - Begründung: Durchsetzende Stelle des Importlaufs inkl. Zeilenverarbeitung und Ablage.
- [PRIMÄR] `src/backend/Centron.BL/Warehousing/ArticleManagement/ArticleImportBL.cs`, `WriteLOG(ArticleImport, string, ArticleImportLogState)` (Zeile 1593) - Begründung: Erzwingt die Protokollierung jedes Importereignisses in `ArticleImportLog`.
- [SEKUNDÄR] `src/webservice/Centron.WebServices.Core/Entities/Warehousing/ArticleImports/ArticleImportType.cs`, Werte `Standard`, `AvailabilityUpdate` („Verfügbarkeit aktualisieren"), `Accessories` („Zubehör Artikel anlegen") - Begründung: Belegt die fachlichen Importarten.
Prüfidee: Eine CSV-Testdatei mit fünf Distributorartikeln importieren und prüfen, dass fünf Distributorartikel-Datensätze entstehen und im Importlog ein Eintrag pro Lauf existiert.
Tracelinks: SyRS-616, SwRS-627, SwRS-628
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Automatisierte Lieferantendatenversorgung ist für den Handelsprozess unverzichtbar.
Status: belegt
Modul: M-48
ID: StRS-604
Titel: Warengruppen als Klassifizierungs- und Kalkulationsrahmen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Controlling / Artikelverantwortlicher
Vorbedingung: Mindestens eine Warengruppe ist angelegt.
Fakt: `MaterialGroupBL.CreateMaterialGroup` legt Warengruppen mit Aufschlagsstaffeln (`MaterialMarkups`) an; `ArticleBL.CalculateArticleMarkups(Article, IList<MaterialMarkup>)` errechnet daraus VK1–VK4, EVP und Mindestpreis. `MaterialGroup` trägt zusätzlich Erlös-/Aufwandskonten, Kostenstelle, Kostenträger und Kennzeichen wie `NotDiscountable`, die per `ArticleTakeOnOptions` auf Artikel übertragen werden.
Aussage: Das System soll Artikel über Warengruppen und Unterwarengruppen klassifizieren und aus deren Aufschlagsstaffeln sowie Buchungs- und Steuerungsattributen Vorgabewerte für die Artikelkalkulation ableiten.
Ergebnis: Ein der Warengruppe zugeordneter Artikel erhält Verkaufspreise, EVP, Mindestpreis sowie Konten- und Kostenzuordnung gemäß der Warengruppendefinition.
Belege:
- [PRIMÄR] `src/backend/Centron.BL/Warehousing/ArticleBL.cs`, `CalculateArticleMarkups(Article article, IList<MaterialMarkup> markups)` (Zeilen 3557–3579), Auswahl `markups.Where(x => x.TillEk <= article.PurchasePrice).OrderByDescending(x => x.TillEk).FirstOrDefault()` - Begründung: Durchsetzende Stelle der warengruppenbasierten Preiskalkulation.
- [PRIMÄR] `src/backend/Centron.BL/Warehousing/InventoryManagement/MaterialGroupBL.cs`, `CreateMaterialGroup(MaterialGroup, AppUser)` (Zeile 77 ff.) - Begründung: Legt Warengruppe samt Aufschlagsstaffeln an und erzwingt deren Konsistenz.
- [SEKUNDÄR] `src/centron/Centron.WPF.UI/Modules/Warehousing/MaterialGroupManagement/Actions/ReCalculateArticleAction.cs`, `ActionName = "Artikel neu kalkulieren"`, `ActionHint = "Kalkuliert alle Artikel, die der Warengruppe angehören neu"` - Begründung: UI-Beleg für den fachlichen Zweck der Warengruppe als Kalkulationsrahmen.
Prüfidee: Warengruppe mit Staffel „ab EK 0 → VK1 +30 %" anlegen, Artikel mit EK 100 zuordnen, neu kalkulieren und VK1 = 130 prüfen.
Tracelinks: SyRS-615, SwRS-629
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Warengruppengestützte Kalkulation ist zentrale Steuerungsfunktion.
Status: belegt
Modul: M-49
ID: StRS-605
Titel: Projektpreise aus Distributor-Tabellen in Sondervereinbarungen übernehmen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertrieb / Projektbetreuer
Vorbedingung: Eine Sondervereinbarung (Projektpreis) existiert; eine Excel-/CSV-Datei des Distributors mit Herstellercodes und Projektpreisen liegt vor.
Fakt: `ProjectPriceImportViewModel` liest über eine konfigurierbare Schnittstelle (`ImportInterfaceViewModel`, Spaltenarten in `ProjectPriceImportColumnKind`) Zeilen aus einer Tabellendatei, erzeugt je Zeile ein `SpecialAgreementArticleDTO` mit `SpecialAgreementI3D` und `SpecialAgreementEK` und stellt Abweichungen zu bestehenden Positionen in `SpecialAgreementDifferenceViewModel` gegenüber.
Aussage: Das System soll projektbezogene Einkaufspreise aus Distributor-Tabellendateien in die Artikelpositionen einer Sondervereinbarung übernehmen und dabei Preisabweichungen zum bisherigen Stand vor der Übernahme anzeigen.
Ergebnis: Die Sondervereinbarung enthält für die importierten Herstellercodes aktualisierte Projekt-EKs; abweichende Preise wurden dem Anwender zur Bestätigung vorgelegt.
Belege:
- [PRIMÄR] `src/centron/Centron.WPF.UI/Modules/ProjectPriceImport/ProjectPriceImportViewModel.cs`, Zeilen 426–451: Erzeugung `new SpecialAgreementArticleDTO { SpecialAgreementI3D = this.SelectedSpecialAgreement.I3D, State = 1 }` und Zuweisung `agreementArticle.SpecialAgreementEK = projectPrice` - Begründung: Durchsetzende Stelle der Preisübernahme in die Sondervereinbarung.
- [PRIMÄR] `src/centron/Centron.WPF.UI/Modules/ProjectPriceImport/ProjectPriceImportViewModel.cs`, Zeilen 527–533 und 587–592: Aufbau der `Differences` und modaler `DifferenceViewModel` - Begründung: Erzwingt die Abweichungsanzeige vor Übernahme.
- [SEKUNDÄR] `src/centron/Centron.WPF.UI/Modules/ProjectPriceImport/ProjectPriceImportColumnKind.cs`, Werte `ManufacturerCode`, `ProjectPrice`, `Validity`, `BundleInformation` - Begründung: Belegt den fachlichen Umfang der Importschnittstelle.
Prüfidee: Datei mit zwei bekannten Herstellercodes und geändertem Preis importieren; prüfen, dass der Differenzdialog beide Positionen mit Alt-/Neuwert zeigt und nach Bestätigung `SpecialAgreementEK` aktualisiert ist.
Tracelinks: StRS-602, SwRS-633, SwRS-634, SwRS-626
Konsolidierung: Kandidat: StRS-602, StRS-606 (getrennte Importwege für Preisregeln)
Übernahmewürdigkeit: übernehmen - Projektpreisgeschäft ist im ITK-Handel abrechnungsrelevant.
Status: belegt
Modul: M-56
ID: StRS-606
Titel: Vertragsbezogener Datenimport für verbrauchsabhängige Abrechnung
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Abrechnung / Vertragsverwaltung
Vorbedingung: Verträge und Artikel sind angelegt; eine Importschnittstelle mit Spaltenzuordnung ist konfiguriert.
Fakt: `SpecialArticleToContractImportViewModel` liest Excel-/CSV-/TXT-Dateien und ordnet Dateispalten über `CustomGatewayDefinitions` den Feldern aus `SpecialArticleToContractImportColumnKind` zu (u. a. `CustomerNumber`, `ContractNumber`, `ArticleCode`, `Price`, `PurchasePrice`, `Quantity`, `BillingDate`, `SubscriptionId`). Das Modul ist in `ModuleRegistration.cs` als „Dynamischer Datenimport - Verträge" registriert.
Aussage: Das System soll periodisch anfallende, vertragsbezogene Verbrauchs- und Preisdaten externer Anbieter über konfigurierbare Spaltenzuordnungen einlesen und als abrechenbare Sonderartikel-Positionen an die zugehörigen Verträge anhängen.
Ergebnis: Zu jedem importierten Datensatz existiert eine Vertragsposition mit Kunde, Vertrag, Artikel, Menge und Preisen, die in die Vertragsfakturierung eingeht.
Belege:
- [PRIMÄR] `src/centron/Centron.WPF.UI/Modules/Sales/SpecialArticleToContractImport/SpecialArticleToContractImportViewModel.cs`, Zeilen 940–1024: Zuordnung `foreach (var row in this.SelectedInterface.CustomGatewayDefinitions.Where(f => f.CentronColumn > 0))` mit `switch ((SpecialArticleToContractImportColumnKind)row.CentronColumn)` - Begründung: Durchsetzende Stelle der konfigurierbaren Spaltenzuordnung.
- [PRIMÄR] `src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs`, `ModuleRegistrationItem.For<SpecialArticleToContractImportAppModuleController>(() => Helper.HasRights(UserRightsConst.Sales.ID, UserRightsConst.Sales.AUTOMATED_BILLING), () => LicenseManager.Instance.HasLicense(LicenseGuids.DynamicDataImportContracts) || ...)` - Begründung: Belegt die fachliche Einordnung als Bestandteil der automatisierten Abrechnung.
- [SEKUNDÄR] `src/centron/Centron.WPF.UI/Modules/Sales/SpecialArticleToContractImport/Settings/InterfaceTemplateKind.cs`, Werte `Ingram`, `ALSO` - Begründung: Belegt vorkonfigurierte Distributorschnittstellen.
Prüfidee: Testdatei mit einer Zeile (Kundennummer, Vertragsnummer, Artikelcode, Menge, Preis) über eine eigene Schnittstellendefinition importieren und prüfen, dass am Vertrag eine Position mit diesen Werten entsteht.
Tracelinks: SyRS-620, SwRS-635
Konsolidierung: Kandidat: StRS-602, StRS-605 (getrennte Importwege für Preisregeln)
Übernahmewürdigkeit: übernehmen - Subscription-/Verbrauchsabrechnung ist tragendes Geschäftsmodell.
Status: belegt
Modul: M-53
ID: StRS-607
Titel: Kundenmatrix zur Bewertung der Produktdurchdringung je Kunde
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertrieb / Kundenbetreuer
Vorbedingung: Kategorien und Produkte der Kundenmatrix sind in den globalen Einstellungen angelegt; ein Kunde ist im CRM geöffnet.
Fakt: `CustomerProductMatrixRating` verknüpft `CustomerNumber` mit `CustomerProductMatrixProductI3D` und einem Wert aus `CustomerProductMatrixRatingValue` (`Nothing` = „Nichts klassifiziert", `Bad` = „Keine Leistung vorhanden", `Medium` = „Von Fremdpartner vorhanden", `Good` = „Von uns eingeführt vorhanden"); jede Änderung wird in `CustomerProductMatrixRatingChangeLogs` mit `EmployeeI3D`, `Timestamp` und `Reason` festgehalten.
Aussage: Das System soll je Kunde für jedes Produkt der Kundenmatrix einen Durchdringungsstatus führen und jede Statusänderung mit Bearbeiter, Zeitpunkt und Begründung protokollieren.
Ergebnis: Der Vertrieb sieht je Kunde, welche Produktkategorien durch das eigene Haus, durch Fremdpartner oder gar nicht abgedeckt sind, und kann die Historie der Einstufung nachvollziehen.
Belege:
- [PRIMÄR] `src/backend/Centron.DAO/Mappings/ProductMatrix/CustomerProductMatrixRatingMaps.cs`, `Table("CustomerProductMatrixRating")` mit `HasMany(f => f.CustomerProductMatrixRatingChangeLogs).Table("CustomerProductMatrixRatingChangeLogs").Not.KeyNullable().Cascade.All()` - Begründung: Erzwingt, dass jede Bewertung ihre Änderungshistorie mitführt.
- [PRIMÄR] `src/backend/Centron.BL/ProductMatrix/ProductMatrixBL.cs`, `GetProductMatrixCustomerProductRating(int, int)` (Zeilen 165–181) - Begründung: Legt fehlende Bewertungen mit Startwert `CustomerProductMatrixRatingValue.Nothing` an und stellt so Vollständigkeit je Kunde sicher.
- [SEKUNDÄR] `src/webservice/Centron.WebServices.Core/Entities/ProductMatrix/CustomerProductMatrixRatingValue.cs`, `[Description("Von uns eingeführt vorhanden")]` - Begründung: Belegt die fachliche Semantik der Bewertungsstufen.
Prüfidee: Bewertung eines Produkts von „Nichts klassifiziert" auf „Von uns eingeführt vorhanden" ändern und prüfen, dass in `CustomerProductMatrixRatingChangeLogs` ein Eintrag mit Mitarbeiter und Zeitstempel entsteht.
Tracelinks: SwRS-636
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Cross-Selling-Steuerung mit Historie ist eigenständiger fachlicher Nutzen.
Status: belegt
Modul: M-54
ID: StRS-608
Titel: Produktlebenszyklus von Lizenz- und Vertragsprodukten überwachen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertrieb / Lizenzmanagement
Vorbedingung: PLM-Lizenz ist aktiv; Produktfamiliengruppen und Produktfamilien sind angelegt.
Fakt: `ProductLifecycleInformation` verknüpft Produktfamilie, Kunde, Rechnung/Rechnungsposition, Artikel, Barcode, Lizenzschlüssel sowie `StartDate`/`EndDate`. `ProductLifecycleSettingsViewModel` hält Einstellungen für Erinnerungsvorlauf (`LicenseReminderFromDate`), Toleranztage (`DaysToTolerate`), Zuständigen Mitarbeiter und eine E-Mail-Vorlage; `PlmViewModel` bietet Kommandos `SendReminderEmailCommand`, `DeactivateLicenseCommand` und `ImportProductLifecycleInformationsCommand`.
Aussage: Das System soll den Lebenszyklus lizenz- und vertragsgebundener Produkte je Kunde mit Start- und Enddatum führen und rechtzeitig vor Ablauf eine Erinnerung an den zuständigen Mitarbeiter oder Kundenkontakt auslösen.
Ergebnis: Ablaufende Lizenzen sind vor Erreichen des Enddatums sichtbar; zu jeder Lizenz existiert eine nachvollziehbare Zuordnung zu Rechnung, Artikel und Kunde.
Belege:
- [PRIMÄR] `src/centron/Centron.WPF.UI/Modules/PLM/ProductLifecycleInformationViewModel.cs`, `ApplyStartDateChanges()` mit `this.EndDate = this.StartDate?.AddMonths(this.ProductFamilyLifetimeInMonths);` (Zeile 537) - Begründung: Durchsetzende Stelle der Enddatumsberechnung aus der Produktfamilien-Laufzeit.
- [PRIMÄR] `src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs`, `// PLM (Lifecycle)` mit `ModuleRegistrationItem.For<PlmAppModuleController>(() => Helper.HasRights(UserRightsConst.Sales.Customer.CustomerCommon.LICENSE_MANAGEMENT), () => LicenseManager.Instance.HasLicense(LicenseGuids.PLM))` - Begründung: Belegt Zweck und Zugangssteuerung des Moduls.
- [SEKUNDÄR] `src/centron/Centron.WPF.UI/Modules/Finances/ProductLifecycleManagement/Settings/ProductLifecycleSettingsViewModel.cs`, Felder `LicenseReminderFromDate`, `DaysToTolerate`, `LicenseEmailTemplateBody` - Begründung: Belegt den Erinnerungsprozess als konfigurierbaren Bestandteil.
Prüfidee: Produktfamilie mit Laufzeit 12 Monaten anlegen, Lizenz mit Startdatum heute erfassen und prüfen, dass das Enddatum auf heute+12 Monate gesetzt wird und die Lizenz im Erinnerungsvorlauf erscheint.
Tracelinks: SwRS-637
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Ablaufüberwachung von Lizenzen ist umsatzsichernd.
Status: belegt
Modul: M-57
ID: StRS-609
Titel: Rechte- und lizenzgesteuerter Zugang zu artikel- und preisführenden Modulen
Ebene: StRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Systemadministrator / Anwender
Vorbedingung: Benutzer ist angemeldet; Rechteprofil und Lizenzumfang des Mandanten sind hinterlegt.
Fakt: `ModuleRegistration.cs` registriert jedes Modul mit zwei Prädikaten: einer Rechteprüfung (`Helper.HasRights(...)`) und einer Lizenzprüfung (`LicenseManager.Instance.HasLicense(...)`). Für den zuständigen Bereich sind das u. a. Artikelverwaltung (`UserRightsConst.Purchase.ID`, `UserRightsConst.Purchase.StockList.ID`), Artikelimport (`UserRightsConst.DataExchange.ARTICLE_IMPORT` = 10713), Warengruppenverwaltung (`UserRightsConst.RIGHT_WARENGRUPPEN` = 10440), Projektpreisimport (`UserRightsConst.Sales.Customer.CustomerCommon.Project_Price_Import` = 20800043), beide Vertragsimporte (`UserRightsConst.Sales.AUTOMATED_BILLING` = 10385) und PLM (`LICENSE_MANAGEMENT` = 20800018).
Aussage: Das System soll den Zugang zu jedem artikel- und preisführenden Modul ausschließlich dann gewähren, wenn der Benutzer das modulspezifische Recht besitzt und der Mandant die zugehörige Lizenz hält.
Ergebnis: Module ohne Recht oder ohne Lizenz erscheinen dem Anwender nicht in der Modulliste und lassen sich nicht öffnen.
Belege:
- [PRIMÄR] `src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs`, `ModuleRegistrationItem.For<ArticleImportAppModuleController>(() => Helper.HasRights(UserRightsConst.DataExchange.ARTICLE_IMPORT), () => LicenseManager.Instance.HasLicense(LicenseGuids.ArticleImport) || LicenseManager.Instance.HasLicense(LicenseGuids.Centron))` - Begründung: Durchsetzende Stelle mit konkreter, zweifacher Bedingung (Recht UND Lizenz) für die Modulanzeige.
- [PRIMÄR] `src/webservice/Centron.WebServices.Core/EntitiesWrongPlace/Administration/Rights/UserRightsConst.cs`, Zeilen 1569, 2028, 2436, 2613 (`RIGHT_WARENGRUPPEN = 10440`, `Project_Price_Import = 20800043`, `AUTOMATED_BILLING = 10385`, `ARTICLE_IMPORT = 10713`) - Begründung: Belegt die konkreten geprüften Rechte-IDs.
- [SEKUNDÄR] `CentronRights.md` - Begründung: Dokumentation des Rechtemodells im Repository-Wurzelverzeichnis.
Prüfidee: Benutzer ohne Recht 10713 anmelden und prüfen, dass „Artikelimport" nicht in der Modulliste erscheint; anschließend Lizenz `ArticleImport` entziehen und prüfen, dass das Modul auch mit Recht verborgen bleibt.
Tracelinks: SwRS-622, SwRS-631
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Zugriffssteuerung auf preisführende Module ist sicherheitskritisch.
Status: belegt
Modul: M-47
@@ -0,0 +1,250 @@
ID: SyRS-610
Titel: Systemweite Eindeutigkeit des Artikelcodes
Ebene: SyRS
Typ: Daten
Qualitätsmerkmal:
Akteur: Artikelverwaltung / Datenbank
Vorbedingung: Ein Artikel wird angelegt oder sein Artikelcode wird geändert.
Fakt: Auf `dbo.ARTIK` besteht der eindeutige Index `ARTIK0` über die Spalte `Artikelcode`. `ArticleBL.GenerateArticleCodeRandom()` erzeugt Codes in einer Schleife und bricht erst ab, wenn `HasRecord(x => x.ArticleCode == generatedString)` false liefert.
Aussage: Das System soll sicherstellen, dass ein Artikelcode systemweit nur einmal vergeben ist, und die Anlage eines Artikels mit bereits vergebenem Code ablehnen.
Ergebnis: Ein zweiter Artikel mit identischem `Artikelcode` kann nicht gespeichert werden; die Datenbank weist den Schreibvorgang zurück.
Belege:
- [PRIMÄR] `SSMS_DB_SCHEMA.sql`, Zeile 57146: `CREATE UNIQUE NONCLUSTERED INDEX [ARTIK0] ON [dbo].[ARTIK] ([Artikelcode] ASC)` - Begründung: Datenbank-Constraint, das die Eindeutigkeit technisch erzwingt.
- [PRIMÄR] `src/backend/Centron.BL/Warehousing/ArticleBL.cs`, `GenerateArticleCodeRandom()` (Zeilen 2001–2014), Schleifenbedingung `if (!Session.GetGenericDAO<Article>().HasRecord(x => x.ArticleCode == generatedString)) break;` - Begründung: Anwendungsseitige Durchsetzung derselben Regel bei automatischer Codevergabe.
Prüfidee: Zwei Artikel mit identischem Artikelcode anzulegen versuchen; der zweite Speichervorgang muss mit einem Eindeutigkeitsfehler scheitern.
Tracelinks: StRS-601, SwRS-623
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Eindeutiger Artikelschlüssel ist Grundvoraussetzung für Beleg- und Bestandsführung.
Status: belegt
Modul: M-47
ID: SyRS-611
Titel: Rangfolge der Preisquellen bei der Positionspreisermittlung
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Preisfindung (ReceiptItemPriceBL)
Vorbedingung: Für eine Belegposition sind Artikel, optional Kunde, optional Vertrag und optional Sondervereinbarung bekannt.
Fakt: `ReceiptItemPriceBL.GetBasePrice` prüft in fester Reihenfolge: (1) `ContractSpecialPrice` über `GetContractSpecialPrice(article, contractI3D)`; nur wenn dieser `null` ist, (2) `CustomerSpecialPrice` über `GetSpecialPrice(article, customer)`; nur wenn auch dieser `null` ist und eine Menge übergeben wurde, (3) Staffelpreis über `GetArticleVolumePrice(article, quantity)`. Ohne Treffer bleibt der Basispreis der kundenspezifische Verkaufspreis aus `GetSellPriceForCustomer(article, customer)`.
Aussage: Das System soll den Basispreis einer Belegposition in der Rangfolge Vertragssonderpreis vor Kundensonderpreis vor Mengenstaffelpreis vor Standard-Verkaufspreis ermitteln und dabei nur die erste zutreffende Quelle anwenden.
Ergebnis: Bei gleichzeitigem Vorliegen von Vertragssonderpreis und Kundensonderpreis wird ausschließlich der Vertragssonderpreis wirksam; der Staffelpreis greift nur, wenn weder Vertrags- noch Kundensonderpreis existiert.
Belege:
- [PRIMÄR] `src/backend/Centron.BL/Sales/Receipts/Internal/ReceiptItemPriceBL.cs`, `GetBasePrice(...)`, Struktur `if (contractSpecialPrice is not null) { ... } else { CustomerSpecialPrice specialPrice = this.GetSpecialPrice(...); if (specialPrice != null) { ... } else if (quantity != null) { ... GetArticleVolumePrice ... } }` (Zeilen 169–276) - Begründung: Die `if/else`-Verschachtelung ist die durchsetzende Stelle der Rangfolge; sie schließt Mehrfachanwendung technisch aus.
- [PRIMÄR] `src/backend/Centron.BL/Sales/Receipts/Internal/ReceiptItemPriceBL.cs`, Zeile 162: `decimal basePrice = (sellPrice == null) ? this.GetSellPriceForCustomer(article, customer) : sellPrice.Value;` - Begründung: Belegt den Standard-VK als Rückfallwert.
- [KONTEXT] `src/backend/Centron.Interfaces/Accounts/SpecialPrices/SpecialPriceKind.cs`, Kommentar „... extend the calculation in the ReceiptItemPriceBL. Take a look at the GetBasePrice Method there." - Begründung: Bestätigt `GetBasePrice` als zentrale Preisfindungsstelle.
Prüfidee: Für Kunde K, Artikel A und Vertrag V gleichzeitig einen Vertragssonderpreis (50 €) und einen Kundensonderpreis (60 €) hinterlegen; Vertragsposition anlegen und prüfen, dass 50 € angesetzt wird.
Tracelinks: StRS-602, SyRS-612, SyRS-613, SyRS-614
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Die Rangfolge ist abrechnungsrelevant und muss unverändert erhalten bleiben.
Status: belegt
Modul: M-52
ID: SyRS-612
Titel: Fünf Berechnungsarten für Kundensonderpreise
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Preisfindung (ReceiptItemPriceBL)
Vorbedingung: Für Kunde und Artikel existiert ein gültiger Kundensonderpreis.
Fakt: `SpecialPriceKind` definiert genau fünf Werte: `SurchargePurchasePrice` = 0 („Aufschlag auf EK"), `ReduceRecommendedSellPrice` = 1 („Abschlag von EVP"), `FixedPrice` = 2 („Festpreis"), `ReduceSellPrice` = 3 („Abschlag auf VK"), `ReduceListPrice` = 4 („Abschlag auf Listenpreis"). `GetBasePrice` bildet jede Art auf eine Kombination aus Basispreis und Prozentwert ab; ein unbekannter Wert löst `ArgumentOutOfRangeException` aus.
Aussage: Das System soll Kundensonderpreise ausschließlich nach den fünf Berechnungsarten Aufschlag auf EK, Abschlag von EVP, Festpreis, Abschlag auf VK und Abschlag auf Listenpreis auswerten und für jede Art die dort definierte Bezugsgröße als Basispreis heranziehen.
Ergebnis: Bei `SurchargePurchasePrice` ist der Basispreis der Einkaufspreis und der Prozentwert wird als negativer Rabatt (Aufschlag) angesetzt; bei `FixedPrice` ist der Basispreis der hinterlegte Wert; bei `ReduceSellPrice` bleibt der kundenspezifische VK Basis und der Wert wirkt als Rabatt.
Belege:
- [PRIMÄR] `src/backend/Centron.BL/Sales/Receipts/Internal/ReceiptItemPriceBL.cs`, `switch (specialPrice.Kind)` (Zeilen 235–257), u. a. `case SpecialPriceKind.SurchargePurchasePrice: basePrice = purchaseBasePrice; discountAsPercentage -= specialPrice.PricePremium; break;` und `default: throw new ArgumentOutOfRangeException();` - Begründung: Durchsetzende Stelle; die Vorzeichenbehandlung je Art ist hier abschließend festgelegt.
- [PRIMÄR] `src/backend/Centron.Interfaces/Accounts/SpecialPrices/SpecialPriceKind.cs`, Enum mit `[Description(...)]`-Attributen - Begründung: Abschließende Definition des zulässigen Wertebereichs.
- [SEKUNDÄR] `src/backend/Centron.DAO/Mappings/Accounts/SpecialPrices/AccountSpecialPriceMaps.cs`, `this.Map(m => m.ValueKind).Column("Basis").CustomType<SpecialPriceKind>()` - Begründung: Belegt die Persistierung der Art in Spalte `Basis`.
Prüfidee: Für einen Artikel mit EK 100 einen Sonderpreis `SurchargePurchasePrice` mit Wert 20 anlegen und prüfen, dass die Position mit Basispreis 100 und Rabatt −20 % (= 120 €) bepreist wird.
Tracelinks: StRS-602, SyRS-611, SwRS-624
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Die Berechnungsarten sind fakturarelevant und im Bestand referenziert.
Status: belegt
Modul: M-52
ID: SyRS-613
Titel: Trefferreihenfolge und Gültigkeitsprüfung innerhalb der Kundensonderpreise
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Preisfindung (ReceiptItemPriceBL)
Vorbedingung: Für den Kunden existieren mehrere Sonderpreissätze auf Artikel- und Warengruppenebene.
Fakt: `ReceiptItemPriceBL.GetSpecialPrice` filtert zunächst auf Gültigkeit (`ValidFrom == null || ValidFrom.Value.Date <= DateTime.Today` und `ValidTo == null || ValidTo.Value.Date >= DateTime.Today`) und prüft dann in dieser Reihenfolge: exakter Artikeltreffer, danach Warengruppe **und** Unterwarengruppe, danach Warengruppe ohne Unterwarengruppe. Zusätzlich wird bei Konzernstrukturen über `GetCompanyGroupCustomerForReceiptData(customer)` der Konzern-Kunde als Preisträger herangezogen.
Aussage: Das System soll aus den zeitlich gültigen Sonderpreisen eines Kunden zuerst einen artikelgenauen Satz, hilfsweise einen Satz für Warengruppe plus Unterwarengruppe und hilfsweise einen Satz für die Warengruppe allein anwenden; bei Konzernzugehörigkeit sind die Sonderpreise des Konzernkunden maßgeblich.
Ergebnis: Existiert für den Artikel ein eigener Sonderpreis, bleibt ein gleichzeitig vorhandener Warengruppensonderpreis wirkungslos. Abgelaufene oder noch nicht begonnene Sätze werden nicht berücksichtigt.
Belege:
- [PRIMÄR] `src/backend/Centron.BL/Sales/Receipts/Internal/ReceiptItemPriceBL.cs`, `GetSpecialPrice(IPricedArticle, Customer)` (Zeilen 588–629): Filter `validSpecialPrices` sowie die drei aufeinanderfolgenden `if (…Any()) return …First();`-Blöcke - Begründung: Durchsetzende Stelle sowohl der Gültigkeitsprüfung als auch der Spezialitätsreihenfolge.
- [PRIMÄR] `src/backend/Centron.BL/Sales/Receipts/Internal/ReceiptItemPriceBL.cs`, Zeile 595: `var customerForReceiptData = this.GetCompanyGroupCustomerForReceiptData(customer) ?? customer;` - Begründung: Legt den Preisträger bei Konzernstrukturen fest.
- [SEKUNDÄR] `src/backend/Centron.BL/Accounts/SpecialPrices/AccountSpecialPriceBL.cs`, `GetAccountSpecialPrices(...)` Filter `filter.ShowOnlyActive` mit `f.ValidTo == null || f.ValidTo.Value >= DateTime.Now.Date` - Begründung: Zeigt dieselbe Gültigkeitssemantik in der Verwaltungssicht.
Prüfidee: Für Kunde K je einen gültigen Sonderpreis auf Artikel A (Festpreis 10 €) und auf dessen Warengruppe (Festpreis 20 €) anlegen; Position mit A muss 10 € ergeben.
Tracelinks: StRS-602, SyRS-611, SwRS-624, SwRS-625
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Spezialitätsregel ist abrechnungsrelevant.
Status: belegt
Modul: M-52
ID: SyRS-614
Titel: Vier Verkaufspreisstufen je Artikel mit kundenbezogener Preislistenzuordnung
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Preisfindung / Artikelverwaltung
Vorbedingung: Der Artikel besitzt gefüllte Preisfelder; dem Kunden ist eine Preisliste zugeordnet.
Fakt: `dbo.ARTIK` führt die Spalten `VK_1` bis `VK_4`. `ReceiptItemPriceBL.GetSellPriceForCustomer(price1..price4, int? priceList)` bildet `priceList` 0→price1, 1→price2, 2→price3, 3→price4 ab und liefert für jeden anderen Wert price1. Der so gewählte Preis wird zusätzlich mit dem Währungsfaktor des Artikels multipliziert.
Aussage: Das System soll je Artikel vier Verkaufspreisstufen führen und bei der Preisermittlung genau die durch das Preislistenkennzeichen des Kunden bestimmte Stufe verwenden; bei fehlender oder unbekannter Zuordnung soll die erste Stufe gelten.
Ergebnis: Ein Kunde mit Preisliste 2 erhält den Wert aus `VK_3`; ein Kunde ohne Preislistenzuordnung erhält `VK_1`.
Belege:
- [PRIMÄR] `src/backend/Centron.BL/Sales/Receipts/Internal/ReceiptItemPriceBL.cs`, `GetSellPriceForCustomer(decimal price1, decimal price2, decimal price3, decimal price4, int? priceList)` (Zeilen 482–497), `switch (priceList ?? 0)` mit `default: return price1;` - Begründung: Durchsetzende Stelle der Preislistenauswahl inklusive Rückfallregel.
- [PRIMÄR] `SSMS_DB_SCHEMA.sql`, `CREATE TABLE [dbo].[ARTIK]`, Spalten `[VK_1] [float] NULL` bis `[VK_4] [float] NULL` - Begründung: Belegt die vier Preisstufen im Datenmodell.
- [SEKUNDÄR] `src/backend/Centron.BL/Sales/Receipts/Internal/ReceiptItemPriceBL.cs`, `GetSellPriceForCustomer(IPricedArticle, Customer)` (Zeilen 571–579) mit `price * currencyFactor` - Begründung: Belegt die Währungsumrechnung als Teil des Verkaufspreises.
Prüfidee: Artikel mit VK_1=100, VK_3=80 anlegen, Kunde auf Preisliste 2 setzen und prüfen, dass die Belegposition mit 80 bepreist wird.
Tracelinks: StRS-601, SyRS-611
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Preislistenlogik ist im Bestand fest verankert und fakturarelevant.
Status: belegt
Modul: M-47
ID: SyRS-615
Titel: Aufschlagsstaffel der Warengruppe bestimmt VK, EVP und Mindestpreis
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Artikelkalkulation
Vorbedingung: Der Artikel ist einer Warengruppe (bzw. Unterwarengruppe) mit mindestens einer Aufschlagsstaffel zugeordnet und besitzt einen Einkaufspreis.
Fakt: `ArticleBL.CalculateArticleMarkups` wählt aus den Staffeln denjenigen Satz mit dem größten `TillEk`, der noch kleiner oder gleich dem Einkaufspreis des Artikels ist, und berechnet damit `Price1..Price4 = PurchasePrice * (VkXProcent / 100 + 1)`, `EVP = PurchasePrice * (MarkupEVKProcent / 100 + 1)` sowie `MinPrice = PurchasePrice * (MarkupMinPriceProcent / 100 + 1)`. Ohne passenden Staffelsatz bleiben die bisherigen Artikelpreise unverändert.
Aussage: Das System soll die Verkaufspreise, den empfohlenen Verkaufspreis und den Mindestpreis eines Artikels aus dem Einkaufspreis und dem größten passenden Staffelsatz der zugeordneten Warengruppe berechnen; existiert kein passender Staffelsatz, sollen die bestehenden Preise unverändert bleiben.
Ergebnis: Bei EK = 100 und Staffelsatz `TillEk = 0` mit `Vk1Procent = 30` ergibt sich VK1 = 130; die Unterwarengruppenstaffel hat Vorrang, wenn der Artikel einer Unterwarengruppe zugeordnet ist.
Belege:
- [PRIMÄR] `src/backend/Centron.BL/Warehousing/ArticleBL.cs`, `CalculateArticleMarkups(Article, IList<MaterialMarkup>)`, Zeile 3566: `MaterialMarkup selMarkup = markups.Where(x => x.TillEk <= article.PurchasePrice).OrderByDescending(x => x.TillEk).FirstOrDefault();` und Zeilen 3569–3575 - Begründung: Durchsetzende Stelle der Staffelauswahl und Preisformel.
- [PRIMÄR] `src/backend/Centron.BL/Warehousing/ArticleManagement/ArticleImportBL.cs`, `SetSellPrices(...)` Zeilen 1635–1662: Vorrangprüfung `if (articleCompact.SecondaryMaterialGroupI3D > 0) { … SecondaryMaterialMarkup … } else { … MaterialMarkup … }` - Begründung: Belegt den Vorrang der Unterwarengruppenstaffel gegenüber der Warengruppenstaffel.
- [SEKUNDÄR] `src/centron/Centron.WPF.UI/Modules/Warehousing/MaterialGroupManagement/ViewModel/RebookMaterialGroupDialogViewModel.cs`, Meldungstext „Bitte beachten Sie das unter Umständen die Preise VK 1 bis VK 4, EVP und der Mindestpreis verändert werden." - Begründung: Bestätigt den Umfang der betroffenen Preisfelder aus Anwendersicht.
Prüfidee: Zwei Staffelsätze (TillEk 0 → 30 %, TillEk 500 → 20 %) anlegen; Artikel mit EK 600 neu kalkulieren und VK1 = 720 prüfen.
Tracelinks: StRS-604, SwRS-629, SwRS-627
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Zentrale Kalkulationsregel mit direkter Ergebniswirkung.
Status: belegt
Modul: M-49
ID: SyRS-616
Titel: Automatischer Preisupdate-Lauf aus Distributor-Artikeldaten
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Systemdienst (c-entron Systembenutzer)
Vorbedingung: Einstellung `AutomaticallyPriceUpdateAndDistributorsAndAutomaticallyChanges` ist aktiv und enthält eine priorisierte Distributorliste; die Startzeit ist erreicht.
Fakt: `ArticleImportBL.AutomaticPriceUpdate()` ermittelt über `GetDistributorArticleToUpdate` je Artikel den Distributorpreis mit der besten Priorität (Verknüpfung `Artik.EANCode = HerstellerArtik.EANCODE` bzw. `Artik.Hersteller = HerstellerArtik.CODE`, Priorität aus der Reihenfolge der konfigurierten Distributoren), aktualisiert `Artik.EK` und – falls konfiguriert – `VK_1..VK_4`, `Mindestpreis` und `EVK` und schreibt jede Änderung über `ArticleLogBL.WriteLog(...)` mit Alt-/Neuwert und Differenz fort. `NeedTodayUpdate` verhindert einen zweiten Lauf am selben Tag.
Aussage: Das System soll Einkaufs- und optional Verkaufspreise von Artikeln einmal je Tag automatisch aus den Daten des höchstpriorisierten Distributors aktualisieren und jede Preisänderung mit Alt- und Neuwert protokollieren.
Ergebnis: Nach dem Lauf entspricht `Artik.EK` dem Preis des höchstpriorisierten Distributors mit passendem EAN- oder Herstellercode; für jede geänderte Preisspalte existiert ein Eintrag in `ArtikLog`.
Belege:
- [PRIMÄR] `src/backend/Centron.BL/Warehousing/ArticleManagement/ArticleImportBL.cs`, `AutomaticPriceUpdate()` Zeilen 1830–1874: Auswahl `.OrderBy(f => f.Prio).FirstOrDefault()`, Abbruch `if (articleCompact.PurchasePrice == distributorArticlePrice.PurchasePrice) continue;`, Aufruf `RefreshArticle(articleCompact, appUser.Employee.I3D, withSellPrice)` und `articleLogBL.WriteLog(..., ArticleLogKind.PurchasePrice, appUser)` - Begründung: Durchsetzende Stelle der Preisübernahme und der Protokollpflicht.
- [PRIMÄR] `src/backend/Centron.BL/Warehousing/ArticleManagement/ArticleImportBL.cs`, `NeedTodayUpdate(List<string> startTime, int employeeI3D)` (Zeilen 1721–1733): `return !this.Session.GetGenericDAO<ArticleLog>().HasRecord(f => f.EmployeeI3D == employeeI3D && f.Date > DateTime.Today && f.Kind == ArticleLogKind.PurchasePrice);` - Begründung: Erzwingt genau einen Lauf pro Tag.
- [SEKUNDÄR] `src/backend/Centron.BL/Administration/Settings/AppSettingsConst.cs`, Zeile 2552: `AutomaticallyPriceUpdateAndDistributorsAndAutomaticallyChanges = 1182` - Begründung: Belegt den Konfigurationsschalter.
Prüfidee: Distributorartikel mit abweichendem HEK zu einem Artikel mit gleichem EAN anlegen, Lauf auslösen und prüfen, dass `Artik.EK` übernommen wurde und genau ein `ArtikLog`-Eintrag mit Alt-/Neuwert existiert; zweiten Lauf am selben Tag auslösen und prüfen, dass keine weitere Änderung erfolgt.
Tracelinks: StRS-603, SyRS-615, SwRS-627, SwRS-628
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Automatische EK-Pflege ist margenrelevant und protokollpflichtig.
Status: belegt
Modul: M-48
ID: SyRS-617
Titel: Artikeleinheiten mit UNECE-Code und Zeitfaktor
Ebene: SyRS
Typ: Daten
Qualitätsmerkmal:
Akteur: Stammdatenpflege
Vorbedingung: Die Einheitenverwaltung ist geöffnet.
Fakt: `dbo.ArtikelEinheit` führt `KurzText`, `Bezeichnung`, `VPE`, `Standard`, `Status`, `Zeiteinheit` und `FaktorZuSekunde` sowie `UNECECode nvarchar(10)`. `SupportedUNECECodes.Codes` definiert die acht unterstützten Codes SEC (1 s), MIN (60 s), HUR (3600 s), DAY (86400 s), H87, LS, MTR und KMT; nur die ersten vier tragen einen Sekundenfaktor und gelten damit als Zeiteinheit (`IsTimeUnit => FactorToSeconds.HasValue`).
Aussage: Das System soll Artikeleinheiten mit Kurztext, Bezeichnung, Verpackungseinheit und optionalem UNECE-Code führen und Zeiteinheiten zusätzlich über einen Umrechnungsfaktor auf Sekunden abbilden.
Ergebnis: Jede aktive Einheit ist mit Kurztext und Bezeichnung verfügbar; Zeiteinheiten liefern einen konsistenten Sekundenfaktor für Leistungs- und Zeitabrechnung.
Belege:
- [PRIMÄR] `SSMS_DB_SCHEMA.sql`, `CREATE TABLE [dbo].[ArtikelEinheit]` (Zeile 10182 ff.) mit `[Zeiteinheit] [int] NULL`, `[FaktorZuSekunde] [int] NULL`, `[UNECECode] [nvarchar](10) NULL` - Begründung: Belegt das Datenmodell der Einheiten.
- [PRIMÄR] `src/centron/Centron.WPF.UI/Modules/Warehousing/ArticleUnitManagement/ViewModel/SupportedUNECECodes.cs`, Liste `Codes` und `record UNECECodeInfo(string Code, string Description, int? FactorToSeconds) { public bool IsTimeUnit => FactorToSeconds.HasValue; }` - Begründung: Abschließende Definition des zulässigen Wertebereichs und der Zeiteinheitsregel.
- [SEKUNDÄR] `SSMS_DB_SCHEMA.sql`, Zeile 14918: `INNER JOIN ArtikelEinheit ae on ae.I3D = a.Einheit and ae.Zeiteinheit = 1 and ae.Status = 1` - Begründung: Belegt die Auswertung des Zeiteinheitskennzeichens in Datenbanksichten.
Prüfidee: Einheit „Stunde" mit UNECE-Code HUR und Faktor 3600 anlegen; prüfen, dass sie in Abfragen mit `Zeiteinheit = 1` erscheint.
Tracelinks: StRS-601, SwRS-630
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - UNECE-Codes sind für elektronische Rechnungsformate erforderlich.
Status: belegt
Modul: M-50
ID: SyRS-618
Titel: Konfigurierbare Eindeutigkeit von Seriennummern und Barcodes
Ebene: SyRS
Typ: Daten
Qualitätsmerkmal:
Akteur: Lager / Barcodeverwaltung
Vorbedingung: Eine neue Seriennummer soll zu einem Artikel angelegt werden.
Fakt: `BarcodeBL.ValidateNewBarcode(int articleI3D, string barcode)` wertet zwei Einstellungen aus: bei `SameArticleCanHaveSameSerialNumber` (ID 441) wird jede Seriennummer akzeptiert; sonst wird geprüft, ob bereits ein Barcode mit gleichem `Code` existiert, dessen Status weder `ManuallyBookedOut` noch `Deactivated` ist – bei `DifferentArticleCanHaveSameSerialNumber` (ID 440) eingeschränkt auf denselben Artikel. Auf der Tabelle `dbo.Barcode` existiert kein eindeutiger Index über die Spalte `Barcode`.
Aussage: Das System soll die Eindeutigkeit von Seriennummern über zwei Konfigurationsschalter steuern und die gewählte Regel bei jeder Neuanlage einer Seriennummer anwenden.
Ergebnis: Bei Standardkonfiguration wird eine bereits aktiv vergebene Seriennummer abgelehnt; ausgebuchte oder deaktivierte Seriennummern blockieren die Neuvergabe nicht.
Belege:
- [PRIMÄR] `src/backend/Centron.BL/Warehousing/BarcodeBL.cs`, `ValidateNewBarcode(int, string)` (Zeilen 171–191), Ausdruck `a => a.Code == barcode && !(a.State == (int)BarcodeState.ManuallyBookedOut || a.State == (int)BarcodeState.Deactivated)` - Begründung: Durchsetzende Stelle der Eindeutigkeitsprüfung inklusive Statusausnahmen.
- [PRIMÄR] `src/backend/Centron.BL/Administration/Settings/AppSettingsConst.cs`, Zeilen 2518 und 2522 (`DifferentArticleCanHaveSameSerialNumber = 440`, `SameArticleCanHaveSameSerialNumber = 441`) - Begründung: Belegt die konkreten Konfigurationsschalter.
- [SEKUNDÄR] `SSMS_DB_SCHEMA.sql`, Indizes auf `[dbo].[Barcode]` (Zeilen 58653 ff.: nur nicht-eindeutige Indizes auf `AufPosI3D`, `Auftragsnummer`, `LagerI3D`, `LiefPosI3D`, `RechPosI3D`) - Begründung: Belegt, dass die Eindeutigkeit ausschließlich in der Anwendungsschicht durchgesetzt wird.
Prüfidee: Bei Standardeinstellung dieselbe Seriennummer zweimal für denselben Artikel anlegen; der zweite Versuch muss mit „SN existiert bereits" abgelehnt werden.
Tracelinks: StRS-601, SwRS-631, SwRS-632
Konsolidierung: nein
Übernahmewürdigkeit: Workaround - Die Eindeutigkeit wird nur anwendungsseitig geprüft; ein Datenbank-Constraint fehlt, sodass Fremdzugriffe Dubletten erzeugen können.
Status: belegt
Modul: M-51
ID: SyRS-619
Titel: Artikel- und Lieferantensuche als wiederverwendbare Auswahldialoge
Ebene: SyRS
Typ: Schnittstelle
Qualitätsmerkmal:
Akteur: Anwender in beliebigem Beleg- oder Stammdatenkontext
Vorbedingung: Ein Modul benötigt die Auswahl eines Artikels oder eines Herstellers/Lieferanten.
Fakt: `SearchArticleViewModel` implementiert `IDialogWindow` und `ICloseDialogWindow`, trägt den Titel „Artikelauswahl", nimmt einen `ArticleCompactFilter` als Vorbelegung entgegen und liefert die Auswahl seitenweise über `ListThroughPaging<ArticlePreview>`. `SearchSupplierViewModel` implementiert dieselben Schnittstellen, trägt den Titel „Herstellersuche" und lädt über `ISupplierLogic.SearchSuppliers(searchText, 1, int.MaxValue)`.
Aussage: Das System soll die Auswahl von Artikeln und Lieferanten über zentrale, von beliebigen Modulen aufrufbare Dialoge bereitstellen, die einen vorbelegbaren Filter entgegennehmen und das ausgewählte Objekt an den Aufrufer zurückgeben.
Ergebnis: Aufrufende Module erhalten ein einheitlich gefiltertes Auswahlergebnis; die Artikelauswahl liefert Ergebnisse seitenweise, um große Trefferlisten zu beherrschen.
Belege:
- [PRIMÄR] `src/centron/Centron.WPF.UI/Modules/Warehousing/SearchArticle/ViewModel/SearchArticleViewModel.cs`, Klassendeklaration `SearchArticleViewModel : ViewModelBase, IDialogWindow, ICloseDialogWindow`, Konstruktor `SearchArticleViewModel(ArticleCompactFilter articleCompactFilter = null)` und `public string DialogTitle => "Artikelauswahl";` - Begründung: Belegt die Dialogschnittstelle und die Filterübergabe.
- [PRIMÄR] `src/centron/Centron.WPF.UI/Modules/Warehousing/SupplierSearch/ViewModel/SearchSupplierViewModel.cs`, `public string DialogTitle => "Herstellersuche";` und `LoadSuppliers(string searchText)` - Begründung: Belegt den zweiten Auswahldialog mit identischem Schnittstellenmuster.
- [SEKUNDÄR] `src/centron/Centron.WPF.UI/Modules/Warehousing/SearchArticle/DataPager/ListThroughPaging.cs` - Begründung: Belegt die seitenweise Ergebnisbereitstellung.
Prüfidee: Artikelauswahl aus zwei verschiedenen Modulen mit unterschiedlichem Vorfilter öffnen und prüfen, dass jeweils nur die gefilterten Artikel erscheinen und das ausgewählte Objekt an das aufrufende Modul zurückgegeben wird.
Tracelinks: StRS-601, SwRS-639
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Wiederverwendbare Auswahldialoge vermeiden redundante Suchimplementierungen.
Status: belegt
Modul: M-55
ID: SyRS-620
Titel: Preisermittlung importierter Vertragspositionen über die zentrale Preisfindung
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertragsimport (CustomGatewayBL)
Vorbedingung: Eine Importdatei mit Kundennummer, Vertragsnummer, Artikelcode und optional Preisen wurde eingelesen.
Fakt: `CustomGatewayBL` löst je Importzeile Artikel, Vertrag und Kunde auf, ermittelt über `ReceiptItemPriceBL.GetSpecialAgreement(...)` eine gültige Sondervereinbarung und berechnet den Einkaufspreis mit `GetPurchaseBasePrice(...)` sowie den Verkaufspreis mit `GetBasePrice(...)`; anschließend gilt `price = basePrice * (1m - (discount / 100m))`. Ob Importpreise oder ermittelte Preise gewinnen, steuern die Schalter `IgnoreImportPurchasePrices`, `IgnoreImportRetailPrices` und `UseSpecialPriceForArticleIfAvailable`.
Aussage: Das System soll für importierte Vertragspositionen Einkaufs- und Verkaufspreise über dieselbe Preisfindung ermitteln wie für manuell erfasste Belegpositionen und dabei je Importdefinition konfigurierbar entscheiden, ob Preise aus der Datei oder aus der Preisfindung maßgeblich sind.
Ergebnis: Importierte Positionen tragen konsistente Preise; bei aktiviertem `UseSpecialPriceForArticleIfAvailable` schlägt ein hinterlegter Kunden- oder Vertragssonderpreis den Dateipreis.
Belege:
- [PRIMÄR] `src/backend/Centron.BL/Gateway/CustomGatewayBL.cs`, Zeilen 186–210: `var specialAgreement = ... priceBL.GetSpecialAgreement(article.ArticleI3D, customer, CentronObjectKindNumeric.InvoiceClass)`, `priceBL.GetPurchaseBasePrice(art, warehouse: null, specialAgreement: specialAgreement, quantity: article.Quantity, contractI3D)` und `var (basePrice, discount) = priceBL.GetBasePrice(...); var price = basePrice * (1m - (discount / 100m));` - Begründung: Durchsetzende Stelle; belegt die Wiederverwendung der zentralen Preisfindung und die konkrete Preisformel.
- [PRIMÄR] `src/backend/Centron.BL/Gateway/CustomGatewayBL.cs`, Zeile 191: Bedingung `article.Import.IgnoreImportPurchasePrices || (… && article.PurchasePrice <= 0)` - Begründung: Legt fest, wann der Dateipreis verworfen und neu ermittelt wird.
- [SEKUNDÄR] `src/centron/Centron.WPF.UI/Modules/Sales/SpecialArticleToContractImport/SpecialArticleToContractImportColumnKind.cs`, Werte `Price`, `PurchasePrice`, `UnitPrice` - Begründung: Belegt, dass Preise auch aus der Datei stammen können.
Prüfidee: Importdatei mit Preis 0 für einen Artikel mit Kundensonderpreis einlesen; prüfen, dass die erzeugte Vertragsposition den Sonderpreis und nicht 0 trägt.
Tracelinks: StRS-606, SyRS-611, SwRS-635
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Konsistente Preisfindung über alle Erfassungswege ist abrechnungskritisch.
Status: belegt
Modul: M-53
ID: SyRS-621
Titel: Zeitraumgefilterte Aktionspreise im Preisspiegel
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Einkauf / Vertrieb
Vorbedingung: Zu einem Artikel sind Aktionspreise erfasst; der Preisspiegel wird geöffnet.
Fakt: `ActionPrice` (Tabelle `HerstellerArtikAktionspreis`) trägt `Distributor`, `Preis`, `GueltigAb`, `GueltigBis`. `PriceMatrixViewModel.GetPriceItemsFromArticleActionPrices` ermittelt den Artikel zuerst über den Herstellercode, hilfsweise über den EAN-Code, und übernimmt anschließend nur Aktionspreise mit `f.EffectiveFrom.StartOfDay() <= DateTime.Now && f.EffectiveUntil >= DateTime.Now`.
Aussage: Das System soll Aktionspreise eines Artikels als eigene Preisquelle im Preisspiegel anzeigen und dabei ausschließlich Preise berücksichtigen, deren Gültigkeitszeitraum den aktuellen Zeitpunkt einschließt.
Ergebnis: Abgelaufene oder zukünftige Aktionspreise erscheinen nicht im Preisspiegel; gültige erscheinen mit Distributor, Preis und Zeitraum.
Belege:
- [PRIMÄR] `src/centron/Centron.WPF.UI/Modules/Finances/Receipts/PriceMatrix/PriceMatrixViewModel.cs`, `GetPriceItemsFromArticleActionPrices(...)` Zeile 450: `.Where(f => f.EffectiveFrom.StartOfDay() <= DateTime.Now && f.EffectiveUntil >= DateTime.Now) // Only valid action-prices` - Begründung: Durchsetzende Stelle des Zeitraumfilters.
- [PRIMÄR] `src/centron/Centron.WPF.UI/Modules/Finances/Receipts/PriceMatrix/PriceMatrixViewModel.cs`, Zeilen 427–444: Artikelermittlung zuerst über `ArticleCompactFilter { ManufacturerCode = ... }`, dann über `ArticleCompactFilter { EANCode = ... }` - Begründung: Legt die Zuordnungsreihenfolge Herstellercode vor EAN fest.
- [KONTEXT] `docs/reference/receipts/actionprice-system.md`, Abschnitt „Display Rules" - Begründung: Interne Dokumentation bestätigt die Filterregel.
Prüfidee: Zwei Aktionspreise anlegen (einer mit `GueltigBis` gestern, einer mit Zeitraum heute) und prüfen, dass nur der zweite im Preisspiegel erscheint.
Tracelinks: StRS-601, SwRS-638, SwRS-640
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Zeitbefristete Aktionspreise sind einkaufsseitig etabliert.
Status: belegt
Modul: M-47
@@ -0,0 +1,167 @@
ID: StRS-701
Titel: Mehrlagerfähige Artikelbestandsführung mit Hauptlager und Nebenlagern
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Lagermitarbeiter / Warenwirtschaft
Vorbedingung: Ein Artikel ist angelegt und als lagerwirksam gekennzeichnet (ARTIK.Abbuchung = 'J').
Fakt: Der Bestand wird an zwei getrennten Stellen geführt: im Hauptlager als Spalte `ARTIK.Menge` und je Nebenlager als Satz in `NebenlagerArtikel` (Spalte `Bestand`). `ArticleStockRepository.UpdateArticleStock` verzweigt anhand des übergebenen Nebenlager-Schlüssels; die Kennung `-1` bzw. `null` steht durchgängig für das Hauptlager.
Aussage: Das System soll den Bestand eines Artikels getrennt je Lager führen, wobei genau ein Hauptlager (Kennung -1) und beliebig viele Nebenlager unterstützt werden.
Ergebnis: Für jeden lagerwirksamen Artikel ist die Menge je Lager abfragbar; Buchungen wirken ausschließlich auf das adressierte Lager.
Belege:
- [PRIMÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\backend\Centron.DAO\Repositories\Warehousing\StockManagement\ArticleStockRepository.cs - Klasse `ArticleStockRepository`, Methode `UpdateArticleStock(int articleI3D, int? articleSecondaryStorageI3D, double quantity, bool increaseQuantity = false)`; die durchsetzende Verzweigung lautet `if (articleSecondaryStorageI3D is not > 0)` und bucht in diesem Fall auf `ArticleMainStock` (Tabelle ARTIK, Spalte Menge), sonst auf den Nebenlagersatz. Begründung: Das ist die einzige Stelle, an der die Lagerzuordnung einer Bestandsbuchung entschieden wird.
- [PRIMÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\SSMS_DB_SCHEMA.sql - `CREATE TABLE [dbo].[NebenlagerArtikel]` mit den Spalten `[NebenlagerI3D]`, `[ArtikelI3D]`, `[Bestand]`, `[Mindestbestand]`, `[Reparaturbestand]`. Begründung: Belegt die physische Trennung der Bestandsführung je Nebenlager.
- [SEKUNDÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\backend\Centron.BL\Logistics\Warehousing\StockBL.cs - `StockBL.GetMainWarehouse()` gibt `this.GetWarehouse(-1)` zurück, `GetWarehouseCaption` liefert bei leerem Ergebnis den Text "Hauptlager". Begründung: Bestätigt -1 als feste Kennung des Hauptlagers.
Prüfidee: Artikel in Hauptlager und zwei Nebenlager einbuchen; prüfen, dass `ARTIK.Menge` und die jeweiligen `NebenlagerArtikel.Bestand` unabhängig voneinander fortgeschrieben werden und eine Buchung auf Nebenlager A den Bestand in Nebenlager B unverändert lässt.
Tracelinks: SyRS-702, SwRS-703, SwRS-704, SwRS-705
Konsolidierung: Kandidat: StRS-701 / SwRS-704 - Tabelle `Nebenlager` (Legacy) und Tabelle `Warehouses` (neu) bilden dasselbe fachliche Konzept "Lager" doppelt ab.
Übernahmewürdigkeit: übernehmen - Mehrlagerfähigkeit ist tragende Fachanforderung der Warenwirtschaft.
Status: belegt
Modul: M-58
ID: StRS-706
Titel: Inventur als Stichtagszählung mit Komplett- und Teilinventur
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Inventurverantwortlicher
Vorbedingung: Eine Inventur ist angelegt und befindet sich im Status Open oder OpenWithoutBC; mindestens ein Lager ist zur Zählung ausgewählt.
Fakt: Beim Lagerabschluss unterscheidet das System Komplett- und Teilinventur. Nur bei Komplettinventur werden Bestände nicht gezählter, lagerwirksamer Artikel auf 0 gesetzt; bei Teilinventur bricht die Methode vor der Nullsetzung ab.
Aussage: Das System soll Inventuren wahlweise als Komplettinventur oder als Teilinventur durchführen, wobei ausschließlich bei der Komplettinventur nicht gezählte Bestände des abgeschlossenen Lagers auf null gesetzt werden.
Ergebnis: Nach Lagerabschluss entsprechen die Bestände des Lagers dem Zählergebnis; bei Teilinventur bleiben nicht gezählte Artikel unverändert.
Belege:
- [PRIMÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\backend\Centron.DAO\Repositories\Warehousing\InventoryManagement\InventoryRepository.cs - Klasse `InventoryRepository`, Methode `CloseStorageUpdateArticleStockFromNotScannedArticles(int inventoryI3D, int storageI3D, bool isCompleteInventory)`; die durchsetzende Bedingung ist `if (!isCompleteInventory) return;` mit dem Kommentar "Die Bestände dürfen bei einer Teilinventur von einem anderen Lager nicht verändert werden." Anschließend `UPDATE Artik SET Menge = 0 ... WHERE a.Menge <> 0 AND a.Abbuchung = 'J' AND NOT (a.I3D in (SELECT ArtikelI3D FROM InventurBuchungen ...))`. Begründung: Genau diese Bedingung trennt Komplett- von Teilinventur.
- [PRIMÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\webservice\Centron.WebServices.Core\Entities\Warehousing\InventoryManagement\InventoryType.cs - `enum InventoryType { Partial = 1, PartialAllStorage = 2, Complete = 3, CompleteAllStorage = 4 }`. Begründung: Definiert die zulässigen Inventurarten.
- [SEKUNDÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\centron\Centron.WPF.UI\Modules\Warehousing\Inventory\ViewModels\WizardViewModels\ResultViewModel.cs - `ResultViewModel.FinalizeInventory()` übergibt `FinalizeInventoryOption == InventoryType.Complete || FinalizeInventoryOption == InventoryType.CompleteAllStorage` als Parameter `isCompleteInventory`. Begründung: Zeigt die Abbildung der UI-Auswahl auf die Fachregel.
Prüfidee: Teilinventur für Lager A abschließen, in der ein lagerführender Artikel nicht gezählt wurde: dessen Bestand muss unverändert bleiben. Denselben Fall als Komplettinventur abschließen: der Bestand muss auf 0 stehen und ein Satz in `InventurBuchungen` mit `Nachher = 0` existieren.
Tracelinks: SyRS-707, SwRS-708
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - handelsrechtlich erforderliche Bestandsaufnahme; die Unterscheidung Komplett/Teil ist fachlich zwingend.
Status: belegt
Modul: M-59
ID: StRS-709
Titel: Kommissionierung nur für ausdrücklich kommissionierpflichtige Artikel
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Lagermitarbeiter (Kommissionierer)
Vorbedingung: Ein Auftrag befindet sich im aktiven Status (AufKopf.Status = 1) und enthält mindestens eine Position.
Fakt: Die Kommissionierungssicht `cvw_ConsignmentOrder` nimmt nur Auftragspositionen auf, deren Artikel `ARTIK.Kommisionieren = 1` gesetzt hat und deren Warengruppe ungleich dem in `Stammdat` unter I3D 421 hinterlegten Wert ist. Die Positionssicht `cvw_ConsignmentOrderPosition` filtert zusätzlich auf `P.Art = 1`.
Aussage: Das System soll ausschließlich Auftragspositionen zur Kommissionierung anbieten, deren Artikel als kommissionierpflichtig gekennzeichnet ist und deren Warengruppe nicht der konfigurierten Ausschlusswarengruppe entspricht.
Ergebnis: Aufträge ohne kommissionierpflichtige Artikel erscheinen nicht in der Kommissionierliste; Dienstleistungs- bzw. Ausschlusswarengruppen werden nicht kommissioniert.
Belege:
- [PRIMÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\SSMS_DB_SCHEMA.sql - `CREATE VIEW [dbo].[cvw_ConsignmentOrder]`, durchsetzende Bedingung: `WHERE (A.Kommisionieren = 1) AND (A.Warengruppe <> (SELECT Wert FROM dbo.Stammdat WHERE (I3D = 421)))`. Begründung: Filterbedingung der Datenquelle, auf der die gesamte Kommissionierung aufsetzt.
- [PRIMÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\backend\Centron.DAO\Mappings\Warehousing\Commissioning\ArticelHeaderMaps.cs - Klasse `ArticelHeaderMaps` mit `ReadOnly(); Table("cvw_ConsignmentOrder");`. Begründung: Belegt, dass die Kommissionier-BL ausschließlich über diese gefilterte Sicht liest.
- [SEKUNDÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\backend\Centron.DAO\Mappings\Warehousing\ArticleMaps.cs - `Map(aa => aa.Picking).Column("Kommisionieren");`. Begründung: Zeigt die Artikeleigenschaft, die die Kommissionierpflicht steuert.
Prüfidee: Auftrag mit einem Artikel anlegen, bei dem `Kommisionieren = 0` ist: Der Auftrag darf im Kommissioniermodul nicht erscheinen. Flag auf 1 setzen: Der Auftrag muss erscheinen.
Tracelinks: SyRS-710, SwRS-711
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - verhindert, dass nicht lagerbezogene Positionen den Kommissionierprozess belasten.
Status: belegt
Modul: M-60
ID: StRS-717
Titel: Versandart als eigenständiges Stammdatum
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Stammdatenverwalter
Vorbedingung: Der Benutzer hat die Einstellungsmaske "RMA Versandart" geöffnet.
Fakt: Die Versandart ist als Entität `RmaSendKind` auf die Tabelle `VersandArt` gemappt und trägt genau zwei fachliche Felder: `Name` (Spalte Name, Länge 50) und `IsActive` (auf die int-Spalte Status gemappt). Weitere Merkmale wie Gewicht, Kosten oder Dienstleister sind im Stammsatz nicht vorhanden.
Aussage: Das System soll Versandarten als eigenständige Stammdaten mit Bezeichnung und Aktiv-Kennzeichen verwalten, die als Versandweg ausgewählt werden können.
Ergebnis: Versandarten sind anleg- und deaktivierbar und stehen als Auswahlwerte zur Verfügung.
Belege:
- [PRIMÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\backend\Centron.DAO\Mappings\CustomerArea\RmaArea\RmaSendKindMaps.cs - Klasse `RmaSendKindMaps`, Konstruktor: `this.Table("VersandArt"); this.Map(m => m.Name).Column("Name").Length(50); this.Map(m => m.IsActive).Column("Status");`. Begründung: Definiert vollständig und abschließend die Felder des Versandart-Stammsatzes.
- [PRIMÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\SSMS_DB_SCHEMA.sql - `CREATE TABLE [dbo].[Versandart]([I3D] int IDENTITY, [Name] varchar(50) NULL, [Status] int NULL, CONSTRAINT [PK_Versandart] ...)`. Begründung: Bestätigt den Feldumfang auf DB-Ebene.
- [SEKUNDÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\centron\Centron.WPF.UI\Modules\Logistic\ShippingMethodSettings\ShippingMethodSettingsView.xaml - Grid mit exakt zwei Spalten: `<dxg:GridColumn FieldName="Name" .../>` und `<dxg:GridColumn FieldName="IsActive" .../>`. Begründung: Die Pflegemaske bietet keine weiteren Felder an.
Prüfidee: Neue Versandart anlegen und deaktivieren; prüfen, dass in `VersandArt` ein Satz mit Name und Status entsteht und keine weiteren Attribute erfasst werden können.
Tracelinks: SyRS-718, SwRS-719
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Versandartenkatalog wird fachlich benötigt; der aktuelle Feldumfang ist allerdings sehr schmal.
Status: belegt
Modul: M-63
ID: StRS-726
Titel: Bestellvorschlagsliste als Beschaffungsauslöser
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Einkäufer
Vorbedingung: Der Benutzer besitzt das Recht SHOW_ORDER_SUGGESTION_LIST und das System besitzt die Lizenz OrderSuggestionList oder Centron.
Fakt: Das Modul `OrderSuggestionListAppModuleController` wird in `ModuleRegistration.cs` mit einer Rechte- und einer Lizenzbedingung registriert. Die zugehörige `OrderSuggestionListBL` ermittelt über Roh-SQL beschaffungsbedürftige Artikel je Lager und erzeugt daraus Lieferantenbestellungen.
Aussage: Das System soll auf Basis von Beständen, Mindestbeständen, offenen Aufträgen und Zulauf eine Bestellvorschlagsliste erzeugen, aus der Lieferantenbestellungen erstellt werden können.
Ergebnis: Der Einkäufer erhält je Artikel und Lager einen Bedarfsvorschlag und kann daraus Bestellungen anlegen.
Belege:
- [PRIMÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\centron\Centron.WPF.UI\Modules\ModuleRegistration.cs - `ModuleRegistrationItem.For<OrderSuggestionListAppModuleController>(() => Helper.HasRights(UserRightsConst.Purchase.SHOW_ORDER_SUGGESTION_LIST), () => LicenseManager.Instance.HasLicense(LicenseGuids.OrderSuggestionList) || LicenseManager.Instance.HasLicense(LicenseGuids.Centron))`. Begründung: Durchsetzende Stelle für den Modulzugang (Recht und Lizenz).
- [PRIMÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\backend\Centron.BL\Purchasing\OrderSuggestionList\OrderSuggestionListBL.cs - Klasse `OrderSuggestionListBL` mit den SQL-Bausteinen `_sqlArticle`, `_sqlOrder`, `_sqlWH`, `_sqlDistri`; Ermittlung je `ArticleI3D`/`WarehouseI3D`. Begründung: Ort der fachlichen Vorschlagsermittlung.
- [KONTEXT] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\webservice\Centron.WebServices.Core\EntitiesWrongPlace\Administration\Rights\UserRightsConst.cs - `[Obsolete] public const int SHOW_ORDER_SUGGESTION_LIST = 10230;`. Begründung: Das steuernde Recht ist als obsolet markiert, wird aber weiterhin geprüft.
Prüfidee: Benutzer ohne Recht 10230 anmelden: Das Modul "Bestellvorschlagsliste" darf nicht erscheinen. Mit Recht und Lizenz muss die Liste Artikel mit Unterdeckung enthalten.
Tracelinks: SyRS-727, SwRS-728, SwRS-729
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - zentrale Beschaffungsfunktion; die Obsolet-Markierung des Rechts ist bei einer Neuentwicklung zu bereinigen.
Status: belegt
Modul: M-66
ID: StRS-730
Titel: Reisekostenabrechnung derzeit nicht ausgeliefert
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Mitarbeiter / Reisekostenprüfer
Vorbedingung: Anwendung gestartet, beliebige Rechtekonstellation.
Fakt: Die Registrierung des Moduls `TravelExpenseAppModuleController` ist in `ModuleRegistration.cs` auskommentiert; der begleitende Kommentar nennt Grund und Auftraggeber. Gleichzeitig existieren Modulklassen, eine Settings-Maske und zwei getrennte Datenmodelle (`Reisekosten*`-Tabellen und `TravelExpenseCategories`).
Aussage: Das System soll Reisekosten und Auslagen von Mitarbeitern erfassen und abrechnen; die vorhandene Implementierung ist jedoch bewusst deaktiviert und nicht produktiv nutzbar.
Ergebnis: Das Modul erscheint nicht in der Modulliste; erfasste Daten sind über die Oberfläche nicht erreichbar.
Belege:
- [PRIMÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\centron\Centron.WPF.UI\Modules\ModuleRegistration.cs - auskommentierter Block: `// SKA : Hide the travel expense module for now. The module is not finished yet. Ordered from Volker Lehnert` gefolgt von `// ModuleRegistrationItem.For<TravelExpenseAppModuleController>(// () => Helper.HasAnyRight(UserRightsConst.Purchase.TRAVEL_EXPENSE_ADMIN)),`. Begründung: Die Abwesenheit des Registrierungseintrags ist die durchsetzende Stelle, die das Modul unerreichbar macht.
- [SEKUNDÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\centron\Centron.WPF.UI\Modules\Purchasing\TravelExpense\TravelExpenseAppModuleController.cs - `public string ModuleName => "Reisekosten/Auslagen";` und `public string Description => "Verwalten und Abrechnen der Mitarbeiterreisekosten.";`, `GetRights()` liefert `null`. Begründung: Belegt die fachliche Absicht des Moduls und das Fehlen jeder Rechteanforderung.
- [SEKUNDÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\SSMS_DB_SCHEMA.sql - `CREATE TABLE [dbo].[ReisekostenAbrechnungen]`, `[ReisekostenBelege]`, `[ReisekostenBelegePKW]`, `[ReisekostenFahrten]`, `[ReisekostenReisen]` neben `CREATE TABLE [dbo].[TravelExpenseCategories]`. Begründung: Belegt zwei parallele Datenmodelle für denselben Sachverhalt.
Prüfidee: Anwendung mit einem Benutzer starten, der das Recht TRAVEL_EXPENSE_ADMIN (20800025) besitzt: Das Modul "Reisekosten/Auslagen" darf in der Modulübersicht nicht auftauchen.
Tracelinks: SyRS-731
Konsolidierung: Kandidat: StRS-730 / SyRS-731 - Legacy-Datenmodell `Reisekosten*` und neues Modell `TravelExpense*` bilden dieselbe Fachlichkeit in zwei getrennten Implementierungen ab.
Übernahmewürdigkeit: veraltet - Modul ist unfertig und bewusst abgeschaltet; für eine Neuentwicklung ist nur die fachliche Absicht, nicht die Umsetzung zu übernehmen.
Status: belegt
Modul: M-67
ID: StRS-732
Titel: RMA-Vorgang mit Unterscheidung nach Retourenart
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Werkstatt- / RMA-Sachbearbeiter
Vorbedingung: Der Benutzer besitzt das Recht RIGHT_RMAANLEGEN (20400210); die Lizenz RMAWorkshop oder RmaBeta ist vorhanden.
Fakt: Das RMA-Modul wird in `ModuleRegistration.cs` mit Recht und Lizenz registriert. Die Tabelle `Rma` führt die Pflichtspalte `RmaKind`, deren Wertebereich im Enum `RmaKind` als Unknown / OwnRma / CustomerRma / ForeignRma definiert ist.
Aussage: Das System soll Retourenvorgänge als RMA verwalten und dabei zwischen eigenen Geräten, Kundengeräten und Fremdgeräten unterscheiden.
Ergebnis: Jeder RMA-Vorgang trägt eine eindeutige Nummer, einen Kundenbezug und eine Retourenart und ist über die RMA-Übersicht auffindbar.
Belege:
- [PRIMÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\centron\Centron.WPF.UI\Modules\ModuleRegistration.cs - `ModuleRegistrationItem.For<RmaOverviewAppModulController>(() => Helper.HasRights(UserRightsConst.RIGHT_RMAANLEGEN), () => LicenseManager.Instance.HasLicense(LicenseGuids.RMAWorkshop) || LicenseManager.Instance.HasLicense(LicenseGuids.RmaBeta))`. Begründung: Durchsetzende Stelle für den Modulzugang.
- [PRIMÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\SSMS_DB_SCHEMA.sql - `CREATE TABLE [dbo].[Rma]` mit `[Number] int NOT NULL, [AccountI3D] int NOT NULL, [RmaKind] int NOT NULL, [IsClosed] bit NOT NULL`. Begründung: NOT-NULL-Constraints erzwingen Nummer, Kundenbezug und Retourenart je Vorgang.
- [SEKUNDÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\backend\Centron.Interfaces\CustomerArea\RmaKind.cs - `public enum RmaKind { Unknown, OwnRma, CustomerRma, ForeignRma }`. Begründung: Definiert den fachlichen Wertebereich der Retourenart.
Prüfidee: Benutzer ohne Recht 20400210 anmelden: Das Modul "RMA/Werkstatt" darf nicht erscheinen. Anschließend RMA anlegen und prüfen, dass `Rma.RmaKind` gesetzt und `Rma.Number` vergeben ist.
Tracelinks: SyRS-733, SwRS-734
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Retourenabwicklung ist eigenständiger Geschäftsprozess mit gesetzlicher Nachweispflicht.
Status: belegt
Modul: M-68
ID: StRS-737
Titel: Produktionsauftrag stets mit Bezug zu einem Kundenauftrag
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Produktionsplaner
Vorbedingung: Ein Kundenauftrag existiert; die Lizenz ProductionManagement ist vorhanden.
Fakt: Die Tabelle `ProductionOrders` führt `[OrderI3D] int NOT NULL` und `[OrderNumber] int NOT NULL`; ein Produktionsauftrag kann DB-seitig nicht ohne Auftragsbezug angelegt werden. Der Bezug zur einzelnen Auftragsposition (`OrderItemI3D`) ist dagegen optional.
Aussage: Das System soll jeden Produktionsauftrag zwingend an einen bestehenden Kundenauftrag binden und optional an eine einzelne Auftragsposition.
Ergebnis: Zu jedem Produktionsauftrag ist der auslösende Kundenauftrag nachvollziehbar; auftragslose Produktion (Lagerfertigung) ist nicht abbildbar.
Belege:
- [PRIMÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\SSMS_DB_SCHEMA.sql - `CREATE TABLE [dbo].[ProductionOrders]` mit `[OrderI3D] [int] NOT NULL, [OrderNumber] [int] NOT NULL, [OrderItemI3D] [int] NULL`. Begründung: Der NOT-NULL-Constraint ist die durchsetzende Regel für den Pflichtbezug zum Auftrag.
- [PRIMÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\src\backend\Centron.BL\Production\ProductionOrderBL.cs - `ProductionOrderBL.CreateFilterExpression(ProductionOrderFilter filter)` filtert unter anderem über `filter.OrderI3D` und `filter.OrderNumber`; `GetProductionOrdersByFilter` sortiert mit `f => f.OrderI3D`. Begründung: Der Auftragsbezug ist das führende Ordnungsmerkmal der Produktionsaufträge.
- [SEKUNDÄR] C:\DEV\MasterArbeit\QuellCode\CentronERP\SSMS_DB_SCHEMA.sql - `CREATE TABLE [dbo].[ProductionOrderItems]` mit `[ProductionOrderI3D] int NOT NULL` und `CREATE CLUSTERED INDEX [CI_ProductionOrderItems] ON [dbo].[ProductionOrderItems]([ProductionOrderI3D] ASC)`. Begründung: Zeigt die Kopf-Positions-Struktur des Produktionsauftrags.
Prüfidee: Versuch, einen Produktionsauftrag ohne `OrderI3D` zu speichern: Der Speichervorgang muss mit einer Constraint-Verletzung fehlschlagen.
Tracelinks: SyRS-738, SwRS-739
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - auftragsbezogene Fertigung ist das tragende Geschäftsmodell des Moduls; für Lagerfertigung wäre der Pflichtbezug zu lockern.
Status: belegt
Modul: M-70
@@ -0,0 +1,202 @@
ID: StRS-801
Titel: Zentrale Anmeldung aller c-entron-Anwendungen am Web-Service
Ebene: StRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Mitarbeiter / Kundenbenutzer (WebAccount)
Vorbedingung: Der Benutzer besitzt ein Konto (Tabelle `Sichbenu` bzw. `WebAccounts`) und startet eine c-entron-Anwendung.
Fakt: `Authenticator.GetTicket()` (src/backend/Centron.BL/Administration/Logins/Auth/Authenticator.cs, Z. 94–107) führt für jede Anmeldung nacheinander `Authenticate()`, die Auflösung der `ApplicationKind` über `ApplicationKind.GetKindByLicenseGuid(Auth.ApplicationName)` und danach `AuthenticateUser(...)` aus; ohne bekannte ApplicationKind wird mit `DefaultMessageCodes.ApplicationIDUnknown` abgebrochen.
Aussage: Das System soll jede Anmeldung einer c-entron-Anwendung zentral über den Web-Service durchführen und dabei Identität, Anwendungsart und Berechtigung in einem Vorgang prüfen, bevor eine Sitzung entsteht.
Ergebnis: Bei erfolgreicher Prüfung erhält der Client eine Ticket-ID; andernfalls eine Fehlermeldung mit MessageCode.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Logins/Auth/Authenticator.cs — `Authenticator.GetTicket()` / `AuthenticateUser()`; Abbruchbedingung `if (applicationKind == null) return ... ApplicationIDUnknown` - Begründung: Durchsetzende Stelle der zentralen Anmeldung, ohne die kein Ticket erzeugt wird.
- [PRIMÄR] src/backend/Centron.BL/Administration/Logins/Auth/Authenticator.cs — `ValidateRights(applicationKind, user)` prüft `applicationKind.DisallowingRight` und `applicationKind.RequiredRight` über `AppRightsBL.HasUserRight` - Begründung: Belegt, dass die Anwendungsberechtigung Teil der Anmeldung ist.
Prüfidee: Anmeldung mit gültigen Zugangsdaten, aber unbekannter `Application`-GUID: Erwartet wird ein Fehler mit MessageCode `ApplicationIDUnknown` und kein Eintrag in der Ticket-Tabelle.
Tracelinks: SyRS-811, SyRS-812, SwRS-827, SwRS-828, SwRS-830
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Zentrale Authentifizierung ist tragende Sicherheitsfunktion des Gesamtsystems.
Status: belegt
Modul: M-75
ID: StRS-802
Titel: Zwei-Faktor-Authentifizierung als zusätzlicher Anmeldeschutz
Ebene: StRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Administrator (Konfiguration) / Benutzer (Durchführung)
Vorbedingung: Im Web-Service Connection Manager ist `TwoFactorAuthEnabled` gesetzt und für den Benutzer ist `UseTwoFactorAuthentication` aktiv.
Fakt: `BasicAuthenticator.AuthenticateInternal()` ruft nach erfolgreicher Kennwortprüfung `TwoFactorAuthBL.ValidateTwoFactor(...)` auf und gibt bei Misserfolg `DefaultMessageCodes.TwoFactorAuthFailed` zurück (src/backend/Centron.BL/Administration/Logins/Auth/BasicAuthenticator.cs, Z. 62–70).
Aussage: Das System soll die Benutzeranmeldung um einen zweiten Faktor erweitern können, sodass die Anmeldung ohne bestandenen zweiten Faktor scheitert.
Ergebnis: Ohne bestandene Zweitfaktor-Prüfung wird kein Ticket erzeugt und die Anmeldung schlägt fehl.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Logins/Auth/BasicAuthenticator.cs — `_twoFactorAuthBL.ValidateTwoFactor(...)`; Rückgabe `Result<LoggedInUser>.AsError(..., DefaultMessageCodes.TwoFactorAuthFailed)` - Begründung: Durchsetzende Stelle, an der die Anmeldung ohne zweiten Faktor abgebrochen wird.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, Tabelle `dbo.Sichbenu`, Spalten `[UseTwoFactorAuthentication] [bit] NOT NULL`, `[TwoFactorAuthKey] [nvarchar](200) NULL`, `[TwoFactorValidDurationInDays] [int] NULL` - Begründung: Persistente Steuerung je Benutzerkonto als DB-Constraint/Schema.
Prüfidee: Benutzer mit `UseTwoFactorAuthentication = 1` und aktiviertem `TwoFactorAuthEnabled` anmelden und den zweiten Faktor verweigern: Erwartet wird Fehlercode `TwoFactorAuthFailed` und kein Ticket.
Tracelinks: SyRS-813, SyRS-814, SwRS-831, SwRS-832
Konsolidierung: Kandidat: StRS-802 / SwRS-832 — zwei getrennte 2FA-Implementierungen (siehe A8_Meta.md)
Übernahmewürdigkeit: übernehmen - Zusätzlicher Anmeldeschutz ist regulatorisch und sicherheitstechnisch gefordert.
Status: belegt
Modul: M-76
ID: StRS-803
Titel: Maschinenzugriff auf die API über personengebundene Access Tokens
Ebene: StRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Externes System / Integrationspartner
Vorbedingung: Es besteht die Lizenz `LicenseGuids.AccessTokenModule` und ein Mitarbeiter, für den der Token ausgestellt wird.
Fakt: `AccessTokenBL.CreatePersonalToken(...)` legt einen Token an, der über `IssuedForEmployee` fest an genau einen Mitarbeiter gebunden ist, und gibt den Klartext-Token nur einmalig bei der Erstellung zurück (src/backend/Centron.BL/Administration/AccessTokens/AccessTokenBL.cs, Z. 125–194).
Aussage: Das System soll externen Systemen einen API-Zugriff ermöglichen, der ohne interaktive Anmeldung auskommt, aber eindeutig einem Mitarbeiter zugeordnet und jederzeit widerrufbar ist.
Ergebnis: Ein API-Aufruf mit gültigem Token wird im Kontext des zugeordneten Mitarbeiters ausgeführt und protokolliert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/AccessTokens/AccessTokenBL.cs — `CreatePersonalToken()` mit `IssuedForEmployee = issuedForEmployee` und Rückgabe `(accessToken, plainToken)`; `Deactivate()` setzt `token.IsActive = false` - Begründung: Durchsetzende Stelle für Bindung und Widerrufbarkeit.
- [PRIMÄR] src/backend/Centron.BL/Administration/Logins/AuthenticationTicketBL.cs — `GetAuthTicketInfo()` löst über `_appUserBL.GetAppUserI3DForEmployee(...)` den AppUser zum Token auf - Begründung: Beweist die tatsächliche Ausführung im Mitarbeiterkontext.
Prüfidee: Token erzeugen, API-Aufruf mit `Authorization: Bearer <token>` absetzen, Token deaktivieren, Aufruf wiederholen: Der zweite Aufruf muss mit „Token ist deaktiviert." scheitern.
Tracelinks: SyRS-812, SyRS-816, SwRS-833, SwRS-834, SwRS-835
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Für Integrationen unverzichtbar und sicherheitsrelevant.
Status: belegt
Modul: M-77
ID: StRS-804
Titel: Lizenzabhängige Freischaltung von Anwendungen und Einzelfunktionen
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: c-entron Web-Service / Lizenzserver
Vorbedingung: Eine gültige Lizenzdatei wurde vom Lizenzserver geladen (`LicenseManager.LoadLicenses()`).
Fakt: `LicenseManager.CheckLicense(app, applicationVersion, user)` prüft je Lizenz-GUID Version, Anzahl und Gültigkeit und liefert bei Überschreitung `DefaultMessageCodes.LicenseMaximumReached` (src/backend/Centron.BL/Administration/Licensing/LicenseManager.cs, Z. 258–302); `HasLicense(Guid)` prüft Einzelfunktionen.
Aussage: Das System soll Anwendungen und Einzelfunktionen ausschließlich dann bereitstellen, wenn die zugehörige Lizenz-GUID vorhanden, versionsgültig und die Nutzungsanzahl nicht ausgeschöpft ist.
Ergebnis: Fehlende oder ausgeschöpfte Lizenzen führen zu einer definierten Fehlermeldung; die Funktion bleibt gesperrt.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Licensing/LicenseManager.cs — `CheckLicense()`: `if (currentlyUsedLicenses >= maxNumberOfLicenses) results.Add(... LicenseMaximumReached)` - Begründung: Durchsetzende Bedingung der Lizenzobergrenze.
- [PRIMÄR] src/backend/Centron.BL/PasswordManager/PasswordManagerBL.cs, Z. 94 — `if (LicenseManager.Instance.HasLicense(LicenseGuids.PasswordManager) == false) return ... DefaultMessageCodes.LicenseNotFound` - Begründung: Beispielhafte Durchsetzung einer Einzelfunktions-Lizenz in der BL.
- [KONTEXT] docs/reference/security/licensing-system.md — Beschreibung von `Applications` vs. `Only Licenses` - Begründung: Fachliche Einordnung des Lizenzmodells.
Prüfidee: Lizenzdatei ohne `LicenseGuids.PasswordManager` einspielen: Aufrufe von `PasswordManagerBL.GetPasswordManagerCustomersEmployeesRights` müssen mit `LicenseNotFound` abbrechen.
Tracelinks: SyRS-815, SyRS-816, SwRS-836
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Lizenzierung ist Geschäftsmodell-tragend und im Code durchgesetzt.
Status: belegt
Modul: M-78
ID: StRS-805
Titel: Zentrale, verschlüsselte Verwaltung von Kundenzugangsdaten
Ebene: StRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Servicetechniker / Administrator
Vorbedingung: Lizenz `LicenseGuids.PasswordManager` vorhanden und ein Masterkey ist hinterlegt.
Fakt: Zugangsdaten werden als `ModuleCustomPropertyValue.ValueEncryptedString` mit `new AESCryptoLogic().EncryptText(<wert>, masterKey)` verschlüsselt abgelegt und nur mit `DecryptText(..., masterKey)` wieder lesbar gemacht (src/backend/Centron.BL/PasswordManager/PasswordManagerBL.cs, Z. 700 und Z. 1052).
Aussage: Das System soll Zugangsdaten von Kundensystemen zentral speichern und dabei die geheimen Werte ausschließlich verschlüsselt mit einem separaten Masterkey ablegen.
Ergebnis: In der Datenbank stehen nur AES-verschlüsselte Base64-Werte; ohne Masterkey ist keine Entschlüsselung möglich.
Belege:
- [PRIMÄR] src/backend/Centron.BL/PasswordManager/PasswordManagerBL.cs, Z. 1051–1052 — `case CustomizationDataTypes.EncryptedText: customPropertyData.EncryptedString = new AESCryptoLogic().DecryptText(propertyValue?.ValueEncryptedString, masterKey);` - Begründung: Durchsetzende Stelle der Ver-/Entschlüsselung mit Masterkey.
- [PRIMÄR] src/backend/Centron.Common/TextCoding/AESCryptoLogic.cs — `EncryptByteArray()` nutzt `Aes.Create()` mit aus `securityKey` abgeleitetem Key/IV - Begründung: Belegt das eingesetzte Verfahren (AES).
- [PRIMÄR] src/backend/Centron.BL/Administration/CentronConfigDb/CentronConfigurationDbBL.cs — `EncryptWithMasterKey()` bricht mit „Es wurde kein Masterkey hinterlegt" ab - Begründung: Erzwingt die Existenz des Masterkeys.
Prüfidee: Passwortfeld über die UI speichern und den Datensatz in `ModuleCustomPropertyValues` direkt per SQL lesen: Der Wert darf nicht im Klartext lesbar sein.
Tracelinks: SyRS-817, SyRS-818, SwRS-837, SwRS-838, SwRS-839, SwRS-840
Konsolidierung: Kandidat: StRS-805 / SwRS-839 — `PasswordManagementArea` vs. `PasswordManager` (siehe A8_Meta.md)
Übernahmewürdigkeit: übernehmen - Kernfunktion mit hoher Schutzbedarfsklasse.
Status: belegt
Modul: M-79
ID: StRS-806
Titel: Umsetzung des DSGVO-Löschanspruchs für Kontaktdaten
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Datenschutzbeauftragter / Administrator
Vorbedingung: Der anfragende Benutzer besitzt das Recht `UserRightsConst.DsgvoModule.DSGVO_DELETE_CONTACT`.
Fakt: `DataSecurityBL.DsgvoDeleteRightDeleteContacts(currentUser, contacts)` prüft das Recht, löscht die ausgewählten Kontaktobjekte innerhalb einer Transaktion (`Session.WithTransaction`) und liefert ein Textprotokoll mit Kopfzeile „c-entron Löschprotokoll" zurück (src/backend/Centron.BL/Administration/DataSecurity/DataSecurityBL.cs, Z. 787–854).
Aussage: Das System soll auf Anfrage betroffener Personen deren Kontaktdaten löschen bzw. anonymisieren und den Vorgang in einem Löschprotokoll dokumentieren.
Ergebnis: Die betroffenen Kontaktdatensätze sind anonymisiert und der Anwender erhält ein Löschprotokoll als Nachweis.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/DataSecurity/DataSecurityBL.cs, Z. 789–790 — `if (!currentUser.HasUserRight(UserRightsConst.DsgvoModule.DSGVO_DELETE_CONTACT)) return Result<string>.AsError("Insufficient rights!");` - Begründung: Durchsetzende Rechteprüfung vor der Löschung.
- [PRIMÄR] src/backend/Centron.BL/Administration/DataSecurity/DataSecurityBL.cs, Z. 26 — `internal const string DsgvoDeletedContactMessage = "DSGVO: Auf Anfrage gelöscht.";` - Begründung: Belegt die Anonymisierungs-Kennzeichnung statt physischer Löschung.
Prüfidee: Aufruf mit einem Benutzer ohne `DSGVO_DELETE_CONTACT`: Erwartet wird „Insufficient rights!" und keinerlei Datenänderung.
Tracelinks: SyRS-819, SyRS-820, SwRS-841
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Gesetzliche Pflicht (Art. 17 DSGVO).
Status: belegt
Modul: M-80
ID: StRS-807
Titel: Digitale Signatur ausgehender PDF-Dokumente
Ebene: StRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Administrator (Einrichtung) / System (Ausführung)
Vorbedingung: Ein PKCS#12-Zertifikat mit privatem Schlüssel ist in den Anwendungseinstellungen hinterlegt.
Fakt: `PdfSigningBL.SignPdfDocument(byte[])` erzeugt mit `new Pkcs7Signer(certi, HashAlgorithmType.SHA256, tsaClient)` eine Signatur und setzt `signatureBuilder.ApplicationName = "c-entron.NET"` (src/backend/Centron.BL/Security/PdfSigningBL.cs); `IsPdfSigningAvailable()` liefert nur `true`, wenn `ApplicationSettingID.PdfSigningCertificate` gesetzt ist.
Aussage: Das System soll ausgehende PDF-Dokumente mit einem hinterlegten Zertifikat digital signieren, sodass Urheber und Unverfälschtheit nachweisbar sind.
Ergebnis: Das erzeugte PDF enthält eine PKCS#7-Signatur mit SHA-256 und optionalem qualifiziertem Zeitstempel.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Security/PdfSigningBL.cs — `SignPdfDocument()`: `new Pkcs7Signer(certi, HashAlgorithmType.SHA256, tsaClient)` - Begründung: Durchsetzende Stelle der Signaturerzeugung inkl. Hashverfahren.
- [PRIMÄR] src/backend/Centron.BL/Administration/Documents/Dsgvo/OrderProcessingContractOnlinePdfDocumentHandler.cs, Z. 276–279 — `if (signingBL.IsPdfSigningAvailable()) { var signed = signingBL.SignPdfDocument(resultStream.ToArray()); }` - Begründung: Belegt den produktiven Einsatz an Vertragsdokumenten.
Prüfidee: Ein AV-Vertrag wird online bestätigt; das erzeugte PDF muss in einem PDF-Reader eine gültige digitale Signatur mit dem hinterlegten Zertifikat ausweisen.
Tracelinks: SyRS-821, SwRS-842
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Beweiskraft signierter Dokumente ist fachlich gefordert.
Status: belegt
Modul: M-81
ID: StRS-808
Titel: Nachvollziehbarkeit von Änderungen an gekennzeichneten Datensätzen
Ebene: StRS
Typ: nicht-funktional
Qualitätsmerkmal: Wartbarkeit (Analysierbarkeit)
Akteur: Administrator / Revision
Vorbedingung: Die Entität ist mit `ChangeTrackingConfigurationAttribute` und mindestens einer Eigenschaft mit `TrackChangesAttribute` versehen.
Fakt: `ChangeTrackingEventListener.OnPreUpdate()` vergleicht bei jedem NHibernate-Update Alt- und Neuwert der markierten Eigenschaften und speichert bei Abweichung einen `ChangeLog`-Datensatz mit `ObjectI3D`, `ObjectKind`, `Property`, `OldValue`, `NewValue`, `Date` und `AppUser` (src/backend/Centron.DAO/ChangeTracking/ChangeTrackingEventListener.cs, Z. 52–142).
Aussage: Das System soll Änderungen an fachlich gekennzeichneten Feldern automatisch mit Alt-/Neuwert, Zeitpunkt und verursachendem Benutzer protokollieren.
Ergebnis: Zu jedem geänderten Feld existiert ein `ChangeLog`-Eintrag, der über `ChangeLogBL.GetChangeLogs(objectI3D, objectKind)` abrufbar ist.
Belege:
- [PRIMÄR] src/backend/Centron.DAO/ChangeTracking/ChangeTrackingEventListener.cs, Z. 78–86 — `if (object.Equals(oldValue, newValue) == false) { ChangeLog log = this.CreateChangeLog(...); session.Save(log); }` - Begründung: Durchsetzende Stelle der Protokollierung.
- [PRIMÄR] src/backend/Centron.BL/CheckListArea/ChangeTracking/ChangeLogBL.cs — `GetChangeLogs(int objectI3D, CentronObjectKindNumeric objectKind)` - Begründung: Belegt die Auswertbarkeit der Historie.
Prüfidee: Ein mit `TrackChanges` markiertes Feld ändern und speichern: In `ChangeLog` muss genau ein neuer Eintrag mit korrektem Alt-/Neuwert und dem angemeldeten Benutzer entstehen.
Tracelinks: SyRS-822, SwRS-843
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Revisionssicherheit ist für ein ERP-System erforderlich.
Status: belegt
Modul: M-83
ID: StRS-809
Titel: Mandantenfähigkeit mit genau einem Standardmandanten
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Administrator
Vorbedingung: Mindestens ein Datensatz in `dbo.Mandant` mit `Standard = 1` und `Status = 1`.
Fakt: `MandatorBL.GetDefaultMandator()` liest genau den Mandanten mit `f.Default == 1`; `MandatoryBL.GetMandators()` liefert nur Mandanten mit `f.State == 1` (src/backend/Centron.BL/Administration/Company/MandatorBL.cs, Z. 19–22; src/backend/Centron.BL/Administration/Mandatory/MandatoryBL.cs, Z. 33–36).
Aussage: Das System soll mehrere Mandanten mit eigenen Stammdaten führen und dabei genau einen Mandanten als Standardmandanten für übergreifende Vorbelegungen verwenden.
Ergebnis: Übergreifende Logik (z. B. Nummernkreise, Länderzuordnung) fällt eindeutig auf den Standardmandanten zurück.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Company/MandatorBL.cs — `GetDefaultMandator() => Session.GetGenericDAO<Mandator>().GetEntity(f => f.Default == 1)` - Begründung: Durchsetzende Selektionsbedingung des Standardmandanten.
- [PRIMÄR] src/backend/Centron.BL/Administration/Mandatory/MandatoryBL.cs, Z. 91 — SQL `LEFT OUTER JOIN dbo.Mandant m ON m.Standard = 1 AND m.Status = 1` - Begründung: Standardmandant wird auch in der Nummernkreis-Ermittlung als Fallback erzwungen.
Prüfidee: Standardkennzeichen aller Mandanten entfernen: Die Nummernkreis-Ermittlung ohne Filialbezug darf keinen Nummernkreis mehr liefern.
Tracelinks: SyRS-823, SwRS-844
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Mandantenfähigkeit ist grundlegendes Strukturmerkmal.
Status: belegt
Modul: M-73
ID: StRS-810
Titel: Automatisierte, zentral steuerbare Hintergrundverarbeitung
Ebene: StRS
Typ: nicht-funktional
Qualitätsmerkmal: Zuverlässigkeit (Fehlertoleranz)
Akteur: c-entron Web-Service (Host)
Vorbedingung: Der Web-Service läuft; Tabelle `dbo.BackgroundServices` ist vorhanden.
Fakt: Alle Hintergrunddienste erben von `ManagedBackgroundService`, das vor jedem Zyklus `BackgroundServiceBL.IsServiceEnabled(serviceName)` auswertet, bei `false` die Ausführung überspringt und Fehler mit exponentiellem Backoff bis `MaxBackoffDelay = 5 Minuten` abfängt (src/webservice/Centron.Host/AspNetCore/HostedServices/ManagedBackgroundService.cs, Z. 56–157).
Aussage: Das System soll wiederkehrende Aufgaben automatisch im Hintergrund ausführen, diese je Dienst zentral aktivierbar halten und bei Fehlern nicht den Gesamtbetrieb gefährden.
Ergebnis: Deaktivierte Dienste laufen nicht; fehlerhafte Dienste protokollieren den Fehler und laufen verzögert weiter, ohne den Host zu beenden.
Belege:
- [PRIMÄR] src/webservice/Centron.Host/AspNetCore/HostedServices/ManagedBackgroundService.cs, Z. 60–82 — `bool isEnabled = GetIsEnabledCached(serviceName); if (isEnabled) { ... } else { _logger.Debug($"{serviceName} is disabled, skipping execution."); }` - Begründung: Durchsetzende Ein-/Ausschaltbedingung je Dienst.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[BackgroundServices]` mit `[ServiceName] [nvarchar](255) NOT NULL, [IsEnabled] [bit] NOT NULL` - Begründung: Persistenz des Schaltzustands als DB-Schema.
- [KONTEXT] docs/Background Service/DataQualityService.md — Beschreibung von Zyklus und Fehlerbehandlung - Begründung: Fachliche Einordnung des Musters.
Prüfidee: `IsEnabled = 0` für „DataQualityService" setzen und maximal 60 Sekunden warten: Im Log darf kein „execution starts" mehr erscheinen.
Tracelinks: SyRS-824, SwRS-845
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Zentrale Betriebssteuerung ist etabliert und aktiv genutzt.
Status: belegt
Modul: M-87
@@ -0,0 +1,343 @@
ID: SyRS-811
Titel: Auswahl des Authentifizierungsverfahrens zur Laufzeit
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: c-entron Web-Service (AuthenticatorFactory)
Vorbedingung: Eine Anmeldeanfrage (LoginRequest / JwtLoginRequest) ist eingegangen.
Fakt: `AuthenticatorFactory.GetAuthenticator(authObject)` wählt anhand der systemweiten Einstellung `AppSettingsGroupBL.GetAuthenticationSettings().SystemAuthenticationMethod` und des Typs des `AuthObject` zwischen `BasicAuthenticator`, `ActiveDirectoryAuthenticator`, `OpenIdConnectAuthenticator` und `WebAccountAuthenticator` (src/backend/Centron.BL/Administration/Logins/Auth/AuthenticatorFactory.cs, Z. 44–95).
Aussage: Das System soll das Authentifizierungsverfahren je Anmeldeanfrage aus der systemweiten Einstellung und dem Anmeldetyp bestimmen und bei nicht freigeschaltetem Verfahren die Anmeldung ablehnen.
Ergebnis: Es wird genau ein `IAuthenticator` erzeugt; ist das Verfahren nicht aktiviert, liefert die Factory ein Fehler-Result („Active Directory authentication is not enabled" bzw. „JWT authentication is not enabled").
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Logins/Auth/AuthenticatorFactory.cs, Z. 113–116 — `TryCreateActiveDirectory(): ActiveDirectoryEnabled ? new ActiveDirectoryAuthenticator(...) : Result<IAuthenticator>.AsError("Active Directory authentication is not enabled")` - Begründung: Durchsetzende Bedingung der Verfahrensfreischaltung.
- [PRIMÄR] src/backend/Centron.BL/Administration/Logins/Auth/AuthenticatorFactory.cs, Z. 132–133 — `if (licenseManager.HasLicense(LicenseGuids.OpenIDConnectAuthentication) == false) return Result<IAuthenticator>.AsError("No license for OpenIDConnectAuthentication");` - Begründung: Lizenzbedingung als durchgesetzte Regel für OIDC.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `dbo.Sichbenu`, Spalte `[AuthentificationKind] [int] NOT NULL` - Begründung: Persistierte Verfahrenszuordnung je Benutzer.
Prüfidee: Bei `SystemAuthenticationMethod.ActiveDirectory` und deaktiviertem `ActiveDirectoryAuthEnabled` muss die Anmeldung mit „Active Directory authentication is not enabled" scheitern.
Tracelinks: StRS-801, SwRS-828, SwRS-831
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Erlaubt den geordneten Wechsel zwischen lokaler und externer Identitätsquelle.
Status: belegt
Modul: M-75
ID: SyRS-812
Titel: Zugriffsprüfung am Web-Service über Ticket oder Access Token
Ebene: SyRS
Typ: Schnittstelle
Qualitätsmerkmal:
Akteur: c-entron Web-Service (TicketAuthenticationHandler)
Vorbedingung: Ein HTTP-Request enthält einen Wert im Query-Parameter `access_token` oder im Header `Authorization: Bearer <wert>`.
Fakt: `TicketAuthenticationHandler.HandleAuthenticateAsync()` liest den Token, ruft `AuthenticationTicketBL.GetAuthTicketInfo(token, ipAddress, apiMethod)` auf und lehnt bei ungültigem Wert mit „Invalid authentication ticket or access token." ab; `GetAuthTicketInfo` prüft zuerst das Sitzungs-Ticket und danach den Access Token (src/webservice/Centron.Host/AspNetCore/TicketAuthenticationHandler.cs, Z. 44–95; src/backend/Centron.BL/Administration/Logins/AuthenticationTicketBL.cs, Z. 25–48).
Aussage: Das System soll jeden API-Request entweder über ein gültiges Sitzungs-Ticket oder über einen gültigen Access Token authentifizieren und die aufrufende Identität als Claim `CustomClaimTypes.UserIdentifier` bereitstellen.
Ergebnis: Authentifizierte Requests tragen einen ClaimsPrincipal mit Benutzer-ID und Authentifizierungsart; nicht authentifizierte Requests werden abgewiesen.
Belege:
- [PRIMÄR] src/webservice/Centron.Host/AspNetCore/TicketAuthenticationHandler.cs, Z. 53–56 — `var validationResult = this.ValidateTicketOrAccessToken(token); if (!validationResult.IsValid) return AuthenticateResult.Fail(...)` - Begründung: Durchsetzende Stelle der Zugriffsprüfung für alle ASP.NET-Core-Endpunkte.
- [PRIMÄR] src/backend/Centron.BL/Administration/Logins/AuthenticationTicketBL.cs, Z. 27–45 — Reihenfolge „First try: ConnectionTicket" / „Second try: Access Token" - Begründung: Legt die Prüfreihenfolge verbindlich fest.
Prüfidee: Request ohne `Authorization`-Header und ohne `access_token` absetzen: HTTP 401; Request mit widerrufenem Token: ebenfalls 401.
Tracelinks: StRS-801, StRS-803, SwRS-835
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Einheitlicher Eintrittspunkt für alle API-Zugriffe.
Status: belegt
Modul: M-77
ID: SyRS-813
Titel: Konfigurierbares Zweitfaktor-Verfahren
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Administrator (Web-Service Connection Manager)
Vorbedingung: `WebServiceConfigHelper.Current.TwoFactorAuthEnabled` ist gesetzt.
Fakt: `TwoFactorAuthBL.GetTwoFactorValidator()` erzeugt abhängig von `WebServiceConfigHelper.Current.TwoFactorAuthType` entweder einen `RadiusTwoFactorValidator` oder einen `EmailTwoFactorValidator` und wirft bei unbekanntem Wert `ArgumentOutOfRangeException` (src/backend/Centron.BL/Administration/Logins/TwoFactor/TwoFactorAuthBL.cs, Z. 183–193).
Aussage: Das System soll das Verfahren für den zweiten Faktor systemweit konfigurierbar zwischen RADIUS-Server und E-Mail-Link umschalten können.
Ergebnis: Alle 2FA-Prüfungen laufen über den konfigurierten Validator; ein nicht unterstützter Wert führt zu einem harten Konfigurationsfehler.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Logins/TwoFactor/TwoFactorAuthBL.cs, Z. 185–190 — `switch { TwoFactorAuthType.RadiusServer => new RadiusTwoFactorValidator(), TwoFactorAuthType.EmailLink => new EmailTwoFactorValidator(), _ => throw new ArgumentOutOfRangeException(...) }` - Begründung: Durchsetzende Auswahl des Verfahrens.
- [PRIMÄR] src/backend/Centron.BL/Administration/Logins/TwoFactor/TwoFactorAuthBL.cs, Z. 41–45 — `if (WebServiceConfigHelper.Current.TwoFactorAuthEnabled == false) return Result.AsSuccess();` - Begründung: Globaler Schalter, der die gesamte 2FA deaktiviert.
Prüfidee: `TwoFactorAuthType` auf `EmailLink` stellen und eine Anmeldung auslösen: Es muss eine Mail über die Vorlage `MailTemplateReferences.Auth.TwoFactor` versendet werden.
Tracelinks: StRS-802, SyRS-814, SwRS-832
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Erlaubt Anpassung an vorhandene Kundeninfrastruktur.
Status: belegt
Modul: M-76
ID: SyRS-814
Titel: Gültigkeitsdauer des zweiten Faktors je Anwendung, Gerät und IP-Adresse
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: c-entron Web-Service (TwoFactorAuthBL)
Vorbedingung: Der Benutzer hat den zweiten Faktor mindestens einmal bestätigt.
Fakt: `TwoFactorAuthBL.HasToValidateTwoFactor()` ermittelt die Dauer aus `user.TwoFactorValidDurationInDays ?? WebServiceConfigHelper.Current.TwoFactorValidDurationInDays`, erzwingt bei `<= 0` immer eine neue Prüfung und vergleicht sonst `lastTwoFactorAuth.Value.Date.AddDays(dauer) < DateTime.Now`; der letzte Zeitpunkt wird in `TwoFactorAuthLastLogins` je Kombination aus UserKind, UserI3D, ApplicationName, MachineName und IpAddress gespeichert (src/backend/Centron.BL/Administration/Logins/TwoFactor/TwoFactorAuthBL.cs, Z. 82–179).
Aussage: Das System soll den bestandenen zweiten Faktor für eine konfigurierbare Anzahl an Kalendertagen je Kombination aus Benutzer, Anwendung, Rechnername und IP-Adresse als gültig anerkennen.
Ergebnis: Innerhalb der Gültigkeit entfällt die erneute Zweitfaktor-Abfrage; bei Wechsel von Gerät oder IP-Adresse wird erneut geprüft.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Logins/TwoFactor/TwoFactorAuthBL.cs, Z. 127–130 — `var twoFactorAuthIsValidUntil = lastTwoFactorAuth.Value.Date.AddDays(twoFactorIsValidDuration); bool twoFactorAuthNeeded = twoFactorAuthIsValidUntil < DateTime.Now;` - Begründung: Durchsetzende Berechnung der Gültigkeit.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[TwoFactorAuthLastLogins]` mit `[UserKind]`, `[UserI3D]`, `[ApplicationName]`, `[MachineName]`, `[IpAddress]`, `[LastLogin]` (alle NOT NULL) - Begründung: Schlüsselumfang der Gültigkeit als Schema-Constraint.
Prüfidee: Nach erfolgreicher 2FA von derselben Maschine erneut anmelden (kein zweiter Faktor gefordert), danach `IpAddress` in `TwoFactorAuthLastLogins` verändern: Die nächste Anmeldung muss den zweiten Faktor erneut anfordern.
Tracelinks: StRS-802, SyRS-813
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Balanciert Sicherheit und Benutzbarkeit, aktiv genutzt.
Status: belegt
Modul: M-76
ID: SyRS-815
Titel: Lizenzprüfung nach Produkt, Version und Anzahl gleichzeitiger Nutzungen
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: c-entron Web-Service (LicenseManager)
Vorbedingung: Eine Anmeldung für eine `ApplicationKind` läuft; die Lizenzdatei wurde geladen.
Fakt: `LicenseManager.CheckLicense()` ruft je Lizenz-GUID `CheckLicenseVersion(license, versionNumber)` auf und vergleicht anschließend `TicketBL.GetTicketCount(license, app.LicenseUsageKind, user)` gegen `GetLicenseCount(license)`; ist die Zahl erreicht, wird `LicenseMaximumReached` gemeldet (src/backend/Centron.BL/Administration/Licensing/LicenseManager.cs, Z. 258–302).
Aussage: Das System soll vor Erzeugung einer neuen Sitzung prüfen, ob die Lizenz für das Produkt existiert, für die Anwendungsversion gültig ist und die Anzahl gleichzeitiger Nutzungen nicht überschritten wird.
Ergebnis: Bei ausgeschöpfter Lizenz wird kein neues Ticket erzeugt und der Anwender erhält „Die maximale Anzahl an Lizenzen wurde erreicht."
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Licensing/LicenseManager.cs, Z. 279–283 — `if (currentlyUsedLicenses >= maxNumberOfLicenses) results.Add(Result<Guid>.AsError("Die maximale Anzahl an Lizenzen wurde erreicht.", DefaultMessageCodes.LicenseMaximumReached));` - Begründung: Durchsetzende Bedingung der Nutzungsobergrenze.
- [PRIMÄR] src/backend/Centron.BL/Administration/Logins/Auth/Authenticator.cs, Z. 131–139 — `var result = LicenseManager.CheckLicense(applicationKind, appVersion, userResult); if (result.Status != ResultStatus.Success) return Result<string>.FromResult(result);` - Begründung: Einbindung der Lizenzprüfung in den Anmeldeablauf.
Prüfidee: Mit ausgeschöpfter Lizenzanzahl von einem weiteren Gerät anmelden: Erwartet wird MessageCode `LicenseMaximumReached` und kein neuer Ticket-Datensatz.
Tracelinks: StRS-804, SwRS-837
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Lizenzobergrenzen sind vertragsrelevant.
Status: belegt
Modul: M-78
ID: SyRS-816
Titel: Lizenzabhängige Obergrenze aktiver Access Tokens
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Administrator / Mitarbeiter
Vorbedingung: Die Lizenz `LicenseGuids.AccessTokenModule` besitzt einen Zählwert größer null.
Fakt: `AccessTokenBL.LicenseCheckCanActivateMoreToken()` zählt `Query<AccessToken>().Count(t => t.IsActive && !t.IsDeleted && (t.ExpiresAt == null || t.ExpiresAt > DateTime.Now))` und lehnt bei Erreichen des Lizenzzählwerts weitere Anlagen bzw. Aktivierungen ab; ein Zählwert von 0 gilt als unbegrenzt (src/backend/Centron.BL/Administration/AccessTokens/AccessTokenBL.cs, Z. 430–451).
Aussage: Das System soll die Anzahl gleichzeitig aktiver, nicht abgelaufener Access Tokens auf den lizenzierten Zählwert begrenzen.
Ergebnis: Beim Überschreiten wird „Sie können keine weitere Token anlegen / aktivieren." zurückgegeben und kein Token angelegt oder aktiviert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/AccessTokens/AccessTokenBL.cs, Z. 444–450 — `var activeLicenseCount = ... Count(t => t.IsActive && !t.IsDeleted && (t.ExpiresAt == null || t.ExpiresAt > DateTime.Now)); if (activeLicenseCount < maxLicenseCountResult.Data!.Value) return Result.AsSuccess(); return Result.AsError(...)` - Begründung: Durchsetzende Zählbedingung.
- [PRIMÄR] src/backend/Centron.Interfaces/Administration/Logins/LicenseGuids.cs, Z. 395 — `public static readonly Guid AccessTokenModule = Guid.Parse("8880E3D5-D832-48C4-8686-8EEBD729AE07");` - Begründung: Belegt die referenzierte Lizenz.
Prüfidee: Lizenzzählwert auf 1 setzen, einen Token anlegen, zweiten Token anlegen: Der zweite Aufruf muss mit der Maximalanzahl-Meldung scheitern.
Tracelinks: StRS-803, StRS-804, SwRS-834
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Verhindert unbegrenzte Maschinenzugriffe.
Status: belegt
Modul: M-77
ID: SyRS-817
Titel: Wählbarer Speicherort des Passwortmanager-Masterkeys
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Administrator
Vorbedingung: Die Passwortmanager-Einstellungen (`PasswordManagerSettingsDTO`) sind geladen.
Fakt: `CentronConfigurationDbBL` hält ein Dictionary `MasterPasswordSaveLocation -> IMasterPasswordStorage` mit den Implementierungen `MasterPasswordConfigurationDatabaseStorage` und `MasterPasswordSecureFileStorage` und leitet alle Masterkey-Zugriffe über `_masterPasswordStorages[settings.MasterPasswordSaveLocation]` (src/backend/Centron.BL/Administration/CentronConfigDb/CentronConfigurationDbBL.cs, Z. 29–33, 52–132).
Aussage: Das System soll den Masterkey wahlweise in einer separaten Konfigurationsdatenbank oder in einer geschützten Datei außerhalb der Fachdatenbank ablegen, und alle Lese- und Schreibzugriffe über die gewählte Ablage führen.
Ergebnis: Der Masterkey liegt nie in der Fachdatenbank neben den verschlüsselten Werten, sondern an dem konfigurierten separaten Ort.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/CentronConfigDb/CentronConfigurationDbBL.cs, Z. 29–33 — Dictionary-Initialisierung mit `MasterPasswordSaveLocation.ConfigurationDatabase` und `MasterPasswordSaveLocation.SecureFile` - Begründung: Durchsetzende Auswahl der Ablage.
- [PRIMÄR] src/backend/Centron.BL/Administration/CentronConfigDb/CentronConfigurationDbBL.cs, Z. 112–126 — `GetHotlineMasterKey()` entschlüsselt das gelesene Material mit `AESCryptoLogic.DecryptText(result.Data)` - Begründung: Belegt, dass der Masterkey auch am Ablageort verschlüsselt liegt.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Administration/CentronConfigDb/CentronConfigDbSettingsViewModel.cs — Eigenschaften `IsConfigurationDatabaseMode`, `IsSecureFileMode`, `SetHotlineMasterKeyCommand` - Begründung: Konfigurationsschalter in der Oberfläche.
Prüfidee: Ablageart auf `SecureFile` stellen, Masterkey setzen und prüfen, dass in der Konfigurationsdatenbank kein Schlüssel mehr gelesen wird, die Entschlüsselung aber weiterhin funktioniert.
Tracelinks: StRS-805, SwRS-838
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Trennung von Schlüssel und Geheimnis ist Voraussetzung des Schutzkonzepts.
Status: belegt
Modul: M-86
ID: SyRS-818
Titel: Richtlinienbasierte Rechte im Passwortmanager
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: c-entron Web-Service (PasswordManagerBL)
Vorbedingung: Lizenz `LicenseGuids.PasswordManager` vorhanden; Richtlinien (`PasswordManagerGuideline`) sind je Kunde/Mitarbeiter zugeordnet.
Fakt: `PasswordManagerBL.GetPasswordManagerCustomersEmployeesRights()` liest über die Named Query `NamedQueryEnums.PasswordManager.GetEmployeesRightsForCustomers` je Kunde und Mitarbeiter die Flags `SealBreak`, `SealingAllowed`, `AccessDataEditable`, `AccessDataVisible`, `VPNAccessesEditable`, `TwoFactorAuthentification`, `Notification`, `AccessDataDeletable` und bildet sie auf `PasswordManagerGuidelineRights` ab (src/backend/Centron.BL/PasswordManager/PasswordManagerBL.cs, Z. 92–188).
Aussage: Das System soll den Zugriff auf Zugangsdaten je Kunde und Mitarbeiter über Richtlinien mit den Einzelrechten Sichtbarkeit, Bearbeitung, Löschung, Versiegelung und Siegelbruch steuern.
Ergebnis: Die Oberfläche erhält je Kunde/Mitarbeiter ein Rechte-Flagfeld, das die zulässigen Aktionen bestimmt.
Belege:
- [PRIMÄR] src/backend/Centron.BL/PasswordManager/PasswordManagerBL.cs, Z. 125–164 — Aufbau von `PasswordManagerGuidelineRights` aus den acht Einzelflags - Begründung: Durchsetzende Ableitung der Einzelrechte.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, Tabellen `PasswordManagerGuidelineCustomers`, `PasswordManagerGuidelineEmployees`, `PasswordManagerGuidelineDepartments`, `PasswordManagerGuidelineExcludedCustomers` sowie Spalte `[TwoFactorAuthentification] [bit] NULL` (Z. 46316) - Begründung: Persistiertes Richtlinienmodell.
Prüfidee: Für einen Mitarbeiter `AccessDataVisible = 0` setzen: Die Zugangsdaten des betroffenen Kunden dürfen für diesen Mitarbeiter nicht mehr entschlüsselt angezeigt werden.
Tracelinks: StRS-805, SwRS-840, SwRS-841
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Feingranulare Zugriffssteuerung ist Kernanforderung des Moduls.
Status: belegt
Modul: M-79
ID: SyRS-819
Titel: Löschprotokoll als Nachweis für DSGVO-Löschungen
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Datenschutzbeauftragter
Vorbedingung: Es wurden Kontaktobjekte für die Löschung ausgewählt und das Recht `DSGVO_DELETE_CONTACT` liegt vor.
Fakt: `DataSecurityBL.DsgvoDeleteRightDeleteContacts()` baut einen `StringBuilder` mit der Kopfzeile „c-entron Löschprotokoll", reicht ihn an alle `DoDelete*`-Methoden durch und gibt ihn nur bei erfolgreicher Transaktion als Ergebnis zurück (src/backend/Centron.BL/Administration/DataSecurity/DataSecurityBL.cs, Z. 792–853).
Aussage: Das System soll für jeden DSGVO-Löschvorgang ein Protokoll erzeugen, das alle tatsächlich geänderten Objekte benennt, und dieses nur bei vollständig erfolgreicher Transaktion ausgeben.
Ergebnis: Der Anwender erhält ein Protokoll; bei einem Fehler wird die gesamte Transaktion zurückgerollt und kein Protokoll geliefert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/DataSecurity/DataSecurityBL.cs, Z. 795 und Z. 849–853 — `var deleteResult = Session.WithTransaction(() => { ... }); if (deleteResult.Status == ResultStatus.Error) return Result<string>.FromResult(deleteResult); return Result<string>.AsSuccess(deleteProtocol.ToString());` - Begründung: Durchsetzende Kopplung von Transaktion und Protokollausgabe.
- [PRIMÄR] src/backend/Centron.BL/Administration/DataSecurity/DataSecurityBL.cs, Z. 27 — `DsgvoDeletedContactMessageWithEmployeeInfo = "DSGVO: Auf Anfrage gelöscht. (Durchgeführt von {0} am {1:d} um {1:t} Uhr)"` - Begründung: Belegt die Aufnahme von Bearbeiter und Zeitpunkt in die Kennzeichnung.
Prüfidee: Löschung mit einem Objekt auslösen, das eine Fremdschlüsselverletzung erzeugt: Es darf kein Protokoll zurückkommen und keine Teilinformation gelöscht bleiben.
Tracelinks: StRS-806, SyRS-820
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Nachweispflicht gegenüber Aufsichtsbehörden.
Status: belegt
Modul: M-80
ID: SyRS-820
Titel: Rechtegeschützte DSGVO-Datenbankbereinigung
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Datenschutzbeauftragter / Administrator
Vorbedingung: Der Benutzer besitzt `UserRightsConst.DsgvoModule.ACCESS_CLEANUP_DATABASE` und `ModuleFeatures.IsDsgvoDatabaseCleanupAvailable` ist wahr.
Fakt: `DataSecurityBL.GetDataSecurityCleanUpStats()` ermittelt Mengengerüste zu Altdaten (z. B. `SELECT COUNT(*) AS Item1 FROM dbo.Taetigkeiten WHERE Datum < :Date`), die zugehörige Ausführung `DataSecurityExecuteCleanUp()` prüft dieselbe Rechte-/Feature-Bedingung und gibt anschließend ohne weitere Anweisungen `Result.AsSuccess()` zurück (src/backend/Centron.BL/Administration/DataSecurity/DataSecurityBL.cs, Z. 34–70).
Aussage: [HYPOTHESE] Das System soll Altdatenbestände nach konfigurierbaren Aufbewahrungsfristen ermitteln und nach Freigabe durch einen berechtigten Benutzer physisch bereinigen.
Ergebnis: Erwartet wäre eine Reduktion der ermittelten Datensatzmengen; tatsächlich verändert `DataSecurityExecuteCleanUp` im vorliegenden Stand keine Daten.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/DataSecurity/DataSecurityBL.cs, Z. 64–70 — `public Result DataSecurityExecuteCleanUp(...) { if (!currentUser.HasUserRight(UserRightsConst.DsgvoModule.ACCESS_CLEANUP_DATABASE) || !ModuleFeatures.IsDsgvoDatabaseCleanupAvailable) return Result.AsError("Insufficient rights!"); return Result.AsSuccess(); }` - Begründung: Die Rechteprüfung ist durchgesetzt, die Bereinigungslogik fehlt vollständig.
- [SEKUNDÄR] src/backend/Centron.BL/Administration/DataSecurity/DataSecurityBL.cs, Z. 87 — Anzeigetext `$"Es gibt {sqlData.Item1} CRM-Einträge die älter als {date:d} sind."` - Begründung: UI-Text belegt die fachliche Absicht der Bereinigung.
Prüfidee: `DataSecurityExecuteCleanUp` mit ausgewählten Statistikarten aufrufen und die Mengengerüste erneut abfragen: Bleiben die Zahlen unverändert, ist die Bereinigung nicht implementiert.
Tracelinks: StRS-806, SyRS-819
Konsolidierung: nein
Übernahmewürdigkeit: Workaround - Rechteprüfung vorhanden, Fachlogik fehlt; als bekannte Lücke zu führen.
Status: HYPOTHESE
Modul: M-80
Begründung Hypothese: Es fehlt die Information, ob die Bereinigung bewusst deaktiviert wurde (Sicherheitsabschaltung) oder ob sie in einer anderen Komponente ausgeführt wird; im vorliegenden Quellstand existiert keine löschende Anweisung.
ID: SyRS-821
Titel: Optionaler qualifizierter Zeitstempel bei der PDF-Signierung
Ebene: SyRS
Typ: Schnittstelle
Qualitätsmerkmal:
Akteur: c-entron Web-Service (PdfSigningBL)
Vorbedingung: Ein Signaturzertifikat ist hinterlegt; `ApplicationSettingID.PdfSigningTsaServerUrl` ist optional gesetzt.
Fakt: `PdfSigningBL.SignPdfDocument()` erzeugt nur bei nicht leerer `TsaServerUrl` einen `TsaClient(new Uri(settings.TsaServerUrl), HashAlgorithmType.SHA256[, username, password])` und übergibt ihn an den `Pkcs7Signer`; das TSA-Passwort wird über `AESCryptoLogic` verschlüsselt in `ApplicationSettingID.PdfSigningTsaServerPassword` abgelegt (src/backend/Centron.BL/Security/PdfSigningBL.cs).
Aussage: Das System soll die PDF-Signatur auf Wunsch um einen Zeitstempel eines externen Zeitstempeldienstes ergänzen und die Zugangsdaten zu diesem Dienst nur verschlüsselt speichern.
Ergebnis: Bei konfigurierter TSA-URL enthält die Signatur einen RFC-3161-Zeitstempel; ohne Konfiguration wird ohne Zeitstempel signiert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Security/PdfSigningBL.cs — `if (!string.IsNullOrWhiteSpace(settings.TsaServerUrl)) { ... tsaClient = new TsaClient(new Uri(settings.TsaServerUrl), HashAlgorithmType.SHA256, settings.TsaServerUsername, settings.TsaServerPassword); }` - Begründung: Durchsetzende Bedingung für die Zeitstempelnutzung.
- [PRIMÄR] src/backend/Centron.BL/Security/PdfSigningBL.cs — `var crypted = _cryptoLogic.EncryptText(settings.TsaServerPassword); updateSettings.UpdateString(ApplicationSettingID.PdfSigningTsaServerPassword, crypted);` - Begründung: Verschlüsselte Ablage des Dienstpassworts.
Prüfidee: TSA-URL konfigurieren, Dokument signieren und die Signaturzeit im PDF prüfen; anschließend `ApplicationSettings` auslesen: Das TSA-Passwort darf dort nicht im Klartext stehen.
Tracelinks: StRS-807, SwRS-842
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Zeitstempel erhöhen die Beweiskraft und sind konfigurierbar umgesetzt.
Status: belegt
Modul: M-81
ID: SyRS-822
Titel: Attributgesteuerte Auswahl der zu historisierenden Felder
Ebene: SyRS
Typ: Daten
Qualitätsmerkmal:
Akteur: Entwickler / Persistenzschicht
Vorbedingung: Die NHibernate-Session ist mit dem `ChangeTrackingEventListener` als `IPreUpdateEventListener` registriert.
Fakt: `ChangeTrackingEventListener.GetEntityData(type)` sammelt genau die Eigenschaften, die ein `TrackChangesAttribute` tragen, und verwirft Entitäten ohne solche Eigenschaften mit einer Warnung; nur Typen mit `ChangeTrackingConfigurationAttribute` werden überhaupt betrachtet (src/backend/Centron.DAO/ChangeTracking/ChangeTrackingEventListener.cs, Z. 179–217).
Aussage: Das System soll ausschließlich die per Attribut ausgezeichneten Entitäten und Felder historisieren, damit der Umfang der Änderungshistorie deklarativ und nachvollziehbar festgelegt ist.
Ergebnis: Nicht ausgezeichnete Felder erzeugen keine `ChangeLog`-Einträge; falsch konfigurierte Typen erzeugen eine Warnung im Log.
Belege:
- [PRIMÄR] src/backend/Centron.DAO/ChangeTracking/ChangeTrackingEventListener.cs, Z. 187–197 — `.Where(f => f.CanRead && f.GetCustomAttributes(typeof(TrackChangesAttribute), true).Any())` - Begründung: Durchsetzende Filterbedingung des Historisierungsumfangs.
- [PRIMÄR] src/backend/Centron.DAO/ChangeTracking/ChangeTrackingEventListener.cs, Z. 200–204 — `if (entityData.Properties.Any() == false) { Logger.Warn("Can't track the changes of entity of type '{0}', because it doesn't have any TrackChanges attribute."); return null; }` - Begründung: Definiertes Verhalten bei Fehlkonfiguration.
Prüfidee: Ein nicht mit `TrackChanges` markiertes Feld einer historisierten Entität ändern: Es darf kein `ChangeLog`-Eintrag entstehen.
Tracelinks: StRS-808, SwRS-843
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Deklarativer Ansatz begrenzt Datenvolumen und ist wartbar.
Status: belegt
Modul: M-83
ID: SyRS-823
Titel: Nummernkreisermittlung mit Vorrang Filiale vor Standardmandant
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: c-entron Web-Service (MandatoryBL)
Vorbedingung: Für die gesuchte `NumberGroupEnum` existieren Nummernkreise in `dbo.Nummernkreis`.
Fakt: `MandatoryBL.GetNumberGroup(numberGroup, currentEmployeeI3D, branchI3D)` sortiert die Kandidaten mit `ORDER BY CASE WHEN nu.MandantI3D = fm.I3D THEN 0 ELSE 1 END, nu.FilialI3D DESC`, schließt `nu.Beschreibung <> '[nicht verwendet]'` aus und liefert den ersten Treffer mit `Current > 0` (src/backend/Centron.BL/Administration/Mandatory/MandatoryBL.cs, Z. 83–112).
Aussage: Das System soll den zu verwendenden Nummernkreis vorrangig aus dem Mandanten der Filiale des Mitarbeiters und erst ersatzweise aus dem Standardmandanten bestimmen und dabei nur Nummernkreise mit gesetztem Zählerstand verwenden.
Ergebnis: Belege einer Filiale erhalten die Nummer aus dem filialspezifischen Kreis; existiert keiner, greift der Kreis des Standardmandanten.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Mandatory/MandatoryBL.cs, Z. 92–96 — SQL-Bedingungen `WHERE ((nu.MandantI3D = fm.I3D AND nu.FilialI3D = f.I3D) OR nu.MandantI3D = m.I3D) AND (ISNULL(nu.FilialI3D,0)<=0 OR nu.FilialI3D = f.I3D) AND nu.Beschreibung <> '[nicht verwendet]'` - Begründung: Durchsetzende Auswahlregel als SQL.
- [PRIMÄR] src/backend/Centron.BL/Administration/Mandatory/MandatoryBL.cs, Z. 105–111 — `foreach (var item in searchResult) { if (item.Current.GetValueOrDefault(0) > 0) return item; }` - Begründung: Nur aktive Nummernkreise werden verwendet.
Prüfidee: Für eine Filiale einen Nummernkreis mit `Aktuell = 0` anlegen: Die Beleganlage muss auf den Kreis des Standardmandanten zurückfallen.
Tracelinks: StRS-809
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Regel ist fachlich etabliert und im SQL eindeutig festgelegt.
Status: belegt
Modul: M-73
ID: SyRS-824
Titel: Ausführungsintervall und Fehler-Backoff der Hintergrunddienste
Ebene: SyRS
Typ: nicht-funktional
Qualitätsmerkmal: Zuverlässigkeit (Wiederherstellbarkeit)
Akteur: c-entron Web-Service (ManagedBackgroundService)
Vorbedingung: Der Dienst wurde als Hosted Service registriert.
Fakt: `ManagedBackgroundService.ExecuteAsync()` wartet zunächst eine Minute (`await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken)`), führt danach zyklisch `GetExecutionInterval()` aus und verlängert nach Fehlern über `GetDelayWithBackoff()` exponentiell bis maximal `MaxBackoffDelay = TimeSpan.FromMinutes(5)`; bei Ausnahmen wird zusätzlich `DAOFactory.Instance.TryRecoverConnectionPool(exception)` gerufen (src/webservice/Centron.Host/AspNetCore/HostedServices/ManagedBackgroundService.cs, Z. 28, 36, 84–93, 141–157).
Aussage: Das System soll Hintergrunddienste in einem je Dienst definierten Intervall ausführen, bei wiederholten Fehlern das Intervall exponentiell verlängern und den Datenbankverbindungspool aktiv wiederherstellen.
Ergebnis: Dauerfehler führen zu maximal einem Versuch alle fünf Minuten statt zu Dauerlast; nach Erfolg wird das reguläre Intervall wiederhergestellt.
Belege:
- [PRIMÄR] src/webservice/Centron.Host/AspNetCore/HostedServices/ManagedBackgroundService.cs, Z. 148–153 — `var backoffMultiplier = Math.Min(Math.Pow(2, _consecutiveFailures), MaxBackoffDelay.TotalSeconds / baseInterval.TotalSeconds); ... if (backoffDelay > MaxBackoffDelay) backoffDelay = MaxBackoffDelay;` - Begründung: Durchsetzende Backoff-Berechnung.
- [PRIMÄR] src/webservice/Centron.Host/AspNetCore/HostedServices/ManagedBackgroundService.cs, Z. 86–88 — `_consecutiveFailures++; _logger.Error(...); DAOFactory.Instance.TryRecoverConnectionPool(exception);` - Begründung: Definierte Fehlerreaktion.
Prüfidee: Datenbank während eines Dienstzyklus abschalten und die Logabstände beobachten: Die Abstände müssen sich verdoppeln und bei fünf Minuten stagnieren.
Tracelinks: StRS-810, SwRS-845
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Verhindert Überlastung bei Störungen.
Status: belegt
Modul: M-87
ID: SyRS-825
Titel: Begrenzte Gültigkeitsdauer der Sitzungstickets je Anwendungsart
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: c-entron Web-Service (TicketBL)
Vorbedingung: Für eine Anmeldung wurde ein Ticket erzeugt.
Fakt: `TicketBL.GetExpireDate(applicationKind)` setzt die Gültigkeit anhand `applicationKind.ExpirationKind` auf 30 Minuten (`TicketExpireInMinutes`), 5 Minuten (`MonitoringConnector`), 1440 Minuten (`OneDay`) oder auf `AppSettingsConst.TicketReleaseTime`, mindestens jedoch 30 Minuten (`Math.Max(setting.GetValueOrDefault(TicketExpireInMinutes), TicketExpireInMinutes)`); abgelaufene Tickets werden über `DeleteExpiredTickets()` entfernt (src/backend/Centron.BL/Administration/Logins/TicketBL.cs, Z. 26–34, 136–164).
Aussage: Das System soll Sitzungstickets nur zeitlich begrenzt gültig halten, die Dauer je Anwendungsart festlegen und die konfigurierbare Dauer nach unten auf 30 Minuten begrenzen.
Ergebnis: Nach Ablauf ist das Ticket ungültig; die Anwendung muss sich neu anmelden.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Logins/TicketBL.cs, Z. 150–153 — `case ExpirationKind.FromSettings: var setting = new AppSettingsBL(Session).GetSettings(AppSettingsConst.TicketReleaseTime).GetInt(...); expireDurationInMinutes = Math.Max(setting.GetValueOrDefault(TicketExpireInMinutes), TicketExpireInMinutes);` - Begründung: Durchsetzende Untergrenze der Gültigkeitsdauer.
- [PRIMÄR] src/backend/Centron.BL/Administration/Logins/TicketBL.cs, Z. 128–133 — `if ((newExpireDate - ticket.ExpiryDate).TotalMinutes < 5) return Result.AsSuccess();` - Begründung: Regel für die Verlängerung bei Aktivität.
Prüfidee: `TicketReleaseTime` auf 5 setzen und ein Ticket erzeugen: Die gespeicherte `ExpiryDate` muss dennoch 30 Minuten in der Zukunft liegen.
Tracelinks: StRS-801, SyRS-812
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Zeitlich begrenzte Sitzungen sind Sicherheitsanforderung.
Status: belegt
Modul: M-75
ID: SyRS-826
Titel: Zwei parallele Speicher für Anwendungseinstellungen
Ebene: SyRS
Typ: Daten
Qualitätsmerkmal:
Akteur: c-entron Web-Service (AppSettingsBL)
Vorbedingung: Eine Komponente fordert Einstellungen über `AppSettingsBL.GetSettings(...)` an.
Fakt: `AppSettingsBL.GetSettings(params object[] settings)` akzeptiert ausschließlich Werte der Typen `AppSettingsConst` (Legacy-Tabelle `Stammdat`) und `ApplicationSettingID` (Tabelle `ApplicationSettings`), lädt beide Mengen getrennt und wirft bei anderen Typen `new Exception("Invalid settings type")` (src/backend/Centron.BL/Administration/Settings/AppSettingsBL.cs, Z. 47–65).
Aussage: Das System soll Anwendungseinstellungen aus zwei getrennten Speichern gemeinsam bereitstellen, wobei neue Einstellungen ausschließlich in `ApplicationSettings` geführt werden.
Ergebnis: Aufrufer erhalten eine gemeinsame `SettingsCollection` unabhängig davon, in welcher Tabelle die Einstellung physisch liegt.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Settings/AppSettingsBL.cs, Z. 55–62 — `if (settings.Any(f => f is AppSettingsConst == false && f is ApplicationSettingID == false)) throw new Exception("Invalid settings type"); var oldSettings = ...; var newSettings = ...;` - Begründung: Durchsetzende Typprüfung und Trennung der beiden Speicher.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, Z. 5817–5831 — `CREATE TABLE [dbo].[ApplicationSettings]` mit typisierten Wertspalten `ValueInt`, `ValueFloat`, `ValueDecimal`, `ValueBool`, `ValueDateTime`, `ValueText`, `ValueLargeText` und `PK_ApplicationSettings` - Begründung: Schema des aktuellen Einstellungsspeichers.
- [KONTEXT] docs/guides/development/settings-management.md, Abschnitt „Settings Tables" - Begründung: Beschreibt die historische Doppelung und die Regel „all new settings should be added to the ApplicationSettings table".
Prüfidee: Aufruf `GetSettings("beliebigerString")`: Es muss eine Exception „Invalid settings type" ausgelöst werden.
Tracelinks: SyRS-827, SwRS-842
Konsolidierung: Kandidat: SyRS-826 — `Stammdat` (AppSettingsConst) und `ApplicationSettings` (ApplicationSettingID) als zwei Speicher desselben Konzepts
Übernahmewürdigkeit: Workaround - Doppelspeicher ist historisch bedingt; die Zusammenführung steht laut Doku noch aus.
Status: belegt
Modul: M-74
ID: SyRS-827
Titel: Clientseitiger Stammdaten-Cache beim Anwendungsstart
Ebene: SyRS
Typ: nicht-funktional
Qualitätsmerkmal: Leistungseffizienz (Zeitverhalten)
Akteur: c-entron.NET Client (CentronCache)
Vorbedingung: Der Client ist am Web-Service angemeldet.
Fakt: `CentronCache.RefreshCacheAsync()` lädt über 70 Stammdaten- und Einstellungsmengen (u. a. `CurrentUserAppRights`, `MandatoryData`, `ReceiptSettings`, `DsgvoSettings`) parallel über `Parallel.ForEachAsync(thingsToLoad, ...)`, sammelt Einzelfehler in `_errors`, setzt `IsCacheLoaded = true` und veröffentlicht anschließend ein `CentronCacheRefreshedEvent` (src/centron/Centron.WPF.UI/Modules/Administration/Cache/CentronCache.cs, Z. 121–218).
Aussage: Das System soll häufig benötigte Stammdaten und Einstellungen beim Start des Clients einmalig parallel laden, Ladefehler einzeln erfassen und die Anwendung über den Abschluss des Ladevorgangs benachrichtigen.
Ergebnis: Nach dem Start stehen alle gecachten Listen ohne weitere Serveraufrufe zur Verfügung; bei Ladefehlern wird der Anwender auf mögliche Folgeprobleme hingewiesen.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Administration/Cache/CentronCache.cs, Z. 205–217 — `await Parallel.ForEachAsync(thingsToLoad, async (thingToLoad, _) => await thingToLoad()); this.IsCacheLoaded = true; if (this.HasErrors) { this.ShowVerificationErrorMessage(true); } this.FireCacheRefreshedEvent();` - Begründung: Durchsetzende Ladelogik inkl. Fehlerbehandlung.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Administration/Cache/CentronCache.cs, Z. 349–352 — Meldungstext „Beim Erstellen des Cache ist ein Fehler aufgetreten. ... Bitte starten Sie die c-entron.NET neu." - Begründung: Belegt das definierte Verhalten gegenüber dem Anwender.
Prüfidee: Einen der geladenen Endpunkte serverseitig auf Fehler zwingen: Der Client muss den Cache dennoch fertigstellen und die Warnmeldung anzeigen.
Tracelinks: SyRS-826, SwRS-844
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Zentrale Voraussetzung für die Antwortzeiten der Oberfläche.
Status: belegt
Modul: M-85
@@ -0,0 +1,167 @@
ID: StRS-901
Titel: Zentraler Geschäftspartnerstamm für Kunden, Lieferanten und Kontakte
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertriebsmitarbeiter / Innendienst
Vorbedingung: Benutzer ist am System angemeldet und besitzt Rechte auf dem CRM-Modul.
Fakt: Die Tabelle `Accounts` (SSMS_DB_SCHEMA.sql, Zeile 3405) hält den gemeinsamen Kopfsatz (Name, Matchcode, Phone, Fax, Email, MandatorI3D, Adviser1I3D..Adviser6I3D, IsActive, IsLocked). Die Rollen "Kunde" und "Lieferant" werden über `AccountTypeToAccounts` und die Satelliten-Tabellen `AccountCustomers` (Zeile 3675) bzw. `AccountSuppliers` (Zeile 3795) angehängt; `AccountTypeKind` (src/backend/Centron.Interfaces/Accounts/AccountTypeKind.cs) kennt Customer, Supplier, Contact, Custom und sechs Sondertypen.
Aussage: Das System soll Kunden, Lieferanten, Interessenten und reine Kontakte als einen gemeinsamen Geschäftspartner-Datensatz (Account) führen und die Rolle über zuordenbare Kontoarten abbilden, sodass ein Geschäftspartner gleichzeitig Kunde und Lieferant sein kann.
Ergebnis: Ein Geschäftspartner ist genau einmal als Account gespeichert und trägt eine oder mehrere Kontoarten mit rollenspezifischen Zusatzdaten.
Belege:
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[Accounts]` (Z. 3405), `CREATE TABLE [dbo].[AccountCustomers]` (Z. 3675), `CREATE TABLE [dbo].[AccountSuppliers]` (Z. 3795), `CREATE TABLE [dbo].[AccountTypeToAccounts]` (Z. 3865) - Begründung: Die Schemastruktur erzwingt die Trennung von Kopfsatz und Rollendaten über eine n:m-Zuordnung.
- [SEKUNDÄR] src/backend/Centron.BL/Accounts/AccountBL.cs, `SaveAccount` (Z. 473-537) - Begründung: Der Code leitet aus `account.AccountTypes.Any(f => f.CustomerData != null)` bzw. `SupplierData != null` ab, welche Rolle vorliegt, und prüft die Rechte je Rolle getrennt.
- [KONTEXT] src/centron/Centron.WPF.UI/Modules/Finances/Crm/CrmAppModuleController.cs, `ModuleName`/`GetRights()` (Z. 71-99) - Begründung: Das CRM-Modul ist die fachliche Einstiegsmaske für den Adressstamm.
Prüfidee: Ein Account mit Kontoart Kunde und Lieferant anlegen; prüfen, dass genau ein Satz in `Accounts` sowie je ein Satz in `AccountCustomers` und `AccountSuppliers` mit derselben `AccountI3D`-Verknüpfung entsteht.
Tracelinks: SyRS-909, SyRS-910, SyRS-911, SwRS-925, SwRS-927
Konsolidierung: Kandidat: StRS-901 / SyRS-911 - Accounts vs. Kunden/Kreditor
Übernahmewürdigkeit: übernehmen - Der rollenbasierte Account ist das tragende Datenmodell des Vertriebs.
Status: belegt
Modul: M-88
ID: StRS-902
Titel: Lückenlose CRM-Tätigkeitshistorie je Geschäftspartner
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Vertriebsmitarbeiter / Servicemitarbeiter
Vorbedingung: Ein Account und mindestens ein Ansprechpartner existieren.
Fakt: `AccountActivities` (SSMS_DB_SCHEMA.sql, Z. 3189) speichert je Tätigkeit Caption, Text, TextRtf, AccountI3D, AccountAddressContactI3D, ActivityKind, EditorI3D, Rating, DueDate, ReferenceObjectI3D/ReferenceObjectKind und CampaignI3D. `AccountActivityKind` (src/backend/Centron.Interfaces/Accounts/Activities/AccountActivityKind.cs) definiert u. a. PhoneNote=0, Appointment=1, VisitReport=2, Note=3. `AccountActivitiesBL` erzeugt zusätzlich automatisch Tätigkeiten zu Belegen und Tickets (`CreateActivityForNewReceipt`, `CreateActivityForNewTicketSaves`, `CreateActivityForTicketClosed`).
Aussage: Das System soll alle Kundenkontakte (Telefonnotiz, Termin, Besuchsbericht, Notiz, E-Mail) sowie automatisch erzeugte Ereignisse aus Belegen und Tickets als CRM-Tätigkeit am Geschäftspartner und am Ansprechpartner protokollieren.
Ergebnis: Zu jedem Account existiert eine chronologische, filterbare Tätigkeitshistorie mit Bearbeiter und Bezugsobjekt.
Belege:
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[AccountActivities]` (Z. 3189) - Begründung: Spalten AccountI3D und AccountAddressContactI3D sind NOT NULL, die Zuordnung ist damit erzwungen.
- [PRIMÄR] src/backend/Centron.BL/Accounts/Activities/AccountActivitiesBL.cs, `CreateActivityForNewReceipt`, `CreateActivityForNewTicketSaves`, `CreateActivityForTicketClosed`, `CreateActivityForTicketForwarded` (Z. 568-750) - Begründung: Belegen die automatische Historienfortschreibung aus anderen Modulen.
- [SEKUNDÄR] src/backend/Centron.Interfaces/Accounts/Activities/AccountActivityKind.cs, `[Description("Telefonnotiz")]`, `[Description("Besuchsbericht")]` - Begründung: UI-Bezeichner der fachlichen Tätigkeitsarten.
Prüfidee: Beleg und Ticket zu einem Account anlegen und schließen; prüfen, dass für jedes Ereignis ein Satz in `AccountActivities` mit korrekter `AccountI3D` und passendem `ActivityKind` entsteht.
Tracelinks: SyRS-915, SyRS-916, SwRS-932, SwRS-944
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Die Tätigkeitshistorie ist Kern des CRM-Nutzens.
Status: belegt
Modul: M-89
ID: StRS-903
Titel: Werbesperre des Geschäftspartners respektieren
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Marketingmitarbeiter
Vorbedingung: Ein Account existiert; das Kennzeichen Werbesperre ist gesetzt oder nicht gesetzt.
Fakt: `Accounts.AdvertisingNotAllowed` ist `bit NOT NULL` (SSMS_DB_SCHEMA.sql, Z. 3405 ff.), ergänzt um `AdvertisingNotAllowedInfo nvarchar(200)`. `AccountSearchBL` baut daraus im erweiterten Filter die SQL-Bedingung `AND <acc>.AdvertisingNotAllowed = 1` bzw. `= 0` und kombiniert sie über `IIF(...ISNULL(<contact>.IsMailingAtEmail1Active,1)...)` mit dem Mailing-Kennzeichen des Ansprechpartners (src/backend/Centron.BL/Accounts/AccountSearchBL.cs, Z. 986-1013).
Aussage: Das System soll bei der Selektion von Empfängern für Werbe- und Mailing-Aktionen das Kennzeichen Werbesperre des Accounts sowie das Mailing-Kennzeichen des Ansprechpartners auswerten und gesperrte Empfänger ausschließen können.
Ergebnis: Empfängerlisten für Kampagnen und Serienmails enthalten keine Geschäftspartner mit gesetzter Werbesperre, wenn der Filter entsprechend gesetzt ist.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Accounts/AccountSearchBL.cs, Filterzweig `filter.IncludeOnlyAccountsWithNoAdvertisement` (Z. 986-1013) - Begründung: Die durchsetzende Stelle formt die WHERE-Bedingung auf `AdvertisingNotAllowed`.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/Crm/CrmMainView.xaml, Bindung `CurrentAccount.AdvertisingNotAllowedInfo` (Z. 1325-1329) - Begründung: Pflege- und Anzeigeort des Kennzeichens im CRM.
- [KONTEXT] src/backend/Centron.DAO/Repositories/Accounts/AccountRepository.cs, `WriteAccountIntoKunden`: `customer.werbesperre = account.AdvertisingNotAllowed ? 1 : 0` (Z. 1218) - Begründung: Zeigt die Fortschreibung in die Altstruktur.
Prüfidee: Account mit `AdvertisingNotAllowed = 1` anlegen und eine Empfängerselektion mit `IncludeOnlyAccountsWithNoAdvertisement = false` ausführen; der Account darf nicht im Ergebnis erscheinen.
Tracelinks: StRS-906, SyRS-911, SwRS-937, SwRS-938
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Rechtlich erforderliche Werbeeinwilligungssteuerung.
Status: belegt
Modul: M-88
ID: StRS-904
Titel: Termine und CRM-Aktivitäten mit Outlook/Exchange abgleichen
Ebene: StRS
Typ: Schnittstelle
Qualitätsmerkmal:
Akteur: Mitarbeiter mit Exchange-Postfach
Vorbedingung: Exchange-/Graph-Zugangsdaten sind hinterlegt und der Kalender-Sync ist aktiviert.
Fakt: `AccountActivitiesBL.SyncCrmActivityToOutlookAsync` (src/backend/Centron.BL/Accounts/Activities/AccountActivitiesBL.cs, Z. 838 ff.) prüft `GetGraphCalendarSyncSettings().CentronCalendarSyncEnabled`, ermittelt den Graph-Benutzer über `GraphServiceClientHelper.GetUser` und schreibt ein `Microsoft.Graph.Models.Event`. Die Zuordnung erfolgt über die Tabelle `GroupwareEntryIDs` (SQL im selben Verfahren, Z. 868-873). Zusätzlich existiert ein Outlook-AddIn (src/nexus/CentronNexus.OutlookAddIn/CRM/CrmActivityPage.razor, `@page "/outlook-addin/crmactivitydetails"`).
Aussage: Das System soll CRM-Termine und Zeitplanungen bidirektional mit den Exchange-Postfächern der zuständigen Mitarbeiter abgleichen und die Zuordnung zwischen c-entron-Objekt und Exchange-Element dauerhaft speichern.
Ergebnis: Ein in c-entron erfasster CRM-Termin erscheint im Outlook-Kalender des Bearbeiters und behält bei Änderungen seine Identität.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Accounts/Activities/AccountActivitiesBL.cs, `SyncCrmActivityToOutlookAsync` (Z. 838-980) - Begründung: Implementiert den Abgleich inkl. Abbruchbedingungen (Sync deaktiviert, Mitarbeiter nicht in Sync-Abteilung).
- [SEKUNDÄR] src/nexus/CentronNexus.OutlookAddIn/CRM/CrmActivityPage.razor - Begründung: Belegt die Gegenrichtung, das Anlegen von CRM-Tätigkeiten aus Outlook heraus.
- [KONTEXT] docs/features/exchange-sync-bugprotokoll.md, Tickets 164020, 163184, 164121 - Begründung: Dokumentiert bekannte Fehlerbilder und die eingebauten Schutzfilter des Sync-Pfades.
Prüfidee: CRM-Termin mit `SyncWithOutlook = true` speichern; im Zielpostfach muss ein Kalendereintrag existieren und `GroupwareEntryIDs` einen Satz mit `ObjektArt = AccountActivity` enthalten.
Tracelinks: SyRS-916, SyRS-918, SwRS-933, SwRS-935
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Outlook-Integration ist zentrale Arbeitsweise der Anwender.
Status: belegt
Modul: M-95
ID: StRS-905
Titel: Vertriebskampagnen mit Phasen, Teilnehmern und Entscheidungen steuern
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Kampagnenverantwortlicher
Vorbedingung: Benutzer ist Mitarbeiter und der Kampagne zugeordnet.
Fakt: `Campaigns` (SSMS_DB_SCHEMA.sql, Z. 34310) enthält Caption, StartDate, EndDate, State, PotentialRevenue; zugehörig existieren `CampaignPhases`, `CampaignPhaseActions`, `CampaignPhaseActionExecutes`, `CampaignParticipants`, `CampaignParticipantContactPerson`, `CampaignEmployees`, `CampaignMarkers`. `CampaignBL` (src/backend/Centron.BL/Accounts/Campaigns/CampaignBL.cs) bietet `AddParticipantsToCampaign`, `EnterCampaignParticipantDecision`, `EnterCampaignParticipantPhaseI3D`.
Aussage: Das System soll Kampagnen mit Laufzeit, Phasen, Phasenaktionen, zugeordneten Mitarbeitern sowie Teilnehmern (Accounts und deren Ansprechpartnern) verwalten und je Teilnehmer eine Entscheidung mit Begründungstext festhalten.
Ergebnis: Der Kampagnenfortschritt ist je Teilnehmer nachvollziehbar; Entscheidungen und Phasenzuordnungen sind gespeichert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Accounts/Campaigns/CampaignBL.cs, `EnterCampaignParticipantDecision` (Z. 213), `EnterCampaignParticipantPhaseI3D` (Z. 233), `AddParticipantsToCampaign` (Z. 133) - Begründung: Fachlogik der Kampagnenführung.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/Campaigns/CampaignAppModuleController.cs, `ModuleName => "Kampagnen/Mailing"`, `Description => "Erstellen und Verwalten von Kampagnen"` (Z. 14-17) - Begründung: UI-Bezeichner des Moduls.
- [KONTEXT] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[Campaigns]` (Z. 34310) und Folgetabellen ab Z. 34145 - Begründung: Datenmodell der Kampagne.
Prüfidee: Kampagne mit zwei Phasen und zwei Teilnehmern anlegen, für einen Teilnehmer eine Entscheidung erfassen; `CampaignParticipants` muss Entscheidungsart und -text führen.
Tracelinks: SyRS-923, StRS-906
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Kampagnensteuerung ist eigenständiger Vertriebsprozess.
Status: belegt
Modul: M-92
ID: StRS-906
Titel: Serienmails an selektierte Empfängerkreise versenden und protokollieren
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Marketingmitarbeiter
Vorbedingung: Ein Mailing mit Texten, Anhängen und Empfängerliste ist angelegt.
Fakt: `MailingDataBL` (src/backend/Centron.BL/Mailings/MailingDataBL.cs) verwaltet `MailingData`, `MailingTexts`, `MailingAttachments`, `MailingToCustomer` und `MailingDataToRelationshipKind`. Der Versandassistent `SendMailWizardViewModel` (src/centron/Centron.WPF.UI/Modules/Finances/Campaigns/Pages/Mailing/SendMail/SendMailWizardViewModel.cs) versendet je Empfänger über `EwsEmailAccess.SendEmail`, schreibt Erfolg/Fehler in `Logs` und markiert den Empfänger über `SaveSendMail` (Z. 536 ff.) mit `item.Send = true; item.Date = DateTime.Now`.
Aussage: Das System soll Serienmails empfängerindividuell mit ersetzten Textvariablen versenden, jeden Versand pro Empfänger als gesendet mit Zeitstempel markieren und Fehlversuche protokollieren, sodass ein Wiederaufsetzen ohne Doppelversand möglich ist.
Ergebnis: Jeder Empfänger erhält höchstens eine Mail je Mailing; der Versandstatus ist je Empfänger gespeichert.
Belege:
- [PRIMÄR] src/centron/Centron.WPF.UI/Modules/Finances/Campaigns/Pages/Mailing/SendMail/SendMailWizardViewModel.cs, `SaveSendMail` (Z. 536-545) und die Prüfung `if (this.AlreadySentMail(i)) continue;` (Z. 480) - Begründung: Verhindert Doppelversand beim Wiederaufsetzen.
- [SEKUNDÄR] Log-Texte "E-Mail an {mailAddress} erfolgreich versendet" / "Fehler beim Versand an {mailAddress}" (Z. 513-520) - Begründung: Fachliche Protokollierung im Versandprotokoll.
- [KONTEXT] src/backend/Centron.BL/Mailings/MailingDataBL.cs, `SaveAllMailingTexts` (Z. 135) - Begründung: Persistierung der Versandmarkierung.
Prüfidee: Mailing mit drei Empfängern starten, nach dem zweiten abbrechen und erneut starten; der bereits versendete Empfänger darf keine zweite Mail erhalten.
Tracelinks: StRS-903, SyRS-920, SwRS-937, SwRS-938
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Serienmail ist ein produktiv genutzter Kernprozess des Marketings.
Status: belegt
Modul: M-99
ID: StRS-907
Titel: Eingehende Anrufe dem Geschäftspartner und Ansprechpartner zuordnen
Ebene: StRS
Typ: Schnittstelle
Qualitätsmerkmal:
Akteur: Telefonzentrale / Servicemitarbeiter
Vorbedingung: TAPI-Anbindung ist eingerichtet und Telefonnummern sind am Account bzw. Ansprechpartner gepflegt.
Fakt: `TapiBL` (src/backend/Centron.BL/Accounts/TapiBL.cs) pflegt eine Suchtabelle über `SaveAccountTapiNumbers`, `SaveTapiNumber` und `ResetAndCreateAllAccountTapiNumbers`; `PhoneCallBL.SearchContactPersonByPhoneNumberV2` (src/backend/Centron.BL/Tapi/PhoneCallBL.cs, Z. 149 ff.) normalisiert die Rufnummer über `TapiBL.FormatPhoneNumber` und liefert `CustomerWithContactPerson`. `PhoneCallBL.SaveOrUpdateCall` (Z. 73) persistiert Anrufe mit CallerNumber, StartTime, EndTime und DurationInSeconds.
Aussage: Das System soll bei einem eingehenden Anruf die übermittelte Rufnummer normalisieren, den zugehörigen Geschäftspartner samt Ansprechpartner ermitteln und den Anruf mit Dauer und Richtung protokollieren.
Ergebnis: Der Anwender sieht beim Anruf den erkannten Geschäftspartner; der Anruf ist im Anrufjournal gespeichert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Tapi/PhoneCallBL.cs, `SearchContactPersonByPhoneNumberV2` (Z. 149-...) und `SaveOrUpdateCall` (Z. 73-99) - Begründung: Suche und Persistenz sind hier implementiert.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Administration/PhoneSettings/PhoneSettingsController.cs, `Caption => "Telefonie"` (Z. 23) - Begründung: Einstellungsmodul für die Telefonie.
- [KONTEXT] docs/reference/architecture/tapi.md - Begründung: Beschreibt die eingesetzte TraySoft-AddTAPI.NET-Komponente und die Debug-Wege.
Prüfidee: Anruf mit einer am Ansprechpartner gepflegten Nummer in internationaler Schreibweise simulieren; die Suche muss den Ansprechpartner finden und ein `PhoneCall`-Satz muss entstehen.
Tracelinks: SyRS-912, SwRS-940
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Anruferkennung ist etablierter Servicevorteil.
Status: belegt
Modul: M-101
ID: StRS-908
Titel: Kundenaudits und Umfragen erstellen, versenden und auswerten
Ebene: StRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Auditor / Qualitätsbeauftragter
Vorbedingung: Eine Umfragevorlage ist im Modul "Audit" angelegt und ein Account als Befragter zugeordnet.
Fakt: `SurveyProcessBL` (src/backend/Centron.BL/Accounts/Survey/SurveyProcessBL.cs) verwaltet Vorlagen (`SaveSurvey`, `UpdateSurvey`), Ordner (`GetSurveyFolders`), Fragenkategorien (`GetSurveyQuestionCategories`) und erzeugt aus einer Vorlage eine konkrete Umfrage (`GetSurveyfromTemplate`, Z. 607). `SendEMailToSurveyReleasedFrom` (Z. 265) versendet eine Statusmail mit Link auf `"{sboUrl}/SurveyModule?ID={cryptedSurveyI3D}"`.
Aussage: Das System soll Umfrage-/Auditvorlagen verwalten, daraus kundenbezogene Umfragen erzeugen, diese per verschlüsseltem Link über das ServiceBoard-Online bereitstellen und den Freigebenden bei Statusänderung per E-Mail informieren.
Ergebnis: Der Befragte erreicht die Umfrage über einen personalisierten Link; der Freigebende wird über den Abschluss informiert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Accounts/Survey/SurveyProcessBL.cs, `SendEMailToSurveyReleasedFrom` (Z. 265-317) - Begründung: Enthält Linkbildung und Mailversand.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Survey/SurveyAppModuleController.cs, `ModuleName => "Audit"` (Z. 19) - Begründung: Fachliche Modulbezeichnung im UI weicht vom technischen Namen "Survey" ab.
- [KONTEXT] src/backend/Centron.BL/Accounts/Survey/SurveyProcessBL.cs, `GetSurveyfromTemplate` liest `GetDsgvoSettings().SboUrl` (Z. 613) - Begründung: Zeigt die Abhängigkeit von der ServiceBoard-Online-URL.
Prüfidee: Umfrage aus Vorlage erzeugen und Status auf abgeschlossen setzen; die Mail an den Freigebenden muss den korrekten Link mit verschlüsselter ID enthalten.
Tracelinks: SwRS-943
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Audits sind eigenständiger, kundenbezogener Prozess.
Status: belegt
Modul: M-104
@@ -0,0 +1,331 @@
ID: SyRS-909
Titel: Rechteprüfung beim Anlegen, Ändern und Löschen von Accounts
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Centron.BL (AccountBL)
Vorbedingung: Ein angemeldeter c-entron-Benutzer ruft eine schreibende Account-Operation auf.
Fakt: `AccountBL.ValidateUserRights(int appUserI3D, bool newCustomer, bool getAccount, bool deleteAccount, bool editAccount, bool unlockAccount, bool newSupplier, bool editSupplier)` (src/backend/Centron.BL/Accounts/AccountBL.cs, Z. 1299-1370) lädt über `AppRightsBL.CheckRightsFromUser` die Rechte CREATE_CUSTOMER (20400092), EDIT_CUSTOMER (20400093), DELETE_CUSTOMER (20400016), SEARCH_CUSTOMER (20400088), UNLOCK_CUSTOMER (2040003), RIGHT_LIEFERANTANLEGEN, RIGHT_LIEFERANTAENDERN und gibt je nach Flag `Result.AsError(..., DefaultMessageCodes.RightCheckFailed)` zurück. `SaveAccount` (Z. 473) ruft die Prüfung mit `newCustomer/newSupplier` bei `account.I3D <= 0` und mit `editAccount/editSupplier` sonst auf; `DeleteAccount` (Z. 754) mit `deleteAccount: true`, `UnlockAccount` (Z. 928) mit `unlockAccount: true`. Der Überladung `ValidateUserRights(LoggedInUser ...)` (Z. 1290) lehnt Web-Account-Logins generell ab.
Aussage: Das System soll jede schreibende Operation auf einem Account gegen das rollenspezifische Benutzerrecht prüfen und bei fehlendem Recht die Operation mit dem Meldungscode RightCheckFailed abbrechen; Web-Account-Logins sollen für Account-Schreiboperationen grundsätzlich abgelehnt werden.
Ergebnis: Ohne passendes Recht wird kein Account angelegt, geändert, gelöscht oder entsperrt; der Aufrufer erhält eine Fehlermeldung mit RightCheckFailed.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Accounts/AccountBL.cs, `ValidateUserRights` (Z. 1299-1370): Bedingung `if (!checkRightsResult.Contains(UserRightsConst.Sales.Customer.CustomerCommon.CREATE_CUSTOMER)) return Result.AsError("Fehlende Rechte um Accounts zu erstellen", DefaultMessageCodes.RightCheckFailed);` - Begründung: Durchsetzende Stelle mit konkreter Prüfbedingung.
- [PRIMÄR] src/backend/Centron.BL/Accounts/AccountBL.cs, `ValidateUserRights(LoggedInUser...)` (Z. 1294): `if (loggedInUser.IsWebAccountLogin) return Result.AsError("Webaccount hat keine Rechte für diese Aktion", ...)` - Begründung: Explizite Sperre für Webaccounts.
- [SEKUNDÄR] src/webservice/Centron.WebServices.Core/EntitiesWrongPlace/Administration/Rights/UserRightsConst.cs, Z. 2041, 2073, 2105, 2107, 2122 - Begründung: Numerische Rechte-IDs der geprüften Rechte.
Prüfidee: Benutzer ohne DELETE_CUSTOMER anmelden und `DeleteAccount` aufrufen; das Ergebnis muss Status Error mit `DefaultMessageCodes.RightCheckFailed` und der Text "Fehlende Rechte um Accounts zu löschen" sein.
Tracelinks: StRS-901, SwRS-928, SwRS-929
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Zentrale Zugriffskontrolle des Adressstamms.
Status: belegt
Modul: M-88
ID: SyRS-910
Titel: Löschsperre für Accounts mit offenen Posten, Tickets oder Verträgen
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Centron.BL (AccountBL)
Vorbedingung: Der Benutzer besitzt DELETE_CUSTOMER und fordert das Löschen eines Accounts an, der eine Kundennummer besitzt.
Fakt: `AccountBL.DeleteAccount` (src/backend/Centron.BL/Accounts/AccountBL.cs, Z. 752-803) ermittelt über `_accountRepository.GetCustomerNumberFromAccount` die Kundennummer und bricht ab, wenn `AccountStatisticBL.GetAccountUnpaidInvoiceOverview` einen Eintrag mit `ObjectKind == CentronObjectKindNumeric.InvoiceClass` liefert ("Account kann nicht gelöscht werden da noch offene Posten vorhanden sind.", DefaultMessageCodes.InvalidDeleteRequest), wenn `HelpdeskSearchBL.GetActiveHelpdesksFromCustomer` Treffer liefert oder wenn `ReceiptSearcher.SearchReceipts` mit `ReceiptKinds = ContractClass` und `IncludeClosedReceipts = false` Treffer liefert.
Aussage: Das System soll das Löschen eines Accounts verweigern, solange zu dessen Kundennummer offene Rechnungen, aktive Helpdesk-Tickets oder nicht abgeschlossene Verträge existieren.
Ergebnis: Der Löschvorgang wird mit Meldungscode InvalidDeleteRequest und einer den Grund benennenden Meldung abgewiesen; die Daten bleiben unverändert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Accounts/AccountBL.cs, `DeleteAccount` (Z. 762-800): drei aufeinanderfolgende Abbruchbedingungen mit `Result.AsError(..., DefaultMessageCodes.InvalidDeleteRequest)` - Begründung: Durchgesetzte Integritätsregel vor dem physischen Löschen.
- [SEKUNDÄR] Meldungstexte "…noch offene Posten…", "…noch offene Heldesks…", "…noch offene Verträge…" - Begründung: Fachliche Begründung der Sperre (Tippfehler "Heldesks" im Original).
Prüfidee: Account mit einer offenen Rechnung löschen wollen; Rückgabe muss Status Error und Code InvalidDeleteRequest sein, der Account bleibt in `Accounts` erhalten.
Tracelinks: StRS-901, SyRS-909
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Schützt Buchhaltungs- und Servicebezüge vor Referenzverlust.
Status: belegt
Modul: M-88
ID: SyRS-911
Titel: Doppelte Datenhaltung Accounts/AccountCustomers gegenüber Kunden/Kreditor
Ebene: SyRS
Typ: Daten
Qualitätsmerkmal:
Akteur: Centron.DAO (AccountRepository)
Vorbedingung: Ein Account mit Kontoart Kunde oder Lieferant wird gespeichert.
Fakt: `AccountRepository.WriteAccountIntoKunden(IAccount account, IAccountCustomer accountCustomer, Kunden customer)` (src/backend/Centron.DAO/Repositories/Accounts/AccountRepository.cs, Z. 1160-1240 ff.) schreibt jedes Feld des neuen Modells in die Altstruktur zurück, u. a. `customer.Status = account.IsActive ? 1 : 0`, `customer.Gesperrt = account.IsLocked ? 1 : 0`, `customer.werbesperre = account.AdvertisingNotAllowed ? 1 : 0`, `customer.InnendienstID = account.Adviser1I3D`, `customer.BuchhaltNr = accountCustomer.BookKeepingNumber`. Die Gegenrichtung leistet `FillAccountFromOldCustomerReference` (Z. 583-660). Ergänzend spiegelt `AccountBL.UpdateLockedInOldStructure` (Z. 944-962) das Sperrkennzeichen und `ReactivateAccount` (Z. 714-728) setzt `Kunden.Status`/`Kreditor.Status` auf 1.
Aussage: Das System soll denselben Geschäftspartner nicht dauerhaft in zwei Datenhaltungen (Accounts/AccountCustomers/AccountSuppliers und Kunden/Kreditor/Anschrif/Personen) parallel führen; bis zur Ablösung der Altstruktur soll jede Änderung am Account transaktional und vollständig in die Altstruktur gespiegelt werden.
Ergebnis: Nach jedem Speichern eines Accounts stimmen die gespiegelten Felder in `Kunden` bzw. `Kreditor` mit dem Account überein.
Belege:
- [PRIMÄR] src/backend/Centron.DAO/Repositories/Accounts/AccountRepository.cs, `SaveAccountAsCustomer` (Z. 1124) und `WriteAccountIntoKunden` (Z. 1160) - Begründung: Durchsetzende Stelle der Spiegelung.
- [PRIMÄR] src/backend/Centron.BL/Accounts/AccountBL.cs, `UpdateLockedInOldStructure` (Z. 944-962) - Begründung: Separate Zweitspiegelung des Sperrkennzeichens außerhalb des Repository-Pfades.
- [KONTEXT] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[Kunden]` (Z. 2767), `CREATE TABLE [dbo].[Kreditor]` (Z. 2490), `CREATE TABLE [dbo].[Anschrif]` (Z. 7394), `CREATE TABLE [dbo].[Personen]` (Z. 7324) - Begründung: Belegt den Fortbestand der Altstruktur.
Prüfidee: Account speichern, danach `SELECT Status, Gesperrt, werbesperre, InnendienstID FROM Kunden WHERE I3D = <Kundennummer>` ausführen; alle Werte müssen den Account-Feldern entsprechen.
Tracelinks: StRS-901, StRS-903, SwRS-928, SwRS-929
Konsolidierung: Kandidat: SyRS-911 - Accounts/AccountCustomers/AccountSuppliers vs. Kunden/Kreditor für denselben Geschäftspartnerbegriff
Übernahmewürdigkeit: Workaround - Die Doppelhaltung ist ein Migrationsartefakt und langfristig abzulösen.
Status: belegt
Modul: M-88
ID: SyRS-912
Titel: Sichtbarkeitsbeschränkung auf eigene Kunden
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Centron.BL (AccountAddressBL, AccountActivitiesBL)
Vorbedingung: Der angemeldete Benutzer besitzt das Recht SHOW_ONLY_OWN_CUSTOMER (20400339).
Fakt: `AccountAddressBL.GetAccountAddresses` (src/backend/Centron.BL/Accounts/AccountAddressBL.cs, Z. 110-135) prüft `_appRightsBL.HasUserRight(loggedInUser.UserI3D.Value, UserRightsConst.Sales.Customer.CustomerCommon.SHOW_ONLY_OWN_CUSTOMER)` und bricht mit `Result.AsError("Sie haben nicht das Recht um Anschriften von anderen Kunden zu sehen!", DefaultMessageCodes.RightCheckFailed)` ab, wenn im Ergebnis Accounts enthalten sind, bei denen keiner der Betreuer `Adviser1I3D` bis `Adviser6I3D` der Mitarbeiter-I3D des Benutzers entspricht. `AccountActivitiesBL.OnlyOwn` (Z. 500-524) leitet aus SEARCH_CUSTOMER und SHOW_ONLY_OWN_CUSTOMER die Stufen `All`, `OnlyOwn` oder `None` ab.
Aussage: Das System soll Benutzern mit dem Recht SHOW_ONLY_OWN_CUSTOMER ausschließlich Geschäftspartner und deren Anschriften und Tätigkeiten zugänglich machen, bei denen der Benutzer als einer der sechs Betreuer eingetragen ist.
Ergebnis: Fremde Accounts werden nicht ausgeliefert; der Zugriff wird mit RightCheckFailed abgewiesen.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Accounts/AccountAddressBL.cs, `GetAccountAddresses` (Z. 113-133): Abgleich `f.Adviser1I3D.GetValueOrDefault(0) != employeeI3D && … != employeeI3D` und anschließender Fehler - Begründung: Konkrete durchsetzende Bedingung.
- [PRIMÄR] src/backend/Centron.BL/Accounts/Activities/AccountActivitiesBL.cs, `OnlyOwn` (Z. 515-521) - Begründung: Rechteableitung für die Tätigkeitssuche.
- [SEKUNDÄR] src/webservice/Centron.WebServices.Core/EntitiesWrongPlace/Administration/Rights/UserRightsConst.cs, Z. 2187 `SHOW_ONLY_OWN_CUSTOMER = 20400339` - Begründung: Identität des Rechts.
Prüfidee: Benutzer mit SHOW_ONLY_OWN_CUSTOMER anmelden und Adressen eines Accounts abrufen, bei dem er nicht Betreuer ist; die Anfrage muss mit RightCheckFailed abgewiesen werden.
Tracelinks: StRS-901, StRS-907, SyRS-913
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Mandanten- und Gebietsschutz im Vertrieb.
Status: belegt
Modul: M-90
ID: SyRS-913
Titel: Rechteprüfung für Ansprechpartner getrennt nach Kunde, Lieferant und Kontakt
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Centron.BL (AccountAddressContactBL)
Vorbedingung: Ein Ansprechpartner soll angelegt oder geändert werden.
Fakt: `AccountAddressContactBL.ValidateUserRights(int currentUserI3D, bool createContactPartner, bool editContactPartner, bool isSupplier, bool isCustomer)` (src/backend/Centron.BL/Accounts/AccountAddressContactBL.cs, Z. 375-418) lädt CREATE_CUSTOMER, EDIT_CUSTOMER, CREATE_ADDRESS_CONTACT (20400094), EDIT_CUSTOMER_CONTACT (20400095), RIGHT_LIEFERANTANSPRIGHT_PARTNERANLEGEN, RIGHT_LIEFERANTANSPRIGHT_PARTNERAENDERN, RIGHT_LIEFERANTANLEGEN, RIGHT_LIEFERANTAENDERN. Für Lieferanten genügt eines der beiden Lieferantenrechte, für Kunden eines der beiden Kundenrechte; für reine Kontakte (weder Kunde noch Lieferant) gilt der Kundenzweig.
Aussage: Das System soll das Anlegen und Ändern von Ansprechpartnern jeweils gegen die für die Kontoart passende Rechtekombination prüfen und bei fehlendem Recht mit einer die Kontoart benennenden Fehlermeldung abbrechen.
Ergebnis: Ansprechpartner können nur von berechtigten Benutzern angelegt oder geändert werden.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Accounts/AccountAddressContactBL.cs, `ValidateUserRights` (Z. 389-415): z. B. `if (isCustomer && editContactPartner && (checkRightsResult.Contains(EDIT_CUSTOMER) || checkRightsResult.Contains(EditCustomer.EDIT_CUSTOMER_CONTACT)) == false) return Result.AsError("Fehlendes Recht um Kundenansprechpartner zu bearbeiten");` - Begründung: Durchsetzende Stelle mit ODER-Verknüpfung der zulässigen Rechte.
- [SEKUNDÄR] UserRightsConst.cs, Z. 2047 `CREATE_ADDRESS_CONTACT = 20400094`, Z. 2082 `EDIT_CUSTOMER_CONTACT = 20400095` - Begründung: Identität der Rechte.
Prüfidee: Benutzer nur mit CREATE_ADDRESS_CONTACT (ohne CREATE_CUSTOMER) legt einen Kundenansprechpartner an; das muss gelingen. Ohne beide Rechte muss die Meldung "Fehlendes Recht um Kundenansprechpartner zu erstellen" erscheinen.
Tracelinks: SyRS-912, SyRS-914, SwRS-931
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Feingranulare Rechte werden produktiv genutzt.
Status: belegt
Modul: M-90
ID: SyRS-914
Titel: Rechteprüfung für Anschriften einschließlich Rückrollen von Sprache und Währung
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Centron.BL (AccountAddressBL)
Vorbedingung: Eine Anschrift eines Accounts wird angelegt oder geändert.
Fakt: `AccountAddressBL.ValidateUserRight(int currentUserI3D, bool createAddress, bool editAddress)` (src/backend/Centron.BL/Accounts/AccountAddressBL.cs, Z. 255-280) verlangt zum Anlegen CREATE_ADDRESS (20800124) und zum Ändern sowohl EDIT_CUSTOMER als auch EDIT_LOCATION (20400232). `CheckSpecialUserRightForAddressLanguageBeforeSave` (Z. 282-306) und `CheckSpecialUserRightForAddressCurrencyBeforeSave` (Z. 308-332) setzen bei fehlendem EDIT_CUSTOMER_ADDRESS_LANGUAGE (20800119) bzw. EDIT_CUSTOMER_ADDRESS_CURRENCY (20800140) das jeweilige Feld über `Session.GetOriginalEntityProperty` auf den Ursprungswert zurück, statt den Speichervorgang abzubrechen.
Aussage: Das System soll das Anlegen und Ändern von Anschriften rechtegeprüft durchführen und Änderungen an Sprache und Währung einer Anschrift ohne das jeweilige Sonderrecht stillschweigend auf den Ursprungswert zurücksetzen.
Ergebnis: Unberechtigte Sprach- oder Währungsänderungen werden nicht persistiert; alle übrigen Änderungen der Anschrift werden gespeichert.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Accounts/AccountAddressBL.cs, `ValidateUserRight` (Z. 264-277) mit `DefaultMessageCodes.RightCheckFailed` - Begründung: Durchsetzende Rechteprüfung.
- [PRIMÄR] src/backend/Centron.BL/Accounts/AccountAddressBL.cs, `CheckSpecialUserRightForAddressCurrencyBeforeSave` (Z. 319-325): `address.CurrencyI3D = (int)this.Session.GetSession().GetOriginalEntityProperty(address, nameof(address.CurrencyI3D));` - Begründung: Konkrete Rücksetzlogik als durchsetzende Stelle.
- [SEKUNDÄR] UserRightsConst.cs, Z. 2052, 2093, 2099, 2171 - Begründung: Identität der beteiligten Rechte.
Prüfidee: Benutzer ohne EDIT_CUSTOMER_ADDRESS_CURRENCY ändert die Währung einer bestehenden Anschrift und speichert; nach dem Speichern muss `AccountAddresses.CurrencyI3D` unverändert sein, während andere geänderte Felder gespeichert sind.
Tracelinks: SyRS-912, SyRS-913, SwRS-931
Konsolidierung: nein
Übernahmewürdigkeit: Sonderfall - Stilles Rückrollen statt Abbruch ist ungewöhnlich und sollte fachlich bestätigt werden.
Status: belegt
Modul: M-90
ID: SyRS-915
Titel: Pflichtangaben und Konsistenzregeln beim Speichern einer CRM-Tätigkeit
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Centron.BL (AccountActivitiesBL)
Vorbedingung: Ein Benutzer speichert eine CRM-Tätigkeit.
Fakt: `AccountActivitiesBL.SaveAccountActivity` (src/backend/Centron.BL/Accounts/Activities/AccountActivitiesBL.cs, Z. 87-103) bricht ab bei leerem `Caption` ("Betreff darf nicht leer sein."), bei `AccountI3D <= 0` ("Account muss ausgewählt sein."), bei `AccountAddressContactI3D <= 0` ("Ansprechpartner muss ausgewählt sein."), bei `ActivityKind == AccountActivityKind.Appointment` ohne `DateFrom`/`DateTo` ("Bei einem Termin muss ein Startdatum und Enddatum ausgewählt sein.") und bei `EditorI3D <= 0` ("Bearbeiter muss ausgewählt sein."). Anschließend werden Anlage- und Änderungsstempel inklusive `CreatedVersion`/`ChangedVersion` aus der Assembly-Version gesetzt.
Aussage: Das System soll eine CRM-Tätigkeit nur speichern, wenn Betreff, Account, Ansprechpartner und Bearbeiter gesetzt sind und bei der Tätigkeitsart Termin zusätzlich Start- und Endzeitpunkt vorliegen; Anlage- und Änderungsdaten inklusive Programmversion sollen automatisch gesetzt werden.
Ergebnis: Unvollständige Tätigkeiten werden mit sprechender Fehlermeldung abgewiesen; gespeicherte Tätigkeiten tragen vollständige Audit-Stempel.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Accounts/Activities/AccountActivitiesBL.cs, `SaveAccountActivity` (Z. 93-102) - Begründung: Durchgesetzte Validierungsregeln.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `AccountActivities`: `[AccountI3D] int NOT NULL`, `[AccountAddressContactI3D] int NOT NULL`, `[EditorI3D] int NOT NULL`, `[Caption] nvarchar(255) NOT NULL` (Z. 3189 ff.) - Begründung: DB-Constraint stützt dieselbe Regel.
Prüfidee: Tätigkeit vom Typ Appointment ohne `DateTo` speichern; die Rückgabe muss Status Error mit dem Text "Bei einem Termin muss ein Startdatum und Enddatum ausgewählt sein." liefern.
Tracelinks: StRS-902, SyRS-916, SwRS-932
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Sichert Auswertbarkeit der CRM-Historie.
Status: belegt
Modul: M-89
ID: SyRS-916
Titel: Bedingter Outlook-Abgleich einer CRM-Tätigkeit über Microsoft Graph
Ebene: SyRS
Typ: Schnittstelle
Qualitätsmerkmal:
Akteur: Centron.BL (AccountActivitiesBL, ScheduleBL)
Vorbedingung: Eine CRM-Tätigkeit wurde erfolgreich gespeichert und trägt `SyncWithOutlook = true`.
Fakt: `AccountActivitiesBL.SaveAccountActivity` (Z. 161-171) startet den Abgleich nur, wenn Transaktion erfolgreich war, `SyncWithOutlook` gesetzt ist und `_graphClient != null`; Fehler werden lediglich als Warnung geloggt. `SyncCrmActivityToOutlookAsync` (Z. 838-855) bricht ab, wenn `GetGraphCalendarSyncSettings().CentronCalendarSyncEnabled` false ist oder `ScheduleBL.CheckEmployeeInSyncDepartment(employee.I3D)` false liefert. Betreff und Text stammen wahlweise aus `CrmOutlookCustomSubject`/`CrmOutlookCustomBody`, der Ort wird aus `OutlookAppointementPlace` mit den Platzhaltern `@@Strasse@@`, `@@PLZ@@`, `@@Ort@@` gebildet.
Aussage: Das System soll den Outlook-Abgleich einer CRM-Tätigkeit nur ausführen, wenn der Kalender-Sync global aktiviert und der Bearbeiter einer Sync-Abteilung zugeordnet ist, und soll einen fehlgeschlagenen Abgleich nicht zum Fehlschlag des Speichervorgangs führen lassen.
Ergebnis: Die Tätigkeit ist in jedem Fall in c-entron gespeichert; der Kalendereintrag entsteht nur unter den genannten Bedingungen.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Accounts/Activities/AccountActivitiesBL.cs, `SyncCrmActivityToOutlookAsync` (Z. 840-855) - Begründung: Beide Abbruchbedingungen sind hier implementiert.
- [PRIMÄR] src/backend/Centron.BL/Accounts/Activities/AccountActivitiesBL.cs (Z. 167-170): `_logger.Warn("CRM Outlook sync failed for activity {ActivityI3D}: {Message}", …)` - Begründung: Belegt die Entkopplung von Speichern und Synchronisieren.
- [SEKUNDÄR] src/backend/Centron.BL/Calendar/CalendarBL.cs, `GetCalendarSynchronizationSettings` (Z. 52-89) mit `CrmOutlookUseCustomSubject`, `CrmOutlookCustomBody` - Begründung: Konfigurationsschalter der Vorlagen.
Prüfidee: Kalender-Sync deaktivieren, Tätigkeit mit `SyncWithOutlook = true` speichern; die Tätigkeit muss gespeichert sein und im Postfach darf kein Ereignis entstehen.
Tracelinks: StRS-902, StRS-904, SwRS-932, SwRS-933
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Robuste Entkopplung der externen Schnittstelle.
Status: belegt
Modul: M-89
ID: SyRS-917
Titel: Terminanfrage mit Terminvorschlägen über Exchange abwickeln
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Centron.BL (AppointmentRequestBL)
Vorbedingung: Zu einer Terminanfrage existieren Terminvorschläge im Exchange-Kalender; der Kunde antwortet über den Rückantwortlink mit der GUID der Anfrage.
Fakt: `AppointmentRequestBL.HandleAppointmentRequestReply(AppointmentRequestReply reply)` (src/backend/Centron.BL/AppointmentRequests/AppointmentRequestBL.cs, Z. 29-114) sucht die Anfrage über `filter.Guid`, lädt alle `AppointmentProposal`, baut eine `EwsConnection` aus den Mail-Einstellungen und wirft bei fehlenden Zugangsdaten `new Exception("No Credential found to Connect to EWS")`. Bei `reply.AcceptedProposalI3D == null` werden alle Vorschläge über `item.Delete(DeleteMode.MoveToDeletedItems)` entfernt und der Status auf `AppointmentRequestState.AppointmentProposalsRejected` gesetzt; sonst wird der akzeptierte Termin um " (Akzeptiert)" ergänzt, `appointmentRequest.ContactEmail` als Pflichtteilnehmer aufgenommen, die Kategorie von "Terminvereinbarung (offen)" auf "Terminvereinbarung (akzeptiert)" gewechselt und alle übrigen Vorschläge `SoftDelete`-gelöscht; der Status wird `AppointmentProposalAccepted`.
Aussage: Das System soll auf die Antwort zu einer Terminanfrage hin genau den akzeptierten Terminvorschlag im Exchange-Kalender bestätigen, den Anfragenden als Teilnehmer einladen, alle übrigen Vorschläge entfernen und den Anfragestatus entsprechend fortschreiben.
Ergebnis: Im Exchange-Kalender verbleibt genau ein bestätigter Termin; `AppointmentRequests.RequestState` spiegelt die Entscheidung.
Belege:
- [PRIMÄR] src/backend/Centron.BL/AppointmentRequests/AppointmentRequestBL.cs, `HandleAppointmentRequestReply` (Z. 71-111) - Begründung: Durchgesetzter Zustandsübergang inkl. Exchange-Operationen.
- [SEKUNDÄR] Kategoriewechsel `item.Categories.Remove("Terminvereinbarung (offen)")` / `Add("Terminvereinbarung (akzeptiert)")` (Z. 95-96) - Begründung: Fachliche Kennzeichnung im Outlook-Kalender.
- [KONTEXT] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[AppointmentRequests]` (Z. 25557) mit `[Guid] uniqueidentifier NOT NULL`, `[RequestState] int NOT NULL` - Begründung: Datenmodell inkl. Rückantwort-Schlüssel.
Prüfidee: Terminanfrage mit drei Vorschlägen erzeugen, einen annehmen; anschließend darf im Postfach nur der akzeptierte Termin bestehen und `RequestState` muss `AppointmentProposalAccepted` sein.
Tracelinks: StRS-904, SwRS-934
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Automatisierte Terminvereinbarung mit dem Kunden.
Status: belegt
Modul: M-94
ID: SyRS-918
Titel: Zentrale Konfiguration von Kalenderdarstellung und Kalendersynchronisation
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Administrator
Vorbedingung: Der Administrator öffnet die Kalendereinstellungen.
Fakt: `CalendarBL` (src/backend/Centron.BL/Calendar/CalendarBL.cs) liest und schreibt drei Einstellungsgruppen über `AppSettingsBL`: Darstellung (`GetCalendarRepresentationSettings`/`UpdateCalendarRepresentationSettings`, zehn Schalter von `HelpdeskTimeDisplayRepresentationShortDescription` bis `HelpdeskTimeDisplayDescription`), Synchronisation (`GetCalendarSynchronizationSettings`/`UpdateCalendarSynchronizationSettings` mit `OutlookAppointementCategory`, `OutlookAppointementPlace`, `OutlookAppointementHelpdeskTimeOvertake`, `CrmActivityTypesForOutlookSync`) und Terminvorlagen für Tickets (`GetAppointmentsForTicketsSettings`). Im UI entsprechen dem die Controller unter src/centron/Centron.WPF.UI/Modules/Calendar/Settings/{Representations,Synchronization,CrmOutlookTemplate,AppointmentsForTickets}.
Aussage: Das System soll die Darstellung von Kalendereinträgen, die Outlook-Synchronisationsvorlagen und die Terminvorlagen für Tickets als mandantenweite Anwendungseinstellungen verwalten, die über eigene Einstellungsseiten gepflegt werden.
Ergebnis: Geänderte Kalendereinstellungen sind über `SaveSettings()` persistiert und wirken für alle Benutzer.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Calendar/CalendarBL.cs, `UpdateCalendarRepresentationSettings` (Z. 107-137) und `UpdateCalendarSynchronizationSettings` (Z. 139-177) - Begründung: Durchsetzende Persistenzstelle über `GetSettingsForUpdate`/`SaveSettings`.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Calendar/Settings/Synchronization/CalendarSynchronizationSettingsController.cs und .../Representations/CalendarRepresentationsSettingsController.cs - Begründung: Einstiegspunkte im UI.
Prüfidee: Einstellung `HelpdeskTimeDisplayCustomerData` umschalten, Anwendung neu starten und den Wert erneut lesen; er muss erhalten bleiben.
Tracelinks: StRS-904, SwRS-935
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Notwendige Konfigurationsebene des Kalenders.
Status: belegt
Modul: M-93
ID: SyRS-919
Titel: Fallback-Hierarchie bei der Auflösung von Mailvorlagen
Ebene: SyRS
Typ: funktional
Qualitätsmerkmal:
Akteur: Centron.BL (MailTemplateBL)
Vorbedingung: Ein Vorgang benötigt eine Mailvorlage zu einer `MailTemplateReference`.
Fakt: `MailTemplateBL.MailTemplate(MailTemplateReference, int? branchI3D, int? accountI3D, int? employeeI3D)` (src/backend/Centron.BL/Mail/Templates/MailTemplateBL.cs, Z. 248-317) sucht in der dokumentierten Reihenfolge Account ("customer"), Mitarbeiter ("personal"), Filiale ("branch"), global ("global") und liefert andernfalls eine neu erzeugte Vorlage mit `pulledLocation = "hardcoded default fallback"`. Fehlende Betreff- oder Textfelder werden über `HandleIfSubjectOrBodyNotDefined` aus `mailTemplateReference.DefaultSubject`/`DefaultBody` ergänzt. `GetMailTemplate` (Z. 319-359) protokolliert die Fundstelle über `Logger.Info($"Pulled mailtemplate from {pulledLocation} …")`.
Aussage: Das System soll eine Mailvorlage in der Reihenfolge Geschäftspartner, Mitarbeiter, Filiale, global auflösen, bei fehlender Vorlage einen definierten Standardtext verwenden und die verwendete Fundstelle protokollieren.
Ergebnis: Es wird stets eine Vorlage mit gefülltem Betreff und Text geliefert; die Herkunft ist im Log nachvollziehbar.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Mail/Templates/MailTemplateBL.cs, `MailTemplate(...)` (Z. 265-297) - Begründung: Durchgesetzte Reihenfolge der Fallbacks.
- [SEKUNDÄR] docs/guides/development/create-mail-templates.md, Abschnitt "New Mail Template structure" - Begründung: Beschreibt die Identifikation über ObjectKind, ObjectI3D, SubObjectKind, TemplatePrio.
- [KONTEXT] src/centron/Centron.WPF.UI/Modules/Administration/MailTemplates/MailTemplatesAppModuleController.cs, `ModuleName => "Mailvorlagen"`, `GetRights()` mit `UserRightsConst.Administration.MAIL_TEMPLATE_MANAGEMENT` (20400302) - Begründung: Pflegemodul und Modulrecht.
Prüfidee: Für dieselbe Referenz je eine globale und eine accountbezogene Vorlage anlegen; der Abruf mit `accountI3D` muss die accountbezogene Vorlage liefern, der Abruf ohne `accountI3D` die globale.
Tracelinks: SwRS-936, SyRS-920
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Verhindert fehlende Mailtexte im Produktivbetrieb.
Status: belegt
Modul: M-96
ID: SyRS-920
Titel: Konfigurierbares Mailversandprotokoll mit verschlüsselter Ablage der Zugangsdaten
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Centron.BL (CentronMailFactory, MailSettingsBL)
Vorbedingung: Die Mail- und Kalendereinstellungen sind gepflegt.
Fakt: `CentronMailFactory.GetMail(DAOSession, CentronWebserviceMailType?)` (src/backend/Centron.BL/Mail/Factory/CentronMailFactory.cs) wählt anhand der Einstellung `ApplicationSettingID.CentronWebserviceMailType` zwischen `ExchangeMail`, `GraphMail` und `SMTPMail` (Default) und liefert im Testmodus `TestMail`. `MailSettingsBL.GetMailSettings` entschlüsselt `ExchangePassword` (Z. 117) und `GraphAppSecret` (Z. 133) über `_cryptoLogic.DecryptText`; `SetMailSettings` verschlüsselt sie beim Speichern über `_cryptoLogic.EncryptText` (Z. 212, 227).
Aussage: Das System soll den Mailversandweg (Exchange EWS, Microsoft Graph oder SMTP) als Anwendungseinstellung konfigurierbar halten und alle Kennwörter und Client-Secrets der Mailkonten ausschließlich verschlüsselt persistieren.
Ergebnis: Ein Wechsel des Versandwegs erfordert keine Codeänderung; in der Datenbank stehen keine Klartextkennwörter der Mailkonten.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Mail/MailSettingsBL.cs, `SetMailSettings`: `updateSettings.UpdateString(ApplicationSettingID.ExchangePassword, this._cryptoLogic.EncryptText(settings.ExchangePassword));` (Z. 212) und analog `GraphAppSecret` (Z. 227) - Begründung: Durchsetzende Verschlüsselungsstelle beim Speichern.
- [PRIMÄR] src/backend/Centron.BL/Mail/Factory/CentronMailFactory.cs, `GetMail`: `return centronMailType switch { Exchange => new ExchangeMail(session), Graph => new GraphMail(session), _ => new SMTPMail(session) };` - Begründung: Durchgesetzte Protokollauswahl.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Administration/MailAndCalender/General - Begründung: Pflegeort der Mailkonten im UI.
Prüfidee: Exchange-Kennwort speichern und die zugehörige Einstellung direkt in der Datenbank lesen; der Wert darf nicht dem eingegebenen Klartext entsprechen, nach dem Laden über `GetMailSettings` jedoch schon.
Tracelinks: StRS-906, SyRS-919, SyRS-921, SwRS-937
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Notwendiger Schutz von Postfach-Zugangsdaten.
Status: belegt
Modul: M-97
ID: SyRS-921
Titel: Zugriffsrecht und Geheimnisschutz für Mailscanner-Profile
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Centron.BL (MailScannerBL)
Vorbedingung: Ein Benutzer ruft die Mailscanner-Profile (Virtual Mail Assistant) ab oder speichert sie.
Fakt: `MailScannerBL.GetProfiles(MailScannerProfileFilter, LoggedInUser)` (src/backend/Centron.BL/MailScanner/MailScannerBL.cs, Z. 57-72) prüft `_appRightsBl.CheckRightsFromUser(loggedInUser.UserI3D.Value, UserRightsConst.VirtualMailAssistant.ACCESS_VMA_MODULE)` (20800112) und liefert bei fehlendem Recht `Result.AsError("Fehlendes Recht VMA Profile zu laden", DefaultMessageCodes.RightCheckFailed)`. `SaveProfile` (Z. 74-87) verschlüsselt vor dem Speichern `Password` und `ClientSecret` über `CentronConfigurationDbBL.EncryptWithMasterKey` und entschlüsselt beim Laden über `DecryptWithMasterKey`.
Aussage: Das System soll den Zugriff auf Mailscanner-Profile an das Recht ACCESS_VMA_MODULE binden und Postfach-Kennwörter sowie OAuth-Client-Secrets der Profile ausschließlich mit dem Masterschlüssel verschlüsselt speichern.
Ergebnis: Ohne VMA-Recht werden keine Profile geliefert; in `MailScannerProfiles` stehen `Password` und `ClientSecret` nur verschlüsselt.
Belege:
- [PRIMÄR] src/backend/Centron.BL/MailScanner/MailScannerBL.cs, `GetProfiles` (Z. 59-62) - Begründung: Durchsetzende Rechteprüfung mit konkretem Recht und Fehlercode.
- [PRIMÄR] src/backend/Centron.BL/MailScanner/MailScannerBL.cs, `EncryptProperties` (Z. 104-116), aufgerufen aus `SaveProfile` (Z. 76) - Begründung: Durchsetzende Verschlüsselung vor dem Persistieren.
- [SEKUNDÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[MailScannerProfiles]` (Z. 44033) mit `[MailPassword] nvarchar(max)`, `[ClientSecret] nvarchar(max)`, `[WorkflowI3Ds] nvarchar(max)`, `[ConnectionType] int NOT NULL` - Begründung: Datenmodell des Profils inkl. Workflow-Zuordnung.
Prüfidee: Benutzer ohne ACCESS_VMA_MODULE ruft `GetProfiles` auf; Rückgabe muss RightCheckFailed sein. Anschließend Profil mit Kennwort speichern und Spalte `Password` direkt lesen; sie darf nicht dem Klartext entsprechen.
Tracelinks: SyRS-920
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Postfachzugänge sind besonders schützenswert.
Status: belegt
Modul: M-98
ID: SyRS-922
Titel: Kundengeräte werden logisch gelöscht und lückenlos protokolliert
Ebene: SyRS
Typ: Daten
Qualitätsmerkmal:
Akteur: Centron.BL (AccountDeviceBL)
Vorbedingung: Ein Kundengerät ist einem Account zugeordnet.
Fakt: `AccountDeviceBL.DeleteAccountDevice(AppUser, int)` (src/backend/Centron.BL/Devices/AccountDeviceBL.cs, Z. 81-94) setzt `IsDeleted = true`, `DeletedDate = DateTime.Now`, `DeletedByI3D = currentUser.Employee.I3D` und schreibt über `WriteAccountDeviceLog` den Satz "Das Gerät wurde gelöscht" in `AccountDeviceLogs`; ein physisches Löschen findet nicht statt. `SaveAccountDevice` (Z. 42-78) protokolliert Anlage und Änderung mit Kurzzeichen des Bearbeiters. `SearchAccountDevices` (Z. 126-129) blendet gelöschte Geräte aus, solange `filter.IncludeDeleted == false`. Die Verknüpfung zu Tickets führt `AccountDevicesToTickets` (SSMS_DB_SCHEMA.sql, Z. 7267).
Aussage: Das System soll Kundengeräte ausschließlich logisch löschen, jede Anlage, Änderung und Löschung mit Zeitpunkt und Bearbeiter protokollieren und gelöschte Geräte standardmäßig aus Suchergebnissen ausblenden.
Ergebnis: Historische Ticketbezüge zu einem Gerät bleiben nachvollziehbar; die Gerätehistorie ist vollständig.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Devices/AccountDeviceBL.cs, `DeleteAccountDevice` (Z. 89-93) - Begründung: Durchgesetztes Soft-Delete mit Protokolleintrag.
- [PRIMÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[AccountDevices]` (Z. 7235) mit `[IsDeleted] bit NOT NULL`, `[DeletedDate]`, `[DeletedByI3D]` - Begründung: Datenmodell stützt das Soft-Delete.
- [SEKUNDÄR] src/centron/Centron.WPF.UI/Modules/Finances/Crm/Devices/AccountDeviceEditViewModel.cs - Begründung: Pflegemaske der Kundengeräte im CRM.
Prüfidee: Gerät löschen und danach mit `IncludeDeleted = false` und `= true` suchen; im ersten Fall darf es nicht, im zweiten muss es erscheinen, und `AccountDeviceLogs` muss einen Löscheintrag enthalten.
Tracelinks: SwRS-945
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Nachvollziehbarkeit der Gerätehistorie im Service.
Status: belegt
Modul: M-91
ID: SyRS-923
Titel: Bearbeitungs- und Öffnungsrecht einer Kampagne über Kampagnenmitarbeiter
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Centron.BL (CampaignBL)
Vorbedingung: Eine Kampagne existiert; der Benutzer ist Mitarbeiter des Systems.
Fakt: `CampaignBL.CanEditCampaign(AppUser currentUser, int campaignI3D)` (src/backend/Centron.BL/Accounts/Campaigns/CampaignBL.cs, Z. 421-437) gibt true zurück, wenn `currentUser.IsAdmin()` gilt; andernfalls wird in `CampaignEmployees` der Satz mit `EmployeeI3D == currentUser.Employee.I3D` gesucht und nur bei `user.IsAdmin == true` das Bearbeiten erlaubt. `CanOpenCampaign` (Z. 439-452) verlangt lediglich, dass ein `CampaignEmployees`-Satz für den Mitarbeiter existiert.
Aussage: Das System soll eine Kampagne nur für Systemadministratoren und für die als Kampagnen-Administrator markierten Kampagnenmitarbeiter zur Bearbeitung freigeben und das Öffnen einer Kampagne auf die der Kampagne zugeordneten Mitarbeiter beschränken.
Ergebnis: Nicht zugeordnete Mitarbeiter können die Kampagne weder öffnen noch bearbeiten.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Accounts/Campaigns/CampaignBL.cs, `CanEditCampaign` (Z. 426-436): `if (user == null || user.IsAdmin == false) return Result<bool>.AsSuccess(false);` - Begründung: Durchsetzende Bedingung auf `CampaignEmployees.IsAdmin`.
- [PRIMÄR] src/backend/Centron.BL/Accounts/Campaigns/CampaignBL.cs, `CanOpenCampaign` (Z. 444-451) - Begründung: Zweite durchsetzende Bedingung für das Öffnen.
- [SEKUNDÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[CampaignEmployees]` (Z. 34161) - Begründung: Trägertabelle der Zuordnung.
Prüfidee: Mitarbeiter ohne `CampaignEmployees`-Satz ruft `CanOpenCampaign` auf; das Ergebnis muss false sein. Mitarbeiter mit Satz und `IsAdmin = false` muss bei `CanEditCampaign` false erhalten.
Tracelinks: StRS-905
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Kampagnenhoheit liegt beim Kampagnenteam.
Status: belegt
Modul: M-92
ID: SyRS-924
Titel: Rechteprüfung bei Verwaltung und Löschung von Auftragsverarbeitungsverträgen
Ebene: SyRS
Typ: Sicherheit
Qualitätsmerkmal:
Akteur: Centron.BL (DsgvoBL)
Vorbedingung: Zu einem Account soll ein Auftragsverarbeitungsvertrag (AVV) gespeichert oder gelöscht werden.
Fakt: `DsgvoBL.SaveAccountOrderProcessingContract(AccountOrderProcessingContract contract, AppUser user)` (src/backend/Centron.BL/Administration/Documents/Dsgvo/DsgvoBL.cs, Z. 238-257) bricht mit `Result<AccountOrderProcessingContract>.AsError("Sie haben nicht das Recht um AVVs zu verwalten.")` ab, wenn `user.HasUserRight(UserRightsConst.Sales.Customer.CustomerCommon.ORDER_PROCESSING_CONTRACTS_MANAGEMENT)` (20800019) false ist. `DeleteAccountOrderProcesingContract` (Z. 313-340) verlangt DELETE_ORDER_PROCESSING_CONTRACTS (20800020) und löscht in einer Transaktion Vertrag, zugehöriges `OnlinePdfDocument` und `Document`. `DeleteTestAccountOrderProcessingContract` lässt das Löschen nur zu, wenn `entity.TestMode == true` ("Nur Tests können gelöscht werden").
Aussage: Das System soll das Anlegen und Ändern von Auftragsverarbeitungsverträgen an das Recht ORDER_PROCESSING_CONTRACTS_MANAGEMENT und das Löschen an DELETE_ORDER_PROCESSING_CONTRACTS binden sowie beim Löschen die zugehörigen Dokumente in derselben Transaktion mit entfernen.
Ergebnis: AVV-Daten können nur von berechtigten Benutzern verändert werden; es bleiben keine verwaisten PDF-/Dokumentensätze zurück.
Belege:
- [PRIMÄR] src/backend/Centron.BL/Administration/Documents/Dsgvo/DsgvoBL.cs, Z. 243 `if (user.HasUserRight(...ORDER_PROCESSING_CONTRACTS_MANAGEMENT) == false) return ...AsError("Sie haben nicht das Recht um AVVs zu verwalten.")` - Begründung: Durchsetzende Stelle.
- [PRIMÄR] src/backend/Centron.BL/Administration/Documents/Dsgvo/DsgvoBL.cs, Z. 318 `if (user.HasUserRight(...DELETE_ORDER_PROCESSING_CONTRACTS) == false) return Result.AsError("Sie haben nicht das Recht um AVVs zu löschen.")` - Begründung: Durchsetzende Stelle für das Löschen.
- [SEKUNDÄR] SSMS_DB_SCHEMA.sql, `CREATE TABLE [dbo].[AccountOrderProcessingContracts]` (Z. 14192); UserRightsConst.cs Z. 2023-2024 - Begründung: Datenmodell und Rechte-IDs.
Prüfidee: Benutzer ohne DELETE_ORDER_PROCESSING_CONTRACTS löscht einen AVV; die Operation muss mit der genannten Meldung fehlschlagen und Vertrag sowie Dokument müssen erhalten bleiben.
Tracelinks: StRS-901, SyRS-909
Konsolidierung: nein
Übernahmewürdigkeit: übernehmen - Datenschutzrechtlich relevanter Dokumentenbestand.
Status: belegt
Modul: M-88
@@ -0,0 +1,252 @@
# -*- coding: utf-8 -*-
"""Fuehrt die Teilergebnisse der Erhebungsagenten zu den Ergebnisdateien zusammen
und fuehrt zugleich den Konsistenzcheck durch."""
import os, re, sys, json, collections
WORK = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(os.path.dirname(WORK), "Ergebnisse")
AGENTS = ["A%d" % i for i in range(1, 13)]
LEVELS = ["StRS", "SyRS", "SwRS"]
FIELDS = ["ID", "Titel", "Ebene", "Typ", "Qualitätsmerkmal", "Akteur", "Vorbedingung",
"Fakt", "Aussage", "Ergebnis", "Belege", "Prüfidee", "Tracelinks",
"Konsolidierung", "Übernahmewürdigkeit", "Status", "Modul"]
ID_RE = re.compile(r"^ID:\s*((?:StRS|SyRS|SwRS)-\d+)\s*$")
def parse_blocks(path):
"""Zerlegt eine Agentendatei in Bloecke. Ein Block beginnt mit einer ID:-Zeile."""
if not os.path.exists(path):
return []
with open(path, encoding="utf-8") as fh:
lines = fh.read().replace("\r\n", "\n").split("\n")
blocks, cur = [], None
for line in lines:
if ID_RE.match(line.strip()):
if cur:
blocks.append(cur)
cur = [line.rstrip()]
elif cur is not None:
if line.strip().startswith("```"):
continue
cur.append(line.rstrip())
if cur:
blocks.append(cur)
# abschliessende Leerzeilen entfernen
out = []
for b in blocks:
while b and not b[-1].strip():
b.pop()
out.append(b)
return out
def field(block, name):
"""Liest ein einzeiliges Feld aus einem Block."""
pref = name + ":"
for line in block:
if line.startswith(pref):
return line[len(pref):].strip()
return None
def evidence(block):
"""Liefert die Belegzeilen eines Blocks."""
out, inside = [], False
for line in block:
if line.startswith("Belege:"):
inside = True
continue
if inside:
if line.startswith(" -") or line.startswith("- ") or line.strip().startswith("- ["):
out.append(line.strip())
elif line.strip() and not line.startswith(" "):
break
return out
def main():
if not os.path.isdir(OUT):
os.makedirs(OUT)
all_blocks = []
for a in AGENTS:
for lvl in LEVELS:
p = os.path.join(WORK, "%s_%s.md" % (a, lvl))
for b in parse_blocks(p):
all_blocks.append({"agent": a, "file_level": lvl, "lines": b})
# ---- Index aufbauen -------------------------------------------------
by_id = collections.OrderedDict()
dup_ids = []
for rec in all_blocks:
rid = field(rec["lines"], "ID")
rec["id"] = rid
if rid in by_id:
dup_ids.append(rid)
else:
by_id[rid] = rec
# ---- Ausgabedateien je Ebene ---------------------------------------
counts = {}
for lvl in LEVELS:
recs = [r for r in by_id.values() if (field(r["lines"], "Ebene") or r["file_level"]).strip() == lvl]
recs.sort(key=lambda r: int(r["id"].split("-")[1]))
counts[lvl] = len(recs)
yield_lines = []
for r in recs:
yield_lines.append("```")
yield_lines.extend(r["lines"])
yield_lines.append("```")
yield_lines.append("")
with open(os.path.join(WORK, "_body_%s.md" % lvl), "w", encoding="utf-8") as fh:
fh.write("\n".join(yield_lines))
# ---- Kennzahlen und Pruefungen -------------------------------------
report = {
"gesamt": len(by_id),
"je_ebene": counts,
"doppelte_ids": sorted(set(dup_ids)),
"ohne_beleg": [],
"ohne_uebernahmewuerdigkeit": [],
"ohne_pruefidee": [],
"ohne_modul": [],
"tote_tracelinks": [],
"hypothesen": [],
"risiko": [],
"belegarten": {"PRIMÄR": 0, "SEKUNDÄR": 0, "KONTEXT": 0},
"belege_gesamt": 0,
"nfa_ohne_qm": [],
"nfa_gesamt": 0,
"konsolidierungskandidaten": [],
"je_modul": collections.Counter(),
"je_agent": collections.Counter(),
"typen": collections.Counter(),
"uebernahme": collections.Counter(),
"qm": collections.Counter(),
}
risk_re = re.compile(
r"(sicherheit|berechtigung|recht|abrechn|faktur|rechnung|zahlung|passwort|"
r"authentifiz|autorisier|token|mandant|preis|steuer|mahn|lizenz|verschl|signat)", re.I)
for rid, rec in by_id.items():
b = rec["lines"]
report["je_agent"][rec["agent"]] += 1
mod = field(b, "Modul") or ""
if not mod:
report["ohne_modul"].append(rid)
else:
report["je_modul"][mod.strip()] += 1
ev = evidence(b)
if not ev:
report["ohne_beleg"].append(rid)
for line in ev:
report["belege_gesamt"] += 1
for k in report["belegarten"]:
if "[%s]" % k in line:
report["belegarten"][k] += 1
break
ue = field(b, "Übernahmewürdigkeit")
if not ue:
report["ohne_uebernahmewuerdigkeit"].append(rid)
else:
report["uebernahme"][ue.split("-")[0].strip()] += 1
if not field(b, "Prüfidee"):
report["ohne_pruefidee"].append(rid)
typ = (field(b, "Typ") or "").strip()
report["typen"][typ] += 1
qm = (field(b, "Qualitätsmerkmal") or "").strip()
if typ.lower().startswith("nicht-funktional"):
report["nfa_gesamt"] += 1
if not qm:
report["nfa_ohne_qm"].append(rid)
else:
report["qm"][qm] += 1
status = (field(b, "Status") or "").strip()
text = "\n".join(b)
is_hyp = status.upper().startswith("HYPOTHESE") or "[HYPOTHESE]" in text
if is_hyp:
report["hypothesen"].append(rid)
kons = (field(b, "Konsolidierung") or "").strip()
if kons and not kons.lower().startswith("nein"):
report["konsolidierungskandidaten"].append(rid)
# Risikoeinstufung
haystack = " ".join([field(b, "Titel") or "", typ, field(b, "Aussage") or "", mod])
if risk_re.search(haystack):
has_primary = any("[PRIMÄR]" in e for e in ev)
report["risiko"].append({
"id": rid, "titel": field(b, "Titel") or "",
"primaer": has_primary, "hypothese": is_hyp,
"verstoss": (not has_primary) and (not is_hyp)})
# Tracelinks
tl = field(b, "Tracelinks") or ""
for ref in re.findall(r"(?:StRS|SyRS|SwRS)-\d+", tl):
if ref not in by_id:
report["tote_tracelinks"].append((rid, ref))
with open(os.path.join(WORK, "_report.json"), "w", encoding="utf-8") as fh:
json.dump(report, fh, ensure_ascii=False, indent=1, default=list)
# ---- Hypothesenliste -----------------------------------------------
hyp_rows = []
for rid in report["hypothesen"]:
b = by_id[rid]["lines"]
hyp_rows.append({
"id": rid,
"titel": field(b, "Titel") or "",
"modul": field(b, "Modul") or "",
"aussage": field(b, "Aussage") or "",
"fakt": field(b, "Fakt") or "",
})
with open(os.path.join(WORK, "_hypothesen.json"), "w", encoding="utf-8") as fh:
json.dump(hyp_rows, fh, ensure_ascii=False, indent=1)
# ---- Traceability ---------------------------------------------------
trace = []
for rid, rec in by_id.items():
b = rec["lines"]
lvl = (field(b, "Ebene") or rec["file_level"]).strip()
tl = field(b, "Tracelinks") or ""
refs = re.findall(r"(?:StRS|SyRS|SwRS)-\d+", tl)
st = [r for r in refs if r.startswith("StRS")]
sy = [r for r in refs if r.startswith("SyRS")]
sw = [r for r in refs if r.startswith("SwRS")]
if lvl == "StRS":
st = [rid] + st
elif lvl == "SyRS":
sy = [rid] + sy
else:
sw = [rid] + sw
ev = evidence(b)
first = ev[0] if ev else ""
first = re.sub(r"^\-\s*\[[^\]]+\]\s*", "", first)
first = first.split(" - Begründung")[0].strip()
trace.append({
"strs": ", ".join(dict.fromkeys(st)) or "-",
"syrs": ", ".join(dict.fromkeys(sy)) or "-",
"swrs": ", ".join(dict.fromkeys(sw)) or "-",
"beleg": first,
"sort": int(rid.split("-")[1]),
"lvl": {"StRS": 0, "SyRS": 1, "SwRS": 2}[lvl],
})
trace.sort(key=lambda r: (r["sort"], r["lvl"]))
with open(os.path.join(WORK, "_trace.json"), "w", encoding="utf-8") as fh:
json.dump(trace, fh, ensure_ascii=False, indent=1)
print(json.dumps({k: v for k, v in report.items()
if k in ("gesamt", "je_ebene", "doppelte_ids", "ohne_beleg",
"ohne_uebernahmewuerdigkeit", "ohne_pruefidee", "ohne_modul",
"belegarten", "belege_gesamt", "nfa_gesamt", "nfa_ohne_qm")},
ensure_ascii=False, indent=1))
print("Hypothesen:", len(report["hypothesen"]))
print("Konsolidierungskandidaten:", len(report["konsolidierungskandidaten"]))
print("Tote Tracelinks:", len(report["tote_tracelinks"]), report["tote_tracelinks"][:20])
print("Risikoanforderungen:", len(report["risiko"]),
"davon Verstoesse:", sum(1 for r in report["risiko"] if r["verstoss"]))
print("Module mit Anforderungen:", len(report["je_modul"]))
print("Je Agent:", dict(report["je_agent"]))
if __name__ == "__main__":
main()
@@ -0,0 +1,325 @@
# Glossar
Domänen- und Systembegriffe, die in `StRS.md`, `SyRS.md` und `SwRS.md` verwendet werden.
Jeder Begriff ist mit der Fundstelle belegt, aus der die Bedeutung abgeleitet wurde.
Technische Bezeichner (Klassen, Tabellen, Spalten) bleiben in ihrer Originalschreibweise.
Legende der Belegklassen: `PRIMÄR` = durchgesetzte Regel in Code oder DB-Constraint,
`SEKUNDÄR` = UI-Label, Meldung, Mapping, Konfiguration, `KONTEXT` = Kommentar, Dokumentation.
---
## A
**Abholschein**
Belegart neben Angebot, Auftrag, Lieferschein, Rechnung und Gutschrift.
Beleg: `src/backend/Centron.Interfaces/CentronObjectKindNumeric.cs` — `PickupListClass = 5` mit `[Description("Abholschein")]`. (SEKUNDÄR)
**Adressstamm**
Fachliches Modul zur Pflege von Geschäftspartnern. Es existieren zwei Implementierungen: das klassische Modul „Adressen & Belege" (`AccountManagementAppModuleController`) und das neuere CRM-Modul, das ebenfalls den Anzeigenamen „Adressstamm" trägt (`CrmAppModuleController`). Welches der beiden registriert wird, entscheidet die Einstellung `CrmSettings.IsAccountManagementActive`.
Beleg: `src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs:515-527`; `src/centron/Centron.WPF.UI/Modules/Finances/AccountManagement/AccountManagementAppModuleController.cs:18`; `src/centron/Centron.WPF.UI/Modules/Finances/Crm/CrmAppModuleController.cs:25`. (PRIMÄR)
**AppUser**
Systeminterner Benutzer (Mitarbeiterkonto) des c-entron-Clients. Persistiert in der Tabelle `Sichbenu`.
Beleg: `src/backend/Centron.DAO/Mappings/Administration/AppUserMaps.cs`; `SSMS_DB_SCHEMA.sql` — `CREATE TABLE [dbo].[Sichbenu]`. (PRIMÄR)
**ApplicationKind**
Anwendungsart, mit der sich ein Client am Webservice anmelden darf. Jede Art trägt eine numerische Kennung, einen Anzeigenamen, eine Lizenz-GUID sowie optional ein erforderliches oder ein ausschließendes Recht und eine Gültigkeitsdauer der Sitzung. Es sind 45 Arten definiert.
Beleg: `src/backend/Centron.Interfaces/Administration/Logins/ApplicationKind.cs`. (PRIMÄR)
**Asset / Gerät**
Beim Kunden betriebene Hardware. Der Begriff ist im System nicht einheitlich modelliert; siehe **Gerätedatenhaltung**.
**Aufschlag Stundensatz**
Zuschlagssatz auf Mitarbeiterstunden, verwaltet im gleichnamigen Modul.
Beleg: `src/centron/Centron.WPF.UI/Modules/Administration/HourlySurchargeRates/HourlySurchargeRatesAppModuleController.cs:21` — „Verwalten von Aufschlagssätzen zu Mitarbeiterstunden." (SEKUNDÄR)
## B
**Beleg**
Sammelbegriff für die kaufmännischen Vorgangsdokumente Angebot (`AngKopf`/`AngPos`), Auftrag (`AufKopf`/`AufPos`), Lieferschein (`LiefKopf`/`LiefPos`), Rechnung (`RechKopf`/`RechPos`), Gutschrift und Abholschein. Jede Belegart besitzt ein eigenes Kopf- und Positionstabellenpaar.
Beleg: `SSMS_DB_SCHEMA.sql` — die genannten `CREATE TABLE`-Anweisungen. (PRIMÄR)
**Belegkondition**
Oberbegriff für Zahlungskonditionen und Lieferbedingungen, die einem Beleg zugeordnet werden.
Beleg: `src/centron/Centron.WPF.UI/Modules/Administration/ReceiptConditions/ReceiptConditionManagementAppModuleController.cs:19` — „Verwaltung von Belegkonditionen, Zahlungskonditionen und Lieferbedingungen". (SEKUNDÄR)
**Belegstatus (`ReceiptState`)**
Fachlicher Zustand eines Belegs mit den Ausprägungen `Active` („offen"), `Completed` („abgeschlossen") und `Canceled` („storniert").
Beleg: `src/backend/Centron.Interfaces/Sales/Receipts/ReceiptState.cs`. (PRIMÄR)
**Bestellvorschlagsliste (BVL)**
Modul des Einkaufs, das Bestellvorschläge ermittelt. Im Modulkatalog als veraltet gekennzeichnet (`#pragma warning disable 612`).
Beleg: `src/centron/Centron.WPF.UI/Modules/Purchasing/OrderSuggestionList/OrderSuggestionListAppModuleController.cs:15`; `src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs` (Abschnitt „c-entron Module: Einkauf"). (PRIMÄR)
**BL / BLLogic**
Geschäftslogikschicht (`Centron.BL`) beziehungsweise deren clientseitige Anbindung bei Direktverbindung zur Datenbank. Gegenstück ist **WSLogic**.
Beleg: `docs/getting-started/general-structure.md`. (KONTEXT)
**BLSession**
Arbeitseinheit der Geschäftslogik; kapselt eine NHibernate-Sitzung und stellt über `GetBL<T>()` die fachlichen Logikklassen bereit.
Beleg: `src/webservice/Centron.Controllers/Controllers/Unversioned/JwtAuthController.cs:60` — `using var session = new BLSession();`. (PRIMÄR)
## C
**c-entron Nexus**
Webportal auf Basis von Blazor Server. Enthält unter anderem ServiceBoard, WebCart, Dokumentenfreigabe und Verwaltungsmasken.
Beleg: `README.md` — „c-entron Nexus (aka c-entron Web)"; `src/nexus/CentronNexus/`. (KONTEXT)
**C-FLOW**
Interne Bezeichnung für Ticketvorlagen und die daraus abgeleiteten Ticketprozesse.
Beleg: `CentronRights.md`, Abschnitt 17 — Rechte `UserRightsConst.Sales.Customer.Helpdesk.CFlow.*`. (SEKUNDÄR)
**CentronObjectKindNumeric**
Systemweite numerische Objektart, mit der Belege, Tickets, Kunden, Geräte und weitere Objekte typisiert werden. Die Nummernvergabe stammt aus dem Delphi-Vorgängersystem; neue .NET-Konstanten beginnen ab 7600000.
Beleg: `src/backend/Centron.Interfaces/CentronObjectKindNumeric.cs:6-13` — Kommentar „Neue Konstanten für .Net werden autarg von c-entron Delphi angelegt Sie beginnen ab dem Wert 7600000". (KONTEXT)
**CentronConnectionType**
Betriebsart der Datenanbindung eines Clients: `SqlServer` (Direktzugriff auf die Datenbank) oder `CentronWebServices` (Zugriff über den Webservice).
Beleg: `src/backend/Centron.Interfaces/Administration/Connections/CentronConnectionType.cs`. (PRIMÄR)
**ChangeLog**
Feldbezogenes Änderungsprotokoll mit Objektart, Objekt-Kennung, Eigenschaftsname, altem und neuem Wert, Zeitpunkt und verursachendem Benutzer.
Beleg: `src/backend/Centron.Entities/Entities/ChangeTracking/ChangeLog.cs`; `src/backend/Centron.DAO/ChangeTracking/ChangeTrackingEventListener.cs`. (PRIMÄR)
**ClassContainer**
Dienstverzeichnis des WPF-Clients, über das `ILogic`-Schnittstellen aufgelöst werden.
Beleg: `docs/getting-started/general-structure.md`. (KONTEXT)
## D
**Data Updater**
Modul für Massenänderungen an Datenbeständen (Anzeigename „Data Updater", Lizenz `DataUpdaterV2`).
Beleg: `src/centron/Centron.WPF.UI/Modules/Massenupdates/MassUpdatesAppModuleController.cs:15`; `src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs` (Abschnitt „Stammdaten"). (PRIMÄR)
**DSGVO-Löschung**
Umsetzung des Löschanspruchs. Gelöschte Kontaktdaten werden nicht entfernt, sondern durch die Kennzeichnung „DSGVO: Auf Anfrage gelöscht." ersetzt.
Beleg: `src/backend/Centron.BL/Administration/DataSecurity/DataSecurityBL.cs:26-27`. (PRIMÄR)
## E
**EDI (Electronic Data Interchange)**
Automatisierter Belegaustausch mit Lieferanten. Unterstützt werden unter anderem Alltron, ALSO, ALSO Schweiz, EGIS, Herweck, Komsa und der Standard OpenTrans 2.1.
Beleg: `docs/reference/edi/edi-architecture.md`; `src/backend/Centron.Gateway/EDI_*`. (KONTEXT)
**Erwartetes Event**
Regelmäßig erwartetes technisches Ereignis; bleibt es aus, ist das ein auswertbarer Befund. Eigenes Modul samt Auswertungsmodul.
Beleg: `src/centron/Centron.WPF.UI/Modules/Helpdesk/ExpectedEvents/Controller/ExpectedEventsAppModuleController.cs:21`; `.../ExpectedEventsReporting/Controller/ExpectedEventsReportingAppModuleController.cs:18`. (SEKUNDÄR)
**ESR**
Schweizer Einzahlungsschein-Referenzverfahren. Rechnungen führen dafür eigene Felder.
Beleg: `SSMS_DB_SCHEMA.sql`, Tabelle `RechKopf` — Spalten `ESRKodierzeileBetrag`, `ESRReferenznummer`, `ESRBetrag`. (PRIMÄR)
## F
**Filiale**
Organisatorische Untereinheit eines Mandanten. Trägt Anschrift, Land, Sprache, Buchhaltungsnummer und Preisliste.
Beleg: `SSMS_DB_SCHEMA.sql` — `CREATE TABLE [dbo].[Filiale]` mit Spalte `MandantI3D`. (PRIMÄR)
**Filialbeschränkung**
Einschränkendes Recht, das die Sicht eines Benutzers auf die Daten seiner eigenen Filiale begrenzt. In der Belegsuche als SQL-Fragment umgesetzt.
Beleg: `src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/ContractReceiptSearchConfiguration.cs:174` — `OnlyOwnBranchWhereStatement => "AND (ISNULL(AK.FilialI3D, 0) = :BranchI3D)"`. (PRIMÄR)
## G
**Gerätedatenhaltung**
Im System bestehen vier getrennte Datenhaltungen für denselben fachlichen Gegenstand „Gerät beim Kunden":
`GeraeteKopf`/`GeraetePos` (Altbestand, mit Vertrags- und Klickzählerbezug), `MasterDataList`/`MasterDataListItems` (**Stammblätter**), `AccountDevices` (CRM-Gerätemodell) und `AssetManagementDevices` (aus der Systeminventarisierung befüllt).
Beleg: `SSMS_DB_SCHEMA.sql` — die vier `CREATE TABLE`-Anweisungen. (PRIMÄR)
## H
**Helpdesk / Ticket**
Kundenanfrage oder Störungsmeldung. Persistiert in `hlpdsk_requests` mit den Stammdatentabellen `hlpdsk_status`, `hlpdsk_typen`, `hlpdsk_kategorien` und `hlpdsk_prioritaeten`.
Beleg: `SSMS_DB_SCHEMA.sql` — die genannten Tabellen. (PRIMÄR)
**Hlpdsk-Timer / Helpdeskzeit**
Erfasster Zeitaufwand zu einem Ticket. Grundlage der Ticketabrechnung. Verschieben und Löschen sind nur zulässig, solange die Zeit nicht Bestandteil eines Belegs ist.
Beleg: `CentronRights.md`, Abschnitte 8 und 9 — „But only if the ticket is not part of a receipt." (SEKUNDÄR)
## I
**I3D**
Systemweite Konvention für den Primärschlüssel (`I3D`, „ID 3develop"), definiert als `int IDENTITY(1,1) NOT NULL` mit gruppiertem Primärschlüsselindex. Fremdschlüsselspalten enden auf `I3D`. 1458 der 1535 Tabellen folgen dieser Konvention.
Beleg: `docs/guides/database/database-conventions.md`, Abschnitt „Primary Key Convention"; Auszählung über `SSMS_DB_SCHEMA.sql`. (KONTEXT / PRIMÄR)
**ILogic**
Client-Schnittstelle für einen fachlichen Datenzugriff. Zu jeder `I<Modul>Logic` existieren zwei Implementierungen: `BL<Modul>Logic` für den Direktzugriff und `WS<Modul>Logic` für den Webservice-Zugriff.
Beleg: `docs/getting-started/general-structure.md`, Abschnitt „Dual Implementation Architecture". (KONTEXT)
## K
**Kalkulation**
Vorgangsart der Einkaufsseite mit eigenem Kopf-/Positionspaar `KalkKopf`/`KalkPos`; im Modul „Eingang/Kalk" verarbeitet.
Beleg: `SSMS_DB_SCHEMA.sql`; `src/centron/Centron.WPF.UI/Modules/Finances/Receipts/SupplierReceiptDocuments/SupplierReceiptDocumentsImportAppModuleController.cs:23`. (PRIMÄR)
**Klickabrechnung / Klick-Zähler**
Abrechnung nach Zählerständen von Geräten, typischerweise Drucksystemen. Eigenes Modul „Klick-Zählerverwaltung", eigene Einstellungsseite „ClickBilling", eigene Tabellen `GeraeteClickZaehler` und `GeraeteClickZaehlerHistory`.
Beleg: `src/centron/Centron.WPF.UI/Modules/Finances/DeviceClickCounter/DeviceClickCounterAppModuleController.cs:14`; `SSMS_DB_SCHEMA.sql`. (PRIMÄR)
**Kommissionierung**
Zusammenstellen von Waren für einen Auftrag. Es bestehen zwei Module: das registrierte Modul „Kommissionierung" und ein nicht registriertes Modul „Kommissionierung (Beta)".
Beleg: `src/centron/Centron.WPF.UI/Modules/Warehousing/Commissions/OrderCommissionAppModuleController.cs:18`; `src/centron/Centron.WPF.UI/Modules/Warehousing/Commissioning/CommissioningAppModuleController.cs:15-18`. (PRIMÄR)
**Kontenrahmen**
Buchhalterisches Kontengerüst, verwaltet im Modul `AccountSystemsAppModuleController`.
Beleg: `src/centron/Centron.WPF.UI/Modules/Warehousing/AccountSystems/Controller/AccountSystemsAppModuleController.cs:18`. (SEKUNDÄR)
**Kostenträger / Kostenstelle**
Betriebswirtschaftliche Zuordnungsobjekte. Eigene Tabellen `Kostentraeger` und `Kostenstellen`; Belege referenzieren sie über `KostentraegerI3D` und `KostenstellenI3D`.
Beleg: `SSMS_DB_SCHEMA.sql`, Tabellen `Kostentraeger`, `Kostenstellen` und Tabelle `RechKopf`. (PRIMÄR)
## L
**Lizenz (LicenseGuid)**
Eine Lizenz ist technisch eine GUID mit optionaler Anzahl (`count`), Ablaufdatum (`valid until date`) und Versionsgrenze (`valid until version`). 159 Lizenzkonstanten sind hinterlegt.
Beleg: `docs/reference/security/licensing-system.md`; `src/backend/Centron.Interfaces/Administration/Logins/LicenseGuids.cs`. (KONTEXT / PRIMÄR)
## M
**Mahnstufe**
Stufe des Mahnverfahrens zu einer Rechnung. Das Datenmodell sieht genau drei Stufen mit je eigenem Datum und Bearbeiter vor, ergänzt um eine Mahnsperre.
Beleg: `SSMS_DB_SCHEMA.sql`, Tabelle `RechKopf` — Spalten `Mahnung1Datum`/`Mahnung1BearbeiterI3D` bis `Mahnung3Datum`/`Mahnung3BearbeiterI3D`, `Mahnstufe`, `MahnStop`, `MahnInfo`. (PRIMÄR)
**Mandant**
Oberste organisatorische Einheit der Mandantenfähigkeit; einem Mandanten sind Filialen zugeordnet.
Beleg: `SSMS_DB_SCHEMA.sql` — `CREATE TABLE [dbo].[Mandant]`, `CREATE TABLE [dbo].[MandantenStammdat]`, `Filiale.MandantI3D`. (PRIMÄR)
**MSP (Managed Service Provider)**
Betriebsmodell, in dem Leistungen pauschal je verwaltetem System abgerechnet werden. Eigene Module MSP-Collector, MSP-Auswertung und MSP-Dashboard, gemeinsame Lizenz `LicenseGuids.MspModule`.
Beleg: `src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs` (Abschnitt „Controlling/Analytics"). (PRIMÄR)
## N
**Nummernkreis**
Fortlaufende Vergabe der Belegnummer. Belegkopftabellen führen dafür die Spalte `Nummer` zusätzlich zum technischen Schlüssel `I3D`.
Beleg: `SSMS_DB_SCHEMA.sql`, Tabelle `RechKopf` — `[Nummer] [int] NOT NULL`. (PRIMÄR)
## O
**OPOS (Offene Posten)**
Übersicht der offenen Forderungen. Eigenes Modul; Rechnungen führen dafür das Feld `OposImportInfo`.
Beleg: `src/centron/Centron.WPF.UI/Modules/Finances/Opos/OposOverviewAppModuleController.cs:15-17`; `SSMS_DB_SCHEMA.sql`, Tabelle `RechKopf`. (SEKUNDÄR / PRIMÄR)
## P
**Pauschalabrechnung**
Abrechnung eines Projekts oder Vertrags zu einem Festpreis unabhängig vom tatsächlichen Aufwand. Eigenes Modul mit eigener Lizenz `FlatRateBilling`.
Beleg: `src/centron/Centron.WPF.UI/Modules/Finances/FlatrateBilling/FlatRateProjectAppModuleController.cs:17`; `src/centron/Centron.WPF.UI/Modules/ModuleRegistration.cs` (Abschnitt „Abrechnung"). (PRIMÄR)
**Provisionsschema**
Regelwerk, nach dem Provisionen aus Belegpositionen ermittelt werden. Schemata werden verwaltet, Kunden zugeordnet und ausgewertet; jede dieser drei Aufgaben ist ein eigenes Modul mit eigener Lizenz.
Beleg: `src/centron/Centron.WPF.UI/Modules/Finances/Receipts/Provision/Schemas/ProvisionSchemaManagementAppModuleController.cs:19`, `.../SchemaCustomerAssignments/AssignmentsAppModuleController.cs:19`, `.../Evaluation/ProvisionEvaluationAppModuleController.cs:16`. (PRIMÄR)
## R
**Recht (I3D-Recht)**
Berechtigung, identifiziert über eine ganzzahlige Kennung. Rechte sind hierarchisch über `OwnerRecht` verkettet und liegen in der Tabelle `Sichrech`. Es sind 750 Rechtekonstanten definiert; neue .NET-Modulrechte beginnen ab 20800000.
Beleg: `src/webservice/Centron.WebServices.Core/EntitiesWrongPlace/Administration/Rights/UserRightsConst.cs:11-16`; `docs/guides/development/add-a-new-right.md`; `SSMS_DB_SCHEMA.sql`, Tabelle `Sichrech`. (PRIMÄR)
**Recht, einschränkendes (restricting right)**
Recht, das den Zugriff nicht erweitert, sondern begrenzt — etwa „nur eigene Tickets", „nur eigene Filiale", „nur eigene Abteilung".
Beleg: `CentronRights.md`, Abschnitte 1.1, 1.2, 2.1, 4, 7.1. (SEKUNDÄR)
**Result&lt;T&gt;**
Einheitlicher Ergebnisvertrag der Geschäftslogik mit den Zuständen `Success`, `Warning` und `Error` sowie einer Meldung und einem Meldungscode.
Beleg: `docs/reference/architecture/results-and-responses.md`; `src/backend/Centron.BL/Administration/Logins/UsersBL.cs` (durchgängige Verwendung). (KONTEXT / PRIMÄR)
**RMA (Return Merchandise Authorization)**
Rücksende- und Werkstattvorgang. Eigene Tabelle `Rma`, eigenes Modul „RMA / Werkstatt".
Beleg: `SSMS_DB_SCHEMA.sql` — `CREATE TABLE [dbo].[Rma]`; `src/centron/Centron.WPF.UI/Modules/Rma/RmaOverviewAppModulController.cs:24`. (PRIMÄR)
**RMM (Remote Monitoring and Management)**
Fernüberwachung von Kundensystemen. Angebunden über eigene Einstellungen und Steuerungsschnittstellen.
Beleg: `src/webservice/Centron.Controllers/Controllers/v1/Integrations/RmmController.cs`; `src/webservice/Centron.Controllers/Controllers/v1/DataExchange/RmmConnectionSettingsController.cs`. (PRIMÄR)
## S
**SelfCare-Formular**
Vom Kunden im Webportal auszufüllendes Formular, das an eine Ticketvorlage gebunden ist.
Beleg: `src/webservice/Centron.Controllers/Controllers/v1/SelfCare/SelfCareFormsController.cs`; `src/nexus/CentronNexus/Management/TicketPatterns/Components/SelfCareFormFieldGrid.razor`. (PRIMÄR)
**SEPA-Mandat**
Einzugsermächtigung des Kunden. Rechnungen verweisen über `SepaMandateI3D` darauf.
Beleg: `SSMS_DB_SCHEMA.sql`, Tabelle `RechKopf` — `[SepaMandateI3D] [int] NULL`. (PRIMÄR)
**ServiceBoard**
Arbeitsoberfläche für den Service, sowohl als eigenständige Anwendungsart lizenziert als auch als Bereich im Webportal Nexus umgesetzt.
Beleg: `src/backend/Centron.Interfaces/Administration/Logins/ApplicationKind.cs` — `ServiceBoard`, `ServiceBoardOnline`, `ServiceBoardNext`; `src/nexus/CentronNexus/ServiceBoard/`. (PRIMÄR)
**Sichbenu**
Datenbanktabelle der Benutzerkonten. Enthält unter anderem Kennwort-Hash (`Kennwort`), Mindestlänge (`KennLaenMin`), Änderungsintervall (`KennAendNachTagen`), Kontosperrzeitraum (`KontoDeakVon`/`KontoDeakBis`) und Zwei-Faktor-Angaben.
Beleg: `SSMS_DB_SCHEMA.sql` — `CREATE TABLE [dbo].[Sichbenu]`. (PRIMÄR)
**Sichrech**
Datenbanktabelle der Rechtedefinitionen mit Kennung, Anzeigetext, übergeordnetem Recht und Kennzeichen `Obsolete`.
Beleg: `SSMS_DB_SCHEMA.sql` — `CREATE TABLE [dbo].[Sichrech]`. (PRIMÄR)
**Sonderpreis**
Kundenspezifischer Artikelpreis. Grundlage der Artikelsichtbarkeit im Webshop des Kundenportals.
Beleg: `README.md`, Abschnitt „Contributing / WebCart" — „The available articles come from the customers ‚Sonderpreise'". (KONTEXT)
**Stammblatt**
Geräteblatt zu einem verkauften Gerät mit Seriennummer, Rechnungsbezug, Kundenbezug, Zählerstand und Vertragsbezug. Siehe **Gerätedatenhaltung**.
Beleg: `src/backend/Centron.Entities/Entities/Sales/Receipts/MasterDataLists/MasterDataList.cs`; `src/centron/Centron.WPF.UI/Modules/Finances/MasterDataLists/OverView/MasterDataListOverviewAppModuleController.cs:17-18`. (PRIMÄR)
**Stammdat**
Alte Einstellungstabelle. Neue Einstellungen sind ausdrücklich in `ApplicationSettings` abzulegen.
Beleg: `docs/guides/development/settings-management.md`, Abschnitt „Legacy: Stammdat Table" — „no new settings should be added to this table". (KONTEXT)
## T
**TAPI**
Telefonieschnittstelle für Anrufsteuerung und Anruferkennung; eingebunden über eine angepasste Fremdkomponente.
Beleg: `docs/reference/architecture/tapi.md` — „OUR Traysoft.AddTAPI.dll has been modified to work with .NET 5/6". (KONTEXT)
**Ticket (Sitzung)**
Sitzungsnachweis der Anmeldung am Webservice, nicht zu verwechseln mit dem **Helpdesk-Ticket**. Ein Sitzungsticket verfällt standardmäßig nach 30 Minuten.
Beleg: `src/backend/Centron.BL/Administration/Logins/TicketBL.cs:26` — `private const int TicketExpireInMinutes = 30;`. (PRIMÄR)
**Ticketvorlage (TicketPattern)**
Vorlage, aus der Tickets samt Kategorien, Checklisten, Formularen, Mailvorlagen und Skripten erzeugt werden.
Beleg: `src/nexus/CentronNexus/Management/TicketPatterns/Components/` (Registerkarten Allgemein, Checklisten, Formulare, Mailvorlage, Skripte, Webformular); `src/webservice/Centron.Controllers/Controllers/v1/Tickets/TicketPatternsController.cs`. (PRIMÄR)
## U
**Übernahmewürdigkeit**
Bewertung, ob eine erfasste Anforderung im Zielsystem erhalten bleiben soll. Bewertungsstufen: `übernehmen`, `Workaround`, `Sonderfall`, `veraltet`. Rein methodischer Begriff dieser Spezifikation, kein Systembegriff.
## V
**Vertrag**
Wiederkehrend abzurechnende Kundenvereinbarung. Kopftabelle `VertragKopf` mit 205 Spalten, Positionstabelle `VertragPos`, Vertragsartentabelle `VertragsArt`, Verknüpfung zur erzeugten Rechnung über `VertragRechKopfZuordnung`.
Beleg: `SSMS_DB_SCHEMA.sql` — die genannten Tabellen. (PRIMÄR)
**Vertragsabrechnung**
Automatisierte Erzeugung von Abrechnungsbelegen aus Verträgen.
Beleg: `src/centron/Centron.WPF.UI/Modules/Finances/AutomatedBilling/AutomatedBillingAppModuleController.cs:14`. (SEKUNDÄR)
## W
**Web-Account**
Zugang für Kunden zum Webportal. Eigenes Rechtemodell (`WebAccountRightsConst`) und eigene Anmeldeart (`LoginType=Webaccount`), getrennt vom Mitarbeiterkonto.
Beleg: `src/webservice/Centron.WebServices.Core/EntitiesWrongPlace/Administration/Rights/WebAccountRightsConst.cs`; `src/nexus/CentronNexus/Shared/Authorization/CentronAuthorization.cs:16-18`. (PRIMÄR)
**WebCart**
Webshop im Kundenportal. Sichtbare Artikel stammen aus den Sonderpreisen des Kunden.
Beleg: `README.md`, Abschnitt „Contributing / WebCart"; `src/nexus/CentronNexus/WebCart/`. (KONTEXT)
**WebServiceBL**
Schicht, die Entitäten in DTOs überführt und die fachlichen Rechteprüfungen für den Webservice ausführt.
Beleg: `docs/getting-started/general-structure.md`; `src/backend/Centron.BL/WebServices/`. (KONTEXT)
**WSLogic**
Clientseitige Implementierung einer `ILogic`-Schnittstelle, die den Webservice aufruft. Gegenstück zu **BLLogic**.
Beleg: `docs/getting-started/general-structure.md`, Abschnitt „WS Implementation (Web Service Access)". (KONTEXT)
## Z
**ZUGFeRD / XRechnung**
Standards für die elektronische Rechnung. Im System als eigene Erweiterung `ZUGFeRD21_Extended` sowie über einen Importendpunkt umgesetzt; Rechnungen tragen dafür die Einstellung `IsZugferdInvoiceActive`.
Beleg: `src/backend/Centron.Gateway/ZUGFeRD21_Extended/`; `src/webservice/Centron.Controllers/Controllers/v1/Receipts/ZugferdImportController.cs`; `docs/guides/development/xrechnung.md`; `docs/guides/development/settings-management.md` (Beispiel `ApplicationSettingID.IsZugferdInvoiceActive`). (PRIMÄR)
**Zwei-Faktor-Authentifizierung**
Im System bestehen zwei voneinander unabhängige Verfahren: ein anmeldeseitiges Verfahren über RADIUS oder E-Mail-Link (`TwoFactorAuthBL`) und ein zeitbasiertes Einmalkennwort nach TOTP (`TwoFactorAuthenticationBL` mit `Centron.Core.GoogleAuthenticator`).
Beleg: `src/backend/Centron.BL/Administration/Logins/TwoFactor/TwoFactorAuthBL.cs`; `src/backend/Centron.BL/TwoFactorAuthenticator/TwoFactorAuthenticationBL.cs:51`. (PRIMÄR)
@@ -0,0 +1 @@
{"is_error":true,"duration_api_ms":25332446,"num_turns":1,"stop_reason":"stop_sequence","session_id":"d23d004f-5de4-47d7-a3c9-dbc25dafbe27","total_cost_usd":293.00959234999993,"usage":{"output_tokens_details":{"thinking_tokens":0},"input_tokens":0,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":0,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"},"modelUsage":{"claude-haiku-4-5-20251001":{"inputTokens":6962,"outputTokens":22,"cacheReadInputTokens":0,"cacheCreationInputTokens":0,"webSearchRequests":0,"costUSD":0.007072,"contextWindow":200000,"maxOutputTokens":32000,"canonicalModel":"claude-haiku-4-5","provider":"firstParty","costBasis":"list"},"claude-opus-5":{"inputTokens":4038,"outputTokens":1970258,"cacheReadInputTokens":363710851,"cacheCreationInputTokens":8663246,"webSearchRequests":0,"costUSD":286.42943924999986,"contextWindow":1000000,"maxOutputTokens":64000,"canonicalModel":"claude-opus-5","provider":"firstParty","costBasis":"list"},"claude-sonnet-5":{"inputTokens":296,"outputTokens":136253,"cacheReadInputTokens":17725083,"cacheCreationInputTokens":665977,"webSearchRequests":0,"costUSD":6.573081100000002,"contextWindow":1000000,"maxOutputTokens":64000,"canonicalModel":"claude-sonnet-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"api_error","fast_mode_state":"off","fast_mode_disabled_reason":"extra_usage_disabled","origin":{"kind":"task-notification"},"subagent_stats":{"spawned":34,"requested":{"background":2,"foreground":0,"unset":32},"started_in_background":34,"max_depth":2,"spawned_by_subagents":24,"completed":32,"failed":1,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":22,"budget":0},"by_type":{"general-purpose":10,"Explore":24}},"subtype":"success","api_error_status":429,"result":"You've hit your session limit · resets 5:40pm (Europe/Berlin)","type":"result","duration_ms":446,"uuid":"50278ba1-d25b-441f-bab5-b2c1c385ec79","queued_turn_count":0}
@@ -0,0 +1,178 @@
# Versuch 01 - Baseline (Prompt-only) - Iteration 02
## Metadaten
- **Versuch:** V1 Baseline (Prompt-only)
- **Iteration:** 02 (erste Überarbeitung nach Auswertung von Iteration 01)
- **Codebasis:** c-entron ERP-Suite (Windows, C#/XAML, MSSQL)
- **Zeitstempel:** 2026-08-26
- **Vorgänger:** `01_Prompt.md` (SHA-256 `1B0DB06B…3C02FF`), 24 Läufe an Tag 1
- **Änderungsgrund:** Auswertung der 24 Läufe von Tag 1 über 3.287 erzeugte Anforderungen. Jede Änderung ist an einen gemessenen Befund gekoppelt:
| Änderung | Auslösender Befund |
|---|---|
| Modulinventar als Pflicht-Vorstufe, Mindestabdeckung je Modul, Vertiefung erst danach | Anforderungszahl schwankte je Lauf zwischen 42 und 325 (Faktor 5,9); 55 bis 60 von rund 85 Modulen blieben unanalysiert; die Modultabellen der Analyseberichte reichten von 0 bis 51 Zeilen |
| Hypothesenpflicht kalibriert, `Hypothesen.md` deckungsgleich mit den Inline-Markierungen | Zwei Läufe meldeten null Hypothesen bei 71 bzw. 148 Anforderungen; der Anteil schwankte zwischen 0 % und 26,2 %; mehrfach wichen Sammeldatei und Inline-Markierungen voneinander ab |
| Primärbeleg muss die durchsetzende Stelle benennen | 45,9 % aller Anforderungen trugen genau einen Beleg; in einem Lauf waren 5 von 31 risikorelevanten Anforderungen weder mit `PRIMÄR` noch als `[HYPOTHESE]` gedeckt |
| Belegpflicht verschärft: ohne Beleg keine Anforderung | Eine Anforderung wurde ohne jeden Beleg geschrieben |
| Konsolidierungsbegriff an einem Beispiel kalibriert | Anteil der Konsolidierungskandidaten schwankte je Lauf zwischen 2,4 % und 35,2 % |
| Eigenes Feld `Qualitätsmerkmal` für die ISO-25010-Zuordnung | Die Zuordnung war gefordert, hatte aber keinen Ablageort: nur 33 von 313 nicht-funktionalen Anforderungen führten sie als eigene Angabe |
| Risikoanforderungen im Konsistenzcheck auflisten | Verstöße gegen die risikobasierte Priorisierung fielen erst in der nachgelagerten Auswertung auf, nicht im Lauf selbst |
Unverändert bleiben Prüfidee, Tracelinks, Belegklassifikation und das Blockformat: Prüfidee und Tracelinks waren in **allen** 3.287 Anforderungen gesetzt, 78,6 % der Belege waren `PRIMÄR`.
> Dieser Prompt enthält ausschließlich die **Analyseanweisung** und ist damit unabhängig von einem
> bestimmten Werkzeug oder Modell einsetzbar. Welche Werkzeuge im jeweiligen Lauf zur Verfügung
> stehen und wohin die Ergebnisse geschrieben werden, stellt der Versuchsaufbau beim Start bei.
---
## Prompt
Du bist ein Requirements Engineer im Reverse Requirements Engineering eines Legacy-ERP-Systems. Erzeuge aus der vorliegenden Codebasis eine Anforderungsspezifikation nach **ISO/IEC/IEEE 29148:2018**. Arbeite ausschließlich auf den im Arbeitsverzeichnis liegenden Artefakten (Quellcode, Konfiguration, UI-Ressourcen, ggf. DB-Skripte). Nutze nur Informationen, die du aus diesen Artefakten gewinnen kannst.
### Auftrag
Erzeuge eine konsolidierte Spezifikation auf den drei Ebenen:
1. **StRS** - Stakeholder Requirements Specification (fachliche Sicht, Akteure, Geschäftsziele)
2. **SyRS** - System Requirements Specification (Systemverhalten, Schnittstellen, Performance-, Sicherheitsanforderungen)
3. **SwRS** - Software Requirements Specification (Komponenten, Datenmodelle, Software-interne Regeln)
Ziel ist eine Spezifikation, die als belastbare Basis für eine Web-/SaaS-Neuimplementierung dienen kann.
### Scope (Schritt 1 der RRE-Methodenkette, manuell vorgegeben)
Der Untersuchungsgegenstand ist die **gesamte Codebasis** im Arbeitsverzeichnis. Es gilt bewusst keine Modulbeschränkung: Alle Module, Datenobjekte und Prozesse sind gleichrangig zu erfassen.
**Breite geht vor Tiefe.** Ein fehlendes Requirement führt bei einer Neuimplementierung zu Funktionsverlust; eine oberflächlich erfasste Funktion lässt sich dagegen nachschärfen. Erfasse deshalb zuerst die gesamte Breite und vertiefe erst danach. Halte dich an die Reihenfolge aus dem Abschnitt **Vorgehen**: erst Inventar, dann Mindestabdeckung, dann Vertiefung.
### Vorgehen (statische Analyse, keine Ausführung)
Bearbeite die Schritte 2-6 der RRE-Methodenkette (Schritt 1 Scope ist oben vorgegeben, Schritt 7 Validierung erfolgt manuell durch Fachexperten). Vorgeschaltet ist eine verbindliche Inventarisierung:
**Schritt 0 - Modulinventar (vor der ersten Anforderung).** Verschaffe dir zuerst einen vollständigen Überblick über den Untersuchungsgegenstand und lege ihn im `Analysebericht.md` als Tabelle ab: fachliches Modul beziehungsweise Komponente, Pfad im Arbeitsverzeichnis, ein Satz zur fachlichen Aufgabe. Das Inventar wird erstellt, **bevor** die erste Anforderung formuliert wird. Es ist die Bezugsgröße für die Abdeckung und darf später ergänzt, aber nicht gekürzt werden.
**Schritt 0b - Mindestabdeckung.** Jedes Modul des Inventars erhält **mindestens eine** Anforderung, bevor irgendein Modul vertieft wird. Lässt sich für ein Modul keine belegbare Anforderung bilden, führe es im Inventar als `nicht analysiert` mit einer kurzen Begründung. Ein Modul ohne Anforderung und ohne Begründung ist unzulässig.
**Schritt 0c - Vertiefung nach Risiko.** Erst wenn die Mindestabdeckung steht, vertiefe einzelne Module. Beginne dort, wo Sicherheitsregeln, Abrechnungs- und Fakturierungslogik oder Berechtigungsprüfungen liegen.
2. **Artefakterhebung:** Erfasse Quellcode, Konfiguration, UI-Texte, Datenbankschemata, Schnittstellenbeschreibungen sowie Change-Historie und Projektartefakte (Commit-Messages, Tickets, Release Notes, Migrationsnotizen), soweit als Datei lesbar.
3. **Technische Analyse:** Identifiziere Module, Komponenten, Abhängigkeiten, Statusmaschinen, Validierungslogik, Berechtigungsprüfungen.
4. **Semantische Interpretation:** Leite fachliche Aussagen aus technischen Implementierungen ab (z. B. Statusübergänge → Geschäftsregel).
5. **Formalisierung:** Überführe die Aussagen in klare, testbare Anforderungen mit Kontext, Vorbedingung und Ergebnis.
6. **Traceability-Anreicherung:** Verknüpfe jede Anforderung mit konkreten Artefaktbelegen.
### Pflicht-Eigenschaften jeder Anforderung
- **Belegpflicht:** Jede Anforderung **muss** mindestens einen konkreten Artefaktbeleg führen (Dateipfad, Klasse/Methode, SQL-Statement, UI-String, Konfigurationseintrag). Jeder Beleg erhält eine kurze Begründung, warum er die Aussage trägt. Lässt sich eine Aussage nicht belegen, **schreibe die Anforderung nicht** - erfasse den offenen Punkt stattdessen als Hypothese. Eine Anforderung ohne Beleg ist unter keinen Umständen zulässig.
- **Trennung von Fakt und Interpretation:** Die belegte technische Beobachtung (Feld `Fakt`) wird getrennt von der fachlichen Interpretation (Feld `Aussage`) dokumentiert, damit nachvollziehbar bleibt, was im Artefakt steht und was daraus geschlossen wurde.
- **Risikobasierte Priorisierung:** Anforderungen zu Sicherheitsregeln, Abrechnungs-/Fakturierungslogik und Berechtigungen unterliegen strengeren Evidenzanforderungen: Sie benötigen mindestens einen `PRIMÄR`-Beleg, andernfalls sind sie zwingend als `[HYPOTHESE]` zu kennzeichnen. Ein `PRIMÄR`-Beleg benennt hier die **durchsetzende Stelle** - Datei, Klasse, Methode und die konkrete Prüfung, Bedingung oder das Constraint. Ein Verweis auf eine Datei ohne Angabe der prüfenden Stelle genügt für diese Anforderungen nicht.
- **Belegklassifikation:** Kennzeichne jeden Beleg als
- `PRIMÄR` (durchgesetzte Regel im Code oder DB-Constraint),
- `SEKUNDÄR` (UI-Label, Fehlermeldung, Reportlayout, Mappingtabelle, Konfigurationsschalter),
- `KONTEXT` (Kommentar, Commit-Message, Ticketreferenz).
- **Hypothesenmarkierung:** Aussagen, die sich nicht eindeutig aus Artefakten ableiten lassen, kennzeichnest du explizit mit `[HYPOTHESE]` und einer kurzen Begründung, welche Information zur Bestätigung fehlt. Bei einer Codebasis dieser Größe ist eine Analyse ohne jeden offenen Punkt unplausibel: Führst du keine einzige Hypothese, begründe das ausdrücklich in der Selbstbewertung. Umgekehrt ist eine hohe Hypothesenzahl kein Mangel, sondern ein Hinweis auf ehrliche Abgrenzung.
- **Verifizierbarkeit:** Jede Anforderung enthält mindestens eine Prüfidee oder ein Akzeptanzkriterium.
- **Eindeutigkeit:** Vermeide vage Begriffe ("schnell", "benutzerfreundlich"); definiere domänenspezifische Begriffe beim ersten Auftreten.
- **Übernahmewürdigkeit:** Beurteile für jede Anforderung, ob ihre Funktion im Zielsystem erhalten bleiben soll. Unterscheide `übernehmen` (fachlich weiterhin erforderlich), `Workaround` (historisch gewachsene Behelfslösung), `Sonderfall` (Ausnahme für einen einzelnen Kunden, Mandanten oder Altbestand) und `veraltet` (durch neuere Logik abgelöst oder fachlich überholt). Begründe die Einstufung in einem Halbsatz. Diese Angabe steuert die spätere fachliche Priorisierung; eine Fehleinschätzung ist unkritisch, eine fehlende Angabe nicht.
- **Redundanzfreiheit:** Formuliere jede Anforderung so, dass sie von den übrigen klar abgegrenzt ist. Beschreiben zwei Anforderungen dieselbe fachliche Funktion aus unterschiedlicher Perspektive, führe sie zusammen oder grenze sie im Titel und in der Aussage ausdrücklich gegeneinander ab.
### Formatvorgabe pro Anforderung
```
ID: <StRS|SyRS|SwRS>-<laufende Nummer>
Titel: <kurzer Titel>
Ebene: <StRS | SyRS | SwRS>
Typ: <funktional | nicht-funktional | Schnittstelle | Daten | Sicherheit | ...>
Qualitätsmerkmal: <nur bei nicht-funktionalen Anforderungen: ISO-25010-Merkmal, sonst leer>
Akteur: <Rolle / System / Komponente>
Vorbedingung: <Zustand vor Auslösen>
Fakt: <belegte technische Beobachtung, z. B. Statusübergang, Constraint, Prüfung>
Aussage: Das System soll <...>. (fachliche Interpretation als klare Soll-Aussage)
Ergebnis: <erwartetes Ergebnis / Nachbedingung>
Belege:
- [PRIMÄR] <Pfad/Klasse/Methode/SQL/UI-String> - Begründung: <warum trägt der Beleg die Aussage>
- [SEKUNDÄR] <...> - Begründung: <...>
- [KONTEXT] <...> - Begründung: <...>
Prüfidee: <Akzeptanzkriterium oder Testidee>
Tracelinks: <verwandte StRS-/SyRS-/SwRS-IDs>
Konsolidierung: <nein | Kandidat: <IDs oder Stellen, die dieselbe fachliche Funktion abbilden>>
Übernahmewürdigkeit: <übernehmen | Workaround | Sonderfall | veraltet> - <kurze Begründung>
Status: <belegt | HYPOTHESE>
```
### Traceability
Stelle Forward- und Backward-Traceability zwischen den drei Ebenen her:
- Jede SwRS-Anforderung referenziert die zugehörige SyRS-Anforderung.
- Jede SyRS-Anforderung referenziert die zugehörige StRS-Anforderung.
- Erzeuge zusätzlich eine konsolidierte **Traceability-Tabelle** (Markdown oder CSV): `StRS-ID | SyRS-ID | SwRS-ID | Artefaktbeleg`.
### Nicht-funktionale Anforderungen
- Ordne nicht-funktionale Anforderungen den Qualitätsmerkmalen der **ISO/IEC 25010** zu (z. B. Zuverlässigkeit, Performance-Effizienz, Sicherheit, Wartbarkeit, Übertragbarkeit). Trage die Zuordnung in das dafür vorgesehene Feld `Qualitätsmerkmal` ein, nicht in das Feld `Typ`.
- Leite Betriebs- und Sicherheitsanforderungen gezielt auch aus indirekt sichtbaren Artefakten ab: Konfigurationen, Deployment-Skripte, Logging-Policies, Rechteprüfungen.
### Konsolidierungsbedarf
Die Codebasis enthält fachliche Redundanz: Dieselbe Anforderung kann auf unterschiedlichen Masken oder in unterschiedlichen Modulen mehrfach und teils unterschiedlich implementiert sein. Prüfe daher bei jeder Anforderung, ob andere Anforderungen dieselbe fachliche Funktion abbilden, und vermerke solche Fälle im Feld `Konsolidierung` als Kandidat für eine Zusammenführung im Zielsystem.
**Gemeint sind fachlich gleichartige Konzepte in getrennten Implementierungen**, nicht bloß ähnlich formulierte Anforderungen. Ein Beispiel aus dieser Codebasis: Drucker werden als „Stammblätter" geführt, sonstige Hardware getrennt davon als „Assets" - zwei Datenhaltungen für denselben fachlichen Gegenstand, die im Zielsystem zu einem Asset-Konzept zusammengeführt werden sollen. Zwei Anforderungen, die denselben Sachverhalt nur aus Sicht verschiedener Ebenen beschreiben (etwa StRS und SwRS), sind **kein** Konsolidierungsfall - dafür sind die Tracelinks da.
### Ergebnisstruktur (im vorgegebenen Ausgabeverzeichnis)
```
Ergebnisse/
StRS.md
SyRS.md
SwRS.md
Traceability.md (oder Traceability.csv)
Hypothesen.md (Sammlung aller mit [HYPOTHESE] markierten Aussagen mit offener Frage)
Glossar.md (Domänenbegriffe, die in den Anforderungen verwendet werden)
Analysebericht.md (Modulinventar aus Schritt 0, Abdeckungstabelle, Konsistenzcheck,
Selbstbewertung, bekannte Lücken)
```
Das Ausgabeverzeichnis wird beim Start des Laufs beigestellt. Die analysierte Codebasis wird ausschließlich gelesen und nicht verändert.
### Randbedingungen
- **Keine Halluzinationen.** Wenn ein Artefakt nicht gelesen oder eine Aussage nicht belegt werden kann, ist das offen zu legen, nicht zu erfinden.
- **Keine Generierung von Code.** Es sollen ausschließlich Spezifikationsartefakte entstehen.
- **Keine Annahme über nicht beigestellte Hilfsmittel.** Arbeite mit dem, was dir in diesem Lauf zur Verfügung steht. Setze keine zusätzlichen Analysewerkzeuge, Datenbankzugriffe oder laufende Systeme voraus. Stehen für eine Aussage nur indirekte Belege zur Verfügung, ist sie als `[HYPOTHESE]` zu kennzeichnen.
- **Migrationsperspektive berücksichtigen.** Erkennbare Workarounds, Sonderfälle und überholte Logik gehören in das Feld `Übernahmewürdigkeit`, nicht in das Feld `Status`. `Status` beschreibt ausschließlich die Belegsituation (`belegt` oder `HYPOTHESE`), `Übernahmewürdigkeit` die fachliche Zukunft der Anforderung. Beide Angaben sind unabhängig voneinander: Eine gut belegte Anforderung kann ein Workaround sein, eine Hypothese kann übernahmewürdig sein.
- **Sprache:** Deutsch für Anforderungsaussagen, technische Bezeichner (Klassen, Methoden, Spalten) bleiben in ihrer Originalsprache.
### Abschluss
Führe vor Abgabe einen **Konsistenzcheck über das gesamte Anforderungs-Set** durch und dokumentiere das Ergebnis im `Analysebericht.md`:
- Doppelte oder mehrfach vergebene IDs
- Anforderungen ohne Beleg
- Anforderungen ohne Angabe zur `Übernahmewürdigkeit`
- Tracelinks auf nicht existierende IDs
- Inhaltlich deckungsgleiche Anforderungen, die nicht als Konsolidierungskandidat markiert sind
- **Liste aller risikorelevanten Anforderungen** (Sicherheit, Abrechnung/Fakturierung, Berechtigungen) mit ihrer Belegsituation: ID, Titel, ob ein `PRIMÄR`-Beleg vorliegt, andernfalls die `[HYPOTHESE]`-Kennzeichnung. Diese Liste macht Verstöße gegen die risikobasierte Priorisierung im Lauf selbst sichtbar.
- **Abgleich `Hypothesen.md` gegen die Inline-Markierungen:** Beide müssen dieselben Anforderungen nennen. `Hypothesen.md` enthält genau die Anforderungen mit `[HYPOTHESE]`-Markierung und keine zusätzlichen freien Fragen; offene Punkte ohne zugehörige Anforderung gehören in die Selbstbewertung.
Erstelle außerdem die **Abdeckungstabelle** auf Basis des Modulinventars aus Schritt 0: je Modul die Einstufung `tief | mittel | flach | nicht analysiert` und die Anzahl der daraus erzeugten Anforderungen. Jede Zeile des Inventars muss in der Abdeckungstabelle auftauchen.
Beende den Lauf mit einer kurzen Selbstbewertung im `Analysebericht.md`:
- Wie viele Module des Inventars wurden tief, mittel, flach beziehungsweise gar nicht analysiert? Nenne absolute Zahlen, nicht nur Beispiele.
- Wurde die Mindestabdeckung erreicht, also hat jedes Modul mindestens eine Anforderung? Falls nein: welche Module fehlen und warum?
- An welchen Stellen war der Beleg dünn (hoher Anteil `SEKUNDÄR`/`KONTEXT` oder `[HYPOTHESE]`)?
- Falls keine einzige Hypothese geführt wurde: Begründung, warum die Analyse ohne offene Punkte auskommt.
- Welche Erkenntnisse legen einen Nachschlag in einer Folge-Iteration nahe?
---
### Werkzeugkontext (vom Versuchsaufbau vorgegeben)
Für diesen Lauf stehen zur Verfügung: Lesen und Suchen von Dateien, das Ausführen von
Kommandozeilenbefehlen im Arbeitsverzeichnis sowie die werkzeugeigenen Subagenten.
Nicht verfügbar sind: spezialisierte Agentenrollen aus Konfigurationsdateien, externe
Werkzeugserver.
Triff keine Annahmen über weitere Werkzeuge und versuche nicht, nicht verfügbare
Werkzeuge zu ersetzen.
### Ausgabeverzeichnis (überschreibt anderslautende Pfadangaben oben)
Schreibe ALLE zu erzeugenden Ergebnisdateien in das Verzeichnis
`c:\DEV\MasterArbeit\Versuche\Versuch_01\Iteration 3\claude-opus-5\builtin\high\02_Lauf_2026-08-26_160037_v4.5.0-9d9e\Ergebnisse\`.
Verändere keine Dateien im Arbeitsverzeichnis (der analysierten Codebasis).