diff --git a/csharp/App/Backend/DataTypes/Installation.cs b/csharp/App/Backend/DataTypes/Installation.cs index 54327005a..f5d6cbe48 100644 --- a/csharp/App/Backend/DataTypes/Installation.cs +++ b/csharp/App/Backend/DataTypes/Installation.cs @@ -48,6 +48,7 @@ public class Installation : TreeNode public String ReadRoleId { get; set; } = ""; public String WriteRoleId { get; set; } = ""; public Boolean TestingMode { get; set; } = false; + public Boolean DataCollectionEnabled { get; set; } = true; public int Status { get; set; } = -1; public int Product { get; set; } = (int)ProductType.Salimax; public int Device { get; set; } = 0; diff --git a/csharp/App/Backend/Database/Db.cs b/csharp/App/Backend/Database/Db.cs index 3ee45c6bb..fe5a8770c 100644 --- a/csharp/App/Backend/Database/Db.cs +++ b/csharp/App/Backend/Database/Db.cs @@ -91,6 +91,11 @@ public static partial class Db Connection.Execute("UPDATE User SET Language = 'fr' WHERE Language = 'french'"); Connection.Execute("UPDATE User SET Language = 'it' WHERE Language = 'italian'"); + // Backfill: SQLite-net adds new bool columns as nullable with NULL for existing rows. + // LINQ `.Where(i => i.DataCollectionEnabled)` translates to `WHERE ... = 1` and excludes + // NULL rows, which would silently disable ingestion for every pre-existing installation. + Connection.Execute("UPDATE Installation SET DataCollectionEnabled = 1 WHERE DataCollectionEnabled IS NULL"); + // One-time migration: rebrand to inesco energy Connection.Execute("UPDATE Folder SET Name = 'inesco energy' WHERE Name = 'InnovEnergy'"); Connection.Execute("UPDATE Folder SET Name = 'inesco energy' WHERE Name = 'inesco Energy'"); diff --git a/csharp/App/Backend/DeleteOldData/DeleteOldDataFromS3.cs b/csharp/App/Backend/DeleteOldData/DeleteOldDataFromS3.cs index d6e104823..6eb53179b 100644 --- a/csharp/App/Backend/DeleteOldData/DeleteOldDataFromS3.cs +++ b/csharp/App/Backend/DeleteOldData/DeleteOldDataFromS3.cs @@ -39,7 +39,7 @@ public static class DeleteOldDataFromS3 { var cutoffTimestamp = DateTimeOffset.UtcNow.AddYears(-1).ToUnixTimeSeconds(); var cutoffKey = cutoffTimestamp.ToString(); - var installations = Db.Installations.ToList(); + var installations = Db.Installations.Where(i => i.DataCollectionEnabled).ToList(); Console.WriteLine($"[S3Cleanup] Starting cleanup for {installations.Count} installations, cutoff: {cutoffKey}"); diff --git a/csharp/App/Backend/Services/DailyIngestionService.cs b/csharp/App/Backend/Services/DailyIngestionService.cs index 920b18a52..71981bb7a 100644 --- a/csharp/App/Backend/Services/DailyIngestionService.cs +++ b/csharp/App/Backend/Services/DailyIngestionService.cs @@ -50,7 +50,7 @@ public static class DailyIngestionService Console.WriteLine($"[DailyIngestion] Starting ingestion for all SodioHome installations..."); var installations = Db.Installations - .Where(i => (i.Product == (Int32)ProductType.SodioHome || i.Product == (Int32)ProductType.SodistorePro) && i.Device != 3) // Skip Growatt (device=3) + .Where(i => (i.Product == (Int32)ProductType.SodioHome || i.Product == (Int32)ProductType.SodistorePro) && i.Device != 3 && i.DataCollectionEnabled) // Skip Growatt (device=3) and installations with data collection disabled .ToList(); foreach (var installation in installations) @@ -75,6 +75,13 @@ public static class DailyIngestionService /// public static async Task IngestInstallationAsync(Int64 installationId) { + var installation = Db.GetInstallationById(installationId); + if (installation is null || !installation.DataCollectionEnabled) + { + Console.WriteLine($"[DailyIngestion] Skipping installation {installationId} (data collection disabled)."); + return; + } + await TryIngestFromJson(installationId); IngestFromXlsx(installationId); } @@ -88,6 +95,11 @@ public static class DailyIngestionService { var installation = Db.GetInstallationById(installationId); if (installation is null) return; + if (!installation.DataCollectionEnabled) + { + Console.WriteLine($"[DailyIngestion] Skipping date-range ingest for installation {installationId} (data collection disabled)."); + return; + } var newDaily = 0; var newHourly = 0; diff --git a/csharp/App/Backend/Services/ReportAggregationService.cs b/csharp/App/Backend/Services/ReportAggregationService.cs index ec71be1b2..0bc601ee8 100644 --- a/csharp/App/Backend/Services/ReportAggregationService.cs +++ b/csharp/App/Backend/Services/ReportAggregationService.cs @@ -106,7 +106,7 @@ public static class ReportAggregationService Console.WriteLine("[ReportAggregation] Running Monday weekly report generation..."); var installations = Db.Installations - .Where(i => (i.Product == (Int32)ProductType.SodioHome || i.Product == (Int32)ProductType.SodistorePro) && i.Device != 3) // Skip Growatt (device=3) + .Where(i => (i.Product == (Int32)ProductType.SodioHome || i.Product == (Int32)ProductType.SodistorePro) && i.Device != 3 && i.DataCollectionEnabled) // Skip Growatt (device=3) and installations with data collection disabled .ToList(); var generated = 0; diff --git a/typescript/frontend-marios2/src/content/dashboards/Information/InformationSodistoreHome.tsx b/typescript/frontend-marios2/src/content/dashboards/Information/InformationSodistoreHome.tsx index eaa605f3b..3c87ad13f 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Information/InformationSodistoreHome.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Information/InformationSodistoreHome.tsx @@ -762,6 +762,28 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) { /> +
+ + + + + + +
+
diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/FlatInstallationView.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/FlatInstallationView.tsx index 13ddbb325..374900ece 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/FlatInstallationView.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/FlatInstallationView.tsx @@ -209,46 +209,60 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => { marginLeft: '15px' }} > - {status === -1 ? ( - ) : ( - '' - )} + <> + {status === -1 ? ( + + ) : ( + '' + )} - {status === -2 ? ( - - ) : ( - '' - )} + {status === -2 ? ( + + ) : ( + '' + )} -
+
+ + )} {installation.testingMode && ( (undefined); const [values, setValues] = useState(null); const status = props.current_installation.status; + const dataCollectionDisabled = props.current_installation.dataCollectionEnabled === false; const [ failedToCommunicateWithInstallation, setFailedToCommunicateWithInstallation @@ -417,46 +418,60 @@ function SodioHomeInstallation(props: singleInstallationProps) { marginTop: '-10px' }} > - {status === -1 ? ( - ) : ( - '' - )} + <> + {status === -1 ? ( + + ) : ( + '' + )} - {status === -2 ? ( - - ) : ( - '' - )} + {status === -2 ? ( + + ) : ( + '' + )} -
+
+ + )} {props.current_installation.testingMode && ( - {currentUser.userType !== UserType.client && ( + {currentUser.userType !== UserType.client && !dataCollectionDisabled && ( )} - - } - /> + {!dataCollectionDisabled && ( + + } + /> + )} - {currentUser.userType !== UserType.client && ( + {currentUser.userType !== UserType.client && !dataCollectionDisabled && ( )} - {currentUser.userType == UserType.admin && ( + {currentUser.userType == UserType.admin && !dataCollectionDisabled && ( )} */} - - } - /> + {!dataCollectionDisabled && ( + + } + /> + )} - {props.current_installation.device !== 3 && ( + {props.current_installation.device !== 3 && !dataCollectionDisabled && ( )} +
+ + + + + + +
+
{tabs .filter((tab) => !(isGrowatt && tab.value === 'report')) + .filter((tab) => !dataCollectionDisabled || allowedWhenDisabled.includes(tab.value)) .map((tab) => ( {singleInstallationTabs .filter((tab) => !(isGrowatt && tab.value === 'report')) + .filter((tab) => !dataCollectionDisabled || allowedWhenDisabled.includes(tab.value)) .map((tab) => ( - {status === -1 ? ( - ) : ( - '' - )} + <> + {status === -1 ? ( + + ) : ( + '' + )} - {status === -2 ? ( - - ) : ( - '' - )} + {status === -2 ? ( + + ) : ( + '' + )} -
+
+ + )}
)}
diff --git a/typescript/frontend-marios2/src/interfaces/InstallationTypes.tsx b/typescript/frontend-marios2/src/interfaces/InstallationTypes.tsx index 1d91e5680..a90fd1c90 100644 --- a/typescript/frontend-marios2/src/interfaces/InstallationTypes.tsx +++ b/typescript/frontend-marios2/src/interfaces/InstallationTypes.tsx @@ -36,6 +36,7 @@ export interface I_Installation extends I_S3Credentials { product: number; device: number; testingMode?: boolean; + dataCollectionEnabled?: boolean; status?: number; serialNumber?: string; networkProvider: string; diff --git a/typescript/frontend-marios2/src/lang/de.json b/typescript/frontend-marios2/src/lang/de.json index d05706670..798a2068a 100644 --- a/typescript/frontend-marios2/src/lang/de.json +++ b/typescript/frontend-marios2/src/lang/de.json @@ -86,6 +86,9 @@ "externalEmsOther": "Externes EMS (angeben)", "emsNo": "Nein", "emsOther": "Andere", + "yes": "Ja", + "no": "Nein", + "dataCollectionEnabled": "Datenerfassung", "generalInfo": "Allgemeine Informationen", "installationSetup": "Installationseinrichtung", "couplingType": "AC/DC-Kopplung", diff --git a/typescript/frontend-marios2/src/lang/en.json b/typescript/frontend-marios2/src/lang/en.json index ddf33dcde..21de73b3a 100644 --- a/typescript/frontend-marios2/src/lang/en.json +++ b/typescript/frontend-marios2/src/lang/en.json @@ -68,6 +68,9 @@ "externalEmsOther": "External EMS (specify)", "emsNo": "No", "emsOther": "Other", + "yes": "Yes", + "no": "No", + "dataCollectionEnabled": "Data Collection", "generalInfo": "General Info", "installationSetup": "Installation Setup", "couplingType": "AC/DC Coupling", diff --git a/typescript/frontend-marios2/src/lang/fr.json b/typescript/frontend-marios2/src/lang/fr.json index 79aa856bd..e99292d0e 100644 --- a/typescript/frontend-marios2/src/lang/fr.json +++ b/typescript/frontend-marios2/src/lang/fr.json @@ -80,6 +80,9 @@ "externalEmsOther": "EMS externe (préciser)", "emsNo": "Non", "emsOther": "Autre", + "yes": "Oui", + "no": "Non", + "dataCollectionEnabled": "Collecte de données", "generalInfo": "Informations générales", "installationSetup": "Configuration de l'installation", "couplingType": "Couplage AC/DC", diff --git a/typescript/frontend-marios2/src/lang/it.json b/typescript/frontend-marios2/src/lang/it.json index 4cbfbdaea..151ba9bf9 100644 --- a/typescript/frontend-marios2/src/lang/it.json +++ b/typescript/frontend-marios2/src/lang/it.json @@ -68,6 +68,9 @@ "externalEmsOther": "EMS esterno (specificare)", "emsNo": "No", "emsOther": "Altro", + "yes": "Sì", + "no": "No", + "dataCollectionEnabled": "Raccolta dati", "generalInfo": "Informazioni generali", "installationSetup": "Configurazione installazione", "couplingType": "Accoppiamento AC/DC",