diff --git a/AbstractionLayer.sln b/AbstractionLayer.sln
index 6118c0b0..1d5428a6 100644
--- a/AbstractionLayer.sln
+++ b/AbstractionLayer.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.28803.352
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StartProject", "src\StartProject\StartProject.csproj", "{08B7686D-63C7-498C-90F0-B756E93575DD}"
EndProject
@@ -45,6 +45,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moryx.AbstractionLayer.Test
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moryx.Resources.Wcf", "src\Moryx.Resources.Wcf\Moryx.Resources.Wcf.csproj", "{E1AD28CD-37A6-4AF6-80D7-756586A727B6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moryx.Products.Management.Tests", "src\Tests\Moryx.Products.Management.Tests\Moryx.Products.Management.Tests.csproj", "{0CCA2AFB-1788-44C2-8919-F5CD46BC94AD}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -111,6 +113,10 @@ Global
{E1AD28CD-37A6-4AF6-80D7-756586A727B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1AD28CD-37A6-4AF6-80D7-756586A727B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1AD28CD-37A6-4AF6-80D7-756586A727B6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0CCA2AFB-1788-44C2-8919-F5CD46BC94AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0CCA2AFB-1788-44C2-8919-F5CD46BC94AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0CCA2AFB-1788-44C2-8919-F5CD46BC94AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0CCA2AFB-1788-44C2-8919-F5CD46BC94AD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -130,6 +136,7 @@ Global
{93650069-26E9-4E0D-A336-9D02F1039346} = {D6D69517-7889-4E08-ABEA-D3E069D08A6B}
{583CBD84-DD9F-4834-A89C-1625A05EE15D} = {BA183EBF-FAC1-45AE-9559-09879DB103AC}
{E1AD28CD-37A6-4AF6-80D7-756586A727B6} = {BA183EBF-FAC1-45AE-9559-09879DB103AC}
+ {0CCA2AFB-1788-44C2-8919-F5CD46BC94AD} = {D6D69517-7889-4E08-ABEA-D3E069D08A6B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {23463631-1BA0-41B8-ABA3-1E9741037513}
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 9bdcb1ff..645cb9e0 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -3,7 +3,7 @@
3.2.0
- 3.1.0
+ 3.2.0
3.1.1
diff --git a/VERSION b/VERSION
index 4e32c7b1..c68d476c 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-5.10.1
+5.11.0
diff --git a/src/Moryx.AbstractionLayer.TestTools/DummyProductPartLink.cs b/src/Moryx.AbstractionLayer.TestTools/DummyProductPartLink.cs
new file mode 100644
index 00000000..1af2dae4
--- /dev/null
+++ b/src/Moryx.AbstractionLayer.TestTools/DummyProductPartLink.cs
@@ -0,0 +1,25 @@
+// Copyright (c) 2020, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using Moryx.AbstractionLayer.Products;
+using System.Linq;
+
+namespace Moryx.AbstractionLayer.TestTools
+{
+ ///
+ /// Dummy implementation of a ProductPartLink.
+ ///
+ public class DummyProductPartLink : ProductPartLink
+ {
+ public override bool Equals(object obj)
+ {
+ var toCompareWith = obj as DummyProductPartLink;
+ if (toCompareWith == null)
+ return false;
+
+ return GetType().GetProperties()
+ .All(prop => (prop.GetValue(toCompareWith) is null && prop.GetValue(this) is null)
+ || prop.GetValue(toCompareWith).Equals(prop.GetValue(this)));
+ }
+ }
+}
diff --git a/src/Moryx.AbstractionLayer.TestTools/DummyProductRecipe.cs b/src/Moryx.AbstractionLayer.TestTools/DummyProductRecipe.cs
new file mode 100644
index 00000000..8205b33a
--- /dev/null
+++ b/src/Moryx.AbstractionLayer.TestTools/DummyProductRecipe.cs
@@ -0,0 +1,47 @@
+// Copyright (c) 2020, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using Moryx.AbstractionLayer.Recipes;
+using Moryx.Workflows;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Moryx.AbstractionLayer.TestTools
+{
+ ///
+ /// Dummy implementation of an product instance. Created by the
+ ///
+ public class DummyProductRecipe : ProductRecipe
+ {
+ public override bool Equals(object obj)
+ {
+ var toCompareWith = obj as DummyProductRecipe;
+ if (toCompareWith == null)
+ return false;
+
+ return toCompareWith.Name == Name && toCompareWith.Revision == Revision
+ && toCompareWith.State == State && toCompareWith.Classification == Classification
+ && ((toCompareWith.Origin is null && Origin is null) || Origin.Equals(toCompareWith.Origin))
+ && ((toCompareWith.Product is null && Product is null) || Product.Equals(toCompareWith.Product))
+ && ((toCompareWith.Target is null && Target is null) || Target.Equals(toCompareWith.Target));
+ }
+ }
+
+ public class DummyProductWorkplanRecipe : DummyProductRecipe, IWorkplanRecipe
+ {
+ public IWorkplan Workplan { get; set; }
+
+ public ICollection DisabledSteps { get; set; }
+
+ public override bool Equals(object obj)
+ {
+ var toCompareWith = obj as DummyProductWorkplanRecipe;
+ if (toCompareWith == null)
+ return false;
+
+ return base.Equals(toCompareWith)
+ && ((toCompareWith.Workplan is null && Workplan is null) || Workplan.Equals(toCompareWith.Workplan))
+ && ((toCompareWith.DisabledSteps is null && DisabledSteps is null) || Enumerable.SequenceEqual(DisabledSteps, toCompareWith.DisabledSteps));
+ }
+ }
+}
diff --git a/src/Moryx.AbstractionLayer.TestTools/DummyProductType.cs b/src/Moryx.AbstractionLayer.TestTools/DummyProductType.cs
index a7fc424b..d7d5f653 100644
--- a/src/Moryx.AbstractionLayer.TestTools/DummyProductType.cs
+++ b/src/Moryx.AbstractionLayer.TestTools/DummyProductType.cs
@@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0
using Moryx.AbstractionLayer.Products;
+using System.Collections.Generic;
+using System.Linq;
namespace Moryx.AbstractionLayer.TestTools
{
@@ -15,5 +17,87 @@ protected override ProductInstance Instantiate()
{
return new DummyProductInstance();
}
+
+ public override bool Equals(object obj)
+ {
+ var toCompareWith = obj as DummyProductType;
+ if (toCompareWith == null)
+ return false;
+
+ return toCompareWith.Id == Id && toCompareWith.Name == Name && toCompareWith.State == State
+ && ((toCompareWith.Identity is null && Identity is null) || toCompareWith.Identity.Equals(Identity));
+ }
+ }
+
+
+ ///
+ /// Dummy implementation of a with Product Parts
+ ///
+ public class DummyProductTypeWithParts : DummyProductType
+ {
+ ///
+ protected override ProductInstance Instantiate()
+ {
+ return new DummyProductInstance();
+ }
+
+ ///
+ /// Dummy ProductPartLink
+ ///
+ public DummyProductPartLink ProductPartLink { get; set; }
+
+ ///
+ /// Dummy ProductPartLink enumerable
+ ///
+ public IEnumerable ProductPartLinkEnumerable { get; set; }
+
+ public override bool Equals(object obj)
+ {
+ var toCompareWith = obj as DummyProductTypeWithParts;
+ if (toCompareWith == null)
+ return false;
+
+ return base.Equals(toCompareWith) &&
+ ((toCompareWith.ProductPartLink is null && ProductPartLink is null) ||
+ toCompareWith.ProductPartLink.Equals(ProductPartLink))
+ && ((toCompareWith.ProductPartLinkEnumerable is null && ProductPartLinkEnumerable is null) ||
+ Enumerable.SequenceEqual(toCompareWith.ProductPartLinkEnumerable, ProductPartLinkEnumerable));
+ }
+ }
+
+
+ ///
+ /// Dummy implementation of a with Files
+ ///
+ public class DummyProductTypeWithFiles : DummyProductType
+ {
+ ///
+ protected override ProductInstance Instantiate()
+ {
+ return new DummyProductInstance();
+ }
+
+ ///
+ /// First dummy ProductFile
+ ///
+ public ProductFile FirstProductFile { get; set; }
+
+ ///
+ /// Second dummy ProductFile
+ ///
+ public ProductFile SecondProductFile { get; set; }
+
+ public override bool Equals(object obj)
+ {
+ var toCompareWith = obj as DummyProductTypeWithFiles;
+ if (toCompareWith == null)
+ return false;
+
+ return base.Equals(toCompareWith) &&
+ (toCompareWith.FirstProductFile is null && FirstProductFile is null ||
+ FirstProductFile.GetType().GetProperties().All(prop => prop.GetValue(toCompareWith.FirstProductFile) == prop.GetValue(FirstProductFile)))
+ && (toCompareWith.SecondProductFile is null && SecondProductFile is null ||
+ SecondProductFile.GetType().GetProperties().All(prop => prop.GetValue(toCompareWith.SecondProductFile) == prop.GetValue(SecondProductFile)));
+ }
}
}
diff --git a/src/Moryx.AbstractionLayer.TestTools/DummyWorkplan.cs b/src/Moryx.AbstractionLayer.TestTools/DummyWorkplan.cs
new file mode 100644
index 00000000..874773ed
--- /dev/null
+++ b/src/Moryx.AbstractionLayer.TestTools/DummyWorkplan.cs
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using Moryx.Workflows;
+using System.Linq;
+
+namespace Moryx.AbstractionLayer.TestTools
+{
+ public class DummyWorkplan : Workplan
+ {
+ public override bool Equals(object obj)
+ {
+ var toCompareWith = obj as DummyWorkplan;
+ if (toCompareWith == null)
+ return false;
+
+ return toCompareWith.Id == Id && toCompareWith.Name == Name
+ && toCompareWith.Version == toCompareWith.Version && toCompareWith.State == State
+ && ((toCompareWith.Connectors is null && Connectors is null) || Enumerable.SequenceEqual(toCompareWith.Connectors, Connectors))
+ && ((toCompareWith.Steps is null && Steps is null) || Enumerable.SequenceEqual(toCompareWith.Steps, Steps));
+ }
+ }
+}
diff --git a/src/Moryx.AbstractionLayer/Products/IProductManagement.cs b/src/Moryx.AbstractionLayer/Products/IProductManagement.cs
index 1bd02b85..7dc6c87f 100644
--- a/src/Moryx.AbstractionLayer/Products/IProductManagement.cs
+++ b/src/Moryx.AbstractionLayer/Products/IProductManagement.cs
@@ -123,4 +123,16 @@ TInstance GetInstance(Expression> selector)
IReadOnlyList GetInstances(Expression> selector)
where TInstance : IProductInstance;
}
+
+ ///
+ /// Additional interface for type storage to search for product types by expression
+ /// TODO: Remove in AL 6
+ ///
+ public interface IProductManagementTypeSearch : IProductManagement
+ {
+ ///
+ /// Load types using filter expression
+ ///
+ IReadOnlyList LoadTypes(Expression> selector);
+ }
}
diff --git a/src/Moryx.AbstractionLayer/Products/ProductFacadeExtensions.cs b/src/Moryx.AbstractionLayer/Products/ProductFacadeExtensions.cs
new file mode 100644
index 00000000..54c951aa
--- /dev/null
+++ b/src/Moryx.AbstractionLayer/Products/ProductFacadeExtensions.cs
@@ -0,0 +1,26 @@
+// Copyright (c) 2021, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+
+namespace Moryx.AbstractionLayer.Products
+{
+ ///
+ /// Extensions for the facade
+ ///
+ public static class ProductFacadeExtensions
+ {
+ ///
+ /// Bridge extension for LoadTypes using filter expression
+ ///
+ public static IReadOnlyList LoadTypes(this IProductManagement facade, Expression> selector)
+ {
+ if (facade is IProductManagementTypeSearch typeSearch)
+ return typeSearch.LoadTypes(selector);
+
+ throw new NotSupportedException("Instance of product management does not support expression type search");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Moryx.AbstractionLayer/Products/ProductQuery.cs b/src/Moryx.AbstractionLayer/Products/ProductQuery.cs
index eab917a4..d36f7895 100644
--- a/src/Moryx.AbstractionLayer/Products/ProductQuery.cs
+++ b/src/Moryx.AbstractionLayer/Products/ProductQuery.cs
@@ -1,7 +1,9 @@
// Copyright (c) 2020, Phoenix Contact GmbH & Co. KG
// Licensed under the Apache License, Version 2.0
+using System.Collections.Generic;
using System.Runtime.Serialization;
+using Moryx.Serialization;
namespace Moryx.AbstractionLayer.Products
{
@@ -65,6 +67,52 @@ public class ProductQuery
///
[DataMember]
public Selector Selector { get; set; }
+
+ ///
+ /// List of property filters
+ ///
+ [DataMember]
+ public List PropertyFilters { get; set; }
+ }
+
+ ///
+ /// Property filter wrapper for the filtered entry
+ ///
+ [DataContract]
+ public class PropertyFilter
+ {
+ ///
+ /// Entry to filter
+ ///
+ [DataMember]
+ public Entry Entry { get; set; }
+
+ ///
+ /// Operator for the filter expression
+ ///
+ [DataMember]
+ public PropertyFilterOperator Operator { get; set; }
+ }
+
+ ///
+ /// Property filter operator expression
+ ///
+ public enum PropertyFilterOperator
+ {
+ ///
+ /// Value equals
+ ///
+ Equals,
+
+ ///
+ /// Value is greater then
+ ///
+ GreaterThen,
+
+ ///
+ /// Value is less then
+ ///
+ LessThen
}
///
@@ -76,10 +124,12 @@ public enum RevisionFilter
/// Fetch all revisions, this is the default
///
All = 0,
+
///
/// Fetch only the latest revision
///
Latest = 1,
+
///
/// Fetch only specific revisions
///
diff --git a/src/Moryx.AbstractionLayer/Products/ProductType.cs b/src/Moryx.AbstractionLayer/Products/ProductType.cs
index 6773ce35..f9e9f114 100644
--- a/src/Moryx.AbstractionLayer/Products/ProductType.cs
+++ b/src/Moryx.AbstractionLayer/Products/ProductType.cs
@@ -30,7 +30,7 @@ public abstract class ProductType : IProductType
///
public override string ToString()
{
- return Identity.ToString();
+ return Identity?.ToString();
}
///
diff --git a/src/Moryx.AbstractionLayer/Resources/IResourceGraph.cs b/src/Moryx.AbstractionLayer/Resources/IResourceGraph.cs
index 1e976e00..b508574f 100644
--- a/src/Moryx.AbstractionLayer/Resources/IResourceGraph.cs
+++ b/src/Moryx.AbstractionLayer/Resources/IResourceGraph.cs
@@ -84,6 +84,7 @@ TResource Instantiate(string type)
///
/// Remove a resource permanently and irreversible
///
+ [Obsolete("Permanent removal of resources will be removed in the next major")]
bool Destroy(IResource resource, bool permanent);
}
}
diff --git a/src/Moryx.AbstractionLayer/Resources/IResourceModification.cs b/src/Moryx.AbstractionLayer/Resources/IResourceModification.cs
new file mode 100644
index 00000000..b0dbdec0
--- /dev/null
+++ b/src/Moryx.AbstractionLayer/Resources/IResourceModification.cs
@@ -0,0 +1,37 @@
+// Copyright (c) 2021, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using System;
+
+namespace Moryx.AbstractionLayer.Resources
+{
+ ///
+ /// Facade for unintercepted access to resource objects, creation, modification and removal
+ /// For lifecylce and memory reasons resources are not returned, instead access is granted through delegates
+ ///
+ // TODO: Move into IResourceManagement
+ public interface IResourceModification : IResourceManagement
+ {
+ ///
+ /// Create and initialize a resource
+ ///
+ long Create(Type resourceType, Action initializer);
+
+ ///
+ /// Read data from a resource
+ ///
+ TResult Read(long id, Func accessor);
+
+ ///
+ /// Modify the resource.
+ ///
+ /// Id of the resource
+ /// Modifier delegate, must return true in order to save changes
+ void Modify(long id, Func modifier);
+
+ ///
+ /// Create and initialize a resource
+ ///
+ bool Delete(long id);
+ }
+}
\ No newline at end of file
diff --git a/src/Moryx.AbstractionLayer/Resources/ResourceFacadeExtensions.cs b/src/Moryx.AbstractionLayer/Resources/ResourceFacadeExtensions.cs
new file mode 100644
index 00000000..21955728
--- /dev/null
+++ b/src/Moryx.AbstractionLayer/Resources/ResourceFacadeExtensions.cs
@@ -0,0 +1,78 @@
+// Copyright (c) 2021, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using System;
+
+namespace Moryx.AbstractionLayer.Resources
+{
+ ///
+ /// Additional overloads for the resource facade APIs as well as facade version bridge
+ ///
+ public static class ResourceFacadeExtensions
+ {
+ ///
+ /// Read data from a resource
+ ///
+ public static TResult Read(this IResourceManagement facade, long resourceId, Func accessor)
+ {
+ if (facade is IResourceModification modification)
+ return modification.Read(resourceId, accessor);
+
+ throw new NotSupportedException("Instance of resource management does not support resource modification");
+ }
+
+ ///
+ /// Read data from a resource
+ ///
+ public static TResult Read(this IResourceManagement facade, IResource proxy, Func accessor)
+ {
+ if(facade is IResourceModification modification)
+ return modification.Read(proxy.Id, accessor);
+
+ throw new NotSupportedException("Instance of resource management does not support resource modification");
+ }
+
+ ///
+ /// Modify the resource.
+ ///
+ ///
+ /// Modifier delegate, must return true in order to save changes
+ ///
+ public static void Modify(this IResourceManagement facade, long resourceId, Func modifier)
+ {
+ if (facade is IResourceModification modification)
+ modification.Modify(resourceId, modifier);
+
+ throw new NotSupportedException("Instance of resource management does not support resource modification");
+ }
+
+ ///
+ /// Modify the resource.
+ ///
+ ///
+ /// Modifier delegate, must return true in order to save changes
+ ///
+ public static void Modify(this IResourceManagement facade, IResource proxy, Func modifier)
+ {
+ if (facade is IResourceModification modification)
+ modification.Modify(proxy.Id, modifier);
+
+ throw new NotSupportedException("Instance of resource management does not support resource modification");
+ }
+
+ ///
+ /// Modify the resource.
+ ///
+ ///
+ /// Modifier delegate, must return true in order to save changes
+ ///
+ ///
+ public static void Modify(this IResourceManagement facade, IResource proxy, Func modifier, TContext context)
+ {
+ if (facade is IResourceModification modification)
+ modification.Modify(proxy.Id, resource => modifier(resource, context));
+
+ throw new NotSupportedException("Instance of resource management does not support resource modification");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Moryx.Products.Management/Components/IProductManager.cs b/src/Moryx.Products.Management/Components/IProductManager.cs
index 12d9d7ac..4c160042 100644
--- a/src/Moryx.Products.Management/Components/IProductManager.cs
+++ b/src/Moryx.Products.Management/Components/IProductManager.cs
@@ -28,6 +28,11 @@ internal interface IProductManager : IPlugin
///
IReadOnlyList LoadTypes(ProductQuery query);
+ ///
+ /// Load types using filter expression
+ ///
+ IReadOnlyList LoadTypes(Expression> selector);
+
///
/// Load product instance by id
///
diff --git a/src/Moryx.Products.Management/Components/IProductStorage.cs b/src/Moryx.Products.Management/Components/IProductStorage.cs
index 1d900d58..64763c91 100644
--- a/src/Moryx.Products.Management/Components/IProductStorage.cs
+++ b/src/Moryx.Products.Management/Components/IProductStorage.cs
@@ -4,12 +4,9 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
-using Moryx.AbstractionLayer.Identity;
using Moryx.AbstractionLayer.Products;
using Moryx.AbstractionLayer.Recipes;
-using Moryx.Model.Repositories;
using Moryx.Modules;
-using Moryx.Products.Model;
namespace Moryx.Products.Management
{
@@ -74,4 +71,16 @@ public interface IProductStorage : IPlugin
///
void SaveRecipes(long productId, ICollection recipes);
}
+
+ ///
+ /// Additional interface for type storage to search for product types by expression
+ /// TODO: Remove in AL 6
+ ///
+ public interface IProductSearchStorage : IProductStorage
+ {
+ ///
+ /// Load types using filter expression
+ ///
+ IReadOnlyList LoadTypes(Expression> selector);
+ }
}
diff --git a/src/Moryx.Products.Management/Components/IProductTypeStrategy.cs b/src/Moryx.Products.Management/Components/IProductTypeStrategy.cs
index da35ecf8..01cacc0c 100644
--- a/src/Moryx.Products.Management/Components/IProductTypeStrategy.cs
+++ b/src/Moryx.Products.Management/Components/IProductTypeStrategy.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0
using System;
+using System.Linq.Expressions;
using Moryx.AbstractionLayer;
using Moryx.AbstractionLayer.Products;
using Moryx.Model;
@@ -35,4 +36,16 @@ public interface IProductTypeStrategy : IConfiguredPlugin
void LoadType(IGenericColumns source, IProductType target);
}
+
+ ///
+ /// Additional interface for type strategies to search for product types by expression
+ /// TODO: Remove in AL 6
+ ///
+ public interface IProductTypeSearch : IProductTypeStrategy
+ {
+ ///
+ /// Transform a product class selector to a database compatible expression
+ ///
+ Expression> TransformSelector(Expression> selector);
+ }
}
diff --git a/src/Moryx.Products.Management/Facades/ProductManagementFacade.cs b/src/Moryx.Products.Management/Facades/ProductManagementFacade.cs
index 2b5524cf..ac88750c 100644
--- a/src/Moryx.Products.Management/Facades/ProductManagementFacade.cs
+++ b/src/Moryx.Products.Management/Facades/ProductManagementFacade.cs
@@ -14,7 +14,7 @@
namespace Moryx.Products.Management
{
- internal class ProductManagementFacade : IFacadeControl, IProductManagement, IWorkplansVersions
+ internal class ProductManagementFacade : IFacadeControl, IWorkplansVersions, IProductManagementTypeSearch
{
// Use this delegate in every call for clean health state management
public Action ValidateHealthState { get; set; }
@@ -49,6 +49,12 @@ public IReadOnlyList LoadTypes(ProductQuery query)
return ProductManager.LoadTypes(query);
}
+ public IReadOnlyList LoadTypes(Expression> selector)
+ {
+ ValidateHealthState();
+ return ProductManager.LoadTypes(selector);
+ }
+
public IProductType LoadType(long id)
{
ValidateHealthState();
diff --git a/src/Moryx.Products.Management/Implementation/ProductManager.cs b/src/Moryx.Products.Management/Implementation/ProductManager.cs
index f6295b01..80d1b2bc 100644
--- a/src/Moryx.Products.Management/Implementation/ProductManager.cs
+++ b/src/Moryx.Products.Management/Implementation/ProductManager.cs
@@ -64,6 +64,14 @@ public IReadOnlyList LoadTypes(ProductQuery query)
return Storage.LoadTypes(query);
}
+ public IReadOnlyList LoadTypes(Expression> selector)
+ {
+ if (Storage is IProductSearchStorage searchStorage)
+ return searchStorage.LoadTypes(selector);
+
+ throw new NotSupportedException("Current storage does not support type search");
+ }
+
public IProductType LoadType(long id)
{
return Storage.LoadType(id);
diff --git a/src/Moryx.Products.Management/Implementation/Storage/ProductExpressionHelpers.cs b/src/Moryx.Products.Management/Implementation/Storage/ProductExpressionHelpers.cs
index 057b14f3..fd6b09a6 100644
--- a/src/Moryx.Products.Management/Implementation/Storage/ProductExpressionHelpers.cs
+++ b/src/Moryx.Products.Management/Implementation/Storage/ProductExpressionHelpers.cs
@@ -6,6 +6,7 @@
using System.Linq.Expressions;
using System.Reflection;
using Moryx.AbstractionLayer.Products;
+using Moryx.Products.Model;
namespace Moryx.Products.Management
{
@@ -28,21 +29,26 @@ public static object ExtractExpressionValue(Expression expression)
throw new NotSupportedException("Expression type not supported yet");
}
- public static bool IsTypeQuery(Expression> selector, out ProductType productType)
+
+
+ public static bool IsTypeQuery(Expression> selector, out MemberInfo typeMember, out object memberValue)
{
- productType = null;
+ typeMember = null;
+ memberValue = null;
+
var body = selector.Body;
// Extract the property targeted by the expression
switch (body)
{
case BinaryExpression binary when binary.NodeType == ExpressionType.Equal:
- if (binary.Left is MemberExpression bLeft && bLeft.Member.Name == nameof(ProductInstance.Type))
+ // Extract member and value
+ if (binary.Left is MemberExpression bLeft && IsTypeExpression(bLeft, out typeMember))
{
- productType = ExtractExpressionValue(binary.Right) as ProductType;
+ memberValue = ExtractExpressionValue(binary.Right);
}
- if (binary.Right is MemberExpression bRight && bRight.Member.Name == nameof(ProductInstance.Type))
+ else if (binary.Right is MemberExpression bRight && IsTypeExpression(bRight, out typeMember))
{
- productType = ExtractExpressionValue(binary.Left) as ProductType;
+ memberValue = ExtractExpressionValue(binary.Left);
}
break;
case MethodCallExpression call:
@@ -50,20 +56,51 @@ public static bool IsTypeQuery(Expression> sele
var method = call.Method;
if (method.Name == nameof(Equals))
{
- if (call.Object is MemberExpression callMemEx && callMemEx.Expression is ConstantExpression)
+ if (call.Object is MemberExpression callMemEx && IsTypeExpression(callMemEx, out typeMember))
{
- productType = ExtractExpressionValue(call.Object) as ProductType;
+ memberValue = ExtractExpressionValue(call.Arguments.First());
}
- else
+ else if (call.Arguments.First() is MemberExpression argMemEx && IsTypeExpression(argMemEx, out typeMember))
{
- productType = ExtractExpressionValue(call.Arguments.First()) as ProductType;
+ memberValue = ExtractExpressionValue(call.Object);
}
}
break;
}
-
- return productType != null;
+
+ return memberValue is not null;
+ }
+
+ private static bool IsTypeExpression(MemberExpression expression, out MemberInfo typeMember)
+ {
+ typeMember = null;
+ do
+ {
+ if (expression.Member is PropertyInfo propertyInfo && typeof(IProductType).IsAssignableFrom(propertyInfo.PropertyType))
+ return true;
+ typeMember = expression.Member;
+ expression = expression.Expression as MemberExpression;
+ } while (expression is not null);
+
+ return false;
+ }
+
+ internal static Expression> AsVersionExpression(Expression> expression)
+ {
+ // Extract lamda expression body and column
+ var lambda = (LambdaExpression)expression;
+ var binaryExpression = (BinaryExpression)lambda.Body;
+ var columnExpression = (MemberExpression)binaryExpression.Left;
+
+ // Build new parameter expression
+ var rootEntity = Expression.Parameter(typeof(ProductTypeEntity));
+ var versionExpression = Expression.Property(rootEntity, nameof(ProductTypeEntity.CurrentVersion));
+ var versionColumn = Expression.Property(versionExpression, columnExpression.Member.Name);
+
+ // Build new binary expression
+ var versionBinary = Expression.MakeBinary(binaryExpression.NodeType, versionColumn, binaryExpression.Right);
+ return Expression.Lambda(versionBinary, rootEntity) as Expression>;
}
}
}
diff --git a/src/Moryx.Products.Management/Implementation/Storage/ProductStorage.cs b/src/Moryx.Products.Management/Implementation/Storage/ProductStorage.cs
index 8a90fd97..7580eedb 100644
--- a/src/Moryx.Products.Management/Implementation/Storage/ProductStorage.cs
+++ b/src/Moryx.Products.Management/Implementation/Storage/ProductStorage.cs
@@ -9,6 +9,7 @@
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
+using System.Reflection;
using System.Text.RegularExpressions;
using Moryx.AbstractionLayer.Products;
using Moryx.AbstractionLayer.Recipes;
@@ -23,8 +24,8 @@ namespace Moryx.Products.Management
/// Base class for product storage. Contains basic functionality to load and save a product.
/// Also has the possibility to store a version to each save.
///
- [Plugin(LifeCycle.Singleton, typeof(IProductStorage))]
- internal class ProductStorage : IProductStorage
+ [Plugin(LifeCycle.Singleton, typeof(IProductStorage), typeof(IProductSearchStorage))]
+ internal class ProductStorage : IProductSearchStorage
{
///
/// Optimized constructor delegate for the different product types
@@ -271,6 +272,23 @@ public IReadOnlyList LoadTypes(ProductQuery query)
}).Select(t => t.Name);
productsQuery = productsQuery.Where(p => allTypes.Contains(p.TypeName));
}
+
+ // Filter by type properties properties
+ if (query.PropertyFilters != null && TypeStrategies[query.Type] is IProductTypeSearch typeSearch)
+ {
+ var targetType = typeSearch.TargetType;
+ // Make generic method for the target type
+ var genericMethod = typeof(IProductTypeSearch).GetMethod(nameof(IProductTypeSearch.TransformSelector));
+ var method = genericMethod.MakeGenericMethod(targetType);
+
+ foreach (var propertyFilter in query.PropertyFilters)
+ {
+ var expression = ConvertPropertyFilter(targetType, propertyFilter);
+ var columnExpression = (Expression>)method.Invoke(typeSearch, new object[]{ expression });
+ var versionExpression = AsVersionExpression(columnExpression);
+ productsQuery = productsQuery.Where(versionExpression);
+ }
+ }
}
// Filter by identifier
@@ -341,6 +359,71 @@ public IReadOnlyList LoadTypes(ProductQuery query)
}
}
+ private static Expression ConvertPropertyFilter(Type targetType, PropertyFilter filter)
+ {
+ // Product property expression
+ var productExpression = Expression.Parameter(targetType);
+ var propertyExpresssion = Expression.Property(productExpression, filter.Entry.Identifier);
+
+ var property = targetType.GetProperty(filter.Entry.Identifier);
+ var value = Convert.ChangeType(filter.Entry.Value.Current, property.PropertyType);
+ var constantExpression = Expression.Constant(value);
+
+ Expression expressionBody;
+ switch (filter.Operator)
+ {
+ case PropertyFilterOperator.Equals:
+ expressionBody = Expression.MakeBinary(ExpressionType.Equal, propertyExpresssion, constantExpression);
+ break;
+ case PropertyFilterOperator.GreaterThen:
+ expressionBody = Expression.MakeBinary(ExpressionType.GreaterThan, propertyExpresssion, constantExpression);
+ break;
+ case PropertyFilterOperator.LessThen:
+ expressionBody = Expression.MakeBinary(ExpressionType.LessThan, propertyExpresssion, constantExpression);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ return Expression.Lambda(expressionBody, productExpression);
+ }
+
+ public IReadOnlyList LoadTypes(Expression> selector)
+ {
+ using (var uow = Factory.Create())
+ {
+ var repo = uow.GetRepository();
+ var matchingStrategies = TypeStrategies.Values
+ .Where(i => typeof(TType).IsAssignableFrom(i.TargetType));
+
+ IQueryable query = null;
+ foreach (var typeStrategy in matchingStrategies)
+ {
+ var searchStrategy = typeStrategy as IProductTypeSearch;
+ if (searchStrategy == null)
+ throw new NotSupportedException($"Storage does not support expression search for {typeStrategy.TargetType}");
+
+ var columnExpression = searchStrategy.TransformSelector(selector);
+ var queryFilter = AsVersionExpression(columnExpression);
+ query = query == null
+ ? repo.Linq.Where(queryFilter) // Create query
+ : query.Union(repo.Linq.Where(queryFilter)); // Append query
+ }
+
+ // No query or no result => Nothing to do
+ List entities;
+ if (query == null || (entities = query.ToList()).Count == 0)
+ return new TType[0];
+
+ var loadedProducts = new Dictionary();
+ var instances = entities.Select(entity => Transform(uow, entity, false, loadedProducts)).OfType().ToArray();
+ // Final check against compiled expression
+ var compiledSelector = selector.Compile();
+ // Only return matches against compiled expression
+ return instances.Where(compiledSelector.Invoke).ToArray();
+ }
+ }
+
///
public IProductType LoadType(long id)
{
@@ -383,12 +466,6 @@ public IProductType LoadType(ProductIdentity identity)
}
}
- ///
- public IProductType TransformType(IUnitOfWork context, ProductTypeEntity typeEntity, bool full)
- {
- return Transform(context, typeEntity, full);
- }
-
private IProductType Transform(IUnitOfWork uow, ProductTypeEntity typeEntity, bool full, IDictionary loadedProducts = null, IProductPartLink parentLink = null)
{
// Build cache if this wasn't done before
@@ -515,7 +592,7 @@ private ProductTypeEntity SaveProduct(ProductPartsSaverContext saverContext, IPr
}
strategy.SaveType(modifiedInstance, typeEntity.CurrentVersion);
- saverContext.EntityCache.Add(new ProductIdentity(typeEntity.Identifier,typeEntity.Revision),typeEntity);
+ saverContext.EntityCache.Add(new ProductIdentity(typeEntity.Identifier, typeEntity.Revision), typeEntity);
// And nasty again!
var type = modifiedInstance.GetType();
@@ -586,10 +663,10 @@ where links.All(l => l.Id != link.Id)
private ProductTypeEntity GetPartEntity(ProductPartsSaverContext saverContext, IProductPartLink link)
{
- if (saverContext.EntityCache.ContainsKey((ProductIdentity) link.Product.Identity))
+ if (saverContext.EntityCache.ContainsKey((ProductIdentity)link.Product.Identity))
{
- var part = saverContext.EntityCache[(ProductIdentity) link.Product.Identity];
- EntityIdListener.Listen(part,link.Product);
+ var part = saverContext.EntityCache[(ProductIdentity)link.Product.Identity];
+ EntityIdListener.Listen(part, link.Product);
return part;
}
@@ -630,21 +707,14 @@ public IReadOnlyList LoadInstances(params long[] id)
public IReadOnlyList LoadInstances(ProductType productType)
{
- using (var uow = Factory.Create())
- {
- var repo = uow.GetRepository();
- var entities = repo.Linq
- .Where(e => e.ProductId == productType.Id)
- .ToList();
- return TransformInstances(uow, entities);
- }
+ return LoadInstances(productType.Id);
}
public IReadOnlyList LoadInstances(Expression> selector)
{
- if (IsTypeQuery(selector, out var productType))
+ if (IsTypeQuery(selector, out var member, out var value))
{
- return LoadInstances(productType).OfType().ToList();
+ return LoadInstancesByType(selector, member, value).ToList();
}
else
{
@@ -652,6 +722,42 @@ public IReadOnlyList LoadInstances(Expression LoadInstancesByType(Expression> selector, MemberInfo typeProperty, object value)
+ {
+ using (var uow = Factory.Create())
+ {
+ Expression> instanceSelector;
+ // Select by type or type id
+ if (typeProperty == null || typeProperty.Name == nameof(IProductType.Id))
+ {
+ var productTypeId = (value as IProductType)?.Id ?? (long)value;
+ instanceSelector = i => i.ProductId == productTypeId;
+ }
+ else if (typeProperty.Name == nameof(IProductType.Name))
+ {
+ var productName = (string)value;
+ instanceSelector = i => i.Product.Name == productName;
+ }
+ else if (typeProperty.Name == nameof(IProductType.Identity))
+ {
+ var productIdentity = (ProductIdentity)value;
+ instanceSelector = i => i.Product.Identifier == productIdentity.Identifier && i.Product.Revision == productIdentity.Revision;
+ }
+ else
+ {
+ // TODO: Filter by type specific properties
+ var productType = typeProperty.ReflectedType;
+ instanceSelector = i => false;
+ }
+
+ var repo = uow.GetRepository();
+ var entities = repo.Linq.Where(instanceSelector).ToList();
+
+
+ return TransformInstances(uow, entities).OfType().ToList();
+ }
+ }
+
public IReadOnlyList LoadWithStrategy(Expression> selector)
{
using (var uow = Factory.Create())
@@ -664,7 +770,7 @@ public IReadOnlyList LoadWithStrategy(Expression() // Create query
: query.Union(repo.Linq.Where(queryFilter).Cast()); // Append query
}
@@ -673,7 +779,7 @@ public IReadOnlyList LoadWithStrategy(Expression entities;
if (query == null || (entities = query.ToList()).Count == 0)
return new TInstance[0];
-
+
var instances = TransformInstances(uow, entities).OfType().ToArray();
// Final check against compiled expression
var compiledSelector = selector.Compile();
@@ -752,7 +858,7 @@ private void TransformInstance(IUnitOfWork uow, ProductInstanceEntity entity, Pr
TransformInstance(uow, partEntity, part);
}
}
- else if(linkStrategy.PartCreation == PartSourceStrategy.FromEntities)
+ else if (linkStrategy.PartCreation == PartSourceStrategy.FromEntities)
{
// Load part using the entity and assign PartLink afterwards
var partCollection = partEntityGroups[partGroup.Key.Name].ToList();
diff --git a/src/Moryx.Products.Management/Modification/Endpoint/IProductInteraction.cs b/src/Moryx.Products.Management/Modification/Endpoint/IProductInteraction.cs
index 45a81d0c..945f86ca 100644
--- a/src/Moryx.Products.Management/Modification/Endpoint/IProductInteraction.cs
+++ b/src/Moryx.Products.Management/Modification/Endpoint/IProductInteraction.cs
@@ -14,7 +14,7 @@ namespace Moryx.Products.Management.Modification
{
#if USE_WCF
[ServiceContract]
- [ServiceVersion("5.0.0")]
+ [ServiceVersion("5.1.0")]
#endif
internal interface IProductInteraction
{
diff --git a/src/Moryx.Products.Management/Modification/Endpoint/ProductInteraction.cs b/src/Moryx.Products.Management/Modification/Endpoint/ProductInteraction.cs
index 67555d5d..bd0dc01c 100644
--- a/src/Moryx.Products.Management/Modification/Endpoint/ProductInteraction.cs
+++ b/src/Moryx.Products.Management/Modification/Endpoint/ProductInteraction.cs
@@ -62,12 +62,7 @@ public ProductCustomization GetCustomization()
{
ProductTypes = ReflectionTool
.GetPublicClasses(new IsConfiguredFilter(Config.TypeStrategies).IsConfigured)
- .Select(pt => new ProductDefinitionModel
- {
- Name = pt.Name,
- DisplayName = pt.GetDisplayName() ?? pt.Name,
- BaseDefinition = pt.BaseType?.Name
- }).ToArray(),
+ .Select(Converter.ConvertProductType).ToArray(),
RecipeTypes = ReflectionTool
.GetPublicClasses(new IsConfiguredFilter(Config.RecipeStrategies).IsConfigured)
.Select(rt => new RecipeDefinitionModel
diff --git a/src/Moryx.Products.Management/Modification/IProductConverter.cs b/src/Moryx.Products.Management/Modification/IProductConverter.cs
index 5ef492f3..f66ba2cc 100644
--- a/src/Moryx.Products.Management/Modification/IProductConverter.cs
+++ b/src/Moryx.Products.Management/Modification/IProductConverter.cs
@@ -1,7 +1,7 @@
// Copyright (c) 2020, Phoenix Contact GmbH & Co. KG
// Licensed under the Apache License, Version 2.0
-using Moryx.AbstractionLayer;
+using System;
using Moryx.AbstractionLayer.Products;
using Moryx.AbstractionLayer.Recipes;
using Moryx.Workflows;
@@ -12,6 +12,8 @@ internal interface IProductConverter
{
ProductModel ConvertProduct(IProductType productType, bool flat);
+ ProductDefinitionModel ConvertProductType(Type productType);
+
IProductType ConvertProductBack(ProductModel source, ProductType target);
RecipeModel ConvertRecipe(IRecipe recipe);
diff --git a/src/Moryx.Products.Management/Modification/Model/ProductDefinitionModel.cs b/src/Moryx.Products.Management/Modification/Model/ProductDefinitionModel.cs
index a9b9e9e4..65a19fc0 100644
--- a/src/Moryx.Products.Management/Modification/Model/ProductDefinitionModel.cs
+++ b/src/Moryx.Products.Management/Modification/Model/ProductDefinitionModel.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0
using System.Runtime.Serialization;
+using Moryx.Serialization;
namespace Moryx.Products.Management.Modification
{
@@ -16,5 +17,8 @@ internal class ProductDefinitionModel
[DataMember]
public string BaseDefinition { get; set; }
+
+ [DataMember]
+ public Entry Properties { get; set; }
}
}
diff --git a/src/Moryx.Products.Management/Modification/Model/ProductFileModel.cs b/src/Moryx.Products.Management/Modification/Model/ProductFileModel.cs
new file mode 100644
index 00000000..da0ed6a8
--- /dev/null
+++ b/src/Moryx.Products.Management/Modification/Model/ProductFileModel.cs
@@ -0,0 +1,26 @@
+// Copyright (c) 2020, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using System.Runtime.Serialization;
+
+namespace Moryx.Products.Management.Modification
+{
+ [DataContract]
+ internal class ProductFileModel
+ {
+ [DataMember]
+ public string PropertyName { get; set; }
+
+ [DataMember]
+ public string FileName { get; set; }
+
+ [DataMember]
+ public string MimeType { get; set; }
+
+ [DataMember]
+ public string FilePath { get; set; }
+
+ [DataMember]
+ public string FileHash { get; set; }
+ }
+}
diff --git a/src/Moryx.Products.Management/Modification/Model/ProductModel.cs b/src/Moryx.Products.Management/Modification/Model/ProductModel.cs
index 425fada7..d7c4ba0f 100644
--- a/src/Moryx.Products.Management/Modification/Model/ProductModel.cs
+++ b/src/Moryx.Products.Management/Modification/Model/ProductModel.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2020, Phoenix Contact GmbH & Co. KG
// Licensed under the Apache License, Version 2.0
+using System;
using System.Runtime.Serialization;
using Moryx.AbstractionLayer.Products;
using Moryx.Serialization;
@@ -31,9 +32,14 @@ internal class ProductModel
[DataMember]
public Entry Properties { get; set; }
+ // TODO: AL6 Remove Files and rename FileModels to Files
[DataMember]
+ [Obsolete("Use FileModels Instead")]
public ProductFile[] Files { get; set; }
+ [DataMember]
+ public ProductFileModel[] FileModels { get; set; }
+
[DataMember]
public PartConnector[] Parts { get; set; }
diff --git a/src/Moryx.Products.Management/Modification/Model/RecipeModel.cs b/src/Moryx.Products.Management/Modification/Model/RecipeModel.cs
index 3b7aa4a0..227e8af7 100644
--- a/src/Moryx.Products.Management/Modification/Model/RecipeModel.cs
+++ b/src/Moryx.Products.Management/Modification/Model/RecipeModel.cs
@@ -62,5 +62,11 @@ internal class RecipeModel
///
[DataMember]
public RecipeClassificationModel Classification { get; set; }
+
+ ///
+ /// Whether this Recipe is a clone or not
+ ///
+ [DataMember]
+ public bool IsClone{ get; set; }
}
}
diff --git a/src/Moryx.Products.Management/Modification/ProductConverter.cs b/src/Moryx.Products.Management/Modification/ProductConverter.cs
index 7d9a3bc3..2c26c9da 100644
--- a/src/Moryx.Products.Management/Modification/ProductConverter.cs
+++ b/src/Moryx.Products.Management/Modification/ProductConverter.cs
@@ -39,7 +39,7 @@ internal class ProductConverter : IProductConverter
#endregion
#region To Model
-
+
public ProductModel ConvertProduct(IProductType productType, bool flat)
{
// Base object
@@ -62,7 +62,10 @@ public ProductModel ConvertProduct(IProductType productType, bool flat)
converted.Properties = EntryConvert.EncodeObject(productType, ProductSerialization);
// Files
- converted.Files = ConvertFiles(productType, properties);
+ converted.Files = (from property in properties
+ where property.PropertyType == typeof(ProductFile)
+ select (ProductFile)property.GetValue(productType)).ToArray();
+ converted.FileModels = ConvertFiles(productType, properties);
// Recipes
var recipes = RecipeManagement.GetAllByProduct(productType);
@@ -74,12 +77,34 @@ public ProductModel ConvertProduct(IProductType productType, bool flat)
return converted;
}
- private static ProductFile[] ConvertFiles(IProductType productType, IEnumerable properties)
+ public ProductDefinitionModel ConvertProductType(Type productType)
+ {
+ return new()
+ {
+ Name = productType.Name,
+ DisplayName = productType.GetDisplayName() ?? productType.Name,
+ BaseDefinition = productType.BaseType?.Name,
+ Properties = EntryConvert.EncodeClass(productType, ProductSerialization)
+ };
+ }
+
+ private ProductFileModel[] ConvertFiles(IProductType productType, IEnumerable properties)
{
- var files = (from property in properties
- where property.PropertyType == typeof(ProductFile)
- select (ProductFile)property.GetValue(productType)).ToArray();
- return files;
+ var productFileProperties = properties.Where(p => p.PropertyType == typeof(ProductFile)).ToArray();
+ var fileModels = new ProductFileModel[productFileProperties.Length];
+ for (int i = 0; i < fileModels.Length; i++)
+ {
+ var value = (ProductFile)productFileProperties[i].GetValue(productType);
+ fileModels[i] = new ProductFileModel()
+ {
+ PropertyName = productFileProperties[i].Name,
+ FileName = value?.Name,
+ FileHash = value?.FileHash,
+ FilePath = value?.FilePath,
+ MimeType = value?.MimeType
+ };
+ }
+ return fileModels;
}
private void ConvertParts(IProductType productType, IEnumerable properties, ProductModel converted)
@@ -98,7 +123,7 @@ private void ConvertParts(IProductType productType, IEnumerable pr
Name = property.Name,
DisplayName = displayName,
Type = FetchProductType(property.PropertyType),
- Parts = partModel != null ? new[] { partModel } : new PartModel[0],
+ Parts = partModel is null ? new PartModel[0] : new[] { partModel },
PropertyTemplates = EntryConvert.EncodeClass(property.PropertyType, ProductSerialization)
};
connectors.Add(connector);
@@ -113,7 +138,7 @@ private void ConvertParts(IProductType productType, IEnumerable pr
Name = property.Name,
DisplayName = displayName,
Type = FetchProductType(linkType),
- Parts = links.Select(ConvertPart).ToArray(),
+ Parts = links?.Select(ConvertPart).ToArray(),
PropertyTemplates = EntryConvert.EncodeClass(linkType, ProductSerialization)
};
connectors.Add(connector);
@@ -125,7 +150,7 @@ private void ConvertParts(IProductType productType, IEnumerable pr
private PartModel ConvertPart(IProductPartLink link)
{
// No link, no DTO!
- if (link == null)
+ if (link is null || link.Product is null)
return null;
var part = new PartModel
@@ -155,6 +180,7 @@ public RecipeModel ConvertRecipe(IRecipe recipe)
State = recipe.State,
Revision = recipe.Revision,
Properties = EntryConvert.EncodeObject(recipe, RecipeSerialization),
+ IsClone = recipe.Classification.HasFlag(RecipeClassification.Clone)
};
switch (recipe.Classification & RecipeClassification.CloneFilter)
@@ -251,16 +277,10 @@ public IProductType ConvertProductBack(ProductModel source, ProductType converte
converted.Identity = new ProductIdentity(source.Identifier, source.Revision);
converted.Name = source.Name;
converted.State = source.State;
-
- // Copy extended properties
- var properties = converted.GetType().GetProperties();
- EntryConvert.UpdateInstance(converted, source.Properties, ProductSerialization);
-
- ConvertFilesBack(converted, source, properties);
-
+
// Save recipes
- var recipes = new List(source.Recipes.Length);
- foreach (var recipeModel in source.Recipes)
+ var recipes = new List(source.Recipes?.Length ?? 0);
+ foreach (var recipeModel in source.Recipes ?? Enumerable.Empty())
{
IProductRecipe productRecipe;
if (recipeModel.Id == 0)
@@ -274,15 +294,36 @@ public IProductType ConvertProductBack(ProductModel source, ProductType converte
ConvertRecipeBack(recipeModel, productRecipe, converted);
recipes.Add(productRecipe);
}
- RecipeManagement.Save(source.Id, recipes);
+ if (recipes.Any())
+ RecipeManagement.Save(source.Id, recipes);
+
+ // Product is flat
+ if (source.Properties is null)
+ return converted;
+
+ // Copy extended properties
+ var properties = converted.GetType().GetProperties();
+ EntryConvert.UpdateInstance(converted, source.Properties, ProductSerialization);
+
+ // Copy Files
+ ConvertFilesBack(converted, source, properties);
// Convert parts
- foreach (var partConnector in source.Parts)
+ foreach (var partConnector in source.Parts ?? Enumerable.Empty())
{
+ if (partConnector.Parts is null)
+ continue;
+
var prop = properties.First(p => p.Name == partConnector.Name);
var value = prop.GetValue(converted);
if (partConnector.IsCollection)
{
+ if (value == null)
+ {
+ value = Activator.CreateInstance(typeof(List<>)
+ .MakeGenericType(prop.PropertyType.GetGenericArguments().First()));
+ prop.SetValue(converted, value);
+ }
UpdateCollection((IList)value, partConnector.Parts);
}
else if (partConnector.Parts.Length == 1)
@@ -309,21 +350,25 @@ private void UpdateCollection(IList value, IEnumerable parts)
var unused = new List(value.OfType());
// Iterate over the part models
// Create or update the part links
- var elemType = value.GetType().GetGenericArguments()[0];
+ var elemType = value.GetType().GetInterfaces()
+ .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>))
+ .Select(i => i.GetGenericArguments()[0]).Single();
foreach (var partModel in parts)
{
- var match = unused.Find(r => r.Id == partModel.Id);
+ if (partModel is null)
+ continue;
+
+ var match = unused.Find(r => r.Id == partModel?.Id);
if (match == null)
{
match = (IProductPartLink)Activator.CreateInstance(elemType);
value.Add(match);
}
else
- {
unused.Remove(match);
- }
+
EntryConvert.UpdateInstance(match, partModel.Properties);
- match.Product = (ProductType)ProductManager.LoadType(partModel.Product.Id);
+ match.Product = ProductManager.LoadType(partModel.Product.Id);
}
// Clear all values no longer present in the model
@@ -334,15 +379,25 @@ private void UpdateCollection(IList value, IEnumerable parts)
private void UpdateReference(IProductPartLink value, PartModel part)
{
EntryConvert.UpdateInstance(value, part.Properties);
- value.Product = (ProductType)ProductManager.LoadType(part.Product.Id);
+ value.Product = part.Product is null ? null : ProductManager.LoadType(part.Product.Id);
}
private static void ConvertFilesBack(object converted, ProductModel product, PropertyInfo[] properties)
{
- foreach (var fileModel in product.Files)
+ foreach (var fileModel in product.FileModels)
{
- var prop = properties.First(p => p.Name == fileModel.Name);
- prop.SetValue(converted, fileModel);
+ var prop = properties.Single(p => p.Name == fileModel.PropertyName);
+ var productFile = new ProductFile()
+ {
+ MimeType = fileModel.MimeType,
+ FilePath = fileModel.FilePath,
+ FileHash = fileModel.FileHash,
+ Name = fileModel.FileName
+ };
+ if (productFile.GetType().GetProperties().All(p => p.GetValue(productFile) is null))
+ prop.SetValue(converted, null);
+ else
+ prop.SetValue(converted, productFile);
}
}
#endregion
diff --git a/src/Moryx.Products.Management/Plugins/GenericStrategies/GenericTypeStrategy.cs b/src/Moryx.Products.Management/Plugins/GenericStrategies/GenericTypeStrategy.cs
index 78b32776..a9dfb890 100644
--- a/src/Moryx.Products.Management/Plugins/GenericStrategies/GenericTypeStrategy.cs
+++ b/src/Moryx.Products.Management/Plugins/GenericStrategies/GenericTypeStrategy.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using System.Linq.Expressions;
using Moryx.AbstractionLayer;
using Moryx.AbstractionLayer.Products;
using Moryx.Container;
@@ -20,7 +21,7 @@ namespace Moryx.Products.Management
[ExpectedConfig(typeof(GenericTypeConfiguration))]
[StrategyConfiguration(typeof(IProductType), DerivedTypes = true)]
[Plugin(LifeCycle.Transient, typeof(IProductTypeStrategy), Name = nameof(GenericTypeStrategy))]
- internal class GenericTypeStrategy : TypeStrategyBase
+ internal class GenericTypeStrategy : TypeStrategyBase, IProductTypeSearch
{
///
/// Injected entity mapper
@@ -37,6 +38,11 @@ public override void Initialize(ProductTypeConfiguration config)
EntityMapper.Initialize(TargetType, Config);
}
+ public Expression> TransformSelector(Expression> selector)
+ {
+ return EntityMapper.TransformSelector(selector);
+ }
+
public override bool HasChanged(IProductType current, IGenericColumns dbProperties)
{
return EntityMapper.HasChanged(dbProperties, current);
diff --git a/src/Moryx.Products.Management/Properties/AssemblyInfo.cs b/src/Moryx.Products.Management/Properties/AssemblyInfo.cs
index ba4929bd..963dc772 100644
--- a/src/Moryx.Products.Management/Properties/AssemblyInfo.cs
+++ b/src/Moryx.Products.Management/Properties/AssemblyInfo.cs
@@ -2,3 +2,4 @@
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: InternalsVisibleTo("Moryx.Products.IntegrationTests")]
+[assembly: InternalsVisibleTo("Moryx.Products.Management.Tests")]
diff --git a/src/Moryx.Products.UI.Interaction/ModuleController/ModuleConfig.cs b/src/Moryx.Products.UI.Interaction/ModuleController/ModuleConfig.cs
index c753a320..8d12be75 100644
--- a/src/Moryx.Products.UI.Interaction/ModuleController/ModuleConfig.cs
+++ b/src/Moryx.Products.UI.Interaction/ModuleController/ModuleConfig.cs
@@ -66,5 +66,11 @@ public ModuleConfig()
///
[DataMember]
public List DefaultAspects { get; set; }
+
+ ///
+ /// Set to true to show the product types as a tree instead of a flat list
+ ///
+ [DataMember]
+ public bool ShowProductTypeTree { get; set; }
}
}
diff --git a/src/Moryx.Products.UI.Interaction/Moryx.Products.UI.Interaction.csproj.DotSettings b/src/Moryx.Products.UI.Interaction/Moryx.Products.UI.Interaction.csproj.DotSettings
index 78a064f5..b1f67e85 100644
--- a/src/Moryx.Products.UI.Interaction/Moryx.Products.UI.Interaction.csproj.DotSettings
+++ b/src/Moryx.Products.UI.Interaction/Moryx.Products.UI.Interaction.csproj.DotSettings
@@ -13,7 +13,9 @@
True
True
True
+ True
True
+ True
True
True
True
diff --git a/src/Moryx.Products.UI.Interaction/Products/Aspects/Parts/CollectionPartConnectorViewModel.cs b/src/Moryx.Products.UI.Interaction/Products/Aspects/Parts/CollectionPartConnectorViewModel.cs
index 4b643d83..20c25916 100644
--- a/src/Moryx.Products.UI.Interaction/Products/Aspects/Parts/CollectionPartConnectorViewModel.cs
+++ b/src/Moryx.Products.UI.Interaction/Products/Aspects/Parts/CollectionPartConnectorViewModel.cs
@@ -74,13 +74,14 @@ public override void BeginEdit()
{
base.BeginEdit();
- PartLinks.ForEach(p => p.BeginEdit());
+ PartLinks.BeginEdit();
+ PartConnector.BeginEdit();
}
public override void EndEdit()
{
// End on part links
- PartLinks.ForEach(p => p.EndEdit());
+ PartLinks.EndEdit();
// Add new links
var newLinks = _newLinks.Select(n => n.PartLink);
@@ -91,6 +92,7 @@ public override void EndEdit()
PartConnector.PartLinks.RemoveRange(_removedLinks.Select(l => l.PartLink));
_removedLinks.Clear();
+ PartConnector.EndEdit();
base.EndEdit();
}
@@ -105,8 +107,8 @@ public override void CancelEdit()
_removedLinks.Clear();
// Cancel on existent
- PartLinks.ForEach(p => p.CancelEdit());
-
+ PartLinks.CancelEdit();
+ PartConnector.CancelEdit();
base.CancelEdit();
}
diff --git a/src/Moryx.Products.UI.Interaction/Products/Aspects/Parts/SinglePartConnectorPartViewModel.cs b/src/Moryx.Products.UI.Interaction/Products/Aspects/Parts/SinglePartConnectorPartViewModel.cs
index 5895bc97..bb03a854 100644
--- a/src/Moryx.Products.UI.Interaction/Products/Aspects/Parts/SinglePartConnectorPartViewModel.cs
+++ b/src/Moryx.Products.UI.Interaction/Products/Aspects/Parts/SinglePartConnectorPartViewModel.cs
@@ -68,12 +68,27 @@ public SinglePartConnectorPartViewModel(PartConnectorViewModel partConnector, Pa
PartLink = partLink;
}
+ public override void BeginEdit()
+ {
+ PartLink.BeginEdit();
+ PartConnector.BeginEdit();
+ base.BeginEdit();
+ }
+
+ public override void EndEdit()
+ {
+ PartLink.EndEdit();
+ PartConnector.EndEdit();
+ base.EndEdit();
+ }
+
public override void CancelEdit()
{
// If single used, reset part link
if (!PartConnector.IsCollection)
PartLink = PartConnector.PartLinks.FirstOrDefault();
-
+ PartLink.CancelEdit();
+ PartConnector.CancelEdit();
base.CancelEdit();
}
diff --git a/src/Moryx.Products.UI.Interaction/Products/Filter/PropertyFilterDialogView.xaml b/src/Moryx.Products.UI.Interaction/Products/Filter/PropertyFilterDialogView.xaml
new file mode 100644
index 00000000..0c0a639c
--- /dev/null
+++ b/src/Moryx.Products.UI.Interaction/Products/Filter/PropertyFilterDialogView.xaml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Moryx.Products.UI.Interaction/Products/Filter/PropertyFilterDialogView.xaml.cs b/src/Moryx.Products.UI.Interaction/Products/Filter/PropertyFilterDialogView.xaml.cs
new file mode 100644
index 00000000..a13dbe41
--- /dev/null
+++ b/src/Moryx.Products.UI.Interaction/Products/Filter/PropertyFilterDialogView.xaml.cs
@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+
+namespace Moryx.Products.UI.Interaction
+{
+ ///
+ /// Interaction logic for PropertyFilterDialogView.xaml
+ ///
+ public partial class PropertyFilterDialogView : UserControl
+ {
+ public PropertyFilterDialogView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/src/Moryx.Products.UI.Interaction/Products/Filter/PropertyFilterDialogViewModel.cs b/src/Moryx.Products.UI.Interaction/Products/Filter/PropertyFilterDialogViewModel.cs
new file mode 100644
index 00000000..93201851
--- /dev/null
+++ b/src/Moryx.Products.UI.Interaction/Products/Filter/PropertyFilterDialogViewModel.cs
@@ -0,0 +1,87 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Input;
+using Moryx.ClientFramework.Dialog;
+using Moryx.Controls;
+using Moryx.Products.UI.Interaction.Properties;
+using Moryx.Serialization;
+using Moryx.WpfToolkit;
+using Entry = Moryx.Serialization.Entry;
+
+namespace Moryx.Products.UI.Interaction
+{
+ internal class PropertyFilterDialogViewModel : DialogScreen
+ {
+ private ProductDefinitionViewModel _productType;
+ private EntryViewModel _currentFilter;
+
+ public ICommand ApplyCmd { get; }
+
+ public ICommand CancelCmd { get; }
+
+ public ICommand AddCmd { get; set; }
+
+ public EntryViewModel[] PossibleProperties { get; set; }
+
+ public ProductDefinitionViewModel ProductType
+ {
+ get => _productType;
+ set
+ {
+ _productType = value;
+ NotifyOfPropertyChange();
+ }
+ }
+
+ public EntryViewModel CurrentFilter
+ {
+ get => _currentFilter;
+ set
+ {
+ _currentFilter = value;
+ NotifyOfPropertyChange();
+ }
+ }
+
+ public PropertyFilterDialogViewModel(ProductDefinitionViewModel productType)
+ {
+ ProductType = productType;
+ CurrentFilter = new EntryViewModel(new List());
+
+ // Currently no collections or special units are not supported
+ PossibleProperties = productType.Properties.SubEntries
+ .Where(e => e.Entry.Value.Type != EntryValueType.Collection &&
+ e.Entry.Value.UnitType == EntryUnitType.None).ToArray();
+
+ AddCmd = new RelayCommand(Add, CanAdd);
+ ApplyCmd = new RelayCommand(Apply);
+ CancelCmd = new RelayCommand(Cancel);
+ }
+
+ protected override void OnInitialize()
+ {
+ base.OnInitialize();
+
+ DisplayName = string.Format(Strings.PropertyFilterDialogViewModel_DisplayName, ProductType.DisplayName);
+ }
+
+ private bool CanAdd(object parameters) =>
+ parameters is EntryViewModel;
+
+ private void Add(object obj)
+ {
+ var entry = ((EntryViewModel) obj).Entry;
+ CurrentFilter.SubEntries.Add(new EntryViewModel(entry));
+ }
+
+ private void Apply(object obj)
+ {
+ TryClose(true);
+ }
+
+ private void Cancel(object obj)
+ {
+ TryClose(false);
+ }
+ }
+}
diff --git a/src/Moryx.Products.UI.Interaction/Products/Import/ImportParameterViewModel.cs b/src/Moryx.Products.UI.Interaction/Products/Import/ImportParameterViewModel.cs
deleted file mode 100644
index cf202b4b..00000000
--- a/src/Moryx.Products.UI.Interaction/Products/Import/ImportParameterViewModel.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) 2020, Phoenix Contact GmbH & Co. KG
-// Licensed under the Apache License, Version 2.0
-
-using System;
-using System.ComponentModel;
-using Moryx.Controls;
-using Entry = Moryx.Serialization.Entry;
-
-namespace Moryx.Products.UI.Interaction
-{
- ///
- /// Class holding importer parameter
- ///
- internal class ImportParameterViewModel : EntryViewModel
- {
- ///
- /// Constructor
- ///
- /// Importer parameter
- public ImportParameterViewModel(Entry property) : base(property)
- {
- Model = property;
-
- PropertyChanged += OnPropertyChanged;
- }
-
- ///
- /// Listen to property changed and check if the value was modified. In that case
- /// invoke the value changed event
- ///
- ///
- ///
- private void OnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
- {
- if (propertyChangedEventArgs.PropertyName == nameof(Value))
- ValueChanged?.Invoke(this, Model);
- }
-
- ///
- /// Base importer parameter
- ///
- internal Entry Model { get; }
-
- ///
- /// Event raised when the values was changed
- ///
- public event EventHandler ValueChanged;
- }
-}
diff --git a/src/Moryx.Products.UI.Interaction/Products/Import/ImporterViewModel.cs b/src/Moryx.Products.UI.Interaction/Products/Import/ImporterViewModel.cs
index d8f01693..3bc95e8e 100644
--- a/src/Moryx.Products.UI.Interaction/Products/Import/ImporterViewModel.cs
+++ b/src/Moryx.Products.UI.Interaction/Products/Import/ImporterViewModel.cs
@@ -1,12 +1,13 @@
// Copyright (c) 2020, Phoenix Contact GmbH & Co. KG
// Licensed under the Apache License, Version 2.0
-using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Caliburn.Micro;
using Moryx.Controls;
using Moryx.Products.UI.ProductService;
+using Moryx.Tools;
namespace Moryx.Products.UI.Interaction
{
@@ -26,31 +27,30 @@ public ImporterViewModel(ProductImporter importer, IProductServiceModel productS
private void CreateParameterViewModel(Entry parameters)
{
- if (Parameters != null)
- {
- foreach (var entry in Parameters.SubEntries.Cast())
- {
- entry.ValueChanged -= OnUpdateTriggerChanged;
- }
- }
+ Parameters = new EntryViewModel(parameters.ToSerializationEntry());
+ Parameters.SubEntries.ForEach(e => e.PropertyChanged += OnUpdateTriggerChanged);
+ }
- Parameters = new EntryViewModel(new Serialization.Entry { DisplayName = "Root" });
- foreach (var parameter in parameters.SubEntries)
- {
- var viewModel = new ImportParameterViewModel(parameter.ToSerializationEntry());
- viewModel.ValueChanged += OnUpdateTriggerChanged;
- Parameters.SubEntries.Add(viewModel);
- }
+ private void UpdateParameterViewModel(Entry parameters)
+ {
+ Parameters.SubEntries.ForEach(e => e.PropertyChanged -= OnUpdateTriggerChanged);
+ Parameters.UpdateModel(parameters.ToSerializationEntry());
+ Parameters.SubEntries.ForEach(e => e.PropertyChanged += OnUpdateTriggerChanged);
}
///
- /// Update parameters if a was modified
+ /// Update parameters if a was modified
///
- private async void OnUpdateTriggerChanged(object sender, Serialization.Entry importParameter)
+ private async void OnUpdateTriggerChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
+ var entry = sender as EntryViewModel;
+ if (sender is null || propertyChangedEventArgs.PropertyName != nameof(EntryViewModel.Value))
+ return;
+
+ Parameters.EndEdit();
var parameters = Parameters.Entry;
var updatedParameters = await _productServiceModel.UpdateImportParameters(_importer.Name, parameters.ToServiceEntry());
- CreateParameterViewModel(updatedParameters);
+ UpdateParameterViewModel(updatedParameters);
}
///
@@ -69,8 +69,13 @@ public EntryViewModel Parameters
{
if (Equals(value, _parameters))
return;
- _parameters = value;
- NotifyOfPropertyChange();
+ if (_parameters is null || value is null)
+ {
+ _parameters = value;
+ NotifyOfPropertyChange();
+ }
+ else
+ _parameters.UpdateModel(value.Entry);
}
}
diff --git a/src/Moryx.Products.UI.Interaction/Products/ProductsWorkspaceView.xaml b/src/Moryx.Products.UI.Interaction/Products/ProductsWorkspaceView.xaml
index b7f68504..cfd1e384 100644
--- a/src/Moryx.Products.UI.Interaction/Products/ProductsWorkspaceView.xaml
+++ b/src/Moryx.Products.UI.Interaction/Products/ProductsWorkspaceView.xaml
@@ -105,7 +105,7 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+
+
+
+
@@ -230,7 +258,7 @@
CancelEditCmd="{Binding CancelEditCmd}"
CancelEditContent="{x:Static properties:Strings.ProductsWorkspaceView_Cancel}"
SaveCmd="{Binding SaveCmd}"
- SaveContent="{x:Static properties:Strings.ProductsWorkspaceView_Save}"
+ SaveContent="{x:Static properties:Strings.ProductsWorkspaceView_Save}"
Visibility="{principals:VisibilityPermission Action={x:Static interaction:Permissions.CanEdit}}"/>
-
diff --git a/src/Moryx.Products.UI.Interaction/Recipes/Details/Default/DefaultRecipeDetailsViewModel.cs b/src/Moryx.Products.UI.Interaction/Recipes/Details/Default/DefaultRecipeDetailsViewModel.cs
index 1c0a30b4..d55ff2ed 100644
--- a/src/Moryx.Products.UI.Interaction/Recipes/Details/Default/DefaultRecipeDetailsViewModel.cs
+++ b/src/Moryx.Products.UI.Interaction/Recipes/Details/Default/DefaultRecipeDetailsViewModel.cs
@@ -8,6 +8,5 @@ namespace Moryx.Products.UI.Interaction
[RecipeDetailsRegistration(DetailsConstants.DefaultType)]
internal class DefaultRecipeDetailsViewModel : RecipeDetailsViewModelBase
{
-
}
}
diff --git a/src/Moryx.Products.UI/Connected Services/ProductService/Reference.cs b/src/Moryx.Products.UI/Connected Services/ProductService/Reference.cs
index 616d016c..0def1025 100644
--- a/src/Moryx.Products.UI/Connected Services/ProductService/Reference.cs
+++ b/src/Moryx.Products.UI/Connected Services/ProductService/Reference.cs
@@ -13,7 +13,7 @@ namespace Moryx.Products.UI.ProductService
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="ProductCustomization", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Products.Management.Modification")]
public partial class ProductCustomization : object
{
@@ -65,7 +65,7 @@ public Moryx.Products.UI.ProductService.RecipeDefinitionModel[] RecipeTypes
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="ProductImporter", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Products.Management.Modification")]
public partial class ProductImporter : object
{
@@ -102,7 +102,7 @@ public Moryx.Products.UI.ProductService.Entry Parameters
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="ProductDefinitionModel", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Products.Management.Modification")]
public partial class ProductDefinitionModel : object
{
@@ -113,6 +113,8 @@ public partial class ProductDefinitionModel : object
private string NameField;
+ private Moryx.Products.UI.ProductService.Entry PropertiesField;
+
[System.Runtime.Serialization.DataMemberAttribute()]
public string BaseDefinition
{
@@ -151,10 +153,23 @@ public string Name
this.NameField = value;
}
}
+
+ [System.Runtime.Serialization.DataMemberAttribute()]
+ public Moryx.Products.UI.ProductService.Entry Properties
+ {
+ get
+ {
+ return this.PropertiesField;
+ }
+ set
+ {
+ this.PropertiesField = value;
+ }
+ }
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="RecipeDefinitionModel", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Products.Management.Modification")]
public partial class RecipeDefinitionModel : object
{
@@ -206,7 +221,7 @@ public string Name
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="Entry", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Serialization")]
public partial class Entry : object
{
@@ -318,7 +333,7 @@ public Moryx.Products.UI.ProductService.EntryValue Value
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="EntryValidation", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Serialization")]
public partial class EntryValidation : object
{
@@ -385,7 +400,7 @@ public string Regex
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="EntryValue", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Serialization")]
public partial class EntryValue : object
{
@@ -481,7 +496,7 @@ public Moryx.Products.UI.ProductService.EntryUnitType UnitType
}
}
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="EntryValueType", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Serialization")]
public enum EntryValueType : int
{
@@ -535,7 +550,7 @@ public enum EntryValueType : int
Stream = 15,
}
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="EntryUnitType", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Serialization")]
public enum EntryUnitType : int
{
@@ -554,7 +569,7 @@ public enum EntryUnitType : int
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="ProductQuery", Namespace="http://schemas.datacontract.org/2004/07/Moryx.AbstractionLayer.Products")]
public partial class ProductQuery : object
{
@@ -567,6 +582,8 @@ public partial class ProductQuery : object
private string NameField;
+ private Moryx.Products.UI.ProductService.PropertyFilter[] PropertyFiltersField;
+
private Moryx.Products.UI.ProductService.RecipeFilter RecipeFilterField;
private short RevisionField;
@@ -629,6 +646,19 @@ public string Name
}
}
+ [System.Runtime.Serialization.DataMemberAttribute()]
+ public Moryx.Products.UI.ProductService.PropertyFilter[] PropertyFilters
+ {
+ get
+ {
+ return this.PropertyFiltersField;
+ }
+ set
+ {
+ this.PropertyFiltersField = value;
+ }
+ }
+
[System.Runtime.Serialization.DataMemberAttribute()]
public Moryx.Products.UI.ProductService.RecipeFilter RecipeFilter
{
@@ -695,7 +725,44 @@ public string Type
}
}
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.Diagnostics.DebuggerStepThroughAttribute()]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
+ [System.Runtime.Serialization.DataContractAttribute(Name="PropertyFilter", Namespace="http://schemas.datacontract.org/2004/07/Moryx.AbstractionLayer.Products")]
+ public partial class PropertyFilter : object
+ {
+
+ private Moryx.Products.UI.ProductService.Entry EntryField;
+
+ private Moryx.Products.UI.ProductService.PropertyFilterOperator OperatorField;
+
+ [System.Runtime.Serialization.DataMemberAttribute()]
+ public Moryx.Products.UI.ProductService.Entry Entry
+ {
+ get
+ {
+ return this.EntryField;
+ }
+ set
+ {
+ this.EntryField = value;
+ }
+ }
+
+ [System.Runtime.Serialization.DataMemberAttribute()]
+ public Moryx.Products.UI.ProductService.PropertyFilterOperator Operator
+ {
+ get
+ {
+ return this.OperatorField;
+ }
+ set
+ {
+ this.OperatorField = value;
+ }
+ }
+ }
+
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="RecipeFilter", Namespace="http://schemas.datacontract.org/2004/07/Moryx.AbstractionLayer.Products")]
public enum RecipeFilter : int
{
@@ -710,7 +777,7 @@ public enum RecipeFilter : int
WithoutRecipes = 2,
}
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="RevisionFilter", Namespace="http://schemas.datacontract.org/2004/07/Moryx.AbstractionLayer.Products")]
public enum RevisionFilter : int
{
@@ -725,7 +792,7 @@ public enum RevisionFilter : int
Specific = 2,
}
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="Selector", Namespace="http://schemas.datacontract.org/2004/07/Moryx.AbstractionLayer.Products")]
public enum Selector : int
{
@@ -740,12 +807,29 @@ public enum Selector : int
Parts = 2,
}
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
+ [System.Runtime.Serialization.DataContractAttribute(Name="PropertyFilterOperator", Namespace="http://schemas.datacontract.org/2004/07/Moryx.AbstractionLayer.Products")]
+ public enum PropertyFilterOperator : int
+ {
+
+ [System.Runtime.Serialization.EnumMemberAttribute()]
+ Equals = 0,
+
+ [System.Runtime.Serialization.EnumMemberAttribute()]
+ GreaterThen = 1,
+
+ [System.Runtime.Serialization.EnumMemberAttribute()]
+ LessThen = 2,
+ }
+
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="ProductModel", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Products.Management.Modification")]
public partial class ProductModel : object
{
+ private Moryx.Products.UI.ProductService.ProductFileModel[] FileModelsField;
+
private Moryx.Products.UI.ProductService.ProductFile[] FilesField;
private long IdField;
@@ -766,6 +850,19 @@ public partial class ProductModel : object
private string TypeField;
+ [System.Runtime.Serialization.DataMemberAttribute()]
+ public Moryx.Products.UI.ProductService.ProductFileModel[] FileModels
+ {
+ get
+ {
+ return this.FileModelsField;
+ }
+ set
+ {
+ this.FileModelsField = value;
+ }
+ }
+
[System.Runtime.Serialization.DataMemberAttribute()]
public Moryx.Products.UI.ProductService.ProductFile[] Files
{
@@ -897,6 +994,88 @@ public string Type
}
}
+ [System.Diagnostics.DebuggerStepThroughAttribute()]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.Runtime.Serialization.DataContractAttribute(Name="ProductFileModel", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Products.Management.Modification")]
+ public partial class ProductFileModel : object
+ {
+
+ private string FileHashField;
+
+ private string FileNameField;
+
+ private string FilePathField;
+
+ private string MimeTypeField;
+
+ private string PropertyNameField;
+
+ [System.Runtime.Serialization.DataMemberAttribute()]
+ public string FileHash
+ {
+ get
+ {
+ return this.FileHashField;
+ }
+ set
+ {
+ this.FileHashField = value;
+ }
+ }
+
+ [System.Runtime.Serialization.DataMemberAttribute()]
+ public string FileName
+ {
+ get
+ {
+ return this.FileNameField;
+ }
+ set
+ {
+ this.FileNameField = value;
+ }
+ }
+
+ [System.Runtime.Serialization.DataMemberAttribute()]
+ public string FilePath
+ {
+ get
+ {
+ return this.FilePathField;
+ }
+ set
+ {
+ this.FilePathField = value;
+ }
+ }
+
+ [System.Runtime.Serialization.DataMemberAttribute()]
+ public string MimeType
+ {
+ get
+ {
+ return this.MimeTypeField;
+ }
+ set
+ {
+ this.MimeTypeField = value;
+ }
+ }
+
+ [System.Runtime.Serialization.DataMemberAttribute()]
+ public string PropertyName
+ {
+ get
+ {
+ return this.PropertyNameField;
+ }
+ set
+ {
+ this.PropertyNameField = value;
+ }
+ }
+ }
+
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
[System.Runtime.Serialization.DataContractAttribute(Name="ProductFile", Namespace="http://schemas.datacontract.org/2004/07/Moryx.AbstractionLayer.Products")]
@@ -965,7 +1144,7 @@ public string Name
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="PartConnector", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Products.Management.Modification")]
public partial class PartConnector : object
{
@@ -1062,7 +1241,7 @@ public string Type
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="RecipeModel", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Products.Management.Modification")]
public partial class RecipeModel : object
{
@@ -1071,6 +1250,8 @@ public partial class RecipeModel : object
private long IdField;
+ private bool IsCloneField;
+
private string NameField;
private Moryx.Products.UI.ProductService.Entry PropertiesField;
@@ -1109,6 +1290,19 @@ public long Id
}
}
+ [System.Runtime.Serialization.DataMemberAttribute()]
+ public bool IsClone
+ {
+ get
+ {
+ return this.IsCloneField;
+ }
+ set
+ {
+ this.IsCloneField = value;
+ }
+ }
+
[System.Runtime.Serialization.DataMemberAttribute()]
public string Name
{
@@ -1188,7 +1382,7 @@ public long WorkplanId
}
}
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="ProductState", Namespace="http://schemas.datacontract.org/2004/07/Moryx.AbstractionLayer.Products")]
public enum ProductState : int
{
@@ -1204,7 +1398,7 @@ public enum ProductState : int
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="PartModel", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Products.Management.Modification")]
public partial class PartModel : object
{
@@ -1255,7 +1449,7 @@ public Moryx.Products.UI.ProductService.Entry Properties
}
}
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="RecipeClassificationModel", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Products.Management.Modification")]
public enum RecipeClassificationModel : int
{
@@ -1276,7 +1470,7 @@ public enum RecipeClassificationModel : int
Part = 4,
}
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="RecipeState", Namespace="http://schemas.datacontract.org/2004/07/Moryx.AbstractionLayer.Recipes")]
public enum RecipeState : int
{
@@ -1292,7 +1486,7 @@ public enum RecipeState : int
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="DuplicateProductResponse", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Products.Management.Modification")]
public partial class DuplicateProductResponse : object
{
@@ -1344,8 +1538,8 @@ public bool InvalidSource
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
- [System.Runtime.Serialization.DataContractAttribute(Name="ImportStateModel", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Products.Management")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
+ [System.Runtime.Serialization.DataContractAttribute(Name="ImportStateModel", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Products.Management.Modification")]
public partial class ImportStateModel : object
{
@@ -1396,7 +1590,7 @@ public System.Guid Session
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="WorkplanModel", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Products.Management.Modification")]
public partial class WorkplanModel : object
{
@@ -1462,7 +1656,7 @@ public int Version
}
}
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Runtime.Serialization.DataContractAttribute(Name="WorkplanState", Namespace="http://schemas.datacontract.org/2004/07/Moryx.Workflows")]
public enum WorkplanState : int
{
@@ -1477,7 +1671,7 @@ public enum WorkplanState : int
Revoked = 2,
}
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="Moryx.Products.UI.ProductService.IProductInteraction")]
public interface IProductInteraction
{
@@ -1534,13 +1728,13 @@ public interface IProductInteraction
System.Threading.Tasks.Task GetRecipeProviderNameAsync();
}
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
public interface IProductInteractionChannel : Moryx.Products.UI.ProductService.IProductInteraction, System.ServiceModel.IClientChannel
{
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
public partial class ProductInteractionClient : System.ServiceModel.ClientBase, Moryx.Products.UI.ProductService.IProductInteraction
{
diff --git a/src/Moryx.Products.UI/Connected Services/ProductService/ConnectedService.json b/src/Moryx.Products.UI/Connected Services/ProductService/dotnet-svcutil.params.json
similarity index 70%
rename from src/Moryx.Products.UI/Connected Services/ProductService/ConnectedService.json
rename to src/Moryx.Products.UI/Connected Services/ProductService/dotnet-svcutil.params.json
index 36bcc430..30552698 100644
--- a/src/Moryx.Products.UI/Connected Services/ProductService/ConnectedService.json
+++ b/src/Moryx.Products.UI/Connected Services/ProductService/dotnet-svcutil.params.json
@@ -1,7 +1,7 @@
-{
- "ProviderId": "Microsoft.VisualStudio.ConnectedService.Wcf",
- "Version": "15.0.40203.910",
- "ExtendedData": {
+{
+ "providerId": "Microsoft.Tools.ServiceModel.Svcutil",
+ "version": "2.0.1",
+ "options": {
"inputs": [
"http://localhost/metadata/products"
],
@@ -12,6 +12,7 @@
"namespaceMappings": [
"*, Moryx.Products.UI.ProductService"
],
+ "outputFile": "Reference.cs",
"targetFramework": "net45",
"typeReuseMode": "None"
}
diff --git a/src/Moryx.Products.UI/IProductServiceModel.cs b/src/Moryx.Products.UI/IProductServiceModel.cs
index 81c42d03..088d33ca 100644
--- a/src/Moryx.Products.UI/IProductServiceModel.cs
+++ b/src/Moryx.Products.UI/IProductServiceModel.cs
@@ -66,6 +66,7 @@ public interface IProductServiceModel : IHttpServiceConnector
/// State of the import
Task Import(string importerName, Entry parameters);
+ // TODO: AL6 Update ServiceReference and change to string
///
/// Returns the current importer state
///
diff --git a/src/Moryx.Products.UI/ProductServiceModel.cs b/src/Moryx.Products.UI/ProductServiceModel.cs
index 06d5d399..4ffa6e04 100644
--- a/src/Moryx.Products.UI/ProductServiceModel.cs
+++ b/src/Moryx.Products.UI/ProductServiceModel.cs
@@ -22,7 +22,7 @@ public class ProductServiceModel : WebHttpServiceConnectorBase, IProductServiceM
public override string ServiceName => nameof(IProductInteraction);
///
- protected override string ClientVersion => "5.0.0";
+ protected override string ClientVersion => "5.1.0";
///
public ProductServiceModel(IWcfClientFactory clientFactory, IModuleLogger logger)
diff --git a/src/Moryx.Products.UI/ViewModels/PartConnectorViewModel.cs b/src/Moryx.Products.UI/ViewModels/PartConnectorViewModel.cs
index 60d850d0..1e33cf04 100644
--- a/src/Moryx.Products.UI/ViewModels/PartConnectorViewModel.cs
+++ b/src/Moryx.Products.UI/ViewModels/PartConnectorViewModel.cs
@@ -89,17 +89,20 @@ public void CopyToModel()
///
public void BeginEdit()
{
+ PartLinks.BeginEdit();
}
///
public void EndEdit()
{
+ PartLinks.EndEdit();
CopyToModel();
}
///
public void CancelEdit()
{
+ PartLinks.CancelEdit();
CopyFromModel();
}
diff --git a/src/Moryx.Products.UI/ViewModels/PartLinkViewModel.cs b/src/Moryx.Products.UI/ViewModels/PartLinkViewModel.cs
index c27e5bba..2683caee 100644
--- a/src/Moryx.Products.UI/ViewModels/PartLinkViewModel.cs
+++ b/src/Moryx.Products.UI/ViewModels/PartLinkViewModel.cs
@@ -5,13 +5,14 @@
using Moryx.AbstractionLayer.UI;
using Moryx.Controls;
using Moryx.Products.UI.ProductService;
+using System.ComponentModel;
namespace Moryx.Products.UI
{
///
/// View model that represents a single part connector
///
- public class PartLinkViewModel : PropertyChangedBase, IIdentifiableObject
+ public class PartLinkViewModel : PropertyChangedBase, IIdentifiableObject, IEditableObject
{
private EntryViewModel _properties;
@@ -38,8 +39,13 @@ public EntryViewModel Properties
get { return _properties; }
private set
{
- _properties = value;
- NotifyOfPropertyChange();
+ if (_properties is null)
+ {
+ _properties = value;
+ NotifyOfPropertyChange();
+ }
+ else
+ _properties.UpdateModel(value.Entry);
}
}
@@ -84,5 +90,22 @@ public void CopyToModel()
{
Model.Properties = Properties.Entry.ToServiceEntry();
}
+
+ public void BeginEdit()
+ {
+ Properties.BeginEdit();
+ }
+
+ public void EndEdit()
+ {
+ Properties.EndEdit();
+ CopyToModel();
+ }
+
+ public void CancelEdit()
+ {
+ Properties.CancelEdit();
+ CopyFromModel();
+ }
}
}
diff --git a/src/Moryx.Products.UI/ViewModels/ProductDefinitionViewModel.cs b/src/Moryx.Products.UI/ViewModels/ProductDefinitionViewModel.cs
new file mode 100644
index 00000000..24c2d298
--- /dev/null
+++ b/src/Moryx.Products.UI/ViewModels/ProductDefinitionViewModel.cs
@@ -0,0 +1,47 @@
+using System.Collections.Generic;
+using Caliburn.Micro;
+using Moryx.Controls;
+using Moryx.Products.UI.ProductService;
+using Entry = Moryx.Serialization.Entry;
+
+namespace Moryx.Products.UI
+{
+ public class ProductDefinitionViewModel : PropertyChangedBase
+ {
+ private EntryViewModel _properties;
+
+ ///
+ /// Underlying model
+ ///
+ public ProductDefinitionModel Model { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ProductDefinitionViewModel(ProductDefinitionModel model)
+ {
+ Model = model;
+ Properties = Model.Properties != null
+ ? new EntryViewModel(Model.Properties.ToSerializationEntry())
+ : new EntryViewModel(new List());
+ }
+
+ ///
+ /// Gets the display name of the product definition.
+ ///
+ public string DisplayName => Model.DisplayName;
+
+ ///
+ /// Entry view model of the product properties
+ ///
+ public EntryViewModel Properties
+ {
+ get => _properties;
+ private set
+ {
+ _properties = value;
+ NotifyOfPropertyChange();
+ }
+ }
+ }
+}
diff --git a/src/Moryx.Products.UI.Interaction/Products/ProductQueryViewModel.cs b/src/Moryx.Products.UI/ViewModels/ProductQueryViewModel.cs
similarity index 56%
rename from src/Moryx.Products.UI.Interaction/Products/ProductQueryViewModel.cs
rename to src/Moryx.Products.UI/ViewModels/ProductQueryViewModel.cs
index ae75b05a..0d3cda71 100644
--- a/src/Moryx.Products.UI.Interaction/Products/ProductQueryViewModel.cs
+++ b/src/Moryx.Products.UI/ViewModels/ProductQueryViewModel.cs
@@ -1,12 +1,14 @@
// Copyright (c) 2020, Phoenix Contact GmbH & Co. KG
// Licensed under the Apache License, Version 2.0
+using System.Collections.Generic;
+using System.Linq;
using Caliburn.Micro;
using Moryx.Products.UI.ProductService;
-namespace Moryx.Products.UI.Interaction
+namespace Moryx.Products.UI
{
- internal class ProductQueryViewModel : PropertyChangedBase
+ public class ProductQueryViewModel : PropertyChangedBase
{
private RevisionFilter _revisionFilter;
private Selector _selector;
@@ -14,10 +16,12 @@ internal class ProductQueryViewModel : PropertyChangedBase
private string _identifier;
private short _revision;
private string _name;
+ private ProductDefinitionViewModel _type;
+ private List _propertyFilters;
public RevisionFilter RevisionFilter
{
- get { return _revisionFilter; }
+ get => _revisionFilter;
set
{
_revisionFilter = value;
@@ -27,7 +31,7 @@ public RevisionFilter RevisionFilter
public Selector Selector
{
- get { return _selector; }
+ get => _selector;
set
{
_selector = value;
@@ -37,7 +41,7 @@ public Selector Selector
public RecipeFilter RecipeFilter
{
- get { return _recipeFilter; }
+ get => _recipeFilter;
set
{
_recipeFilter = value;
@@ -47,7 +51,7 @@ public RecipeFilter RecipeFilter
public string Name
{
- get { return _name; }
+ get => _name;
set
{
_name = value;
@@ -57,7 +61,7 @@ public string Name
public string Identifier
{
- get { return _identifier; }
+ get => _identifier;
set
{
_identifier = value;
@@ -67,7 +71,7 @@ public string Identifier
public short Revision
{
- get { return _revision; }
+ get => _revision;
set
{
_revision = value;
@@ -75,6 +79,30 @@ public short Revision
}
}
+ public ProductDefinitionViewModel Type
+ {
+ get => _type;
+ set
+ {
+ // Clear property filters if type was changed
+ if (value == null || (_type != null && value.Model.Name != _type.Model.Name))
+ PropertyFilters = null;
+
+ _type = value;
+ NotifyOfPropertyChange();
+ }
+ }
+
+ public List PropertyFilters
+ {
+ get => _propertyFilters;
+ set
+ {
+ _propertyFilters = value;
+ NotifyOfPropertyChange();
+ }
+ }
+
public ProductQuery GetQuery()
{
var query = new ProductQuery
@@ -82,9 +110,19 @@ public ProductQuery GetQuery()
RevisionFilter = RevisionFilter,
RecipeFilter = RecipeFilter,
Selector = Selector,
- Revision = Revision
+ Revision = Revision,
+ Type = Type?.Model.Name,
};
+ if (PropertyFilters != null)
+ {
+ query.PropertyFilters = PropertyFilters.Select(pf => new PropertyFilter
+ {
+ Entry = pf.Property.ToServiceEntry(),
+ Operator = pf.Operator
+ }).ToArray();
+ }
+
if (!string.IsNullOrWhiteSpace(Identifier))
query.Identifier = Identifier;
diff --git a/src/Moryx.Products.UI/ViewModels/ProductViewModel.cs b/src/Moryx.Products.UI/ViewModels/ProductViewModel.cs
index 35cab828..ecdfae1e 100644
--- a/src/Moryx.Products.UI/ViewModels/ProductViewModel.cs
+++ b/src/Moryx.Products.UI/ViewModels/ProductViewModel.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2020, Phoenix Contact GmbH & Co. KG
// Licensed under the Apache License, Version 2.0
-using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
@@ -10,7 +9,6 @@
using Moryx.Controls;
using Moryx.Products.UI.ProductService;
using Moryx.Tools;
-using Entry = Moryx.Serialization.Entry;
namespace Moryx.Products.UI
{
@@ -48,7 +46,7 @@ private void CopyFromModel()
State = Model.State;
Properties = Model.Properties != null
? new EntryViewModel(Model.Properties.ToSerializationEntry())
- : new EntryViewModel(new List());
+ : new EntryViewModel();
if (Model.Parts != null)
Parts.MergeCollection(Model.Parts, _partMergeStrategy);
@@ -136,8 +134,13 @@ public EntryViewModel Properties
get { return _properties; }
private set
{
- _properties = value;
- NotifyOfPropertyChange();
+ if (_properties is null)
+ {
+ _properties = value;
+ NotifyOfPropertyChange();
+ }
+ else
+ _properties.UpdateModel(value.Entry);
}
}
@@ -145,12 +148,16 @@ private set
public void BeginEdit()
{
Parts.BeginEdit();
+ Recipes.BeginEdit();
+ Properties.BeginEdit();
}
///
public void EndEdit()
{
Parts.EndEdit();
+ Recipes.EndEdit();
+ Properties.EndEdit();
CopyToModel();
}
@@ -158,6 +165,8 @@ public void EndEdit()
public void CancelEdit()
{
Parts.CancelEdit();
+ Recipes.CancelEdit();
+ Properties.CancelEdit();
CopyFromModel();
}
diff --git a/src/Moryx.Products.UI/ViewModels/PropertyFilterViewModel.cs b/src/Moryx.Products.UI/ViewModels/PropertyFilterViewModel.cs
new file mode 100644
index 00000000..97bca6a4
--- /dev/null
+++ b/src/Moryx.Products.UI/ViewModels/PropertyFilterViewModel.cs
@@ -0,0 +1,17 @@
+using Caliburn.Micro;
+using Moryx.Products.UI.ProductService;
+
+namespace Moryx.Products.UI
+{
+ public class PropertyFilterViewModel : PropertyChangedBase
+ {
+ public Serialization.Entry Property { get; set; }
+
+ public PropertyFilterOperator Operator { get; set; }
+
+ public PropertyFilterViewModel(Serialization.Entry model)
+ {
+ Property = model;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Moryx.Products.UI/ViewModels/RecipeViewModel.cs b/src/Moryx.Products.UI/ViewModels/RecipeViewModel.cs
index 71baa120..e97399c1 100644
--- a/src/Moryx.Products.UI/ViewModels/RecipeViewModel.cs
+++ b/src/Moryx.Products.UI/ViewModels/RecipeViewModel.cs
@@ -19,6 +19,7 @@ public class RecipeViewModel : PropertyChangedBase, IEditableObject, IIdentifiab
private string _type;
private int _revision;
private RecipeClassificationModel _classification;
+ private bool _isClone;
private WorkplanViewModel _workplan;
private EntryViewModel _properties;
@@ -55,8 +56,13 @@ public EntryViewModel Properties
get { return _properties; }
private set
{
- _properties = value;
- NotifyOfPropertyChange(nameof(Properties));
+ if (_properties is null)
+ {
+ _properties = value;
+ NotifyOfPropertyChange();
+ }
+ else
+ _properties.UpdateModel(value.Entry);
}
}
@@ -104,6 +110,19 @@ public RecipeClassificationModel Classification
}
}
+ ///
+ /// Gets whether this recipe is a clone
+ ///
+ public bool IsClone
+ {
+ get { return _isClone; }
+ set
+ {
+ _isClone = value;
+ NotifyOfPropertyChange(nameof(IsClone));
+ }
+ }
+
///
/// Gets or sets the revision of the recipe
///
@@ -138,6 +157,7 @@ protected void CopyFromModel()
Name = Model.Name;
Type = Model.Type;
Classification = Model.Classification;
+ IsClone = Model.IsClone;
Revision = Model.Revision;
Properties = new EntryViewModel(Model.Properties.ToSerializationEntry());
}
@@ -150,6 +170,7 @@ protected void CopyToModel()
Model.Name = _name;
Model.Type = _type;
Model.Classification = _classification;
+ Model.IsClone = _isClone;
Model.Revision = _revision;
Model.Properties = Properties.Entry.ToServiceEntry();
Model.WorkplanId = Workplan?.Id ?? 0;
@@ -158,17 +179,20 @@ protected void CopyToModel()
///
public virtual void BeginEdit()
{
+ Properties.BeginEdit();
}
///
public virtual void EndEdit()
{
+ Properties.EndEdit();
CopyToModel();
}
///
public virtual void CancelEdit()
{
+ Properties.CancelEdit();
CopyFromModel();
}
}
diff --git a/src/Moryx.Products.UI/update-service.ps1 b/src/Moryx.Products.UI/update-service.ps1
new file mode 100644
index 00000000..c6d6fc98
--- /dev/null
+++ b/src/Moryx.Products.UI/update-service.ps1
@@ -0,0 +1 @@
+dotnet-svcutil --projectFile .\Moryx.Products.UI.csproj --update
\ No newline at end of file
diff --git a/src/Moryx.Resources.Interaction/Moryx.Resources.Interaction.csproj b/src/Moryx.Resources.Interaction/Moryx.Resources.Interaction.csproj
index e9996d12..0812e3e5 100644
--- a/src/Moryx.Resources.Interaction/Moryx.Resources.Interaction.csproj
+++ b/src/Moryx.Resources.Interaction/Moryx.Resources.Interaction.csproj
@@ -31,4 +31,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Moryx.Resources.Management/Facades/ResourceManagementFacade.cs b/src/Moryx.Resources.Management/Facades/ResourceManagementFacade.cs
index 37696495..e639d620 100644
--- a/src/Moryx.Resources.Management/Facades/ResourceManagementFacade.cs
+++ b/src/Moryx.Resources.Management/Facades/ResourceManagementFacade.cs
@@ -9,7 +9,7 @@
namespace Moryx.Resources.Management
{
- internal class ResourceManagementFacade : IResourceManagement, IFacadeControl
+ internal class ResourceManagementFacade : IResourceManagement, IFacadeControl, IResourceModification
{
#region Dependency Injection
@@ -110,6 +110,52 @@ public IEnumerable GetResources(Func pred
return ResourceGraph.GetResources(predicate).Proxify(TypeController);
}
+ public long Create(Type resourceType, Action initializer)
+ {
+ ValidateHealthState();
+
+ var resource = TypeController.Create(resourceType.ResourceType());
+ initializer(resource);
+ ResourceGraph.Save(resource);
+ return resource.Id;
+ }
+
+ public TResult Read(long id, Func accessor)
+ {
+ ValidateHealthState();
+
+ var resource = ResourceGraph.Get(id);
+ if (resource == null)
+ throw new KeyNotFoundException($"No resource with Id {id} found!");
+
+ var result = accessor(resource);
+ return result;
+ }
+
+ public void Modify(long id, Func modifier)
+ {
+ ValidateHealthState();
+
+ var resource = ResourceGraph.Get(id);
+ if (resource == null)
+ throw new KeyNotFoundException($"No resource with Id {id} found!");
+
+ var result = modifier(resource);
+ if (result)
+ ResourceGraph.Save(resource);
+ }
+
+ public bool Delete(long id)
+ {
+ ValidateHealthState();
+
+ var resource = ResourceGraph.Get(id);
+ if (resource == null)
+ return false;
+
+ return ResourceGraph.Destroy(resource);
+ }
+
///
public event EventHandler ResourceAdded;
diff --git a/src/Moryx.Resources.UI.Interaction/Details/Aspects/Methods/ResourceMethodsAspectViewModel.cs b/src/Moryx.Resources.UI.Interaction/Details/Aspects/Methods/ResourceMethodsAspectViewModel.cs
index f8b8b19d..227a87b7 100644
--- a/src/Moryx.Resources.UI.Interaction/Details/Aspects/Methods/ResourceMethodsAspectViewModel.cs
+++ b/src/Moryx.Resources.UI.Interaction/Details/Aspects/Methods/ResourceMethodsAspectViewModel.cs
@@ -50,8 +50,13 @@ public EntryViewModel MethodInvocationResult
get { return _methodInvocationResult; }
set
{
- _methodInvocationResult = value;
- NotifyOfPropertyChange();
+ if (_methodInvocationResult is null || value is null)
+ {
+ _methodInvocationResult = value;
+ NotifyOfPropertyChange();
+ }
+ else
+ _methodInvocationResult.UpdateModel(value.Entry);
}
}
@@ -87,5 +92,29 @@ private async Task InvokeMethod(object parameters)
else
MethodInvocationResult = new EntryViewModel(new List { resultEntry });
}
+
+ ///
+ public override void BeginEdit()
+ {
+ SelectedMethod?.BeginEdit();
+ MethodInvocationResult?.BeginEdit();
+ base.BeginEdit();
+ }
+
+ ///
+ public override void EndEdit()
+ {
+ SelectedMethod?.EndEdit();
+ MethodInvocationResult?.EndEdit();
+ base.EndEdit();
+ }
+
+ ///
+ public override void CancelEdit()
+ {
+ SelectedMethod?.CancelEdit();
+ MethodInvocationResult?.CancelEdit();
+ base.CancelEdit();
+ }
}
}
diff --git a/src/Moryx.Resources.UI/Connected Services/ResourceService/ConnectedService.json b/src/Moryx.Resources.UI/Connected Services/ResourceService/dotnet-svcutil.params.json
similarity index 70%
rename from src/Moryx.Resources.UI/Connected Services/ResourceService/ConnectedService.json
rename to src/Moryx.Resources.UI/Connected Services/ResourceService/dotnet-svcutil.params.json
index 33dc6209..8d48fbdc 100644
--- a/src/Moryx.Resources.UI/Connected Services/ResourceService/ConnectedService.json
+++ b/src/Moryx.Resources.UI/Connected Services/ResourceService/dotnet-svcutil.params.json
@@ -1,7 +1,7 @@
-{
- "ProviderId": "Microsoft.VisualStudio.ConnectedService.Wcf",
- "Version": "15.0.40203.910",
- "ExtendedData": {
+{
+ "providerId": "Microsoft.Tools.ServiceModel.Svcutil",
+ "version": "2.0.1",
+ "options": {
"inputs": [
"http://localhost/metadata/resources"
],
@@ -12,6 +12,7 @@
"namespaceMappings": [
"*, Moryx.Resources.UI.ResourceService"
],
+ "outputFile": "Reference.cs",
"targetFramework": "net45",
"typeReuseMode": "None"
}
diff --git a/src/Moryx.Resources.UI/ConstructorViewModel.cs b/src/Moryx.Resources.UI/ConstructorViewModel.cs
index 159a03ae..ddb48da8 100644
--- a/src/Moryx.Resources.UI/ConstructorViewModel.cs
+++ b/src/Moryx.Resources.UI/ConstructorViewModel.cs
@@ -13,6 +13,7 @@ namespace Moryx.Resources.UI
public class ConstructorViewModel : PropertyChangedBase
{
private bool _isSelected;
+ private EntryViewModel _parameters;
///
/// Underlying model for the constructor
@@ -27,7 +28,17 @@ public class ConstructorViewModel : PropertyChangedBase
///
/// View model of the parameters for the config editor
///
- public EntryViewModel Parameters { get; private set; }
+ public EntryViewModel Parameters
+ {
+ get { return _parameters; }
+ private set
+ {
+ if (_parameters is null)
+ _parameters = value;
+ else
+ _parameters.UpdateModel(value.Entry);
+ }
+ }
///
/// Flag if this constructor was selected
diff --git a/src/Moryx.Resources.UI/ResourceMethodViewModel.cs b/src/Moryx.Resources.UI/ResourceMethodViewModel.cs
index 90a0172f..afdb9daf 100644
--- a/src/Moryx.Resources.UI/ResourceMethodViewModel.cs
+++ b/src/Moryx.Resources.UI/ResourceMethodViewModel.cs
@@ -4,14 +4,17 @@
using Caliburn.Micro;
using Moryx.Controls;
using Moryx.Resources.UI.ResourceService;
+using System.ComponentModel;
namespace Moryx.Resources.UI
{
///
/// View model that represents an editorVisible of a resource
///
- public class ResourceMethodViewModel : PropertyChangedBase
+ public class ResourceMethodViewModel : PropertyChangedBase, IEditableObject
{
+ private EntryViewModel _parameters;
+
///
/// Underlying model
///
@@ -53,6 +56,33 @@ public void CopyToModel()
///
/// Parameters which are necessary for the method call
///
- public EntryViewModel Parameters { get; private set; }
+ public EntryViewModel Parameters
+ {
+ get { return _parameters; }
+ private set
+ {
+ if (_parameters is null)
+ _parameters = value;
+ else
+ _parameters.UpdateModel(value.Entry);
+ }
+ }
+
+ public void BeginEdit()
+ {
+ Parameters.BeginEdit();
+ }
+
+ public void EndEdit()
+ {
+ Parameters.EndEdit();
+ CopyToModel();
+ }
+
+ public void CancelEdit()
+ {
+ Parameters.CancelEdit();
+ CopyFromModel(Model);
+ }
}
}
diff --git a/src/Moryx.Resources.UI/ResourceViewModel.cs b/src/Moryx.Resources.UI/ResourceViewModel.cs
index cd34f559..1c5f83a4 100644
--- a/src/Moryx.Resources.UI/ResourceViewModel.cs
+++ b/src/Moryx.Resources.UI/ResourceViewModel.cs
@@ -79,6 +79,7 @@ public string Description
}
}
+ // TODO: AL 6 Remove public setter
///
/// Properties of this resource
///
@@ -87,8 +88,13 @@ public EntryViewModel Properties
get { return _properties; }
set
{
- _properties = value;
- NotifyOfPropertyChange();
+ if (_properties is null || value is null)
+ {
+ _properties = value;
+ NotifyOfPropertyChange();
+ }
+ else
+ _properties.UpdateModel(value.Entry);
}
}
@@ -117,19 +123,25 @@ public ResourceMethodViewModel[] Methods
public void BeginEdit()
{
References.BeginEdit();
+ Properties.BeginEdit();
+ Methods.BeginEdit();
}
///
public void EndEdit()
{
+ Properties.EndEdit();
References.EndEdit();
+ Methods.EndEdit();
CopyToModel();
}
///
public void CancelEdit()
{
+ Properties.CancelEdit();
References.CancelEdit();
+ Methods.CancelEdit();
CopyFromModel();
}
@@ -164,7 +176,7 @@ private void CopyFromModel()
Properties = Model.Properties != null
? new EntryViewModel(Model.Properties.ToSerializationEntry())
- : new EntryViewModel(new List());
+ : new EntryViewModel();
Methods = Model.Methods != null
? Model.Methods.Select(m => new ResourceMethodViewModel(m)).ToArray()
diff --git a/src/Moryx.Resources.UI/update-service.ps1 b/src/Moryx.Resources.UI/update-service.ps1
new file mode 100644
index 00000000..ce2da5e5
--- /dev/null
+++ b/src/Moryx.Resources.UI/update-service.ps1
@@ -0,0 +1 @@
+dotnet-svcutil --projectFile .\Moryx.Resources.UI.csproj --update
\ No newline at end of file
diff --git a/src/Tests/Moryx.Products.IntegrationTests/ProductStorageTests.cs b/src/Tests/Moryx.Products.IntegrationTests/ProductStorageTests.cs
index 8c869718..1423307f 100644
--- a/src/Tests/Moryx.Products.IntegrationTests/ProductStorageTests.cs
+++ b/src/Tests/Moryx.Products.IntegrationTests/ProductStorageTests.cs
@@ -18,6 +18,7 @@
using Moryx.AbstractionLayer.Identity;
using Moryx.Model.InMemory;
using Moryx.Model.Repositories;
+using Moryx.Serialization;
using NUnit.Framework;
namespace Moryx.Products.IntegrationTests
@@ -104,6 +105,20 @@ public void PrepareStorage()
JsonColumn = nameof(IGenericColumns.Text8)
},
new GenericTypeConfiguration
+ {
+ TargetType = nameof(DisplayWatchfaceType),
+ PropertyConfigs = new List
+ {
+ new PropertyMapperConfig
+ {
+ PropertyName = nameof(DisplayWatchfaceType.Resolution),
+ Column = nameof(IGenericColumns.Integer1),
+ PluginName = nameof(IntegerColumnMapper)
+ }
+ },
+ JsonColumn = nameof(IGenericColumns.Text8)
+ },
+ new GenericTypeConfiguration
{
TargetType = nameof(NeedleType),
PropertyConfigs = new List(),
@@ -615,32 +630,27 @@ public void LoadLatestRevision(bool exists)
public void GetProductByQuery()
{
// Arrange
- var productMgr = new ProductManager
- {
- Factory = _factory,
- Storage = _storage
- };
var watch = SetupProduct("Jaques Lemans", string.Empty);
_storage.SaveType(watch);
watch = SetupProduct("Jaques Lemans", string.Empty, 17);
_storage.SaveType(watch);
// Act
- var all = productMgr.LoadTypes(new ProductQuery());
- var latestRevision = productMgr.LoadTypes(new ProductQuery { RevisionFilter = RevisionFilter.Latest });
- var byType = productMgr.LoadTypes(new ProductQuery { Type = nameof(NeedleType) });
- var allRevision = productMgr.LoadTypes(new ProductQuery { Identifier = WatchMaterial });
- var latestByType = productMgr.LoadTypes(new ProductQuery
+ var all = _storage.LoadTypes(new ProductQuery());
+ var latestRevision = _storage.LoadTypes(new ProductQuery { RevisionFilter = RevisionFilter.Latest });
+ var byType = _storage.LoadTypes(new ProductQuery { Type = nameof(NeedleType) });
+ var allRevision = _storage.LoadTypes(new ProductQuery { Identifier = WatchMaterial });
+ var latestByType = _storage.LoadTypes(new ProductQuery
{
Type = nameof(WatchType),
RevisionFilter = RevisionFilter.Latest
});
- var usages = productMgr.LoadTypes(new ProductQuery
+ var usages = _storage.LoadTypes(new ProductQuery
{
Identifier = "24",
Selector = Selector.Parent
});
- var needles = productMgr.LoadTypes(new ProductQuery
+ var needles = _storage.LoadTypes(new ProductQuery
{
Name = "needle",
RevisionFilter = RevisionFilter.Latest
@@ -655,6 +665,47 @@ public void GetProductByQuery()
Assert.GreaterOrEqual(needles.Count, 3);
}
+ [Test]
+ public void GetProductByExpression()
+ {
+ // Arrange
+ var watchface = new DisplayWatchfaceType
+ {
+ Name = "ExpressionWatchface",
+ Identity = new ProductIdentity("4742", 0),
+ Resolution = 180
+ };
+ _storage.SaveType(watchface);
+
+ // Act
+ var loaded = _storage.LoadTypes(wf => wf.Resolution == 180);
+ var loaded2 = _storage.LoadTypes(wf => wf.Resolution > 150);
+ var loaded3 = _storage.LoadTypes(new ProductQuery
+ {
+ Type = nameof(DisplayWatchfaceType),
+ PropertyFilters = new List
+ {
+ new()
+ {
+ Operator = PropertyFilterOperator.Equals,
+ Entry = new Entry
+ {
+ Identifier = nameof(DisplayWatchfaceType.Resolution),
+ Value = new EntryValue { Current = "180" }
+ }
+ }
+ }
+ });
+
+ // Assert
+ Assert.AreEqual(loaded.Count, 1);
+ Assert.AreEqual(watchface.Id, loaded[0].Id);
+ Assert.AreEqual(loaded2.Count, 1);
+ Assert.AreEqual(watchface.Id, loaded2[0].Id);
+ Assert.AreEqual(loaded3.Count, 1);
+ Assert.AreEqual(watchface.Id, loaded3[0].Id);
+ }
+
[Test]
public void ShouldReturnNoProductsForWildcardInName()
{
@@ -794,7 +845,7 @@ public void RemoveProduct(bool stillUsed)
public void SaveAndLoadInstance()
{
// Arrange
- var watch = SetupProduct("Jaques Lemans", string.Empty);
+ var watch = SetupProduct("TestWatch", string.Empty);
_storage.SaveType(watch);
// Reload from storage for partlink ids if the object exists
watch = (WatchType) _storage.LoadType(watch.Id);
@@ -828,6 +879,12 @@ public void SaveAndLoadInstance()
var byDateTime = _storage.LoadInstances(i => i.DeliveryDate < DateTime.Now);
var byBool = _storage.LoadInstances(i => i.TimeSet);
var byType = _storage.LoadInstances(i => i.Type == watch);
+ var byType2 = _storage.LoadInstances(i => i.Type.Equals(watch));
+ var byType3 = _storage.LoadInstances(i => watch.Equals(i.Type));
+ var byType4 = _storage.LoadInstances(i => i.Type.Name == "TestWatch");
+ var byType5 = _storage.LoadInstances(i => watch == i.Type);
+ identity = watch.Identity;
+ var byType6 = _storage.LoadInstances(i => i.Type.Identity == identity);
// Assert
Assert.NotNull(watchCopy);
@@ -842,6 +899,11 @@ public void SaveAndLoadInstance()
Assert.LessOrEqual(1, byDateTime.Count);
Assert.LessOrEqual(1, byBool.Count);
Assert.LessOrEqual(1, byType.Count);
+ Assert.LessOrEqual(1, byType2.Count);
+ Assert.LessOrEqual(1, byType3.Count);
+ Assert.LessOrEqual(1, byType4.Count);
+ Assert.LessOrEqual(1, byType5.Count);
+ Assert.LessOrEqual(1, byType6.Count);
}
}
}
diff --git a/src/Tests/Moryx.Products.Management.Tests/Moryx.Products.Management.Tests.csproj b/src/Tests/Moryx.Products.Management.Tests/Moryx.Products.Management.Tests.csproj
new file mode 100644
index 00000000..2204695d
--- /dev/null
+++ b/src/Tests/Moryx.Products.Management.Tests/Moryx.Products.Management.Tests.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net45
+ false
+ full
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Tests/Moryx.Products.Management.Tests/ProductConverterTests.cs b/src/Tests/Moryx.Products.Management.Tests/ProductConverterTests.cs
new file mode 100644
index 00000000..935e733c
--- /dev/null
+++ b/src/Tests/Moryx.Products.Management.Tests/ProductConverterTests.cs
@@ -0,0 +1,261 @@
+// Copyright (c) 2020, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using Moq;
+using Moryx.AbstractionLayer.Identity;
+using Moryx.AbstractionLayer.Products;
+using Moryx.AbstractionLayer.Recipes;
+using Moryx.AbstractionLayer.TestTools;
+using Moryx.Products.Management.Modification;
+using Moryx.Tools;
+using Moryx.Workflows;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Moryx.Products.Management.Tests
+{
+ [TestFixture]
+ public class ProductConverterTests
+ {
+ private Mock _productManagerMock;
+ private Mock _recipeManagementMock;
+ private Mock _workplanManagementMock;
+
+ private IProductConverter _productConverter;
+
+ [SetUp]
+ public void Setup()
+ {
+ _productManagerMock = new Mock();
+ _recipeManagementMock = new Mock();
+ _workplanManagementMock = new Mock();
+
+ _productConverter = new ProductConverter()
+ {
+ ProductManager = _productManagerMock.Object,
+ RecipeManagement = _recipeManagementMock.Object,
+ WorkplanManagement = _workplanManagementMock.Object,
+ };
+ }
+
+ #region Products
+ internal static IEnumerable ProductForwardBackwardConversionTestCaseGenerator()
+ {
+ // Used only to display tests seperately in TestExplorer
+ var testCaseCounter = 0;
+
+ // Create ProductType objects for test cases
+ var dummyProductTypes = new List()
+ {
+ new DummyProductType(),
+ new DummyProductTypeWithParts(),
+ new DummyProductTypeWithParts()
+ {
+ ProductPartLink = new DummyProductPartLink() { Id=1, Product = new DummyProductType() { Id = 2022 } },
+ ProductPartLinkEnumerable = new List(){ new DummyProductPartLink() { Id=2, Product = new DummyProductType() { Id = 2023 } } }
+ },
+ new DummyProductTypeWithFiles(),
+ new DummyProductTypeWithFiles() { FirstProductFile = new ProductFile() { Name="FirstFile" }, SecondProductFile = new ProductFile() { Name="SecondFile" }}
+ };
+ // Create Recipe objects for test cases
+ var dummyRecipeLists = new List>()
+ {
+ new List(),
+ new List() { new ProductRecipe() { Id = 0 } },
+ new List() { new ProductRecipe() { Id = 1923 } }
+ };
+
+ // Create all possible combinations of input settings for the ConvertProduct method
+ foreach (var identity in new IIdentity[] { null, new ProductIdentity("TestIdentifier", 1337) })
+ foreach (ProductState state in Enum.GetValues(typeof(ProductState)))
+ foreach (var flat in new bool[] { true, false })
+ foreach (var dummyType in dummyProductTypes)
+ foreach (var recipes in dummyRecipeLists)
+ yield return new TestCaseData(dummyType, state, identity, recipes, flat, testCaseCounter++);
+ }
+ ///
+ /// Test if the conversion to and back from a ProductModel without modification of the model
+ /// in between works without information loss.
+ ///
+ [TestCaseSource(nameof(ProductForwardBackwardConversionTestCaseGenerator))]
+ public void ForwardBackwardProductConversionWithoutInformationLoss(DummyProductType originalProductType,
+ ProductState state, IIdentity identity, IReadOnlyList recipes, bool flat, int counter)
+ {
+ // Arrange
+ // - Basic ProductType properties
+ originalProductType.Id = 42;
+ originalProductType.Name = "TestName";
+ originalProductType.State = state;
+ originalProductType.Identity = identity;
+ // - Expected behavior from the RecipeManagement
+ if (recipes.Any())
+ ReflectionTool.TestMode = true;
+ _recipeManagementMock.Setup(rm => rm.GetAllByProduct(It.IsAny())).Returns(recipes);
+ _recipeManagementMock.Setup(rm => rm.Get(It.IsAny())).Returns((long id) => new DummyProductRecipe() { Id = id });
+ // - Create target ProductType object
+ var targetDummyProductType = (DummyProductType)Activator.CreateInstance(originalProductType.GetType());
+ targetDummyProductType.Id = 42;
+ // - Expected behavior from the RecipeManagement
+ _productManagerMock.Setup(pm => pm.LoadType(It.IsAny())).Returns((long id) => new DummyProductType() { Id = id });
+ // - Product PartsShould only by included with their id if they already exist
+ var originalProductTypeWithParts = originalProductType as DummyProductTypeWithParts;
+ if (originalProductTypeWithParts is not null && originalProductTypeWithParts.ProductPartLink is not null)
+ {
+ ((DummyProductTypeWithParts)targetDummyProductType).ProductPartLink = ((DummyProductTypeWithParts)originalProductType).ProductPartLink;
+ ((DummyProductTypeWithParts)targetDummyProductType).ProductPartLinkEnumerable = ((DummyProductTypeWithParts)originalProductType).ProductPartLinkEnumerable.Take(1).ToList();
+ }
+
+ // Act
+ var convertedModel = _productConverter.ConvertProduct(originalProductType, flat);
+ var recoveredOriginal = _productConverter.ConvertProductBack(convertedModel, targetDummyProductType);
+
+ // Assert
+ // - Non ProductIdentities will always be transformed into empty ProductIdentities in the Process
+ if (identity is null)
+ originalProductType.Identity = new ProductIdentity("", 0);
+ // - Products originally converted with the flat flag will only affect certain properties
+ if (flat)
+ {
+ Assert.AreEqual(originalProductType.Id, recoveredOriginal.Id);
+ Assert.AreEqual(originalProductType.Name, recoveredOriginal.Name);
+ Assert.AreEqual(originalProductType.State, recoveredOriginal.State);
+ Assert.AreEqual(originalProductType.Identity, recoveredOriginal.Identity);
+ _recipeManagementMock.VerifyNoOtherCalls();
+ _productManagerMock.VerifyNoOtherCalls();
+ return;
+ }
+ // - The following assert uses overwritten Equals methods to check for VALUE equality
+ Assert.AreEqual(originalProductType, recoveredOriginal);
+ // - If there are Recipes the RecipeManagement should be called
+ if (recipes.Any())
+ {
+ _recipeManagementMock.Verify(rm => rm.GetAllByProduct(originalProductType));
+ _recipeManagementMock.Verify(rm => rm.Save(originalProductType.Id, It.Is>(list => list.Count == recipes.Count)));
+ if (recipes.First().Id != 0)
+ _recipeManagementMock.Verify(rm => rm.Get(recipes.First().Id));
+ }
+ // - If there are ProductPartLinks the ProductManagement should be called
+ var targetDummyTypeWithParts = recoveredOriginal as DummyProductTypeWithParts;
+ if (targetDummyTypeWithParts?.ProductPartLink?.Product is not null)
+ {
+ _productManagerMock.Verify(pm => pm.LoadType(targetDummyTypeWithParts.ProductPartLink.Product.Id));
+ _productManagerMock.Verify(pm => pm.LoadType(targetDummyTypeWithParts.ProductPartLinkEnumerable.First().Product.Id));
+ }
+ }
+ #endregion
+
+ #region Recipes
+ public static IEnumerable RecipeForwardBackwardConversionTestCaseGenerator()
+ {
+ // Used only to display tests seperately in TestExplorer
+ var testCaseCounter = 0;
+
+ // Create Recipe objects for test cases
+ var dummyRecipes = new List()
+ {
+ new DummyProductRecipe(),
+ new DummyProductWorkplanRecipe() { Workplan = new DummyWorkplan() { Id=2021 } }
+ };
+ // Create all classifications to consider
+ var classifications = Enum.GetValues(typeof(RecipeClassification)).Cast()
+ .Where(c => c != RecipeClassification.Clone && c != RecipeClassification.CloneFilter).ToList();
+ var clonedClassifications = classifications.Select(c => c | RecipeClassification.Clone).ToList();
+ classifications.AddRange(clonedClassifications);
+
+ // Create all possible combinations of input settings for the ConvertRecipe method
+ foreach (var backupProductType in new List() { null, new DummyProductType()})
+ foreach (RecipeState state in Enum.GetValues(typeof(RecipeState)))
+ foreach (var classification in classifications)
+ foreach (var dummyRecipe in dummyRecipes)
+ {
+ //dummyRecipe.Classification = classification;
+ //dummyRecipe.State = state;
+ var workplanInTargetRecipe = (dummyRecipe as DummyProductWorkplanRecipe)?.Workplan;
+ if (workplanInTargetRecipe is not null)
+ yield return new TestCaseData(dummyRecipe, classification, state, backupProductType, new DummyWorkplan() { Id = 1 }, testCaseCounter++);
+ yield return new TestCaseData(dummyRecipe, classification, state, backupProductType, workplanInTargetRecipe, testCaseCounter++);
+ }
+ }
+
+ //Problem Entry Convert
+ [TestCaseSource(nameof(RecipeForwardBackwardConversionTestCaseGenerator))]
+ public void ForwardBackwardRecipeConversionWithoutInformationLoss(DummyProductRecipe originalRecipe, RecipeClassification classification, RecipeState state,
+ DummyProductType backupProductType, DummyWorkplan workplanInTargetRecipe, int testCaseCounter)
+ {
+ // Arrange
+ // - Basic Workplan properties
+ originalRecipe.Id = 42;
+ originalRecipe.Name = "TestName";
+ originalRecipe.Revision = 1337;
+ originalRecipe.Classification = classification;
+ originalRecipe.State = state;
+ // - Mock workplan requests if there could be a Workplan
+ var originalWorkplanRecipe = originalRecipe as DummyProductWorkplanRecipe;
+ if (originalWorkplanRecipe is not null)
+ {
+ _workplanManagementMock.Setup(wm => wm.LoadWorkplan(It.IsAny()))
+ .Returns((long id) => new DummyWorkplan() { Id = id });
+ }
+ // - Create target object
+ var targetDummyRecipe = (DummyProductRecipe)Activator.CreateInstance(originalRecipe.GetType());
+ targetDummyRecipe.Id = 42;
+ if (originalWorkplanRecipe is not null)
+ ((DummyProductWorkplanRecipe)targetDummyRecipe).Workplan = workplanInTargetRecipe;
+ // - No change of classification on clones should be possible, thereby preset it
+ if (originalRecipe.Classification.HasFlag(RecipeClassification.Clone))
+ targetDummyRecipe.Classification = originalRecipe.Classification;
+
+
+ // Act
+ var convertedModel = _productConverter.ConvertRecipe(originalRecipe);
+ var recoveredOriginal = _productConverter.ConvertRecipeBack(convertedModel, targetDummyRecipe, backupProductType);
+
+
+ // Assert
+ // - Backup products are used for recipes without products
+ if (originalRecipe.Product is null)
+ originalRecipe.Product = backupProductType;
+
+ Assert.AreEqual(originalRecipe, recoveredOriginal);
+ // - If there is a workplan and it changed, reload it at backward conversion
+ if (originalWorkplanRecipe?.Workplan is not null && originalWorkplanRecipe.Workplan.Id != workplanInTargetRecipe.Id)
+ _workplanManagementMock.Verify(wm => wm.LoadWorkplan(originalWorkplanRecipe.Workplan.Id), Times.Once);
+ else
+ _workplanManagementMock.VerifyNoOtherCalls();
+ }
+ #endregion
+
+ #region Workplans
+ public static IEnumerable WorkplanForwardConversionTestCaseGenerator()
+ {
+ foreach (var wpState in Enum.GetValues(typeof(WorkplanState)))
+ yield return new TestCaseData(wpState);
+ }
+
+ //Problem Entry Convert
+ [TestCaseSource(nameof(WorkplanForwardConversionTestCaseGenerator))]
+ public void ForwardWorkplanConversionWithoutInformationLoss(WorkplanState wpState)
+ {
+ // Arrange
+ var originalWorkplan = new DummyWorkplan()
+ {
+ Id=42,
+ Name="TestWorkplan",
+ Version=1,
+ State=wpState
+ };
+
+ // Act
+ var convertedmodel = _productConverter.ConvertWorkplan(originalWorkplan);
+
+ // Assert
+ Assert.AreEqual(originalWorkplan.Id, convertedmodel.Id);
+ Assert.AreEqual(originalWorkplan.Name, convertedmodel.Name);
+ Assert.AreEqual(originalWorkplan.Version,convertedmodel.Version);
+ Assert.AreEqual(originalWorkplan.State,convertedmodel.State);
+ }
+ #endregion
+ }
+}
\ No newline at end of file