diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml
new file mode 100644
index 0000000..53a8e6b
--- /dev/null
+++ b/.gitea/workflows/build.yml
@@ -0,0 +1,73 @@
+# Build- und Testlauf auf beiden Zielplattformen.
+#
+# Zweck: Die Portierung ist nur so lange etwas wert, wie sie nicht wieder zurückschleicht. Ein
+# neues DateTime.Now, ein ToString("N2") ohne Formatanbieter oder eine WinForms-Referenz im Core
+# fallen auf dem Windows-Entwicklungsrechner nicht auf – hier schon.
+#
+# Gitea Actions ist Actions-kompatibel; unter GitHub läuft dieselbe Datei als .github/workflows/.
+
+name: Build & Test
+
+on:
+ push:
+ branches: [main, 'feat/**']
+ pull_request:
+ branches: [main]
+
+jobs:
+ build:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '10.0.x'
+
+ # Windows: die gesamte Projektmappe, inklusive der alten WinForms-Shell.
+ - name: Build (Windows, gesamte Mappe)
+ if: matrix.os == 'windows-latest'
+ run: |
+ dotnet build IBKRTrader.slnx -c Release
+ dotnet test IBKRTrader.slnx --no-build -c Release --logger "trx;LogFileName=test-results.trx"
+
+ # Linux: alles AUSSER IBKRTrader.App (WinForms, net10.0-windows) – das lässt sich hier
+ # naturgemäß nicht bauen. Diese Aufzählung entfällt, sobald die WinForms-Shell aus dem
+ # Build genommen wird; dann baut auch hier wieder die ganze Mappe.
+ - name: Build (Linux, plattformneutrale Projekte)
+ if: matrix.os == 'ubuntu-latest'
+ run: |
+ dotnet build src/IBKRTrader.App.Avalonia -c Release
+ dotnet build src/IBKRTrader.Daemon -c Release
+ dotnet test tests/IBKRTrader.Tests -c Release --logger "trx;LogFileName=test-results.trx"
+
+ # Die Konstruktionsprüfung der Oberfläche braucht mit Avalonia KEIN Anzeigegerät mehr
+ # (SetupWithoutStarting). Mit WinForms war das auf einem Build-Server nicht möglich.
+ - name: Smoke-UI (Fenster-Konstruktion)
+ run: dotnet run --project src/IBKRTrader.App.Avalonia --no-build -c Release -- --smoke-ui
+
+ # Trockenlauf des kopflosen Dienstes: Host bauen, Startprüfungen fahren, nichts starten.
+ - name: Daemon-Prüflauf
+ run: dotnet run --project src/IBKRTrader.Daemon --no-build -c Release -- --check
+
+ # Der eigentliche Portierungs-Wächter: läuft nur unter Linux und schlägt fehl, sobald ein
+ # Projekt wieder eine Windows-Abhängigkeit hereinzieht.
+ - name: Linux-Publish (Daemon + Oberfläche)
+ if: matrix.os == 'ubuntu-latest'
+ run: |
+ dotnet publish src/IBKRTrader.Daemon -c Release -r linux-x64 --self-contained false -o out/daemon
+ dotnet publish src/IBKRTrader.App.Avalonia -c Release -r linux-x64 --self-contained false -o out/gui
+
+ - name: Testergebnisse sichern
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-results-${{ matrix.os }}
+ path: '**/test-results.trx'
+ if-no-files-found: ignore
diff --git a/src/IBKRTrader.App.Avalonia/Shell/ModuleViews.cs b/src/IBKRTrader.App.Avalonia/Shell/ModuleViews.cs
index 9c139cf..b706b0e 100644
--- a/src/IBKRTrader.App.Avalonia/Shell/ModuleViews.cs
+++ b/src/IBKRTrader.App.Avalonia/Shell/ModuleViews.cs
@@ -1,4 +1,14 @@
+using IBKRTrader.App.Avalonia.Views.Modules;
+using IBKRTrader.Core.Logging;
using IBKRTrader.Core.Modularity;
+using IBKRTrader.Core.Trading;
+using IBKRTrader.Core.Workers;
+using IBKRTrader.Modules.Accounting.Persistence;
+using IBKRTrader.Modules.Accounting.Services;
+using IBKRTrader.Modules.CongressTrading.Database;
+using IBKRTrader.Modules.Supervisor.Agent;
+using IBKRTrader.Modules.Supervisor.Persistence;
+using IBKRTrader.Modules.Supervisor.Services;
using Microsoft.Extensions.DependencyInjection;
namespace IBKRTrader.App.Avalonia.Shell;
@@ -11,20 +21,50 @@ namespace IBKRTrader.App.Avalonia.Shell;
/// Modulprojekte bleiben deshalb frei von UI-Code; ihr RegisterUi ist leer, und die Shell
/// verdrahtet die Fenster zentral. Die Modul-Dienste kommen unverändert aus dem DI-Container.
///
-/// Stand: noch leer – die drei Modul-Fenster (CongressTrading, Supervisor,
-/// Accounting) werden in der nächsten Etappe portiert. Bis dahin zeigt die Avalonia-Shell nur die
-/// Core-Ansichten; die WinForms-Shell bleibt daneben vollständig nutzbar.
+/// Registriert wird nur, was auch geladen ist: fehlt ein Modul in dieser Sitzung, entfällt
+/// sein Fenster, und der Launcher zeigt es gar nicht erst an.
///
public static class ModuleViews
{
public static void Register(IModuleUiHost host, IServiceProvider sp)
{
- // Noch keine portierten Modul-Fenster. Das Muster steht in der WinForms-Fassung
- // (UI/ModuleViews.cs) und wird hier eins zu eins übernommen.
+ RegisterIfLoaded(sp, "CongressTrading", () => host.RegisterView(new ModuleView
+ {
+ Id = "congresstrading.main", Title = "Congress Trading", Group = "CongressTrading", Order = 100,
+ CreateView = () => new CongressTradingWindow(
+ host,
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService())
+ }));
+
+ RegisterIfLoaded(sp, "Supervisor", () => host.RegisterView(new ModuleView
+ {
+ Id = "supervisor.main", Title = "Supervisor", Group = "Supervisor", Order = 300,
+ CreateView = () => new SupervisorWindow(
+ host,
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService())
+ }));
+
+ RegisterIfLoaded(sp, "Accounting", () => host.RegisterView(new ModuleView
+ {
+ Id = "accounting.main", Title = "Accounting", Group = "Accounting", Order = 400,
+ CreateView = () => new AccountingWindow(
+ host,
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService())
+ }));
}
/// Registriert die Ansicht nur, wenn das Modul in dieser Sitzung geladen ist.
- internal static void RegisterIfLoaded(IServiceProvider sp, string moduleName, Action register)
+ private static void RegisterIfLoaded(IServiceProvider sp, string moduleName, Action register)
{
if (sp.GetServices().Any(m => string.Equals(m.Name, moduleName, StringComparison.OrdinalIgnoreCase)))
register();
diff --git a/src/IBKRTrader.App.Avalonia/ViewModels/Rows.cs b/src/IBKRTrader.App.Avalonia/ViewModels/Rows.cs
index 6574320..7edc5ec 100644
--- a/src/IBKRTrader.App.Avalonia/ViewModels/Rows.cs
+++ b/src/IBKRTrader.App.Avalonia/ViewModels/Rows.cs
@@ -16,3 +16,30 @@ public sealed record ModuleRow(string Name, string Prefix, string Status);
/// über SelectionColor der RichTextBox ein, in Avalonia wird je Element gebunden.
///
public sealed record LogRow(string Text, global::Avalonia.Media.IBrush Color);
+
+/// Eine offene Position im Modul-Fenster.
+public sealed record PositionRow(string Symbol, int Quantity, decimal AvgPrice, decimal Notional);
+
+/// Eine Zeile der Supervisor-Berichtsliste. Tokens als fertiger Text (Prompt / Completion).
+public sealed record SupervisorReportRow(
+ DateTime CreatedAt, string Profile, string Model, string Question, int ToolCallCount, string Tokens);
+
+// ─── Accounting ──────────────────────────────────────────────────────────────
+// Beträge als bereits formatierter Text: die Umrechnung in die Anzeigewährung passiert beim
+// Laden, und das Format ist damit an einer Stelle festgelegt statt in jeder Spaltendefinition.
+
+/// Eine Monatszeile des Monatsvergleichs (BWA).
+public sealed record MonthlyRow(
+ string Month, string Opening, string Deposits, string Withdrawals,
+ string Volume, string Fees, string Result, string Closing);
+
+/// Eine Buchung im neutralen Ledger.
+public sealed record LedgerRow(
+ DateTime Time, string AccountId, string EventType, string Side, string Symbol,
+ string Currency, decimal Quantity, decimal Price,
+ decimal Gross, decimal Fee, decimal Net, string TransactionId);
+
+/// Ein Ingest-Lauf des Flex-Query-Abrufs.
+public sealed record IngestRunRow(
+ string AccountId, DateTime Started, DateTime? Finished, bool Backfill,
+ int NewEntries, int DuplicateEntries, bool Success, string Delta, string Message);
diff --git a/src/IBKRTrader.App.Avalonia/Views/Modules/AccountingWindow.axaml b/src/IBKRTrader.App.Avalonia/Views/Modules/AccountingWindow.axaml
new file mode 100644
index 0000000..31f30be
--- /dev/null
+++ b/src/IBKRTrader.App.Avalonia/Views/Modules/AccountingWindow.axaml
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/IBKRTrader.App.Avalonia/Views/Modules/AccountingWindow.axaml.cs b/src/IBKRTrader.App.Avalonia/Views/Modules/AccountingWindow.axaml.cs
new file mode 100644
index 0000000..cd03783
--- /dev/null
+++ b/src/IBKRTrader.App.Avalonia/Views/Modules/AccountingWindow.axaml.cs
@@ -0,0 +1,291 @@
+using System.Globalization;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Platform.Storage;
+using IBKRTrader.App.Avalonia.Shell;
+using IBKRTrader.App.Avalonia.ViewModels;
+using IBKRTrader.Core.Logging;
+using IBKRTrader.Core.Modularity;
+using IBKRTrader.Core.Time;
+using IBKRTrader.Modules.Accounting.Logic;
+using IBKRTrader.Modules.Accounting.Persistence;
+using IBKRTrader.Modules.Accounting.Services;
+
+namespace IBKRTrader.App.Avalonia.Views.Modules;
+
+///
+/// Fenster des Accounting-Moduls: Übersicht/BWA, Ledger, Steuer (Platzhalter),
+/// Abrechnung/Export und Abruf/Status.
+///
+/// Alle DB-Zugriffe laufen NUR auf Nutzerinteraktion – nie im Konstruktor, damit die
+/// Konstruktionsprüfung das Fenster auch ohne Datenbank fehlerfrei baut.
+///
+/// Beträge werden beim Laden gegen formatiert:
+/// die Anzeige soll nicht davon abhängen, auf welchem Host die Instanz läuft. Für den PDF-Export
+/// gilt dieselbe Festlegung an einer eigenen Stelle (fest de-DE).
+///
+public partial class AccountingWindow : Window
+{
+ private const string AllAccounts = "(alle)";
+
+ private readonly ILedgerRepository _ledger;
+ private readonly IIngestRunRepository _runs;
+ private readonly AccountingReportService _report;
+ private readonly AccountingIngestService _ingest;
+ private readonly LoggingService _logger;
+
+ public AccountingWindow(IModuleUiHost uiHost,
+ ILedgerRepository ledger,
+ IIngestRunRepository runs,
+ AccountingReportService report,
+ AccountingIngestService ingest,
+ LoggingService logger)
+ {
+ _ledger = ledger;
+ _runs = runs;
+ _report = report;
+ _ingest = ingest;
+ _logger = logger;
+
+ InitializeComponent();
+ WindowMenu.Wire(this.FindControl