Forráskód Böngészése

Benachrichtigungen und Konfiguration verbessert!

Arne Diekmann 8 éve
szülő
commit
00c9d4b19a
24 módosított fájl, 941 hozzáadás és 118 törlés
  1. 52 0
      GreenTree.Nachtragsmanagement.Services/Configuration/ConfigurationHelper.cs
  2. 21 0
      GreenTree.Nachtragsmanagement.Services/Configuration/ConfigurationReferenceElement.cs
  3. 20 1
      GreenTree.Nachtragsmanagement.Services/Configuration/ConfigurationService.cs
  4. 60 0
      GreenTree.Nachtragsmanagement.Services/Configuration/IConfigurationReference.cs
  5. 126 0
      GreenTree.Nachtragsmanagement.Services/Configuration/StateConfigurationReference.cs
  6. 7 0
      GreenTree.Nachtragsmanagement.Services/GreenTree.Nachtragsmanagement.Services.csproj
  7. 61 0
      GreenTree.Nachtragsmanagement.Services/Logging/IBaseLogger.cs
  8. 2 48
      GreenTree.Nachtragsmanagement.Services/Logging/ILogger.cs
  9. 15 0
      GreenTree.Nachtragsmanagement.Services/Logging/IServiceLogger.cs
  10. 7 7
      GreenTree.Nachtragsmanagement.Services/Logging/LoggingExtensions.cs
  11. 163 0
      GreenTree.Nachtragsmanagement.Services/Logging/ServiceLogger.cs
  12. 9 3
      GreenTree.Nachtragsmanagement.Web.Framework/ApplicationContext.cs
  13. 40 2
      GreenTree.Nachtragsmanagement.Web/Controllers/MiscController.cs
  14. 10 0
      GreenTree.Nachtragsmanagement.Web/Extensions/GridViewSettingsHelper.cs
  15. 1 0
      GreenTree.Nachtragsmanagement.Web/GreenTree.Nachtragsmanagement.Web.csproj
  16. 135 29
      GreenTree.Nachtragsmanagement.Web/Implementations/AppendixNotificationPlugin.cs
  17. 33 14
      GreenTree.Nachtragsmanagement.Web/Implementations/DeviationNotificationPlugin.cs
  18. 53 6
      GreenTree.Nachtragsmanagement.Web/Models/Config/ConfigItemDataModel.cs
  19. 26 1
      GreenTree.Nachtragsmanagement.Web/Validation/Config/ConfigItemDataModelValidator.cs
  20. 1 1
      GreenTree.Nachtragsmanagement.Web/Views/Config/View.cshtml
  21. 14 2
      GreenTree.Nachtragsmanagement.Web/Views/Config/_ConfigItemEditPartial.cshtml
  22. 2 0
      GreenTree.Nachtragsmanagement.Web/Views/Config/_ConfigItemGridPartial.cshtml
  23. 75 0
      GreenTree.Nachtragsmanagement.Web/Views/Config/_ConfigItemReferenceEditPartial.cshtml
  24. 8 4
      GreenTree.Nachtragsmanagement.Web/Views/Home/Index.cshtml

+ 52 - 0
GreenTree.Nachtragsmanagement.Services/Configuration/ConfigurationHelper.cs

@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+
+namespace GreenTree.Nachtragsmanagement.Services.Configuration
+{
+    public static class ConfigurationHelper
+    {
+        /// <summary>
+        /// Returns all available configuration references
+        /// </summary>
+        public static Dictionary<string, IConfigurationReference> GetAllConfigurationReferences()
+        {
+            var stateConfigurationReference = new StateConfigurationReference();
+
+            return new Dictionary<string, IConfigurationReference>()
+            {
+                { stateConfigurationReference.Name, stateConfigurationReference }
+            };
+        }
+
+        /// <summary>
+        /// Returns all available configuration references for display
+        /// </summary>
+        public static Dictionary<string, string> GetAllConfigurationReferencesDisplayList()
+        {
+            var stateConfigurationReference = new StateConfigurationReference();
+
+            return new Dictionary<string, string>()
+            {
+                { stateConfigurationReference.Name, stateConfigurationReference.DisplayName }
+            };
+        }
+
+        /// <summary>
+        /// Returns the configurationReference to the corresponding name
+        /// </summary>
+        /// <param name="name">Name of the configurationReference.</param>
+        public static IConfigurationReference GetConfigurationReferenceByName(string name)
+        {
+            var allConfigurationReferences = GetAllConfigurationReferences();
+
+            return
+                allConfigurationReferences
+                    .Any(c => c.Key == name)
+                        ? allConfigurationReferences
+                            .First(c => c.Key == name).Value
+                        : null;
+        }
+    }
+}

+ 21 - 0
GreenTree.Nachtragsmanagement.Services/Configuration/ConfigurationReferenceElement.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace GreenTree.Nachtragsmanagement.Services.Configuration
+{
+    public class ConfigurationReferenceElement
+    {
+        /// <summary>
+        /// Value
+        /// </summary>
+        public object Value { get; set; }
+
+        /// <summary>
+        /// Display text
+        /// </summary>
+        public string Text { get; set; }
+    }
+}

+ 20 - 1
GreenTree.Nachtragsmanagement.Services/Configuration/ConfigurationService.cs

@@ -233,7 +233,26 @@ namespace GreenTree.Nachtragsmanagement.Services.Configuration
 
             try
             {
-                result = Convert.ChangeType(configItem.Value, typeof(T));
+                if (configItem.TypeFullName.StartsWith("ConfigurationReference"))
+                {
+                    var configurationReference = ConfigurationHelper.GetConfigurationReferenceByName(configItem.TypeFullName);
+
+                    if (configurationReference != null)
+                    {
+                        if (configurationReference.IsMultipleSelection)
+                        {
+                            var vals = configurationReference.TransformValueToValueCollection(configItem.Value);
+
+                            result = configurationReference.GetValues<T>(vals);
+                        }
+                        else
+                            result = configurationReference.GetValue<T>(configItem.Value);
+                    }
+                }
+                else
+                {
+                    result = Convert.ChangeType(configItem.Value, typeof(T));
+                }
             }
             catch
             {

+ 60 - 0
GreenTree.Nachtragsmanagement.Services/Configuration/IConfigurationReference.cs

@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace GreenTree.Nachtragsmanagement.Services.Configuration
+{
+    public interface IConfigurationReference
+    {
+        /// <summary>
+        /// Id
+        /// </summary>
+        Guid Id { get; }
+
+        /// <summary>
+        /// Internal name
+        /// </summary>
+        string Name { get; }
+
+        /// <summary>
+        /// Display name
+        /// </summary>
+        string DisplayName { get; }
+
+        /// <summary>
+        /// Determines if mutiple values are selectable
+        /// </summary>
+        bool IsMultipleSelection { get; }
+
+        /// <summary>
+        /// Get available values
+        /// </summary>
+        ConfigurationReferenceElement[] GetAvailableValues();
+
+        /// <summary>
+        /// Transforms mutiple selected values into a single line value
+        /// </summary>
+        /// <param name="values">Selected values.</param>
+        string TransformValueCollectionToValue(string[] values);
+
+        /// <summary>
+        /// Transforms the single line value into a mutiple selected values
+        /// </summary>
+        /// <param name="value">Single line value.</param>
+        string[] TransformValueToValueCollection(string value);
+
+        /// <summary>
+        /// Converts the string converted value into strongly typed value
+        /// </summary>
+        /// <param name="values">The selected value.</param>
+        T GetValue<T>(string value);
+
+        /// <summary>
+        /// Converts the string converted values into strongly typed values
+        /// </summary>
+        /// <param name="values">The selected values.</param>
+        T GetValues<T>(string[] values);
+    }
+}

+ 126 - 0
GreenTree.Nachtragsmanagement.Services/Configuration/StateConfigurationReference.cs

@@ -0,0 +1,126 @@
+using Autofac;
+using GreenTree.Nachtragsmanagement.Core;
+using GreenTree.Nachtragsmanagement.Services.Appendix;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace GreenTree.Nachtragsmanagement.Services.Configuration
+{
+    public class StateConfigurationReference : IConfigurationReference
+    {
+        /// <summary>
+        /// Id
+        /// </summary>
+        public Guid Id
+        {
+            get
+            {
+                return Guid.Parse("DF5E3C83-357C-458D-AA03-9BF7C5C49203");
+            }
+        }
+
+        /// <summary>
+        /// Internal name
+        /// </summary>
+        public string Name
+        {
+            get
+            {
+                return "ConfigurationReference.StateConfigurationReference";
+            }
+        }
+
+        /// <summary>
+        /// Display name
+        /// </summary>
+        public string DisplayName
+        {
+            get
+            {
+                return "Nachtragsstatus";
+            }
+        }
+
+        /// <summary>
+        /// Determines if mutiple values are selectable
+        /// </summary>
+        public bool IsMultipleSelection
+        {
+            get
+            {
+                return true;
+            }
+        }
+
+        /// <summary>
+        /// Get available values
+        /// </summary>
+        public ConfigurationReferenceElement[] GetAvailableValues()
+        {
+            var appendixService = Singleton<IContainer>.Instance.Resolve<IAppendixService>();
+
+            return 
+                appendixService
+                    .GetAllStates()
+                    .Select(s => new ConfigurationReferenceElement
+                    {
+                        Value = s.Id,
+                        Text = s.Description
+                    })
+                    .ToArray();
+        }
+
+        /// <summary>
+        /// Transforms mutiple selected values into a single line value
+        /// </summary>
+        /// <param name="values">Selected values.</param>
+        public string TransformValueCollectionToValue(string[] values)
+        {
+            return
+                String.Join(", ", values);
+        }
+
+        /// <summary>
+        /// Transforms the single line value into a mutiple selected values
+        /// </summary>
+        /// <param name="value">Single line value.</param>
+        public string[] TransformValueToValueCollection(string value)
+        {
+            return
+                String.IsNullOrEmpty(value)
+                    ? new string[0]
+                    : value.Split(',')
+                        .Select(s => s.Trim())
+                        .ToArray();
+        }
+
+        /// <summary>
+        /// Converts the string converted value into strongly typed value
+        /// </summary>
+        /// <param name="values">The selected value.</param>
+        public T GetValue<T>(string value)
+        {
+            return (T)((object)Convert.ToInt32(value));
+        }
+
+        /// <summary>
+        /// Converts the string converted values into strongly typed values
+        /// </summary>
+        /// <param name="values">The selected values.</param>
+        public T GetValues<T>(string[] values)
+        {
+            if (values == null)
+                return default(T);
+
+            return 
+                (T)(
+                    (object)
+                        (values
+                            .Select(v => Convert.ToInt32(v))
+                            .ToArray()));
+        }
+    }
+}

+ 7 - 0
GreenTree.Nachtragsmanagement.Services/GreenTree.Nachtragsmanagement.Services.csproj

@@ -60,12 +60,19 @@
   <ItemGroup>
     <Compile Include="Appendix\AppendixService.cs" />
     <Compile Include="Appendix\IAppendixService.cs" />
+    <Compile Include="Configuration\ConfigurationHelper.cs" />
+    <Compile Include="Configuration\ConfigurationReferenceElement.cs" />
     <Compile Include="Configuration\ConfigurationService.cs" />
+    <Compile Include="Configuration\IConfigurationReference.cs" />
     <Compile Include="Configuration\IConfigurationService.cs" />
+    <Compile Include="Configuration\StateConfigurationReference.cs" />
     <Compile Include="DbContext\DbContextService.cs" />
     <Compile Include="DbContext\IDbContextService.cs" />
     <Compile Include="Deviation\DeviationService.cs" />
     <Compile Include="Deviation\IDeviationService.cs" />
+    <Compile Include="Logging\IBaseLogger.cs" />
+    <Compile Include="Logging\IServiceLogger.cs" />
+    <Compile Include="Logging\ServiceLogger.cs" />
     <Compile Include="Logging\DefaultLogger.cs" />
     <Compile Include="Logging\ILogger.cs" />
     <Compile Include="Logging\LoggingExtensions.cs" />

+ 61 - 0
GreenTree.Nachtragsmanagement.Services/Logging/IBaseLogger.cs

@@ -0,0 +1,61 @@
+using GreenTree.Nachtragsmanagement.Core;
+using GreenTree.Nachtragsmanagement.Core.Domain.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace GreenTree.Nachtragsmanagement.Services.Logging
+{
+    public interface IBaseLogger
+    {
+        /// <summary>
+        /// Determines whether a log level is enabled
+        /// </summary>
+        /// <param name="level">Log level</param>
+        bool IsEnabled(LogLevel level);
+
+        /// <summary>
+        /// Deletes a log item
+        /// </summary>
+        /// <param name="log">Log item</param>
+        void DeleteLog(Log log);
+
+        /// <summary>
+        /// Clears a log
+        /// </summary>
+        void ClearLog();
+
+        /// <summary>
+        /// Gets all log items
+        /// </summary>
+        /// <returns>Log item collection</returns>
+        IList<Log> GetAllLogs();
+
+        /// <summary>
+        /// Gets a log item
+        /// </summary>
+        /// <param name="logId">Log item identifier</param>
+        /// <returns>Log item</returns>
+        Log GetLogById(int logId);
+
+        /// <summary>
+        /// Get log items by identifiers
+        /// </summary>
+        /// <param name="logIds">Log item identifiers</param>
+        /// <returns>Log items</returns>
+        IList<Log> GetLogByIds(int[] logIds);
+
+        /// <summary>
+        /// Inserts a log item
+        /// </summary>
+        /// <param name="logLevel">Log level</param>
+        /// <param name="shortMessage">The short message</param>
+        /// <param name="fullMessage">The full message</param>
+        /// <param name="user">The user to associate log record with</param>
+        /// <returns>A log item</returns>
+        Log InsertLog(LogLevel logLevel, string shortMessage, BaseEntity entity = null, string fullMessage = "", 
+            Core.Domain.User.User user = null);
+    }
+}

+ 2 - 48
GreenTree.Nachtragsmanagement.Services/Logging/ILogger.cs

@@ -8,54 +8,8 @@ using System.Threading.Tasks;
 
 namespace GreenTree.Nachtragsmanagement.Services.Logging
 {
-    public interface ILogger
-    {
-        /// <summary>
-        /// Determines whether a log level is enabled
-        /// </summary>
-        /// <param name="level">Log level</param>
-        bool IsEnabled(LogLevel level);
+    public interface ILogger : IBaseLogger
+    { 
 
-        /// <summary>
-        /// Deletes a log item
-        /// </summary>
-        /// <param name="log">Log item</param>
-        void DeleteLog(Log log);
-
-        /// <summary>
-        /// Clears a log
-        /// </summary>
-        void ClearLog();
-
-        /// <summary>
-        /// Gets all log items
-        /// </summary>
-        /// <returns>Log item collection</returns>
-        IList<Log> GetAllLogs();
-
-        /// <summary>
-        /// Gets a log item
-        /// </summary>
-        /// <param name="logId">Log item identifier</param>
-        /// <returns>Log item</returns>
-        Log GetLogById(int logId);
-
-        /// <summary>
-        /// Get log items by identifiers
-        /// </summary>
-        /// <param name="logIds">Log item identifiers</param>
-        /// <returns>Log items</returns>
-        IList<Log> GetLogByIds(int[] logIds);
-
-        /// <summary>
-        /// Inserts a log item
-        /// </summary>
-        /// <param name="logLevel">Log level</param>
-        /// <param name="shortMessage">The short message</param>
-        /// <param name="fullMessage">The full message</param>
-        /// <param name="user">The user to associate log record with</param>
-        /// <returns>A log item</returns>
-        Log InsertLog(LogLevel logLevel, string shortMessage, BaseEntity entity = null, string fullMessage = "", 
-            Core.Domain.User.User user = null);
     }
 }

+ 15 - 0
GreenTree.Nachtragsmanagement.Services/Logging/IServiceLogger.cs

@@ -0,0 +1,15 @@
+using GreenTree.Nachtragsmanagement.Core;
+using GreenTree.Nachtragsmanagement.Core.Domain.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace GreenTree.Nachtragsmanagement.Services.Logging
+{
+    public interface IServiceLogger : IBaseLogger
+    {
+        
+    }
+}

+ 7 - 7
GreenTree.Nachtragsmanagement.Services/Logging/LoggingExtensions.cs

@@ -11,37 +11,37 @@ namespace GreenTree.Nachtragsmanagement.Services.Logging
 {
     public static class LoggingExtensions
     {
-        public static void Debug(this ILogger logger, string message, Exception exception = null,
+        public static void Debug(this IBaseLogger logger, string message, Exception exception = null,
             Core.Domain.User.User user = null)
         {
             FilteredLog(logger, LogLevel.Debug, message, exception, user);
         }
 
-        public static void Information(this ILogger logger, string message, Exception exception = null,
+        public static void Information(this IBaseLogger logger, string message, Exception exception = null,
             Core.Domain.User.User user = null)
         {
             FilteredLog(logger, LogLevel.Information, message, exception, user);
         }
 
-        public static void Warning(this ILogger logger, string message, Exception exception = null,
+        public static void Warning(this IBaseLogger logger, string message, Exception exception = null,
             Core.Domain.User.User user = null)
         {
             FilteredLog(logger, LogLevel.Warning, message, exception, user);
         }
 
-        public static void Error(this ILogger logger, string message, Exception exception = null,
+        public static void Error(this IBaseLogger logger, string message, Exception exception = null,
             Core.Domain.User.User user = null)
         {
             FilteredLog(logger, LogLevel.Error, message, exception, user);
         }
 
-        public static void Fatal(this ILogger logger, string message, Exception exception = null,
+        public static void Fatal(this IBaseLogger logger, string message, Exception exception = null,
             Core.Domain.User.User user = null)
         {
             FilteredLog(logger, LogLevel.Fatal, message, exception, user);
         }
 
-        private static void FilteredLog(ILogger logger, LogLevel level, string message, Exception exception = null,
+        private static void FilteredLog(IBaseLogger logger, LogLevel level, string message, Exception exception = null,
             Core.Domain.User.User user = null)
         {
             //don't log thread abort exception
@@ -58,7 +58,7 @@ namespace GreenTree.Nachtragsmanagement.Services.Logging
             }
         }
 
-        public static void Entity(this ILogger logger, BaseEntity entity, LogEntityActivity activity, Core.Domain.User.User user = null)
+        public static void Entity(this IBaseLogger logger, BaseEntity entity, LogEntityActivity activity, Core.Domain.User.User user = null)
         {
             if (entity == null)
                 return;

+ 163 - 0
GreenTree.Nachtragsmanagement.Services/Logging/ServiceLogger.cs

@@ -0,0 +1,163 @@
+using GreenTree.Nachtragsmanagement.Core;
+using GreenTree.Nachtragsmanagement.Core.Data;
+using GreenTree.Nachtragsmanagement.Core.Domain.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace GreenTree.Nachtragsmanagement.Services.Logging
+{
+    /// <summary>
+    /// Default logger
+    /// </summary>
+    public partial class ServiceLogger : IServiceLogger
+    {
+        #region Fields
+
+        private readonly IRepository<Log> _logRepository;
+
+        #endregion
+
+        #region Ctor
+
+        /// <summary>
+        /// Initializes a new instance of the DefaultLogger class
+        /// </summary>
+        public ServiceLogger(
+            IRepository<Log> logRepository)
+        {
+            this._logRepository = logRepository;
+        }
+
+        #endregion
+
+        #region Methods
+
+        /// <summary>
+        /// Determines whether a log level is enabled
+        /// </summary>
+        /// <param name="level">Log level</param>
+        /// <returns>Result</returns>
+        public virtual bool IsEnabled(LogLevel level)
+        {
+            switch (level)
+            {
+                case LogLevel.Debug:
+                    return false;
+                default:
+                    return true;
+            }
+        }
+
+        /// <summary>
+        /// Deletes a log item
+        /// </summary>
+        /// <param name="log">Log item</param>
+        public virtual void DeleteLog(Log log)
+        {
+            if (log == null)
+                throw new ArgumentNullException("log");
+
+            _logRepository.Delete(log);
+        }
+
+        /// <summary>
+        /// Clears a log
+        /// </summary>
+        public virtual void ClearLog()
+        {
+            var logs = _logRepository.Table.ToList();
+
+            foreach (var logItem in logs)
+                _logRepository.Delete(logItem);
+        }
+
+        /// <summary>
+        /// Gets all log items
+        /// </summary>
+        /// <returns>Log item collection</returns>
+        public IList<Log> GetAllLogs()
+        {
+            return _logRepository.Table.ToList();
+        }
+
+        /// <summary>
+        /// Gets a log item
+        /// </summary>
+        /// <param name="logId">Log item identifier</param>
+        /// <returns>Log item</returns>
+        public virtual Log GetLogById(int logId)
+        {
+            if (logId == 0)
+                return null;
+
+            return _logRepository.GetById(logId);
+        }
+
+        /// <summary>
+        /// Get log items by identifiers
+        /// </summary>
+        /// <param name="logIds">Log item identifiers</param>
+        /// <returns>Log items</returns>
+        public virtual IList<Log> GetLogByIds(int[] logIds)
+        {
+            if (logIds == null || logIds.Length == 0)
+                return new List<Log>();
+
+            var query = from l in _logRepository.Table
+                        where logIds.Contains(l.Id)
+                        select l;
+            var logItems = query.ToList();
+            //sort by passed identifiers
+            var sortedLogItems = new List<Log>();
+            foreach (int id in logIds)
+            {
+                var log = logItems.Find(x => x.Id == id);
+                if (log != null)
+                    sortedLogItems.Add(log);
+            }
+            return sortedLogItems;
+        }
+
+        /// <summary>
+        /// Inserts a log item
+        /// </summary>
+        /// <param name="logLevel">Log level</param>
+        /// <param name="entity"></param>
+        /// <param name="shortMessage">The short message</param>
+        /// <param name="fullMessage">The full message</param>
+        /// <param name="user">The user to associate log record with</param>
+        /// <returns>A log item</returns>
+        public virtual Log InsertLog(LogLevel logLevel, string shortMessage, BaseEntity entity = null, string fullMessage = "", 
+            Core.Domain.User.User user = null)
+        {
+            var log = new Log
+            {
+                LogLevel = logLevel,
+                ShortMessage = shortMessage,
+                FullMessage = fullMessage,
+                IpAddress = "127.0.0.1",
+                UserId = user == null
+                    ? null
+                    : (int?)user.Id,
+                EntityId = entity == null 
+                    ? null
+                    : (int?)entity.Id,
+                EntityType = entity == null
+                    ? null
+                    : LoggingExtensions.GetTypeNameFromEfEntityType(entity),
+                PageUrl = String.Empty,
+                ReferrerUrl = String.Empty,
+                CreatedOnUtc = DateTime.UtcNow
+            };
+
+            _logRepository.Insert(log);
+
+            return log;
+        }
+
+        #endregion
+    }
+}

+ 9 - 3
GreenTree.Nachtragsmanagement.Web.Framework/ApplicationContext.cs

@@ -28,6 +28,7 @@ using GreenTree.Nachtragsmanagement.Core.Authentication;
 using GreenTree.Nachtragsmanagement.Services.Appendix;
 using GreenTree.Nachtragsmanagement.Services.Scheduling;
 using GreenTree.Nachtragsmanagement.Services.Logging;
+using Autofac.Core;
 
 namespace GreenTree.Nachtragsmanagement.Web.Framework
 {
@@ -103,19 +104,24 @@ namespace GreenTree.Nachtragsmanagement.Web.Framework
             // Register generic data repositorys
             builder.RegisterGeneric(typeof(EfRepository<>)).As(typeof(IRepository<>));
 
+            // Register web helper
+            builder.RegisterType<WebHelper>().As<IWebHelper>();
+
+            // Register logger
+            builder.RegisterType<DefaultLogger>().As<ILogger>();
+            builder.RegisterType<ServiceLogger>().As<IServiceLogger>();
+
             // Register service types
-            builder.RegisterType<ConfigurationService>().As<IConfigurationService>();
             builder.RegisterType<DbRelationService>().As<IDbRelationService>();
             builder.RegisterType<UserService>().As<IUserService>();
             builder.RegisterType<UserHelper>().As<IUserHelper>();
-            builder.RegisterType<WebHelper>().As<IWebHelper>();
             builder.RegisterType<AuthenticationService>().As<IAuthenticationService>();
             builder.RegisterType<SiteService>().As<ISiteService>();
             builder.RegisterType<DeviationService>().As<IDeviationService>();
             builder.RegisterType<AppendixService>().As<IAppendixService>();
             builder.RegisterType<MiscService>().As<IMiscService>();
-            builder.RegisterType<DefaultLogger>().As<ILogger>();
             builder.RegisterType<NotificationService>().As<INotificationService>();
+            builder.RegisterType<ConfigurationService>().As<IConfigurationService>();
             builder.RegisterType<PluginFinder>().As<IPluginFinder>();
             builder.RegisterType<WebAppTypeFinder>().As<ITypeFinder>();
             builder.RegisterType<RoutePublisher>().As<IRoutePublisher>();

+ 40 - 2
GreenTree.Nachtragsmanagement.Web/Controllers/MiscController.cs

@@ -548,7 +548,21 @@ namespace GreenTree.Nachtragsmanagement.Web.Controllers
                 TypeFullName = typeFullName
             };
 
-            return PartialView("~/Views/Config/_ConfigItemValueEditPartial.cshtml", model);
+            if (typeFullName.StartsWith("ConfigurationReference"))
+            {
+                var configurationReference = ConfigurationHelper.GetConfigurationReferenceByName(typeFullName);
+
+                if (configurationReference != null)
+                {
+                    model.IsValueCollection = configurationReference.IsMultipleSelection;
+
+                    ViewData["ConfigReferenceValues"] = configurationReference.GetAvailableValues();
+                }
+
+                return PartialView("~/Views/Config/_ConfigItemReferenceEditPartial.cshtml", model);
+            }
+            else
+                return PartialView("~/Views/Config/_ConfigItemValueEditPartial.cshtml", model);
         }
 
         /// <summary>
@@ -560,6 +574,14 @@ namespace GreenTree.Nachtragsmanagement.Web.Controllers
             var configItem = _configurationService.GetConfigItemById(id);
             var configItemModel = ConfigItemDataModel.FromConfigItem(configItem, true);
 
+            if (configItem != null && configItem.TypeFullName.StartsWith("ConfigurationReference"))
+            {
+                var configurationReference = ConfigurationHelper.GetConfigurationReferenceByName(configItem.TypeFullName);
+
+                if (configurationReference != null)
+                    ViewData["ConfigReferenceValues"] = configurationReference.GetAvailableValues();
+            }
+
             return PartialView("~/Views/Config/_ConfigItemEditPartial.cshtml", configItemModel);
         }
 
@@ -589,9 +611,25 @@ namespace GreenTree.Nachtragsmanagement.Web.Controllers
 
                     configItem.Name = configItemModel.Name;
                     configItem.TypeFullName = configItemModel.TypeFullName;
-                    configItem.Value = configItemModel.Value;
                     configItem.Description = configItemModel.Description;
 
+                    if (ConfigItemDataModel.FullTypeTranslations.ContainsKey(configItemModel.TypeFullName))
+                    {
+                        configItem.Value = configItemModel.Value;
+                    }
+                    else
+                    {
+                        var configurationReference = ConfigurationHelper.GetConfigurationReferenceByName(configItemModel.TypeFullName);
+
+                        if (configurationReference != null)
+                        {
+                            if (configurationReference.IsMultipleSelection)
+                                configItem.Value = configurationReference.TransformValueCollectionToValue(configItemModel.Values);
+                            else
+                                configItem.Value = configItemModel.Value;
+                        }
+                    }
+
                     _configurationService.UpdateConfigItem(configItem);
 
                     _logger.Entity(configItem, Core.Domain.Logging.LogEntityActivity.Update, _userHelper.FromCookies());

+ 10 - 0
GreenTree.Nachtragsmanagement.Web/Extensions/GridViewSettingsHelper.cs

@@ -58,6 +58,8 @@ namespace GreenTree.Nachtragsmanagement.Web.Extensions
             s.SettingsExport.FileName = "Baustellenliste";
             s.SettingsPopup.CustomizationWindow.Width = new Unit(250, UnitType.Pixel);
             s.SettingsPopup.CustomizationWindow.Height = new Unit(350, UnitType.Pixel);
+            s.SettingsPopup.HeaderFilter.Width = new Unit(353, UnitType.Pixel);
+            s.SettingsPopup.HeaderFilter.Height = new Unit(360, UnitType.Pixel);
             s.SettingsBehavior.EnableCustomizationWindow = true;
             s.SettingsBehavior.AllowHeaderFilter = true;
             s.SettingsPager.AlwaysShowPager = true;
@@ -384,6 +386,8 @@ namespace GreenTree.Nachtragsmanagement.Web.Extensions
             s.SettingsExport.FileName = "Vertragsabweichungsliste";
             s.SettingsPopup.CustomizationWindow.Width = new Unit(250, UnitType.Pixel);
             s.SettingsPopup.CustomizationWindow.Height = new Unit(350, UnitType.Pixel);
+            s.SettingsPopup.HeaderFilter.Width = new Unit(353, UnitType.Pixel);
+            s.SettingsPopup.HeaderFilter.Height = new Unit(360, UnitType.Pixel);
             s.SettingsBehavior.EnableCustomizationWindow = true;
             s.SettingsBehavior.AllowHeaderFilter = true;
             s.SettingsPager.AlwaysShowPager = true;
@@ -876,6 +880,8 @@ namespace GreenTree.Nachtragsmanagement.Web.Extensions
             s.SettingsExport.FileName = "Nachtragsliste";
             s.SettingsPopup.CustomizationWindow.Width = new Unit(250, UnitType.Pixel);
             s.SettingsPopup.CustomizationWindow.Height = new Unit(350, UnitType.Pixel);
+            s.SettingsPopup.HeaderFilter.Width = new Unit(353, UnitType.Pixel);
+            s.SettingsPopup.HeaderFilter.Height = new Unit(360, UnitType.Pixel);
             s.SettingsBehavior.EnableCustomizationWindow = true;
             s.SettingsBehavior.AllowHeaderFilter = true;
             s.SettingsPager.AlwaysShowPager = true;
@@ -1393,6 +1399,8 @@ namespace GreenTree.Nachtragsmanagement.Web.Extensions
             s.SettingsExport.FileName = "Benachrichtigungsliste";
             s.SettingsPopup.CustomizationWindow.Width = new Unit(250, UnitType.Pixel);
             s.SettingsPopup.CustomizationWindow.Height = new Unit(350, UnitType.Pixel);
+            s.SettingsPopup.HeaderFilter.Width = new Unit(353, UnitType.Pixel);
+            s.SettingsPopup.HeaderFilter.Height = new Unit(360, UnitType.Pixel);
             s.SettingsBehavior.EnableCustomizationWindow = true;
             s.SettingsBehavior.AllowHeaderFilter = true;
             s.SettingsPager.AlwaysShowPager = true;
@@ -1574,6 +1582,8 @@ namespace GreenTree.Nachtragsmanagement.Web.Extensions
             s.SettingsExport.FileName = "Logliste";
             s.SettingsPopup.CustomizationWindow.Width = new Unit(250, UnitType.Pixel);
             s.SettingsPopup.CustomizationWindow.Height = new Unit(350, UnitType.Pixel);
+            s.SettingsPopup.HeaderFilter.Width = new Unit(353, UnitType.Pixel);
+            s.SettingsPopup.HeaderFilter.Height = new Unit(360, UnitType.Pixel);
             s.SettingsBehavior.EnableCustomizationWindow = true;
             s.SettingsBehavior.AllowHeaderFilter = true;
             s.SettingsPager.AlwaysShowPager = true;

+ 1 - 0
GreenTree.Nachtragsmanagement.Web/GreenTree.Nachtragsmanagement.Web.csproj

@@ -355,6 +355,7 @@
     <Content Include="Views\Misc\_HelpPageEditPartial.cshtml" />
     <Content Include="Views\Misc\_HelpPageViewPartial.cshtml" />
     <Content Include="Views\Misc\_HelpPageHtmlEditPartial.cshtml" />
+    <Content Include="Views\Config\_ConfigItemReferenceEditPartial.cshtml" />
     <None Include="Web.Debug.config">
       <DependentUpon>Web.config</DependentUpon>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

+ 135 - 29
GreenTree.Nachtragsmanagement.Web/Implementations/AppendixNotificationPlugin.cs

@@ -12,6 +12,7 @@ using System.Net.Mail;
 using System.Net;
 using System.Globalization;
 using Quartz;
+using GreenTree.Nachtragsmanagement.Services.Logging;
 
 namespace GreenTree.Nachtragsmanagement.Web.Implementations
 {
@@ -22,6 +23,7 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
         private readonly IUserService _userService;
         private readonly IConfigurationService _configurationService;
         private readonly IAppendixService _appendixService;
+        private readonly IServiceLogger _logger;
 
         #endregion
 
@@ -73,6 +75,14 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
                         "Verhandlungsprotokolle überprüfen",
                         "Erstellt automatisch Benachrichtigungen für Nachträge, die nach 2 Wochen zwar verhandelt sind, " +
                         "jedoch noch kein Protokoll aufweisen."
+                    ),
+                    new NotificationJob
+                    (
+                        Guid.Parse("B39FB44A-6E57-458C-A403-2C4FA7581F15"),
+                        "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationOrder",
+                        "Bestellnummern überprüfen",
+                        "Erstellt automatisch Benachrichtigungen für Nachträge, die nach 2 Wochen zwar verhandelt sind, " +
+                        "jedoch noch keine Bestellnummer aufweisen."
                     )
                 };
             }
@@ -99,7 +109,8 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
                 return
                     "Erstellt automatisch Benachrichtigungen für Nachträge, die nach 8 Wochen nach Einreichung noch keinen " +
                     "Verhandlungstermin gesetzt haben und ändert den entsprechenden Status ab. Außerdem werden alle 2 Wochen " +
-                    "Benachrichtigungen für Nachträge erstellt, die zwar verhandelt sind, jedoch noch kein Protokoll aufweisen.";
+                    "Benachrichtigungen für Nachträge erstellt, die zwar verhandelt sind, jedoch noch kein Protokoll aufweisen oder " +
+                    "keine Bestellnummer aufweisen.";
             }
         }
         #endregion
@@ -115,11 +126,13 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
         public AppendixNotificationPlugin(
             IUserService userService,
             IConfigurationService configurationService,
-            IAppendixService appendixService)
+            IAppendixService appendixService,
+            IServiceLogger logger)
         {
             _userService = userService;
             _configurationService = configurationService;
             _appendixService = appendixService;
+            _logger = logger;
         }
 
         #region Quartz implmentation
@@ -135,8 +148,6 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
 
             var mailNotifications = context.JobDetail.JobDataMap.Get("MailNotifications") as IEnumerable<MailNotification>;
 
-            if (mailNotifications == null) return;
-
             ProcessNotifications(mailNotifications);
         }
 
@@ -150,24 +161,38 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
         /// <param name="mailNotifications">The notifications which shall be generated.</param>
         public void ProcessNotifications(IEnumerable<MailNotification> mailNotifications)
         {
-            if (mailNotifications == null || !mailNotifications.Any()) return;
+            if (mailNotifications == null || (mailNotifications != null && !mailNotifications.Any())) return;
 
             foreach (var notification in mailNotifications)
             {
-                switch (notification.NotificationJobSystemName)
+                try
                 {
-                    case "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationDate":
-                        {
-                            ProcessNegotiationDateNotification(notification);
-                        }
-                        break;
-                    case "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationProtocol":
-                        {
-                            ProcessNegotiationProtocolNotification(notification);
-                        }
-                        break;
-                    default:
-                        continue;
+                    switch (notification.NotificationJobSystemName)
+                    {
+                        case "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationDate":
+                            {
+                                ProcessNegotiationDateNotification(notification);
+                            }
+                            break;
+                        case "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationProtocol":
+                            {
+                                ProcessNegotiationProtocolNotification(notification);
+                            }
+                            break;
+                        case "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationOrder":
+                            {
+                                ProcessNegotiationOrderNotification(notification);
+                            }
+                            break;
+                        default:
+                            continue;
+                    }
+                }
+                catch (Exception ex)
+                {
+                    _logger.Error(
+                        String.Format(
+                            "Fehler bei Mail-Benachrichtigung in \"{0}\" für \"{1}", SystemName, notification.NotificationJobSystemName), ex);
                 }
             }
         }
@@ -182,8 +207,11 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
             var ageDays = _configurationService.TryGetConfigItemValue<int>(
                 "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationDate.AgeDays", 56);
 
-            var stateSetId = _configurationService.TryGetConfigItemValue<int>(
-                "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationDate.StateSet", 2);
+            var stateSetId = _configurationService.TryGetConfigItemValue<int[]>(
+                "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationDate.StateSet", new[] { 13 })[0];
+
+            var stateConditionIds = _configurationService.TryGetConfigItemValue<int[]>(
+                "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationDate.StateCondition", new[] { 1, 5, 6, 12 });
 
             var interval = _configurationService.TryGetConfigItemValue<int>(
                 "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationDate.Interval", 2);
@@ -198,13 +226,14 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
                 .Where(a => a.OfferingDate.HasValue &&
                             (DateTime.Now - a.OfferingDate).Value.Days >= ageDays &&
                             a.StateId != stateSetId && a.StateId != finishStateId &&
-                            a.NegotiationDate == null)
+                            stateConditionIds.Contains(a.StateId.Value) && a.NegotiationDate == null)
                 .ToList();
 
             var currentCalendarWeek = GetCalendarWeek(DateTime.Now);
 
             appendices = appendices
-                .Where(a => ((currentCalendarWeek - GetCalendarWeek(a.OfferingDate.Value) % interval == 0)))
+                .Where(a => (currentCalendarWeek - GetCalendarWeek(a.OfferingDate.Value) % interval == 0) ||
+                            (currentCalendarWeek - GetCalendarWeek(a.OfferingDate.Value) < 0))
                 .ToList();
 
             if (appendices.Any())
@@ -224,7 +253,7 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
 
         /// <summary>
         /// Sets the corresponding status for appendices which negotiation date is older than N weeks and notifies 
-        /// the correspondig recipients
+        /// the correspondig recipients when the protocol does not exist
         /// </summary>
         /// <param name="mailNotification">The notification which shall be generated.</param>
         private void ProcessNegotiationProtocolNotification(MailNotification mailNotification)
@@ -232,9 +261,6 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
             var ageDays = _configurationService.TryGetConfigItemValue<int>(
                 "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationProtocol.AgeDays", 14);
 
-            var stateConditionId = _configurationService.TryGetConfigItemValue<int>(
-                "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationProtocol.StateCondition", 3);
-
             var interval = _configurationService.TryGetConfigItemValue<int>(
                 "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationProtocol.Interval", 2);
 
@@ -253,7 +279,8 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
             var currentCalendarWeek = GetCalendarWeek(DateTime.Now);
 
             appendices = appendices
-                .Where(a => ((currentCalendarWeek - GetCalendarWeek(a.OfferingDate.Value) % interval == 0)))
+                .Where(a => (currentCalendarWeek - GetCalendarWeek(a.OfferingDate.Value) % interval == 0) ||
+                            (currentCalendarWeek - GetCalendarWeek(a.OfferingDate.Value) < 0))
                 .ToList();
 
             if (appendices.Any())
@@ -264,6 +291,46 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
             }
         }
 
+        /// <summary>
+        /// Sets the corresponding status for appendices which negotiation date is older than N weeks and notifies 
+        /// the correspondig recipients when the order number is still missing
+        /// </summary>
+        /// <param name="mailNotification">The notification which shall be generated.</param>
+        private void ProcessNegotiationOrderNotification(MailNotification mailNotification)
+        {
+            var ageDays = _configurationService.TryGetConfigItemValue<int>(
+                "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationOrder.AgeDays", 14);
+
+            var interval = _configurationService.TryGetConfigItemValue<int>(
+                "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin.ProcessNegotiationOrder.Interval", 2);
+
+            interval = interval == 0
+                ? 1
+                : interval;
+
+            var finishStateId = _appendixService.GetFinishState().Id;
+
+            var appendices = _appendixService.GetAllAppendices()
+                .Where(a => a.NegotiationDate.HasValue &&
+                            (DateTime.Now - a.NegotiationDate).Value.Days >= ageDays &&
+                            String.IsNullOrEmpty(a.OrderNumber) && a.StateId != finishStateId)
+                .ToList();
+
+            var currentCalendarWeek = GetCalendarWeek(DateTime.Now);
+
+            appendices = appendices
+                .Where(a => (currentCalendarWeek - GetCalendarWeek(a.OfferingDate.Value) % interval == 0) ||
+                            (currentCalendarWeek - GetCalendarWeek(a.OfferingDate.Value) < 0))
+                .ToList();
+
+            if (appendices.Any())
+            {
+                var mailBody = GenerateNegotiationOrderMailBody(appendices);
+
+                SendNotification(mailNotification, "Autom. Übersicht verhandelte Nachträge o. Bestellnummer", mailBody);
+            }
+        }
+
         #endregion
 
         #region Mail body generation
@@ -301,7 +368,7 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
         }
 
         /// <summary>
-        /// Generates a mail body with a list of all appendices with a negotiation date later than N weeks
+        /// Generates a mail body with a list of all appendices with a negotiation date later than N weeks and not existing protocol
         /// </summary>
         /// <param name="appendices">Appendices matching that criteria.</param>
         public string GenerateNegotiationProtocolMailBody(IEnumerable<Appendix> appendices)
@@ -325,7 +392,46 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
             {
                 appendicesList += String.Format(
                     "<li>Nachtrag <b>\"{0}\"</b> in Baustelle <b>\"{1}\"</b> - Verhandlungsdatum: <b>{2:dd.MM.yyyy}</b>",
-                    appendix.CustomNumber, appendix.Site.CustomNumber, appendix.NegotiationDate);
+                    appendix.CustomNumber, 
+                    appendix.Site == null
+                        ? String.Empty
+                        : appendix.Site.CustomNumber, 
+                    appendix.NegotiationDate);
+            }
+
+            return String.Format(template, appendicesList);
+        }
+
+        /// <summary>
+        /// Generates a mail body with a list of all appendices with a negotiation date later than N weeks and not existing order number
+        /// </summary>
+        /// <param name="appendices">Appendices matching that criteria.</param>
+        public string GenerateNegotiationOrderMailBody(IEnumerable<Appendix> appendices)
+        {
+            var template =
+                "<html>" +
+                "   <body>" +
+                "       <h3>Übersicht \"Verhandelte Nachträge ohne Bestellnummer\"</h3>" +
+                "       <p>Folgende Nachträge haben ein Verhandlungstermin älter als zwei Wochen, jedoch noch keine Bestellnummer:" +
+                "       <ul>" +
+                "           {0}" +
+                "       </ul>" +
+                "   </body>" +
+                "</html>";
+
+            if (!appendices.Any()) return String.Format(template, String.Empty);
+
+            var appendicesList = String.Empty;
+
+            foreach (var appendix in appendices)
+            {
+                appendicesList += String.Format(
+                    "<li>Nachtrag <b>\"{0}\"</b> in Baustelle <b>\"{1}\"</b> - Verhandlungsdatum: <b>{2:dd.MM.yyyy}</b>",
+                    appendix.CustomNumber,
+                    appendix.Site == null
+                        ? String.Empty
+                        : appendix.Site.CustomNumber,
+                    appendix.NegotiationDate);
             }
 
             return String.Format(template, appendicesList);

+ 33 - 14
GreenTree.Nachtragsmanagement.Web/Implementations/DeviationNotificationPlugin.cs

@@ -14,6 +14,7 @@ using System.Globalization;
 using GreenTree.Nachtragsmanagement.Services.Deviation;
 using GreenTree.Nachtragsmanagement.Core.Domain.Deviation;
 using Quartz;
+using GreenTree.Nachtragsmanagement.Services.Logging;
 
 namespace GreenTree.Nachtragsmanagement.Web.Implementations
 {
@@ -24,6 +25,7 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
         private readonly IUserService _userService;
         private readonly IConfigurationService _configurationService;
         private readonly IDeviationService _deviationService;
+        private readonly IServiceLogger _logger;
 
         #endregion
 
@@ -108,11 +110,13 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
         public DeviationNotificationPlugin(
             IUserService userService,
             IConfigurationService configurationService,
-            IDeviationService deviationService)
+            IDeviationService deviationService,
+            IServiceLogger logger)
         {
             _userService = userService;
             _configurationService = configurationService;
             _deviationService = deviationService;
+            _logger = logger;
         }
 
         #region Quartz implmentation
@@ -128,8 +132,6 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
 
             var mailNotifications = context.JobDetail.JobDataMap.Get("MailNotifications") as IEnumerable<MailNotification>;
 
-            if (mailNotifications == null) return;
-
             ProcessNotifications(mailNotifications);
         }
 
@@ -143,19 +145,28 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
         /// <param name="mailNotifications">The notifications which shall be generated.</param>
         public void ProcessNotifications(IEnumerable<MailNotification> mailNotifications)
         {
-            if (mailNotifications == null || !mailNotifications.Any()) return;
+            if (mailNotifications == null || (mailNotifications != null && !mailNotifications.Any())) return;
 
             foreach (var notification in mailNotifications)
             {
-                switch (notification.NotificationJobSystemName)
+                try
+                {
+                    switch (notification.NotificationJobSystemName)
+                    {
+                        case "GreenTree.Nachtragsmanagement.DeviationNotificationPlugin.ProcessDeviationReceipt":
+                            {
+                                ProcessDeviationReceiptNotification(notification);
+                            }
+                            break;
+                        default:
+                            continue;
+                    }
+                }
+                catch (Exception ex)
                 {
-                    case "GreenTree.Nachtragsmanagement.DeviationNotificationPlugin.ProcessDeviationReceipt":
-                        {
-                            ProcessDeviationReceiptNotification(notification);
-                        }
-                        break;
-                    default:
-                        continue;
+                    _logger.Error(
+                        String.Format(
+                            "Fehler bei Mail-Benachrichtigung in \"{0}\" für \"{1}", SystemName, notification.NotificationJobSystemName), ex);
                 }
             }
         }
@@ -233,7 +244,11 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
                         "<li>Vertragsabweichung <b>\"{0}\"</b> " +
                         "in Baustelle <b>\"{1}\"</b> - " +
                         "Einreichdatum: <b>{2:dd.MM.yyyy} ({3} Tage)</b>",
-                        deviation.CustomNumber, deviation.Site.CustomNumber, deviation.ReceiptDate,
+                        deviation.CustomNumber, 
+                        deviation.Site == null
+                            ? String.Empty
+                            : deviation.Site.CustomNumber, 
+                        deviation.ReceiptDate,
                         (DateTime.Now - deviation.ReceiptDate).Value.Days);
                 }
             }
@@ -257,7 +272,11 @@ namespace GreenTree.Nachtragsmanagement.Web.Implementations
                         "<li>Vertragsabweichung <b>\"{0}\"</b> " +
                         "in Baustelle <b>\"{1}\"</b> - " +
                         "Einreichdatum: <b>{2:dd.MM.yyyy} ({3} Tage)</b>",
-                        deviation.CustomNumber, deviation.Site.CustomNumber, deviation.ReceiptDate,
+                        deviation.CustomNumber,
+                        deviation.Site == null
+                            ? String.Empty
+                            : deviation.Site.CustomNumber,
+                        deviation.ReceiptDate,
                         (DateTime.Now - deviation.ReceiptDate).Value.Days);
                 }
             }

+ 53 - 6
GreenTree.Nachtragsmanagement.Web/Models/Config/ConfigItemDataModel.cs

@@ -1,4 +1,6 @@
-using System;
+using GreenTree.Nachtragsmanagement.Services.Configuration;
+using GreenTree.Nachtragsmanagement.Web.Extensions;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Web;
@@ -23,6 +25,8 @@ namespace GreenTree.Nachtragsmanagement.Web.Models.Config
         public string TypeFullName { get; set; }
         public string TypeDescription { get; set; }
         public string Value { get; set; }
+        public bool IsValueCollection { get; set; }
+        public string[] Values { get; set; }
         public string Description { get; set; }
 
         public static ConfigItemDataModel FromConfigItem(Core.Domain.Config.ConfigItem configItemEntity, bool newWhenIsNull)
@@ -36,27 +40,70 @@ namespace GreenTree.Nachtragsmanagement.Web.Models.Config
             if (configItemEntity == null && !newWhenIsNull)
                 throw new ArgumentNullException("configItemEntity", "Cannot create ConfigItemDataModel from NULL configItem entity.");
 
-            return new ConfigItemDataModel
+            var configItemModel = new ConfigItemDataModel
             {
                 Id = configItemEntity.Id,
                 Name = configItemEntity.Name,
                 TypeFullName = configItemEntity.TypeFullName,
-                TypeDescription = FullTypeTranslations[configItemEntity.TypeFullName],
-                Value = configItemEntity.Value,
                 Description = configItemEntity.Description
             };
+
+            if (FullTypeTranslations.ContainsKey(configItemEntity.TypeFullName))
+            {
+                configItemModel.Value = configItemEntity.Value;
+                configItemModel.TypeDescription = FullTypeTranslations[configItemEntity.TypeFullName];
+                configItemModel.IsValueCollection = false;
+            }
+            else
+            {
+                var configurationReference = ConfigurationHelper.GetConfigurationReferenceByName(configItemEntity.TypeFullName);
+
+                if (configurationReference != null)
+                {
+                    configItemModel.TypeDescription = configurationReference.DisplayName;
+                    configItemModel.IsValueCollection = configurationReference.IsMultipleSelection;
+                    configItemModel.Value = configItemEntity.Value;
+
+                    if (configurationReference.IsMultipleSelection)
+                        configItemModel.Values = configurationReference.TransformValueToValueCollection(configItemEntity.Value);
+                }
+                else
+                {
+                    configItemModel.TypeDescription = "Unbekannt";
+                }
+            }
+
+            return configItemModel;
         }
 
         public Core.Domain.Config.ConfigItem ToConfigItem()
         {
-            return new Core.Domain.Config.ConfigItem
+            var configItem = new Core.Domain.Config.ConfigItem
             {
                 Id = this.Id,
                 Name = this.Name,
                 TypeFullName = this.TypeFullName,
-                Value = this.Value,
                 Description = this.Description
             };
+
+            if (FullTypeTranslations.ContainsKey(this.TypeFullName))
+            {
+                configItem.Value = this.Value;
+            }
+            else
+            {
+                var configurationReference = ConfigurationHelper.GetConfigurationReferenceByName(this.TypeFullName);
+
+                if (configurationReference != null)
+                {
+                    if (configurationReference.IsMultipleSelection)
+                        configItem.Value = configurationReference.TransformValueCollectionToValue(this.Values);
+                    else
+                        configItem.Value = this.Value;
+                }
+            }
+
+            return configItem;
         }
     }
 }

+ 26 - 1
GreenTree.Nachtragsmanagement.Web/Validation/Config/ConfigItemDataModelValidator.cs

@@ -1,4 +1,8 @@
-using FluentValidation;
+using Autofac;
+using FluentValidation;
+using GreenTree.Nachtragsmanagement.Core;
+using GreenTree.Nachtragsmanagement.Services.Configuration;
+using GreenTree.Nachtragsmanagement.Services.Misc;
 using GreenTree.Nachtragsmanagement.Web.Models.Config;
 using GreenTree.Nachtragsmanagement.Web.Models.Deviation;
 using System;
@@ -19,6 +23,27 @@ namespace GreenTree.Nachtragsmanagement.Web.Validation.Config
             RuleFor(m => m.TypeFullName)
                 .NotEmpty()
                     .WithMessage("Der Wertetyp wird benötigt");
+
+            RuleFor(m => m)
+                .Must(m => NameDoesNotExist(m))
+                    .WithMessage("Ein Konfigurationselement mit diesem Namen existiert bereits.");
+        }
+
+        private bool NameDoesNotExist(ConfigItemDataModel model)
+        {
+            var configService = Singleton<IContainer>.Instance.Resolve<IConfigurationService>();
+
+            if (model == null) return false;
+
+            var existingConfigItem = configService.GetConfigItemByName(model.Name);
+
+            if (existingConfigItem != null && existingConfigItem.Id == model.Id) return true;
+
+            if (existingConfigItem != null && existingConfigItem.Id != model.Id) return false;
+
+            if (existingConfigItem == null) return true;
+
+            return false;
         }
     }
 }

+ 1 - 1
GreenTree.Nachtragsmanagement.Web/Views/Config/View.cshtml

@@ -105,7 +105,7 @@
 @Html.Partial("~/Views/Shared/_PopupDialogYesNo.cshtml", new GreenTree.Nachtragsmanagement.Web.Models.Global.YesNoDialogModel
 {
 	PopupName = "devPopupControlDeleteConfigItem",
-	Content = "<div class='dialogTextConfigItem' style='padding: 12px'>Sind Sie sicher, dass Sie die Benachrichtigung " +
+	Content = "<div class='dialogTextConfigItem' style='padding: 12px'>Sind Sie sicher, dass Sie die Einstellung " +
 			  "\"{configItem}\" löschen möchten?</div>",
 	HeaderText = "\"{configItem}\" löschen",
 	YesFunction = "function (s, e) { deleteConfigItem(); }",

+ 14 - 2
GreenTree.Nachtragsmanagement.Web/Views/Config/_ConfigItemEditPartial.cshtml

@@ -50,6 +50,8 @@
 	{
 		if (Model.Name.Length > 40)
 			s.HeaderText = "\"" + Model.Name.Substring(0, 15) + " ... " + Model.Name.Substring(Model.Name.Length - 15, 15) + "\" bearbeiten";
+		else
+			s.HeaderText = "\"" + Model.Name + "\" bearbeiten";
 	}
 
 	s.Modal = true;
@@ -77,15 +79,21 @@
 				t.Width = new Unit(100, UnitType.Percentage);
 			}).Render();
 
-			ViewContext.Writer.Write(Html.CustomLabelFor(m => m.TypeFullName, "Datentyp:"));
+			ViewContext.Writer.Write(Html.CustomLabelFor(m => m.TypeFullName, "Daten- / Referenztyp:"));
 			ViewContext.Writer.Write(Html.ValidationMessageFor(m => m.TypeFullName).ToHtmlString());
 			Html.DevExpress().ComboBoxFor(m => m.TypeFullName, t =>
 			{
 				t.Width = new Unit(60, UnitType.Percentage);
+
 				t.Properties.Items.AddRange(
 					GreenTree.Nachtragsmanagement.Web.Models.Config.ConfigItemDataModel.FullTypeTranslations
 						.Select(f => new ListEditItem(f.Value, f.Key))
 						.ToList());
+				t.Properties.Items.AddRange(
+					GreenTree.Nachtragsmanagement.Services.Configuration.ConfigurationHelper.GetAllConfigurationReferencesDisplayList()
+						.Select(f => new ListEditItem(f.Value, f.Key))
+						.ToList());
+
 				t.Properties.ClientSideEvents.SelectedIndexChanged = "function (s, e) { getValueTypePartialEdit(); }";
 			}).Render();
 
@@ -93,7 +101,11 @@
 			{
 				ViewContext.Writer.Write(Html.CustomLabelFor(m => m.Value, "Wert:"));
 				ViewContext.Writer.Write(Html.ValidationMessageFor(m => m.Value).ToHtmlString());
-				Html.RenderPartial("~/Views/Config/_ConfigItemValueEditPartial.cshtml", Model);
+
+				if (!String.IsNullOrEmpty(Model.TypeFullName) && Model.TypeFullName.StartsWith("ConfigurationReference"))
+					Html.RenderPartial("~/Views/Config/_ConfigItemReferenceEditPartial.cshtml", Model);
+				else
+					Html.RenderPartial("~/Views/Config/_ConfigItemValueEditPartial.cshtml", Model);
 			}
 			ViewContext.Writer.Write("</div>");
 

+ 2 - 0
GreenTree.Nachtragsmanagement.Web/Views/Config/_ConfigItemGridPartial.cshtml

@@ -20,6 +20,8 @@
 			? 400
 			: (int)ViewData["ScrollHeight"];
 	s.SettingsPager.AlwaysShowPager = true;
+	s.SettingsPager.PageSizeItemSettings.Visible = true;
+	s.SettingsPager.PageSize = 50;
 
 	if (userContext.CurrentUser.HasFunction("Misc-ConfigItems-Edit"))
 	{

+ 75 - 0
GreenTree.Nachtragsmanagement.Web/Views/Config/_ConfigItemReferenceEditPartial.cshtml

@@ -0,0 +1,75 @@
+@using GreenTree.Nachtragsmanagement.Web.Extensions
+
+@model GreenTree.Nachtragsmanagement.Web.Models.Config.ConfigItemDataModel
+
+<div class="configItemValueEdit">
+
+	<script type="text/javascript">
+
+		var textSeparator = ", ";
+
+		function onListBoxSelectionChanged(s, e) {
+			updateText();
+		}
+
+		function updateText() {
+			var selectedItems = Values.GetSelectedItems();
+			devDropDownListConfigItemValues.SetText(getSelectedItemsText(selectedItems));
+		}
+
+		function getSelectedItemsText(items) {
+			var texts = [];
+			for (var i = 0; i < items.length; i++)
+				if (items[i].index != 0)
+					texts.push(items[i].text);
+			return texts.join(textSeparator);
+		}
+
+	</script>
+
+	@if (Model.IsValueCollection)
+	{
+		Html.DevExpress().DropDownEdit(t =>
+		{
+			t.Name = "devDropDownListConfigItemValues";
+			t.Width = new Unit(100, UnitType.Percentage);
+
+			t.SetDropDownWindowTemplateContent(l =>
+			{
+				Html.DevExpress().ListBox(lb =>
+				{
+					lb.Name = "Values";
+					lb.Width = new Unit(100, UnitType.Percentage);
+					lb.Properties.TextField = "Text";
+					lb.Properties.ValueField = "Value";
+					lb.Properties.SelectionMode = ListEditSelectionMode.CheckColumn;
+					lb.ControlStyle.Border.BorderStyle = BorderStyle.None;
+					lb.PreRender = (sender, e) =>
+					{
+						var listBox = sender as MVCxListBox;
+
+						foreach (ListEditItem listItem in listBox.Items)
+						{
+							if (Model.Values == null || !Model.Values.Any(m => m == (string)listItem.Value)) continue;
+
+							listItem.Selected = true;
+						}
+					};
+					lb.Properties.ClientSideEvents.SelectedIndexChanged = "function (s, e) { onListBoxSelectionChanged(s, e); }";
+				}).BindList(ViewData["ConfigReferenceValues"]).Render();
+			});
+
+			t.Properties.ClientSideEvents.Init = "function (s, e) { s.SetText(getSelectedItemsText(Values.GetSelectedItems())); }";
+		}).Bind(ViewData["ConfigReferenceText"]).Render();
+	}
+	else
+	{
+		Html.DevExpress().ComboBoxFor(m => m.Value, t =>
+		{
+			t.Width = new Unit(100, UnitType.Percentage);
+			t.Properties.TextField = "Text";
+			t.Properties.ValueField = "Value";
+		}).BindList(ViewData["ConfigReferenceValues"]).GetHtml();
+	}
+
+</div>

+ 8 - 4
GreenTree.Nachtragsmanagement.Web/Views/Home/Index.cshtml

@@ -60,15 +60,19 @@
 
 	function callCustomEventListener(name) {
 		for (var i = 0; i < eventListeners.length; i++) {
-			eventListeners[i].func();
-			eventListeners.splice(i, 1);
+			if (eventListeners[i].name == name) {
+				eventListeners[i].func();
+				eventListeners.splice(i, 1);
+			}
 		}
 	}
 
 	function callCustomEventListener(name, params) {
 		for (var i = 0; i < eventListeners.length; i++) {
-			eventListeners[i].func(params);
-			eventListeners.splice(i, 1);
+			if (eventListeners[i].name == name) {
+				eventListeners[i].func(params);
+				eventListeners.splice(i, 1);
+			}
 		}
 	}