Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b49ed8b47 | |||
| e725cac742 | |||
| 15d1a26251 | |||
| 05caa559b4 | |||
| 22130bcb41 |
@@ -0,0 +1,181 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace Logof_Client;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles schema versioning and migrations for JSON data structures.
|
||||||
|
/// This system allows tracking schema changes and automatically migrating
|
||||||
|
/// old data to new formats when the schema version changes.
|
||||||
|
/// </summary>
|
||||||
|
public static class SchemaMigration
|
||||||
|
{
|
||||||
|
// Current schema version - increment when you make breaking changes
|
||||||
|
public const int CURRENT_SCHEMA_VERSION = 3;
|
||||||
|
|
||||||
|
// Dictionary of migration functions, keyed by the version they migrate TO
|
||||||
|
private static readonly Dictionary<int, Func<JObject, JObject>> Migrations = new()
|
||||||
|
{
|
||||||
|
// Example: Migration from v1 to v2
|
||||||
|
// When loading v1 data, this will be called to upgrade to v2
|
||||||
|
// { 2, MigrateV1ToV2 },
|
||||||
|
|
||||||
|
// Example: Migration from v2 to v3
|
||||||
|
// { 3, MigrateV2ToV3 },
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks the schema version of loaded JSON and applies migrations if needed.
|
||||||
|
/// </summary>
|
||||||
|
public static JObject MigrateIfNeeded(JObject jsonObject, int? loadedVersion)
|
||||||
|
{
|
||||||
|
// If no version found, assume it's version 1 (legacy data)
|
||||||
|
int sourceVersion = loadedVersion ?? 1;
|
||||||
|
|
||||||
|
// If versions match, no migration needed
|
||||||
|
if (sourceVersion == CURRENT_SCHEMA_VERSION)
|
||||||
|
return jsonObject;
|
||||||
|
|
||||||
|
// If loaded version is newer than current, warn and return as-is
|
||||||
|
if (sourceVersion > CURRENT_SCHEMA_VERSION)
|
||||||
|
{
|
||||||
|
Logger.Log($"Warning: Data schema version {sourceVersion} is newer than application schema version {CURRENT_SCHEMA_VERSION}. " +
|
||||||
|
"This might indicate data was created with a newer version of the application.",
|
||||||
|
Logger.LogType.Warning);
|
||||||
|
return jsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply migrations sequentially from source version to current version
|
||||||
|
Logger.Log($"Migrating data from schema version {sourceVersion} to {CURRENT_SCHEMA_VERSION}", Logger.LogType.Info);
|
||||||
|
|
||||||
|
for (int targetVersion = sourceVersion + 1; targetVersion <= CURRENT_SCHEMA_VERSION; targetVersion++)
|
||||||
|
{
|
||||||
|
if (Migrations.TryGetValue(targetVersion, out var migration))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
jsonObject = migration(jsonObject);
|
||||||
|
Logger.Log($"Successfully migrated to schema version {targetVersion}", Logger.LogType.Info);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Log($"Error during migration to version {targetVersion}: {ex.Message}", Logger.LogType.Error);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Log($"No migration defined from version {targetVersion - 1} to {targetVersion}", Logger.LogType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Example migration function: from v1 to v2.
|
||||||
|
/// Rename 'old_field_name' to 'new_field_name'
|
||||||
|
/// </summary>
|
||||||
|
private static JObject MigrateV1ToV2(JObject obj)
|
||||||
|
{
|
||||||
|
// Example: Rename a field
|
||||||
|
if (obj.TryGetValue("old_field_name", out var value))
|
||||||
|
{
|
||||||
|
obj["new_field_name"] = value;
|
||||||
|
obj.Remove("old_field_name");
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Example migration function: from v2 to v3.
|
||||||
|
/// Add a new required field with a default value.
|
||||||
|
/// </summary>
|
||||||
|
private static JObject MigrateV2ToV3(JObject obj)
|
||||||
|
{
|
||||||
|
// Example: Add a new field that didn't exist before
|
||||||
|
if (!obj.ContainsKey("new_required_field"))
|
||||||
|
{
|
||||||
|
obj["new_required_field"] = "default_value";
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a new migration function that will be called when upgrading to a specific version.
|
||||||
|
/// Call this during application startup to register your custom migrations.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="targetVersion">The schema version this migration upgrades TO</param>
|
||||||
|
/// <param name="migration">The migration function that transforms the JObject</param>
|
||||||
|
public static void RegisterMigration(int targetVersion, Func<JObject, JObject> migration)
|
||||||
|
{
|
||||||
|
if (targetVersion <= 1)
|
||||||
|
throw new ArgumentException("Target version must be greater than 1");
|
||||||
|
|
||||||
|
if (Migrations.ContainsKey(targetVersion))
|
||||||
|
Logger.Log($"Warning: Overwriting existing migration for version {targetVersion}", Logger.LogType.Warning);
|
||||||
|
|
||||||
|
Migrations[targetVersion] = migration;
|
||||||
|
Logger.Log($"Registered migration to schema version {targetVersion}", Logger.LogType.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper method: Add a new property to an object if it doesn't exist
|
||||||
|
/// </summary>
|
||||||
|
public static void AddPropertyIfMissing(JObject obj, string propertyName, object defaultValue)
|
||||||
|
{
|
||||||
|
if (!obj.ContainsKey(propertyName))
|
||||||
|
{
|
||||||
|
obj[propertyName] = JToken.FromObject(defaultValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper method: Rename a property in a JSON object
|
||||||
|
/// </summary>
|
||||||
|
public static void RenameProperty(JObject obj, string oldName, string newName)
|
||||||
|
{
|
||||||
|
if (obj.TryGetValue(oldName, out var value))
|
||||||
|
{
|
||||||
|
obj[newName] = value;
|
||||||
|
obj.Remove(oldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper method: Move a nested property to the root level
|
||||||
|
/// </summary>
|
||||||
|
public static void FlattenProperty(JObject obj, string sourcePath, string targetPropertyName)
|
||||||
|
{
|
||||||
|
var value = obj.SelectToken(sourcePath);
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
obj[targetPropertyName] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper method: Merge a root property into a nested object
|
||||||
|
/// </summary>
|
||||||
|
public static void NestProperty(JObject obj, string propertyName, string nestedPath)
|
||||||
|
{
|
||||||
|
if (obj.TryGetValue(propertyName, out var value))
|
||||||
|
{
|
||||||
|
var parts = nestedPath.Split('.');
|
||||||
|
JObject current = obj;
|
||||||
|
|
||||||
|
for (int i = 0; i < parts.Length - 1; i++)
|
||||||
|
{
|
||||||
|
if (!current.ContainsKey(parts[i]))
|
||||||
|
current[parts[i]] = new JObject();
|
||||||
|
current = (JObject)current[parts[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
current[parts[^1]] = value;
|
||||||
|
obj.Remove(propertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+54
-3
@@ -2,12 +2,19 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Logof_Client;
|
namespace Logof_Client;
|
||||||
|
|
||||||
public class Settings
|
public class Settings
|
||||||
{
|
{
|
||||||
public static Settings _instance = new();
|
public static Settings _instance = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Schema version - increment this when you make breaking changes to the Settings structure
|
||||||
|
/// </summary>
|
||||||
|
public int schemaVersion { get; set; } = SchemaMigration.CURRENT_SCHEMA_VERSION;
|
||||||
|
|
||||||
public AddressSets addressSets = new();
|
public AddressSets addressSets = new();
|
||||||
public Customers customers = new();
|
public Customers customers = new();
|
||||||
public PdfExportSettings pdfExport { get; set; } = new();
|
public PdfExportSettings pdfExport { get; set; } = new();
|
||||||
@@ -27,7 +34,9 @@ public class Settings
|
|||||||
Directory.CreateDirectory(Global._instance.config_path);
|
Directory.CreateDirectory(Global._instance.config_path);
|
||||||
// if (!string.IsNullOrEmpty(Global._instance.config_path)) _instance.settingsPath = Global._instance.config_path;
|
// if (!string.IsNullOrEmpty(Global._instance.config_path)) _instance.settingsPath = Global._instance.config_path;
|
||||||
|
|
||||||
var json = JsonConvert.SerializeObject(_instance);
|
// Always save with current schema version
|
||||||
|
_instance.schemaVersion = SchemaMigration.CURRENT_SCHEMA_VERSION;
|
||||||
|
var json = JsonConvert.SerializeObject(_instance, Formatting.Indented);
|
||||||
File.WriteAllText(Path.Combine(Global._instance.config_path,"config.json"), json);
|
File.WriteAllText(Path.Combine(Global._instance.config_path,"config.json"), json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +47,24 @@ public class Settings
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var contents = File.ReadAllText(Path.Combine(Global._instance.config_path, "config.json"));
|
var contents = File.ReadAllText(Path.Combine(Global._instance.config_path, "config.json"));
|
||||||
_instance = JsonConvert.DeserializeObject<Settings>(contents);
|
|
||||||
|
// First, parse as JObject to check schema version
|
||||||
|
var jsonObject = JObject.Parse(contents);
|
||||||
|
int? loadedVersion = jsonObject["schemaVersion"]?.Value<int>();
|
||||||
|
|
||||||
|
// Apply migrations if needed
|
||||||
|
jsonObject = SchemaMigration.MigrateIfNeeded(jsonObject, loadedVersion);
|
||||||
|
|
||||||
|
// Now deserialize the migrated JSON
|
||||||
|
_instance = jsonObject.ToObject<Settings>();
|
||||||
|
|
||||||
|
// Save after migration to persist the upgraded schema
|
||||||
|
if (loadedVersion != SchemaMigration.CURRENT_SCHEMA_VERSION)
|
||||||
|
{
|
||||||
|
Logger.Log("Settings file was migrated to new schema version and saved.", Logger.LogType.Info);
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
MainWindow._instance.RefreshCustomerItems();
|
MainWindow._instance.RefreshCustomerItems();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -88,6 +114,11 @@ public class Global
|
|||||||
{
|
{
|
||||||
public static Global _instance;
|
public static Global _instance;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Schema version - increment this when you make breaking changes to the Global structure
|
||||||
|
/// </summary>
|
||||||
|
public int schemaVersion { get; set; } = SchemaMigration.CURRENT_SCHEMA_VERSION;
|
||||||
|
|
||||||
public Global()
|
public Global()
|
||||||
{
|
{
|
||||||
_instance = this;
|
_instance = this;
|
||||||
@@ -116,6 +147,9 @@ public class Global
|
|||||||
"logofclient")))
|
"logofclient")))
|
||||||
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
"logofclient"));
|
"logofclient"));
|
||||||
|
|
||||||
|
// Always save with current schema version
|
||||||
|
_instance.schemaVersion = SchemaMigration.CURRENT_SCHEMA_VERSION;
|
||||||
var json = JsonConvert.SerializeObject(_instance, Formatting.Indented);
|
var json = JsonConvert.SerializeObject(_instance, Formatting.Indented);
|
||||||
File.WriteAllText(
|
File.WriteAllText(
|
||||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient",
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient",
|
||||||
@@ -135,7 +169,24 @@ public class Global
|
|||||||
var contents = File.ReadAllText(Path.Combine(
|
var contents = File.ReadAllText(Path.Combine(
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient",
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient",
|
||||||
"global.config"));
|
"global.config"));
|
||||||
_instance = JsonConvert.DeserializeObject<Global>(contents) ?? new Global();
|
|
||||||
|
// First, parse as JObject to check schema version
|
||||||
|
var jsonObject = JObject.Parse(contents);
|
||||||
|
int? loadedVersion = jsonObject["schemaVersion"]?.Value<int>();
|
||||||
|
|
||||||
|
// Apply migrations if needed
|
||||||
|
jsonObject = SchemaMigration.MigrateIfNeeded(jsonObject, loadedVersion);
|
||||||
|
|
||||||
|
// Now deserialize the migrated JSON
|
||||||
|
_instance = jsonObject.ToObject<Global>() ?? new Global();
|
||||||
|
|
||||||
|
// Save after migration to persist the upgraded schema
|
||||||
|
if (loadedVersion != SchemaMigration.CURRENT_SCHEMA_VERSION)
|
||||||
|
{
|
||||||
|
Logger.Log("Global settings file was migrated to new schema version and saved.", Logger.LogType.Info);
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
_instance.NormalizePaths();
|
_instance.NormalizePaths();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
+1
-1
@@ -154,7 +154,7 @@
|
|||||||
<TextBlock TextWrapping="Wrap" FontSize="9" Text="Generiert Versandetiketten und Bundleitzettel" HorizontalAlignment="Stretch" TextAlignment="Left"></TextBlock>
|
<TextBlock TextWrapping="Wrap" FontSize="9" Text="Generiert Versandetiketten und Bundleitzettel" HorizontalAlignment="Stretch" TextAlignment="Left"></TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Width="250" IsEnabled="False"
|
<Button Width="250" IsEnabled="False" Click="BtnRepair_OnClick"
|
||||||
HorizontalContentAlignment="Center" x:Name="BtnRepair" VerticalAlignment="Stretch"
|
HorizontalContentAlignment="Center" x:Name="BtnRepair" VerticalAlignment="Stretch"
|
||||||
Margin="0,0,0,10">
|
Margin="0,0,0,10">
|
||||||
<StackPanel Orientation="Vertical">
|
<StackPanel Orientation="Vertical">
|
||||||
|
|||||||
+33
-1
@@ -135,6 +135,26 @@ public partial class MainWindow : Window
|
|||||||
//await MessageBox.Show(_instance, $"{result.Count} Einträge fehlerhaft.", "Fertig");
|
//await MessageBox.Show(_instance, $"{result.Count} Einträge fehlerhaft.", "Fertig");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void StartAddressRepairer(KasAddressList set)
|
||||||
|
{
|
||||||
|
//var addresses = DataImport.ImportKasAddressList(path); // Ihr Code hier
|
||||||
|
|
||||||
|
var progressWindow = new ProgressWindow();
|
||||||
|
|
||||||
|
progressWindow.Show(_instance);
|
||||||
|
|
||||||
|
var processor = new AddressRepair(progressWindow);
|
||||||
|
await processor.Perform(set);
|
||||||
|
|
||||||
|
// foreach (var item in result)
|
||||||
|
// {
|
||||||
|
// }
|
||||||
|
|
||||||
|
progressWindow.Close();
|
||||||
|
Settings.Save();
|
||||||
|
//await MessageBox.Show(_instance, $"{result.Count} Einträge fehlerhaft.", "Fertig");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void MnuExit_OnClick(object? sender, RoutedEventArgs e)
|
private void MnuExit_OnClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -704,7 +724,7 @@ public partial class MainWindow : Window
|
|||||||
if (pers.PersonError != null)
|
if (pers.PersonError != null)
|
||||||
{
|
{
|
||||||
BtnShorten.IsEnabled = true;
|
BtnShorten.IsEnabled = true;
|
||||||
// BtnRepair.IsEnabled = true;
|
BtnRepair.IsEnabled = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1346,4 +1366,16 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
PopulateNavTree();
|
PopulateNavTree();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BtnRepair_OnClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
MakeCalcManVisible();
|
||||||
|
if (LstCustomerAdressSets.SelectedIndex == -1)
|
||||||
|
{
|
||||||
|
MessageBox.Show(null, "Bitte zunächst ein Adress-Set auswählen", "Kein Adress-Set ausgewählt");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StartAddressRepairer((KasAddressList)LstCustomerAdressSets.SelectedItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ See [mypapercloud.de](https://mypapercloud.de/logof) for more.
|
|||||||
Please report any bugs you find to fierke@mypapertown.de, thank you!
|
Please report any bugs you find to fierke@mypapertown.de, thank you!
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
Feel free to contribute to this project using your MyPaperCloud-Account (request it via fierke@mypapertown.de) or a local git.mypapercloud.de-Account you are able to create.
|
See [CODE OF CONDUCT](https://git.mypapercloud.de/fierke/logofclient/src/branch/main/CODE_OF_CONDUCT.md)
|
||||||
+17
-8
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Logof_Client;
|
namespace Logof_Client;
|
||||||
|
|
||||||
@@ -7,20 +8,28 @@ public class AddressRepair(ProgressWindow progressWindow)
|
|||||||
{
|
{
|
||||||
private readonly ProgressWindow _progress = progressWindow;
|
private readonly ProgressWindow _progress = progressWindow;
|
||||||
|
|
||||||
public KasAddressList Perform(KasAddressList all_addresses,
|
public async Task Perform(KasAddressList list)
|
||||||
List<(int, List<AddressCheck.ErrorTypes>)> failed_addresses)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
foreach (var person in list.KasPersons)
|
||||||
|
{
|
||||||
|
|
||||||
|
// German PLZ too short (e.g. Dresden)
|
||||||
|
if (person.IsGermany() && person.plz.Length <= 4)
|
||||||
|
{
|
||||||
|
while (person.plz.Length <= 4)
|
||||||
|
{
|
||||||
|
person.plz = "0" + person.plz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.Log($"Error while performing address repair: {ex.Message}", Logger.LogType.Error);
|
Logger.Log($"Error while performing address shortener: {ex.Message}", Logger.LogType.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -989,7 +989,7 @@ public class PdfBuilder
|
|||||||
KasAddressList list = Settings._instance.addressSets.GetAddressSetByID(setID);
|
KasAddressList list = Settings._instance.addressSets.GetAddressSetByID(setID);
|
||||||
|
|
||||||
var document = new PdfDocument();
|
var document = new PdfDocument();
|
||||||
document.Info.Title = $"Laufzettel für {list.Name}";
|
document.Info.Title = $"Leitzettel für {list.Name}";
|
||||||
document.Info.Subject = "powered by logofclient";
|
document.Info.Subject = "powered by logofclient";
|
||||||
document.Info.Author = "logofclient";
|
document.Info.Author = "logofclient";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user