|
|
@@ -29,7 +29,7 @@ internal class Update_8_58 : DatabaseUpdateScript
|
|
|
.Add(x => x.Processed)
|
|
|
.Add(x => x.Posted))
|
|
|
.ToArray<TimeSheet>();
|
|
|
- if(timeSheets.Length == 0)
|
|
|
+ if (timeSheets.Length == 0)
|
|
|
{
|
|
|
Logger.Send(LogType.Information, "", $"Migrating TimeSheet.Processed -> TimeSheet.Posted: Done");
|
|
|
return;
|
|
|
@@ -63,7 +63,7 @@ internal class Update_8_58 : DatabaseUpdateScript
|
|
|
.Add(toColumn) // The order here matters a lot; since I've made a lot of obsolete links just point directly to the new one, then we have to set the new before the old.
|
|
|
.Add(fromColumn))
|
|
|
.ToArray<T>();
|
|
|
- if(items.Length == 0)
|
|
|
+ if (items.Length == 0)
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
@@ -145,9 +145,9 @@ internal class Update_8_58 : DatabaseUpdateScript
|
|
|
|
|
|
var variables = new List<DigitalFormVariable>();
|
|
|
var layouts = new List<DigitalFormLayout>();
|
|
|
- foreach(var item in qaQuestions)
|
|
|
+ foreach (var item in qaQuestions)
|
|
|
{
|
|
|
- if(formSequences.TryGetValue(item.FormID, out var sequence))
|
|
|
+ if (formSequences.TryGetValue(item.FormID, out var sequence))
|
|
|
{
|
|
|
sequence++;
|
|
|
}
|
|
|
@@ -178,12 +178,12 @@ internal class Update_8_58 : DatabaseUpdateScript
|
|
|
var nButtons = 0;
|
|
|
|
|
|
var i = 1;
|
|
|
- foreach(var question in item.Questions)
|
|
|
+ foreach (var question in item.Questions)
|
|
|
{
|
|
|
layout.RowHeights.Add("Auto");
|
|
|
var row = layout.RowHeights.Count;
|
|
|
|
|
|
- if(question.Answer == QAAnswer.Comment)
|
|
|
+ if (question.Answer == QAAnswer.Comment)
|
|
|
{
|
|
|
var label = new DFLayoutLabel { Caption = question.Question, Row = row, Column = 1, ColumnSpan = 3 };
|
|
|
label.Style.HorizontalTextAlignment = DFLayoutAlignment.Middle;
|
|
|
@@ -225,7 +225,7 @@ internal class Update_8_58 : DatabaseUpdateScript
|
|
|
optionProperties.Options = DFLayoutOptionFieldProperties.WriteOptions(buttons);
|
|
|
|
|
|
var colourExpression = "null";
|
|
|
- foreach(var (option, colour) in buttons.Zip(colors))
|
|
|
+ foreach (var (option, colour) in buttons.Zip(colors))
|
|
|
{
|
|
|
colourExpression = $"If([{code}] == {QuoteString(option)}, {QuoteString(colour)}, {colourExpression})";
|
|
|
}
|
|
|
@@ -319,7 +319,7 @@ internal class Update_8_58 : DatabaseUpdateScript
|
|
|
|
|
|
var values = DigitalForm.DeserializeFormSaveData(instance) ?? new();
|
|
|
var items = values.ToLoadStorage().Items().ToArray();
|
|
|
- foreach(var (key, value) in items)
|
|
|
+ foreach (var (key, value) in items)
|
|
|
{
|
|
|
if (!Guid.TryParse(key, out var id)) continue;
|
|
|
if (!mappings.TryGetValue(id, out var code)) continue;
|
|
|
@@ -333,8 +333,23 @@ internal class Update_8_58 : DatabaseUpdateScript
|
|
|
filter: Filter<DigitalForm>.Where(x => x.ID).InList(qaQuestions.ToArray(x => x.FormID)));
|
|
|
}
|
|
|
|
|
|
- private class ExtraMap<TFrom, TTo>
|
|
|
+ private interface IExtraMap
|
|
|
{
|
|
|
+ public Type TFrom { get; }
|
|
|
+ public Type TTo { get; }
|
|
|
+
|
|
|
+ IProperty From { get; }
|
|
|
+
|
|
|
+ IProperty To { get; }
|
|
|
+
|
|
|
+ Func<object?, object?>? Map { get; }
|
|
|
+ }
|
|
|
+
|
|
|
+ private class ExtraMap<TFrom, TTo> : IExtraMap
|
|
|
+ {
|
|
|
+ Type IExtraMap.TFrom => typeof(TFrom);
|
|
|
+ Type IExtraMap.TTo => typeof(TTo);
|
|
|
+
|
|
|
public IProperty From { get; }
|
|
|
|
|
|
public IProperty To { get; }
|
|
|
@@ -376,20 +391,20 @@ internal class Update_8_58 : DatabaseUpdateScript
|
|
|
|
|
|
var maps = new List<(IProperty from, IProperty to, Func<object?, object?>? map)>();
|
|
|
|
|
|
- foreach(var map in extraMaps ?? [])
|
|
|
+ foreach (var map in extraMaps ?? [])
|
|
|
{
|
|
|
currentMaps.Add(map.From.Name);
|
|
|
|
|
|
maps.Add((map.From, map.To, map.Map));
|
|
|
}
|
|
|
|
|
|
- foreach(var fromProperty in DatabaseSchema.LocalProperties(typeof(TFrom)))
|
|
|
+ foreach (var fromProperty in DatabaseSchema.LocalProperties(typeof(TFrom)))
|
|
|
{
|
|
|
if (currentMaps.Contains(fromProperty.Name)) continue;
|
|
|
|
|
|
- if(DatabaseSchema.Property(typeof(TTo), fromProperty.Name) is IProperty toProperty)
|
|
|
+ if (DatabaseSchema.Property(typeof(TTo), fromProperty.Name) is IProperty toProperty)
|
|
|
{
|
|
|
- if(fromProperty.PropertyType != toProperty.PropertyType)
|
|
|
+ if (fromProperty.PropertyType != toProperty.PropertyType)
|
|
|
{
|
|
|
throw new Exception($"Cannot migrate {typeof(TFrom).Name}.{fromProperty.Name} -> {typeof(TTo).Name}.{toProperty.Name}: type mismatch");
|
|
|
}
|
|
|
@@ -421,9 +436,9 @@ internal class Update_8_58 : DatabaseUpdateScript
|
|
|
var newItem = new TTo();
|
|
|
newItem.SetObserving(false);
|
|
|
newItem.ID = item.ID;
|
|
|
- foreach(var (from, to, map) in maps)
|
|
|
+ foreach (var (from, to, map) in maps)
|
|
|
{
|
|
|
- if(map is not null)
|
|
|
+ if (map is not null)
|
|
|
{
|
|
|
to.Setter()(newItem, map(from.Getter()(item)));
|
|
|
}
|
|
|
@@ -462,7 +477,7 @@ internal class Update_8_58 : DatabaseUpdateScript
|
|
|
&& x.IsGenericMethod
|
|
|
&& x.GetGenericArguments().Length == 1)
|
|
|
.First();
|
|
|
- foreach(var entity in DbFactory.ProviderFactory.Types.Where(x => x.HasInterface(typeof(IEntityDocument))))
|
|
|
+ foreach (var entity in DbFactory.ProviderFactory.Types.Where(x => x.HasInterface(typeof(IEntityDocument))))
|
|
|
{
|
|
|
var entityMethod = method.MakeGenericMethod(entity);
|
|
|
|
|
|
@@ -475,10 +490,10 @@ internal class Update_8_58 : DatabaseUpdateScript
|
|
|
entityMethod.Invoke(null, [provider, documentFromProp.Name, documentToProp.Name]);
|
|
|
}
|
|
|
|
|
|
- foreach(var entity in DbFactory.ProviderFactory.Types)
|
|
|
+ foreach (var entity in DbFactory.ProviderFactory.Types)
|
|
|
{
|
|
|
MethodInfo? entityMethod = null;
|
|
|
- foreach(var property in DatabaseSchema.LocalProperties(entity))
|
|
|
+ foreach (var property in DatabaseSchema.LocalProperties(entity))
|
|
|
{
|
|
|
if (property.Parent is null
|
|
|
|| property.Parent.PropertyType != typeof(LocalityLink)
|
|
|
@@ -567,6 +582,191 @@ internal class Update_8_58 : DatabaseUpdateScript
|
|
|
RenameTable<RequisitionKanban, PickingListKanban>(provider);
|
|
|
}
|
|
|
|
|
|
+ private static Dictionary<Type, Dictionary<string, IExtraMap>> _filterMaps = [];
|
|
|
+
|
|
|
+ private static IExtraMap[] GetFilterMaps()
|
|
|
+ {
|
|
|
+ return new IExtraMap[]
|
|
|
+ {
|
|
|
+ new ExtraMap<Kanban, Kanban>(DatabaseSchema.PropertyStrict(typeof(Kanban), "Category"), DatabaseSchema.PropertyStrict<Kanban>(x => x.Status))
|
|
|
+ {
|
|
|
+ Map = x =>
|
|
|
+ {
|
|
|
+ if(x is string category)
|
|
|
+ {
|
|
|
+ if (String.IsNullOrWhiteSpace(category) || category.Equals("Open"))
|
|
|
+ return KanbanStatus.Open;
|
|
|
+ if(category.Equals("In Progress"))
|
|
|
+ return KanbanStatus.InProgress;
|
|
|
+ if (category.Equals("Waiting"))
|
|
|
+ return KanbanStatus.Waiting;
|
|
|
+ if (category.Equals("Complete"))
|
|
|
+ return KanbanStatus.Complete;
|
|
|
+ return KanbanStatus.Open;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return KanbanStatus.Open;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool CheckFilter(IFilter filter, Dictionary<string, IExtraMap>? maps)
|
|
|
+ {
|
|
|
+ var changed = false;
|
|
|
+ if(maps is not null && maps.TryGetValue(filter.Property, out var map))
|
|
|
+ {
|
|
|
+ filter.Property = map.To.Name;
|
|
|
+ var value = map.Map?.Invoke(filter.Value);
|
|
|
+ if(value is null)
|
|
|
+ {
|
|
|
+ filter.Value = null;
|
|
|
+ changed = true;
|
|
|
+ }
|
|
|
+ else if (filter.Type.IsAssignableFrom(value.GetType()))
|
|
|
+ {
|
|
|
+ filter.Value = value;
|
|
|
+ changed = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!changed)
|
|
|
+ {
|
|
|
+ var columnName = ConvertColumnName(filter.Type, filter.Property);
|
|
|
+ if(columnName is not null)
|
|
|
+ {
|
|
|
+ filter.Property = columnName;
|
|
|
+ changed = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foreach(var and in filter.Ands)
|
|
|
+ {
|
|
|
+ changed = CheckFilter(and, maps) || changed;
|
|
|
+ }
|
|
|
+ foreach(var or in filter.Ors)
|
|
|
+ {
|
|
|
+ changed = CheckFilter(or, maps) || changed;
|
|
|
+ }
|
|
|
+ return changed;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void UpdateFilters<T>(IProvider provider)
|
|
|
+ where T : Entity, IDatabaseStoredSettings, new()
|
|
|
+ {
|
|
|
+ var settings = provider.Query(
|
|
|
+ Filter<T>.Where(x => x.Section).IsEqualTo(nameof(CoreFilterDefinitions)),
|
|
|
+ Columns.None<T>()
|
|
|
+ .Add(x => x.ID)
|
|
|
+ .Add(x => x.Key)
|
|
|
+ .Add(x => x.Contents))
|
|
|
+ .ToArray<T>();
|
|
|
+
|
|
|
+ var changedSettings = new List<T>();
|
|
|
+ foreach(var setting in settings)
|
|
|
+ {
|
|
|
+ if (setting.Key.IsNullOrWhiteSpace()) continue;
|
|
|
+
|
|
|
+ var entityName = setting.Key.Split('.')[^1];
|
|
|
+ var entity = CoreUtils.Entities.Where(x => x.Name == entityName
|
|
|
+ && x.IsSubclassOf(typeof(Entity)))
|
|
|
+ .FirstOrDefault();
|
|
|
+
|
|
|
+ var filters = Serialization.Deserialize<CoreFilterDefinitions>(setting.Contents);
|
|
|
+ if(filters is not null)
|
|
|
+ {
|
|
|
+ var changed = false;
|
|
|
+ foreach(var filter in filters)
|
|
|
+ {
|
|
|
+ var deserialiseEntity = entity is not null ? typeof(Filter<>).MakeGenericType(entity) : typeof(IFilter);
|
|
|
+ IFilter? deserialised = null;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ deserialised = (Serialization.Deserialize(deserialiseEntity, filter.Filter, strict: true) as IFilter)!;
|
|
|
+ if(deserialised is null)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ var dict = Serialization.Deserialize<Dictionary<string, object?>>(filter.Filter) ?? [];
|
|
|
+ if (dict.TryGetValue("Expression", out var expression) && expression is string str)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var exp = CoreUtils.StringToExpression(str);
|
|
|
+ if(exp is MemberExpression memExp)
|
|
|
+ {
|
|
|
+ deserialiseEntity = memExp.Expression?.Type;
|
|
|
+ if(deserialiseEntity is not null)
|
|
|
+ {
|
|
|
+ deserialiseEntity = typeof(Filter<>).MakeGenericType(deserialiseEntity);
|
|
|
+ deserialised = (Serialization.Deserialize(deserialiseEntity, filter.Filter, strict: true) as IFilter)!;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch(Exception)
|
|
|
+ {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(deserialised is null)
|
|
|
+ {
|
|
|
+ Logger.Send(LogType.Error, "", $"Error in deserialising filter '{filter.Name}' of {setting.Key}: {CoreUtils.FormatException(e)}");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var type = deserialised.GetType().GetSuperclassDefinition(typeof(Filter<>))!.GenericTypeArguments[0];
|
|
|
+ if (CheckFilter(deserialised, _filterMaps.GetValueOrDefault(type)))
|
|
|
+ {
|
|
|
+ filter.Filter = Serialization.Serialize(deserialised);
|
|
|
+ changed = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (changed)
|
|
|
+ {
|
|
|
+ setting.Contents = Serialization.Serialize(filters);
|
|
|
+ changedSettings.Add(setting);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ provider.Save(changedSettings);
|
|
|
+ }
|
|
|
+ private static void UpdateFilters(IProvider provider)
|
|
|
+ {
|
|
|
+ _filterMaps = GetFilterMaps()
|
|
|
+ .GroupBy(x => x.TFrom)
|
|
|
+ .ToDictionary(x => x.Key, x => x.ToDictionary(x => x.From.Name));
|
|
|
+
|
|
|
+ UpdateFilters<GlobalSettings>(provider);
|
|
|
+ UpdateFilters<UserSettings>(provider);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static string? ConvertColumnName(Type entity, string originalColumnName)
|
|
|
+ {
|
|
|
+ var i = 0;
|
|
|
+ var changed = false;
|
|
|
+ while(i < originalColumnName.Length)
|
|
|
+ {
|
|
|
+ var index = originalColumnName.IndexOf("Link.", i);
|
|
|
+ if (index == -1) break;
|
|
|
+
|
|
|
+ var columnName = originalColumnName[0..index];
|
|
|
+ if(DatabaseSchema.Property(entity, columnName) is IProperty property)
|
|
|
+ {
|
|
|
+ originalColumnName = $"{property.Name}.{originalColumnName[(index + 5)..]}";
|
|
|
+ changed = true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ i = index + 5;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return changed ? originalColumnName : null;
|
|
|
+ }
|
|
|
+
|
|
|
private static void UpdateColumns<T>(IProvider provider)
|
|
|
where T : Entity, IDatabaseStoredSettings, new()
|
|
|
{
|
|
|
@@ -595,22 +795,11 @@ internal class Update_8_58 : DatabaseUpdateScript
|
|
|
var changed = false;
|
|
|
foreach(var column in columns)
|
|
|
{
|
|
|
- var i = 0;
|
|
|
- while(i < column.ColumnName.Length)
|
|
|
+ var columnName = ConvertColumnName(entity, column.ColumnName);
|
|
|
+ if(columnName is not null)
|
|
|
{
|
|
|
- var index = column.ColumnName.IndexOf("Link.", i);
|
|
|
- if (index == -1) break;
|
|
|
-
|
|
|
- var columnName = column.ColumnName[0..index];
|
|
|
- if(DatabaseSchema.Property(entity, columnName) is IProperty property)
|
|
|
- {
|
|
|
- column.ColumnName = $"{property.Name}.{column.ColumnName[(index + 5)..]}";
|
|
|
- changed = true;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- i = index + 5;
|
|
|
- }
|
|
|
+ column.ColumnName = columnName;
|
|
|
+ changed = true;
|
|
|
}
|
|
|
}
|
|
|
if (changed)
|
|
|
@@ -736,6 +925,7 @@ internal class Update_8_58 : DatabaseUpdateScript
|
|
|
UpdateTimeSheets(provider);
|
|
|
|
|
|
ConvertLinks(provider);
|
|
|
+ UpdateFilters(provider);
|
|
|
UpdateColumns(provider);
|
|
|
|
|
|
UpdateSecurityTokens(provider);
|