diff --git a/TrafagSalesExporter/Components/Pages/Dashboard.razor b/TrafagSalesExporter/Components/Pages/Dashboard.razor index c1ab69a..800ee7f 100644 --- a/TrafagSalesExporter/Components/Pages/Dashboard.razor +++ b/TrafagSalesExporter/Components/Pages/Dashboard.razor @@ -310,41 +310,19 @@ } } - private async Task RefreshLiveDataAsync() + private Task RefreshLiveDataAsync() { - var runningSiteIds = _dashboardRows - .Where(r => Orchestrator.IsExporting(r.SiteId)) - .Select(r => r.SiteId) - .Distinct() - .ToList(); - - if (runningSiteIds.Count == 0) - { - _anyRunning = false; - return; - } - - using var db = await DbFactory.CreateDbContextAsync(); - var appLogs = await db.AppEventLogs - .Where(l => l.SiteId != null && runningSiteIds.Contains(l.SiteId.Value)) - .OrderByDescending(l => l.Timestamp) - .Take(200) - .ToListAsync(); - - var latestAppLogsBySite = appLogs - .GroupBy(l => l.SiteId!.Value) - .ToDictionary(g => g.Key, g => g.OrderByDescending(x => x.Timestamp).First()); - foreach (var row in _dashboardRows) { - if (!latestAppLogsBySite.TryGetValue(row.SiteId, out var appLog)) + if (!Orchestrator.IsExporting(row.SiteId)) continue; - row.LiveMessage = $"{appLog.Category}: {appLog.Message}"; - row.LiveDetails = appLog.Details ?? string.Empty; + row.LiveMessage = Orchestrator.GetExportStatus(row.SiteId); + row.LiveDetails = string.Empty; } _anyRunning = _dashboardRows.Any(r => Orchestrator.IsExporting(r.SiteId)); + return Task.CompletedTask; } private class DashboardRow diff --git a/TrafagSalesExporter/HANDOFF_2026-04-15.md b/TrafagSalesExporter/HANDOFF_2026-04-15.md new file mode 100644 index 0000000..da30fd3 --- /dev/null +++ b/TrafagSalesExporter/HANDOFF_2026-04-15.md @@ -0,0 +1,347 @@ +# TrafagSalesExporter Handoff + +Stand: 2026-04-15 + +## Zielbild + +Die App wurde von einem reinen BI1/HANA-Exporter zu einer kombinierten Plattform erweitert: + +- `BI1` und `SAGE` bleiben auf direktem HANA-Zugriff +- `SAP` läuft separat über SAP Gateway / OData +- SAP-Quellen können gelesen, gejoint und auf das zentrale `SalesRecord`-Schema gemappt werden +- Standort-Exporte werden lokal als Excel geschrieben +- Zusätzlich werden Datensätze in eine zentrale SQLite-Tabelle geschrieben +- Ein konsolidierter Export liest aus dieser zentralen Tabelle + +## Wichtigste umgesetzte Funktionen + +### 1. Zentrale Credentials pro Quellsystem + +Es gibt zentrale Zugangsdaten in `ExportSettings` für: + +- `SAP` +- `BI1` +- `SAGE` + +Zusätzlich gibt es pro Standort optionale Overrides: + +- `UsernameOverride` +- `PasswordOverride` + +Auflösungsreihenfolge: + +1. Standort-Override +2. zentrale Credentials des Quellsystems +3. bei HANA zusätzlich Fallback auf alten `HanaServer.Username/Password` + +## 2. SAP von BI1/HANA getrennt + +`SAP` nutzt nicht mehr den HANA-Pfad, sondern eine eigene Gateway/OData-Strecke. + +Pro SAP-Standort gibt es: + +- `SapServiceUrl` +- `SapEntitySet` +- `SapEntitySetsCache` +- `SapEntitySetsRefreshedAtUtc` + +Refresh der SAP-Quellen erfolgt nur auf Knopfdruck. + +Beispiel Service URL: + +```text +http://travt762.sap.trafag.com:8000/sap/opu/odata/sap/ZPOWERBI_EINKAUF_SRV/ +``` + +Wichtig: + +- Service URL immer nur bis zum Service +- Entity Set separat auswählen + +## 3. SAP-Quellen, Joins und Feldmappings + +Für SAP gibt es mehrere neue Modelle: + +- `SapSourceDefinition` +- `SapJoinDefinition` +- `SapFieldMapping` + +Unterstützt wird: + +- mehrere SAP-Quellen pro Standort +- Alias pro Quelle +- Primärquelle +- Join-Definitionen +- Mapping von `Alias.Feldname` auf zentrales Schema + +UI-Erweiterungen: + +- `Quellen refreshen` +- `Felder aus Quellen laden` +- Join-Key-Auswahl aus Metadaten +- `Auto-Match` für gleiche Feldnamen zwischen Primärquelle und anderen Quellen + +## 4. Zentrale Datenspeicherung + +Neue Tabelle: + +- `CentralSalesRecords` + +Verwendung: + +- pro Standort werden alte zentrale Sätze dieses Standorts ersetzt +- konsolidierte Excel liest aus `CentralSalesRecords` + +Wichtig: + +- zentrale Excel wird nicht appendet +- sie wird aus dem aktuellen Zustand der zentralen Tabelle neu erstellt + +## 5. Exportpfade + +Neue Konfigurationsmöglichkeiten: + +Zentral in `Settings`: + +- `LocalSiteExportFolder` +- `LocalConsolidatedExportFolder` + +Pro Standort: + +- `LocalExportFolderOverride` + +Fallback wenn leer: + +```text +./output +``` + +relativ zum App-Verzeichnis. + +## 6. SharePoint + +SharePoint-Upload ist optional. + +Wenn keine vollständige SharePoint-Konfiguration vorhanden ist: + +- Excel wird trotzdem lokal erzeugt +- kein Upload nach SharePoint + +Benötigte SharePoint-Werte: + +- `Tenant ID` +- `Client ID` +- `Client Secret` + +Das sind Entra App Registration Werte, nicht normale Benutzer-Credentials. + +## 7. Config Import/Export + +Es gibt JSON-Import/Export der Konfiguration mit Checkbox: + +- mit Secrets +- ohne Secrets + +Enthalten sind u. a.: + +- SharePoint Config +- ExportSettings +- HanaServers +- Sites +- Transformation Rules +- SAP-Quellen +- SAP-Joins +- SAP-Mappings + +## 8. Logging und Live-Status + +Neue technische Logs über `AppEventLogs`. + +Sichtbar: + +- auf `/logs` +- im Dashboard als `Live-Status` + +Geloggt werden u. a.: + +- HANA-Query Start +- SAP Refresh +- SAP Reads +- Transformationen +- Excel-Erstellung +- zentrale Tabellenspeicherung +- Export erfolgreich / fehlgeschlagen + +## 9. Excel öffnen + +Im Dashboard gibt es neben `Export` den Button: + +- `Excel öffnen` + +Dieser nutzt `ExportLogs.FilePath`. + +Voraussetzungen: + +- letzter Export erfolgreich +- `FilePath` gespeichert +- Datei existiert lokal + +## 10. Management Cockpit + +Es gibt einen neuen Menüpunkt: + +- `Management Cockpit` + +Funktion: + +- Auswahl vorhandener Excel-Dateien +- Analyse einer exportierten Standort-Datei +- Kennzahlen für Geschäftsinhaber / Management + +Aktuell enthalten: + +- Umsatz +- geschätzte Kosten +- geschätzte Marge +- Rechnungsanzahl +- Kundenanzahl +- Top Kunden +- Top Produktgruppen +- Top Sales Owner +- Datenqualitätshinweise +- automatische Management-Aussagen + +## Wichtige Dateien + +### Modelle + +- `Models/Site.cs` +- `Models/ExportSettings.cs` +- `Models/ExportLog.cs` +- `Models/CentralSalesRecord.cs` +- `Models/SapSourceDefinition.cs` +- `Models/SapJoinDefinition.cs` +- `Models/SapFieldMapping.cs` +- `Models/ManagementCockpitModels.cs` +- `Models/ConfigTransferPackage.cs` + +### Services + +- `Services/SiteExportService.cs` +- `Services/ConsolidatedExportService.cs` +- `Services/CentralSalesRecordService.cs` +- `Services/SapGatewayService.cs` +- `Services/SapCompositionService.cs` +- `Services/ConfigTransferService.cs` +- `Services/AppEventLogService.cs` +- `Services/ManagementCockpitService.cs` +- `Services/DatabaseInitializationService.cs` +- `Services/ExportOrchestrationService.cs` + +### UI + +- `Components/Pages/Standorte.razor` +- `Components/Pages/Settings.razor` +- `Components/Pages/Dashboard.razor` +- `Components/Pages/Logs.razor` +- `Components/Pages/ManagementCockpit.razor` +- `Components/Layout/NavMenu.razor` + +## Datenbank / Migrationen + +Viele Änderungen laufen über `DatabaseInitializationService`. + +Wichtige neue oder erweiterte Tabellen/Felder: + +- `Sites` + - `UsernameOverride` + - `PasswordOverride` + - `SapServiceUrl` + - `SapEntitySet` + - `SapEntitySetsCache` + - `SapEntitySetsRefreshedAtUtc` + - `LocalExportFolderOverride` +- `ExportSettings` + - zentrale SAP/BI1/SAGE Credentials + - `LocalSiteExportFolder` + - `LocalConsolidatedExportFolder` + - `DebugLoggingEnabled` +- `ExportLogs` + - `FilePath` +- neue Tabellen: + - `AppEventLogs` + - `CentralSalesRecords` + - SAP-Konfigtabellen + +## Aktuell offenes Hauptproblem + +### Zentrale Speicherung hängt noch + +Die große Problemstelle war die zentrale SQLite-Speicherung. + +Bereits probiert: + +- EF `RemoveRange + SaveChanges` +- EF Batch-Speichern +- Dashboard-Polling reduziert +- SQLite WAL + busy timeout +- direkte SQLite-Inserts in einer großen Transaktion +- jetzt: kleine abgeschlossene Transaktionen pro Batch + +Aktueller Stand: + +- zentrale Excel ist jetzt sehr schnell +- das Hängen wurde stark eingegrenzt +- zuletzt wurde der Schreibpfad so umgebaut, dass: + - Löschen in eigener kurzer Transaktion läuft + - Inserts batchweise mit Commit pro Batch laufen + +Datei: + +- `Services/CentralSalesRecordService.cs` + +Die nächste Session sollte genau dort weiter debuggen, falls es noch hängt. + +Wichtig: + +- Das Problem ist nicht SAP +- nicht SharePoint +- nicht mehr der große EF-Insert +- sondern sehr wahrscheinlich SQLite-Commit/Lock-Verhalten rund um die zentrale Tabelle + +## Letzte bekannte Beobachtung + +Der User meldete zuletzt: + +- vorher Hänger bei `Zentrale Tabelle: Abschluss speichern...` +- danach wurde auf Commit pro Batch umgestellt +- neue Session soll testen, ob es jetzt bei + - `Batch x/y speichern...` + - `Batch x/y abschliessen...` + - oder gar nicht mehr hängt + +## Build-Status + +Letzter Build: + +```text +dotnet build TrafagSalesExporter.sln +``` + +Ergebnis: + +- erfolgreich +- bekannte Warnungen bleiben: + - SAP HANA Architekturwarnung `MSB3270` + - MudBlazor Analyzer `Dense` + +## Hinweise für nächste Session + +1. Zuerst aktuellen Export testen +2. Genaue letzte Live-Status-Meldung notieren +3. `Services/CentralSalesRecordService.cs` prüfen +4. Falls nötig: + - SQLite pragmas weiter anpassen + - zentrale Tabelle temporär ganz abschaltbar machen + - oder Schreiben über separate DB / Queue entkoppeln + diff --git a/TrafagSalesExporter/NEXT_STEPS_2026-04-15.md b/TrafagSalesExporter/NEXT_STEPS_2026-04-15.md new file mode 100644 index 0000000..3572d96 --- /dev/null +++ b/TrafagSalesExporter/NEXT_STEPS_2026-04-15.md @@ -0,0 +1,102 @@ +# Next Steps + +Stand: 2026-04-15 + +## 1. Erstes Ziel + +Prüfen, ob die aktuelle Version beim Standort-Export noch in der zentralen SQLite-Speicherung hängen bleibt. + +Wichtig: + +- App neu starten +- denselben Standort erneut exportieren +- letzte sichtbare `Live-Status`-Meldung exakt notieren + +Interessant sind vor allem diese Fälle: + +- `Zentrale Tabelle: Batch x/y speichern...` +- `Zentrale Tabelle: Batch x/y abschliessen...` +- `Zentrale Tabelle aktualisiert` +- `Export erfolgreich` + +## 2. Hauptverdächtiger + +Datei: + +- `Services/CentralSalesRecordService.cs` + +Aktueller Stand: + +- alte Sätze werden in eigener Transaktion gelöscht +- Inserts laufen in Batches von 25 +- jeder Batch wird separat committed + +Wenn es noch hängt, dort zuerst ansetzen. + +## 3. Falls es weiter hängt + +In dieser Reihenfolge prüfen: + +1. Batchgröße weiter reduzieren + - z. B. `10` statt `25` +2. Direkt vor und direkt nach `transaction.CommitAsync()` zusätzlich technische Logs setzen +3. Prüfen, ob parallel noch andere SQLite-Zugriffe laufen +4. Optional zentrale Speicherung vorübergehend per Setting deaktivierbar machen +5. Falls nötig zentrale Speicherung in separate DB-Datei auslagern + +## 4. Dashboard / UI prüfen + +Zu testen: + +- `Excel öffnen` wird nach neuem erfolgreichen Export aktiv +- `Export erfolgreich` zeigt `Pfad=...` +- Dashboard-Live-Status setzt sich nach Abschluss sauber zurück + +Dateien: + +- `Components/Pages/Dashboard.razor` +- `Services/SiteExportService.cs` +- `Models/ExportLog.cs` + +## 5. SAP-Funktionalität kurz gegenprüfen + +Zu testen: + +- `Quellen refreshen` +- `Felder aus Quellen laden` +- `Auto-Match` +- SAP-Export eines Standorts + +Dateien: + +- `Components/Pages/Standorte.razor` +- `Services/SapGatewayService.cs` +- `Services/SapCompositionService.cs` + +## 6. Management Cockpit prüfen + +Zu testen: + +- vorhandene Excel-Datei auswählbar +- Analyse läuft +- Kennzahlen plausibel + +Dateien: + +- `Components/Pages/ManagementCockpit.razor` +- `Services/ManagementCockpitService.cs` + +## 7. Wenn Stabilität vor Funktion geht + +Sinnvolle pragmatische Zwischenlösung: + +- zentrale SQLite-Speicherung per Setting abschaltbar machen +- Export lokal und zentral Excel weiter erlauben +- zentrale DB erst wieder aktivieren, wenn der Commit-Pfad stabil ist + +## 8. Referenzdatei + +Für den vollständigen Kontext zuerst lesen: + +- `HANDOFF_2026-04-15.md` + diff --git a/TrafagSalesExporter/Program.cs b/TrafagSalesExporter/Program.cs index 1cadb5b..7496499 100644 --- a/TrafagSalesExporter/Program.cs +++ b/TrafagSalesExporter/Program.cs @@ -11,7 +11,7 @@ builder.Services.AddRazorComponents() builder.Services.AddMudServices(); builder.Services.AddDbContextFactory(options => - options.UseSqlite("Data Source=trafag_exporter.db;Default Timeout=10")); + options.UseSqlite("Data Source=trafag_exporter.db;Default Timeout=60")); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/TrafagSalesExporter/Services/CentralSalesRecordService.cs b/TrafagSalesExporter/Services/CentralSalesRecordService.cs index 3a36ecf..48964e1 100644 --- a/TrafagSalesExporter/Services/CentralSalesRecordService.cs +++ b/TrafagSalesExporter/Services/CentralSalesRecordService.cs @@ -39,6 +39,7 @@ public class CentralSalesRecordService : ICentralSalesRecordService updateStatus?.Invoke("Zentrale Tabelle: neue Saetze vorbereiten..."); await InsertRecordsInCommittedBatchesAsync(connection, site, recordList, updateStatus); + updateStatus?.Invoke("Zentrale Tabelle aktualisiert"); await _appEventLogService.WriteAsync( "Export", diff --git a/TrafagSalesExporter/Services/DatabaseInitializationService.cs b/TrafagSalesExporter/Services/DatabaseInitializationService.cs index 1632ee9..40e1e53 100644 --- a/TrafagSalesExporter/Services/DatabaseInitializationService.cs +++ b/TrafagSalesExporter/Services/DatabaseInitializationService.cs @@ -45,6 +45,7 @@ public class DatabaseInitializationService : IDatabaseInitializationService private static void EnsureSchema(AppDbContext db) { EnsureSitesTableSupportsOptionalHanaServer(db); + RepairBrokenSiteForeignKeys(db); AddColumnIfMissing(db, "HanaServers", "DatabaseName", "TEXT NOT NULL DEFAULT ''"); AddColumnIfMissing(db, "HanaServers", "UseSsl", "INTEGER NOT NULL DEFAULT 0"); AddColumnIfMissing(db, "HanaServers", "ValidateCertificate", "INTEGER NOT NULL DEFAULT 0"); @@ -175,6 +176,221 @@ FROM Sites_old;"; enableFk.ExecuteNonQuery(); } + private static void RepairBrokenSiteForeignKeys(AppDbContext db) + { + var conn = db.Database.GetDbConnection(); + if (conn.State != ConnectionState.Open) + conn.Open(); + + var tablesToRepair = new[] + { + ("ExportLogs", GetExportLogsCreateSql()), + ("AppEventLogs", GetAppEventLogsCreateSql()), + ("CentralSalesRecords", GetCentralSalesRecordsCreateSql()), + ("SapSourceDefinitions", GetSapSourceDefinitionsCreateSql()), + ("SapJoinDefinitions", GetSapJoinDefinitionsCreateSql()), + ("SapFieldMappings", GetSapFieldMappingsCreateSql()) + }; + + foreach (var (tableName, createSql) in tablesToRepair) + { + if (TableReferencesSitesOld(conn, tableName)) + RebuildTable(conn, tableName, createSql); + } + } + + private static bool TableReferencesSitesOld(System.Data.Common.DbConnection connection, string tableName) + { + using var command = connection.CreateCommand(); + command.CommandText = "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = $tableName;"; + + var parameter = command.CreateParameter(); + parameter.ParameterName = "$tableName"; + parameter.Value = tableName; + command.Parameters.Add(parameter); + + var sql = command.ExecuteScalar()?.ToString() ?? string.Empty; + return sql.Contains("Sites_old", StringComparison.OrdinalIgnoreCase); + } + + private static void RebuildTable(System.Data.Common.DbConnection connection, string tableName, string createSql) + { + using var disableFk = connection.CreateCommand(); + disableFk.CommandText = "PRAGMA foreign_keys = OFF;"; + disableFk.ExecuteNonQuery(); + + using var transaction = connection.BeginTransaction(); + + var tempTableName = $"{tableName}_repair_old"; + + using (var rename = connection.CreateCommand()) + { + rename.Transaction = transaction; + rename.CommandText = $"ALTER TABLE {tableName} RENAME TO {tempTableName};"; + rename.ExecuteNonQuery(); + } + + using (var create = connection.CreateCommand()) + { + create.Transaction = transaction; + create.CommandText = createSql; + create.ExecuteNonQuery(); + } + + var columns = GetSharedColumns(connection, transaction, tableName, tempTableName); + if (columns.Count > 0) + { + var columnList = string.Join(", ", columns); + + using var copy = connection.CreateCommand(); + copy.Transaction = transaction; + copy.CommandText = $"INSERT INTO {tableName} ({columnList}) SELECT {columnList} FROM {tempTableName};"; + copy.ExecuteNonQuery(); + } + + using (var drop = connection.CreateCommand()) + { + drop.Transaction = transaction; + drop.CommandText = $"DROP TABLE {tempTableName};"; + drop.ExecuteNonQuery(); + } + + transaction.Commit(); + + using var enableFk = connection.CreateCommand(); + enableFk.CommandText = "PRAGMA foreign_keys = ON;"; + enableFk.ExecuteNonQuery(); + } + + private static List GetSharedColumns(System.Data.Common.DbConnection connection, System.Data.Common.DbTransaction transaction, string newTableName, string oldTableName) + { + var newColumns = GetTableColumns(connection, transaction, newTableName); + var oldColumns = GetTableColumns(connection, transaction, oldTableName); + + return newColumns.Where(oldColumns.Contains).ToList(); + } + + private static HashSet GetTableColumns(System.Data.Common.DbConnection connection, System.Data.Common.DbTransaction transaction, string tableName) + { + var columns = new HashSet(StringComparer.OrdinalIgnoreCase); + + using var command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = $"PRAGMA table_info({tableName})"; + + using var reader = command.ExecuteReader(); + while (reader.Read()) + { + var name = reader["name"]?.ToString(); + if (!string.IsNullOrWhiteSpace(name)) + columns.Add(name); + } + + return columns; + } + + private static string GetExportLogsCreateSql() => @" +CREATE TABLE ExportLogs ( + Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + Timestamp TEXT NOT NULL, + SiteId INTEGER NOT NULL, + Land TEXT NOT NULL, + TSC TEXT NOT NULL, + Status TEXT NOT NULL, + RowCount INTEGER NOT NULL, + ErrorMessage TEXT NULL, + FileName TEXT NOT NULL DEFAULT '', + FilePath TEXT NOT NULL DEFAULT '', + DurationSeconds REAL NOT NULL, + FOREIGN KEY (SiteId) REFERENCES Sites (Id) +);"; + + private static string GetAppEventLogsCreateSql() => @" +CREATE TABLE AppEventLogs ( + Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + Timestamp TEXT NOT NULL, + Level TEXT NOT NULL, + Category TEXT NOT NULL, + SiteId INTEGER NULL, + Land TEXT NOT NULL, + Message TEXT NOT NULL, + Details TEXT NOT NULL, + FOREIGN KEY (SiteId) REFERENCES Sites (Id) +);"; + + private static string GetCentralSalesRecordsCreateSql() => @" +CREATE TABLE CentralSalesRecords ( + Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + StoredAtUtc TEXT NOT NULL, + SiteId INTEGER NOT NULL, + SourceSystem TEXT NOT NULL, + ExtractionDate TEXT NOT NULL, + Tsc TEXT NOT NULL, + InvoiceNumber TEXT NOT NULL, + PositionOnInvoice INTEGER NOT NULL, + Material TEXT NOT NULL, + Name TEXT NOT NULL, + ProductGroup TEXT NOT NULL, + Quantity TEXT NOT NULL, + SupplierNumber TEXT NOT NULL, + SupplierName TEXT NOT NULL, + SupplierCountry TEXT NOT NULL, + CustomerNumber TEXT NOT NULL, + CustomerName TEXT NOT NULL, + CustomerCountry TEXT NOT NULL, + CustomerIndustry TEXT NOT NULL, + StandardCost TEXT NOT NULL, + StandardCostCurrency TEXT NOT NULL, + PurchaseOrderNumber TEXT NOT NULL, + SalesPriceValue TEXT NOT NULL, + SalesCurrency TEXT NOT NULL, + Incoterms2020 TEXT NOT NULL, + SalesResponsibleEmployee TEXT NOT NULL, + InvoiceDate TEXT NULL, + OrderDate TEXT NULL, + Land TEXT NOT NULL, + DocumentType TEXT NOT NULL, + FOREIGN KEY (SiteId) REFERENCES Sites (Id) +);"; + + private static string GetSapSourceDefinitionsCreateSql() => @" +CREATE TABLE SapSourceDefinitions ( + Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + SiteId INTEGER NOT NULL, + Alias TEXT NOT NULL, + EntitySet TEXT NOT NULL, + IsPrimary INTEGER NOT NULL DEFAULT 0, + IsActive INTEGER NOT NULL DEFAULT 1, + SortOrder INTEGER NOT NULL DEFAULT 0, + FOREIGN KEY (SiteId) REFERENCES Sites (Id) +);"; + + private static string GetSapJoinDefinitionsCreateSql() => @" +CREATE TABLE SapJoinDefinitions ( + Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + SiteId INTEGER NOT NULL, + LeftAlias TEXT NOT NULL, + RightAlias TEXT NOT NULL, + LeftKeys TEXT NOT NULL, + RightKeys TEXT NOT NULL, + JoinType TEXT NOT NULL DEFAULT 'Left', + IsActive INTEGER NOT NULL DEFAULT 1, + SortOrder INTEGER NOT NULL DEFAULT 0, + FOREIGN KEY (SiteId) REFERENCES Sites (Id) +);"; + + private static string GetSapFieldMappingsCreateSql() => @" +CREATE TABLE SapFieldMappings ( + Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + SiteId INTEGER NOT NULL, + TargetField TEXT NOT NULL, + SourceExpression TEXT NOT NULL, + IsRequired INTEGER NOT NULL DEFAULT 0, + IsActive INTEGER NOT NULL DEFAULT 1, + SortOrder INTEGER NOT NULL DEFAULT 0, + FOREIGN KEY (SiteId) REFERENCES Sites (Id) +);"; + private static void AddColumnIfMissing(AppDbContext db, string table, string column, string type) { var conn = db.Database.GetDbConnection();