Arne Diekmann 8 лет назад
Родитель
Сommit
fd41e4bb85
33 измененных файлов с 1034 добавлено и 334 удалено
  1. 3 23
      GreenTree.Nachtragsmanagement.Core/Domain/Misc/MailNotification.cs
  2. 0 16
      GreenTree.Nachtragsmanagement.Core/Domain/Misc/NotificationEvent.cs
  3. 0 16
      GreenTree.Nachtragsmanagement.Core/Domain/Misc/NotificationEventType.cs
  4. 1 2
      GreenTree.Nachtragsmanagement.Core/GreenTree.Nachtragsmanagement.Core.csproj
  5. 38 0
      GreenTree.Nachtragsmanagement.Core/Plugins/INotificationPlugin.cs
  6. 0 6
      GreenTree.Nachtragsmanagement.Data/AppendixObjectContext.cs
  7. 0 2
      GreenTree.Nachtragsmanagement.Data/GreenTree.Nachtragsmanagement.Data.csproj
  8. 1 12
      GreenTree.Nachtragsmanagement.Data/Mapping/Misc/MailNotificationMap.cs
  9. 0 22
      GreenTree.Nachtragsmanagement.Data/Mapping/Misc/NotificationEventMap.cs
  10. 0 22
      GreenTree.Nachtragsmanagement.Data/Mapping/Misc/NotificationEventTypeMap.cs
  11. 8 0
      GreenTree.Nachtragsmanagement.Services/GreenTree.Nachtragsmanagement.Services.csproj
  12. 0 76
      GreenTree.Nachtragsmanagement.Services/Misc/IMiscService.cs
  13. 17 0
      GreenTree.Nachtragsmanagement.Services/Misc/INotificationService.cs
  14. 1 123
      GreenTree.Nachtragsmanagement.Services/Misc/MiscService.cs
  15. 26 0
      GreenTree.Nachtragsmanagement.Services/Misc/NotificationService.cs
  16. 2 0
      GreenTree.Nachtragsmanagement.Services/packages.config
  17. 13 0
      GreenTree.Nachtragsmanagement.Web.Framework/ApplicationContext.cs
  18. 112 0
      GreenTree.Nachtragsmanagement.Web/Controllers/AppendixController.cs
  19. 3 0
      GreenTree.Nachtragsmanagement.Web/Extensions/GridViewSettingsHelper.cs
  20. 3 0
      GreenTree.Nachtragsmanagement.Web/Global.asax.cs
  21. 19 0
      GreenTree.Nachtragsmanagement.Web/GreenTree.Nachtragsmanagement.Web.csproj
  22. 9 1
      GreenTree.Nachtragsmanagement.Web/Models/Appendix/AppendixDataModel.cs
  23. 49 0
      GreenTree.Nachtragsmanagement.Web/Models/Appendix/InvoiceDataModel.cs
  24. 87 0
      GreenTree.Nachtragsmanagement.Web/Scheduling/AppendixNotificationPlugin.cs
  25. 12 0
      GreenTree.Nachtragsmanagement.Web/Scheduling/JobScheduler.cs
  26. 12 0
      GreenTree.Nachtragsmanagement.Web/Scheduling/JobWorker.cs
  27. 28 0
      GreenTree.Nachtragsmanagement.Web/Validation/Appendix/InvoiceDataModelValidator.cs
  28. 1 0
      GreenTree.Nachtragsmanagement.Web/Validation/AppendixValidatorFactory.cs
  29. 95 13
      GreenTree.Nachtragsmanagement.Web/Views/Appendices/_AppendixEditPartial.cshtml
  30. 98 0
      GreenTree.Nachtragsmanagement.Web/Views/Appendices/_InvoiceEditPartial.cshtml
  31. 29 0
      GreenTree.Nachtragsmanagement.Web/Views/Appendices/_InvoiceListPartial.cshtml
  32. 364 0
      GreenTree.Nachtragsmanagement.Web/job_scheduling_data_2_0.xsd
  33. 3 0
      GreenTree.Nachtragsmanagement.Web/packages.config

+ 3 - 23
GreenTree.Nachtragsmanagement.Core/Domain/Misc/MailNotification.cs

@@ -11,41 +11,21 @@ namespace GreenTree.Nachtragsmanagement.Core.Domain.Misc
         #region Fields
 
         /// <summary>
-        /// Invoices related to the appendix
+        /// Users related to the notification
         /// </summary>
         private ICollection<User.User> _users;
 
         #endregion
 
-        /// <summary>
-        /// Id of the corresponding NotificationEventType
-        /// </summary>
-        public int? NotificationEventTypeId { get; set; }
-
-        /// <summary>
-        /// Corresponding NotificationEventType
-        /// </summary>
-        public NotificationEventType NotificationEventType { get; set; }
-
         /// <summary>
         /// The cron expression if the notification will be sent by time
         /// </summary>
         public string CronExpression { get; set; }
 
         /// <summary>
-        /// Id of the corresponding NotificationEvent
-        /// </summary>
-        public int? NotificationEventId { get; set; }
-
-        /// <summary>
-        /// Corresponding NotificationEvent
-        /// </summary>
-        public NotificationEvent NotificationEvent { get; set; }
-
-        /// <summary>
-        /// The logic by which the notification is generated
+        /// The system name of the corresponding plugin generating the notification
         /// </summary>
-        public string NotificationLogicType { get; set; }
+        public string NotificationPluginSystemName { get; set; }
 
         /// <summary>
         /// Determines if data is daily summarized before notificated

+ 0 - 16
GreenTree.Nachtragsmanagement.Core/Domain/Misc/NotificationEvent.cs

@@ -1,16 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace GreenTree.Nachtragsmanagement.Core.Domain.Misc
-{
-    public class NotificationEvent : BaseEntity
-    {
-        /// <summary>
-        /// Description
-        /// </summary>
-        public string Description { get; set; }
-    }
-}

+ 0 - 16
GreenTree.Nachtragsmanagement.Core/Domain/Misc/NotificationEventType.cs

@@ -1,16 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace GreenTree.Nachtragsmanagement.Core.Domain.Misc
-{
-    public class NotificationEventType : BaseEntity
-    {
-        /// <summary>
-        /// Description
-        /// </summary>
-        public string Description { get; set; }
-    }
-}

+ 1 - 2
GreenTree.Nachtragsmanagement.Core/GreenTree.Nachtragsmanagement.Core.csproj

@@ -99,8 +99,6 @@
     <Compile Include="Domain\Invoice\Invoice.cs" />
     <Compile Include="Domain\Misc\DbContextSpec.cs" />
     <Compile Include="Domain\Misc\MailNotification.cs" />
-    <Compile Include="Domain\Misc\NotificationEvent.cs" />
-    <Compile Include="Domain\Misc\NotificationEventType.cs" />
     <Compile Include="Domain\Site\Site.cs" />
     <Compile Include="Domain\User\Function.cs" />
     <Compile Include="Domain\User\Role.cs" />
@@ -108,6 +106,7 @@
     <Compile Include="ITypeFinder.cs" />
     <Compile Include="IWebHelper.cs" />
     <Compile Include="Plugins\BasePlugin.cs" />
+    <Compile Include="Plugins\INotificationPlugin.cs" />
     <Compile Include="Plugins\IPlugin.cs" />
     <Compile Include="Plugins\IPluginFinder.cs" />
     <Compile Include="Plugins\LoadPluginsMode.cs" />

+ 38 - 0
GreenTree.Nachtragsmanagement.Core/Plugins/INotificationPlugin.cs

@@ -0,0 +1,38 @@
+using GreenTree.Nachtragsmanagement.Core.Domain.Misc;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace GreenTree.Nachtragsmanagement.Core.Plugins
+{
+    public interface INotificationPlugin
+    {
+        /// <summary>
+        /// Id
+        /// </summary>
+        Guid Id { get; }
+
+        /// <summary>
+        /// System name
+        /// </summary>
+        string SystemName { get; }
+
+        /// <summary>
+        /// Displayed name
+        /// </summary>
+        string Name { get; }
+
+        /// <summary>
+        /// Further description on how this plugin works
+        /// </summary>
+        string Description { get; }
+
+        /// <summary>
+        /// Process all mail notifications registered for that plugin
+        /// </summary>
+        /// <param name="mailNotifications">The notifications which shall be generated.</param>
+        void ProcessNotifications(IEnumerable<MailNotification> mailNotifications);
+    }
+}

+ 0 - 6
GreenTree.Nachtragsmanagement.Data/AppendixObjectContext.cs

@@ -53,8 +53,6 @@ namespace GreenTree.Nachtragsmanagement.Data
             var stateSet = Set<State>();
             var appendixSet = Set<Appendix>();
             var mailNotificationSet = Set<MailNotification>();
-            var notificationEventSet = Set<NotificationEvent>();
-            var notificationEventTypeSet = Set<NotificationEventType>();
             var dbContextSpecSet = Set<DbContextSpec>();
 
             _dbSets.AddRange(
@@ -73,8 +71,6 @@ namespace GreenTree.Nachtragsmanagement.Data
                     stateSet,
                     appendixSet,
                     mailNotificationSet,
-                    notificationEventSet,
-                    notificationEventTypeSet,
                     dbContextSpecSet
                 }
             );
@@ -109,8 +105,6 @@ namespace GreenTree.Nachtragsmanagement.Data
             modelBuilder.Configurations.Add(new CategoryValueMap());
             modelBuilder.Configurations.Add(new AppendixMap());
             modelBuilder.Configurations.Add(new MailNotificationMap());
-            modelBuilder.Configurations.Add(new NotificationEventMap());
-            modelBuilder.Configurations.Add(new NotificationEventTypeMap());
             modelBuilder.Configurations.Add(new DbContextSpecMap());
 
             base.OnModelCreating(modelBuilder);

+ 0 - 2
GreenTree.Nachtragsmanagement.Data/GreenTree.Nachtragsmanagement.Data.csproj

@@ -62,8 +62,6 @@
     <Compile Include="Mapping\Invoice\InvoiceMap.cs" />
     <Compile Include="Mapping\Misc\DbContextSpecMap.cs" />
     <Compile Include="Mapping\Misc\MailNotificationMap.cs" />
-    <Compile Include="Mapping\Misc\NotificationEventMap.cs" />
-    <Compile Include="Mapping\Misc\NotificationEventTypeMap.cs" />
     <Compile Include="Mapping\Site\SiteMap.cs" />
     <Compile Include="Mapping\User\FunctionMap.cs" />
     <Compile Include="Mapping\User\RoleMap.cs" />

+ 1 - 12
GreenTree.Nachtragsmanagement.Data/Mapping/Misc/MailNotificationMap.cs

@@ -17,21 +17,10 @@ namespace GreenTree.Nachtragsmanagement.Data.Mapping.Misc
             HasKey(m => m.Id);
 
             Property(m => m.CronExpression);
+            Property(m => m.NotificationPluginSystemName);
             Property(m => m.IsDailySummary);
             Property(m => m.IsWeeklySummary);
             Property(m => m.IsMonthlySummary);
-
-            HasOptional(m => m.NotificationEvent)
-                .WithMany()
-                .HasForeignKey(m => m.NotificationEventId);
-
-            HasOptional(m => m.NotificationEventType)
-                .WithMany()
-                .HasForeignKey(m => m.NotificationEventTypeId);
-
-            HasMany(m => m.Users)
-                .WithMany(u => u.MailNotifications)
-                .Map(m => m.ToTable("UserMailNotifications"));
         }
     }
 }

+ 0 - 22
GreenTree.Nachtragsmanagement.Data/Mapping/Misc/NotificationEventMap.cs

@@ -1,22 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Data.Entity.ModelConfiguration;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using GreenTree.Nachtragsmanagement.Core.Domain.Misc;
-
-namespace GreenTree.Nachtragsmanagement.Data.Mapping.Misc
-{
-    public class NotificationEventMap : EntityTypeConfiguration<NotificationEvent>
-    {
-        public NotificationEventMap()
-        {
-            ToTable("NotificationEvent");
-
-            HasKey(n => n.Id);
-
-            Property(n => n.Description);
-        }
-    }
-}

+ 0 - 22
GreenTree.Nachtragsmanagement.Data/Mapping/Misc/NotificationEventTypeMap.cs

@@ -1,22 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Data.Entity.ModelConfiguration;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using GreenTree.Nachtragsmanagement.Core.Domain.Misc;
-
-namespace GreenTree.Nachtragsmanagement.Data.Mapping.Misc
-{
-    public class NotificationEventTypeMap : EntityTypeConfiguration<NotificationEventType>
-    {
-        public NotificationEventTypeMap()
-        {
-            ToTable("NotificationEventType");
-
-            HasKey(n => n.Id);
-
-            Property(n => n.Description);
-        }
-    }
-}

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

@@ -33,6 +33,12 @@
     <Reference Include="Autofac, Version=4.6.1.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
       <HintPath>..\packages\Autofac.4.6.1\lib\net45\Autofac.dll</HintPath>
     </Reference>
+    <Reference Include="Common.Logging, Version=3.3.1.0, Culture=neutral, PublicKeyToken=af08829b84f0328e, processorArchitecture=MSIL">
+      <HintPath>..\packages\Common.Logging.3.3.1\lib\net40\Common.Logging.dll</HintPath>
+    </Reference>
+    <Reference Include="Common.Logging.Core, Version=3.3.1.0, Culture=neutral, PublicKeyToken=af08829b84f0328e, processorArchitecture=MSIL">
+      <HintPath>..\packages\Common.Logging.Core.3.3.1\lib\net40\Common.Logging.Core.dll</HintPath>
+    </Reference>
     <Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
     <Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
       <HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
@@ -57,8 +63,10 @@
     <Compile Include="DbContext\IDbContextService.cs" />
     <Compile Include="Deviation\DeviationService.cs" />
     <Compile Include="Deviation\IDeviationService.cs" />
+    <Compile Include="Misc\INotificationService.cs" />
     <Compile Include="Misc\MiscService.cs" />
     <Compile Include="Misc\IMiscService.cs" />
+    <Compile Include="Misc\NotificationService.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Site\ISiteService.cs" />
     <Compile Include="Site\SiteService.cs" />

+ 0 - 76
GreenTree.Nachtragsmanagement.Services/Misc/IMiscService.cs

@@ -46,81 +46,5 @@ namespace GreenTree.Nachtragsmanagement.Services.Misc
         void DeleteMailNotification(MailNotification mailNotification);
 
         #endregion
-
-        #region NotificationEvent
-
-        /// <summary>
-        /// Gets all notificationEvents
-        /// </summary>
-        IList<NotificationEvent> GetAllNotificationEvents();
-
-        /// <summary>
-        /// Gets a notificationEvent by specified Id
-        /// </summary>
-        /// <param name="id">NotificationEvent identifier.</param>
-        NotificationEvent GetNotificationEventById(int id);
-
-        /// <summary>
-        /// Gets all notificationEvents to the specified ids
-        /// </summary>
-        IList<NotificationEvent> GetNotificationEventsByIds(int[] ids);
-
-        /// <summary>
-        /// Insert a notificationEvent
-        /// </summary>
-        /// <param name="notificationEvent">NotificationEvent.</param>
-        void InsertNotificationEvent(NotificationEvent notificationEvent);
-
-        /// <summary>
-        /// Update a notificationEvent
-        /// </summary>
-        /// <param name="notificationEvent">NotificationEvent.</param>
-        void UpdateNotificationEvent(NotificationEvent notificationEvent);
-
-        /// <summary>
-        /// Delete a notificationEvent
-        /// </summary>
-        /// <param name="notificationEvent">NotificationEvent.</param>
-        void DeleteNotificationEvent(NotificationEvent notificationEvent);
-
-        #endregion
-
-        #region NotificationEventType
-
-        /// <summary>
-        /// Gets all notificationEventTypes
-        /// </summary>
-        IList<NotificationEventType> GetAllNotificationEventTypes();
-
-        /// <summary>
-        /// Gets a notificationEventType by specified Id
-        /// </summary>
-        /// <param name="id">NotificationEventType identifier.</param>
-        NotificationEventType GetNotificationEventTypeById(int id);
-
-        /// <summary>
-        /// Gets all notificationEventTypes to the specified ids
-        /// </summary>
-        IList<NotificationEventType> GetNotificationEventTypesByIds(int[] ids);
-
-        /// <summary>
-        /// Insert a notificationEventType
-        /// </summary>
-        /// <param name="notificationEventType">NotificationEventType.</param>
-        void InsertNotificationEventType(NotificationEventType notificationEventType);
-
-        /// <summary>
-        /// Update a notificationEventType
-        /// </summary>
-        /// <param name="notificationEventType">NotificationEventType.</param>
-        void UpdateNotificationEventType(NotificationEventType notificationEventType);
-
-        /// <summary>
-        /// Delete a notificationEventType
-        /// </summary>
-        /// <param name="notificationEventType">NotificationEventType.</param>
-        void DeleteNotificationEventType(NotificationEventType notificationEventType);
-
-        #endregion
     }
 }

+ 17 - 0
GreenTree.Nachtragsmanagement.Services/Misc/INotificationService.cs

@@ -0,0 +1,17 @@
+using GreenTree.Nachtragsmanagement.Core.Plugins;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace GreenTree.Nachtragsmanagement.Services.Misc
+{
+    public interface INotificationService
+    {
+        /// <summary>
+        /// Searches for all implementations of the INotificationPlugin
+        /// </summary>
+        IEnumerable<INotificationPlugin> GetNotificationPlugins();
+    }
+}

+ 1 - 123
GreenTree.Nachtragsmanagement.Services/Misc/MiscService.cs

@@ -13,8 +13,6 @@ namespace GreenTree.Nachtragsmanagement.Services.Misc
         #region Fields
 
         private readonly IRepository<MailNotification> _mailNotificationRepository;
-        private readonly IRepository<NotificationEvent> _notificationEventRepository;
-        private readonly IRepository<NotificationEventType> _notificationEventTypeRepository;
 
         #endregion
 
@@ -24,13 +22,9 @@ namespace GreenTree.Nachtragsmanagement.Services.Misc
         /// Initializes a new instance of the MiscService class
         /// </summary>
         public MiscService(
-            IRepository<MailNotification> mailNotificationRepository,
-            IRepository<NotificationEvent> notificationEventRepository,
-            IRepository<NotificationEventType> notificationEventTypeRepository)
+            IRepository<MailNotification> mailNotificationRepository)
         {
             _mailNotificationRepository = mailNotificationRepository;
-            _notificationEventRepository = notificationEventRepository;
-            _notificationEventTypeRepository = notificationEventTypeRepository;
         }
 
         #endregion
@@ -92,121 +86,5 @@ namespace GreenTree.Nachtragsmanagement.Services.Misc
         }
 
         #endregion
-
-        #region NotificationEvent
-
-        /// <summary>
-        /// Gets all notificationEvents
-        /// </summary>
-        public IList<NotificationEvent> GetAllNotificationEvents()
-        {
-            return _notificationEventRepository.Table.ToList();
-        }
-
-        /// <summary>
-        /// Gets a notificationEvent by specified Id
-        /// </summary>
-        /// <param name="id">NotificationEvent identifier.</param>
-        public NotificationEvent GetNotificationEventById(int id)
-        {
-            return _notificationEventRepository.GetById(id);
-        }
-
-        /// <summary>
-        /// Gets all notificationEvents to the specified ids
-        /// </summary>
-        public IList<NotificationEvent> GetNotificationEventsByIds(int[] ids)
-        {
-            return _notificationEventRepository.Table
-                .Where(r => ids.Contains(r.Id))
-                .ToList();
-        }
-
-        /// <summary>
-        /// Insert a mailNotification
-        /// </summary>
-        /// <param name="notificationEvent">NotificationEvent.</param>
-        public void InsertNotificationEvent(NotificationEvent notificationEvent)
-        {
-            _notificationEventRepository.Insert(notificationEvent);
-        }
-
-        /// <summary>
-        /// Update a notificationEvent
-        /// </summary>
-        /// <param name="notificationEvent">NotificationEvent.</param>
-        public void UpdateNotificationEvent(NotificationEvent notificationEvent)
-        {
-            _notificationEventRepository.Update(notificationEvent);
-        }
-
-        /// <summary>
-        /// Delete a notificationEvent
-        /// </summary>
-        /// <param name="notificationEvent">NotificationEvent.</param>
-        public void DeleteNotificationEvent(NotificationEvent notificationEvent)
-        {
-            _notificationEventRepository.Delete(notificationEvent);
-        }
-
-        #endregion
-
-        #region NotificationEventType
-
-        /// <summary>
-        /// Gets all notificationEventTypes
-        /// </summary>
-        public IList<NotificationEventType> GetAllNotificationEventTypes()
-        {
-            return _notificationEventTypeRepository.Table.ToList();
-        }
-
-        /// <summary>
-        /// Gets a notificationEventType by specified Id
-        /// </summary>
-        /// <param name="id">NotificationEventType identifier.</param>
-        public NotificationEventType GetNotificationEventTypeById(int id)
-        {
-            return _notificationEventTypeRepository.GetById(id);
-        }
-
-        /// <summary>
-        /// Gets all notificationEventTypes to the specified ids
-        /// </summary>
-        public IList<NotificationEventType> GetNotificationEventTypesByIds(int[] ids)
-        {
-            return _notificationEventTypeRepository.Table
-                .Where(r => ids.Contains(r.Id))
-                .ToList();
-        }
-
-        /// <summary>
-        /// Insert a appendix
-        /// </summary>
-        /// <param name="notificationEventType">NotificationEventType.</param>
-        public void InsertNotificationEventType(NotificationEventType notificationEventType)
-        {
-            _notificationEventTypeRepository.Insert(notificationEventType);
-        }
-
-        /// <summary>
-        /// Update a notificationEventType
-        /// </summary>
-        /// <param name="notificationEventType">NotificationEventType.</param>
-        public void UpdateNotificationEventType(NotificationEventType notificationEventType)
-        {
-            _notificationEventTypeRepository.Update(notificationEventType);
-        }
-
-        /// <summary>
-        /// Delete a notificationEventType
-        /// </summary>
-        /// <param name="notificationEventType">NotificationEventType.</param>
-        public void DeleteNotificationEventType(NotificationEventType notificationEventType)
-        {
-            _notificationEventTypeRepository.Delete(notificationEventType);
-        }
-
-        #endregion
     }
 }

+ 26 - 0
GreenTree.Nachtragsmanagement.Services/Misc/NotificationService.cs

@@ -0,0 +1,26 @@
+using GreenTree.Nachtragsmanagement.Core.Plugins;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace GreenTree.Nachtragsmanagement.Services.Misc
+{
+    public class NotificationService : INotificationService
+    {
+        /// <summary>
+        /// Searches for all implementations of the INotificationPlugin
+        /// </summary>
+        public IEnumerable<INotificationPlugin> GetNotificationPlugins()
+        {
+            var type = typeof(INotificationPlugin);
+            var types = AppDomain.CurrentDomain.GetAssemblies()
+                .SelectMany(s => s.GetTypes())
+                .Where(p => type.IsAssignableFrom(p) && !p.IsInterface)
+                .OfType<INotificationPlugin>();
+
+            return types;
+        }
+    }
+}

+ 2 - 0
GreenTree.Nachtragsmanagement.Services/packages.config

@@ -1,5 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
   <package id="Autofac" version="4.6.1" targetFramework="net452" />
+  <package id="Common.Logging" version="3.3.1" targetFramework="net452" />
+  <package id="Common.Logging.Core" version="3.3.1" targetFramework="net452" />
   <package id="Newtonsoft.Json" version="10.0.3" targetFramework="net452" />
 </packages>

+ 13 - 0
GreenTree.Nachtragsmanagement.Web.Framework/ApplicationContext.cs

@@ -111,6 +111,7 @@ namespace GreenTree.Nachtragsmanagement.Web.Framework
             builder.RegisterType<DeviationService>().As<IDeviationService>();
             builder.RegisterType<AppendixService>().As<IAppendixService>();
             builder.RegisterType<MiscService>().As<IMiscService>();
+            builder.RegisterType<NotificationService>().As<INotificationService>();
             builder.RegisterType<PluginFinder>().As<IPluginFinder>();
             builder.RegisterType<WebHelper>().As<IWebHelper>();
             builder.RegisterType<WebAppTypeFinder>().As<ITypeFinder>();
@@ -140,6 +141,18 @@ namespace GreenTree.Nachtragsmanagement.Web.Framework
 
             pluginsControllerBuilder.Update(_appContainer);
 
+            // Register notification plugin types
+            var notificationPluginBuilder = new ContainerBuilder();
+            var notificationPluginService = _appContainer.Resolve<INotificationService>();
+            var notificationPlugins = notificationPluginService.GetNotificationPlugins();
+
+            foreach (var notificationPlugin in notificationPlugins)
+            {
+                notificationPluginBuilder.RegisterType(notificationPlugin.GetType()).As<INotificationPlugin>();
+            }
+
+            notificationPluginBuilder.Update(_appContainer);
+
             DependencyResolver.SetResolver(new AutofacDependencyResolver(_appContainer));
         }
 

+ 112 - 0
GreenTree.Nachtragsmanagement.Web/Controllers/AppendixController.cs

@@ -251,6 +251,118 @@ namespace GreenTree.Nachtragsmanagement.Web.Controllers
 
         #endregion
 
+        #region Invoices
+
+        /// <summary>
+        /// Get JSON data of specific invoice
+        /// </summary>
+        /// <param name="id">Invoice id.</param>
+        public ActionResult GetInvoice(int id = -1)
+        {
+            var invoice = _appendixService.GetInvoiceById(id);
+            if (invoice == null)
+                return new JsonResult
+                {
+                    Data = "notFound",
+                    JsonRequestBehavior = JsonRequestBehavior.AllowGet
+                };
+
+            var invoiceModel = InvoiceDataModel.FromInvoice(invoice, false);
+
+            return new JsonResult
+            {
+                Data = JsonConvert.SerializeObject(invoiceModel),
+                JsonRequestBehavior = JsonRequestBehavior.AllowGet
+            };
+        }
+
+        /// <summary>
+        /// Callback result for Invoice list
+        /// </summary>
+        public ActionResult PartialInvoices(int appendixId = -1)
+        {
+            if (appendixId == -1)
+                return PartialView("~/Views/Appendices/_InvoiceListPartial.cshtml", new AppendixDataModel());
+
+            var appendix = _appendixService.GetAppendixById(appendixId);
+
+            if (appendix == null)
+                return PartialView("~/Views/Appendices/_InvoiceListPartial.cshtml", new AppendixDataModel());
+
+            var appendixDataModel = AppendixDataModel.FromAppendix(appendix, false);
+
+            return PartialView("~/Views/Appendices/_InvoiceListPartial.cshtml", appendixDataModel);
+        }
+
+        /// <summary>
+        /// Partial edit for editing of existing or for new invoice
+        /// </summary>
+        /// <param name="id">Id for existing invoice, otherweise -1.</param>
+        /// <param name="appendixId">Id of corresponding Appendix</param>
+        public ActionResult EditInvoice(int appendixId, int id = -1)
+        {
+            var invoice = _appendixService.GetInvoiceById(id);
+            var invoiceModel = InvoiceDataModel.FromInvoice(invoice, true);
+
+            if (id == -1)
+                invoiceModel.AppendixId = appendixId;
+
+            return PartialView("~/Views/Appendices/_InvoiceEditPartial.cshtml", invoiceModel);
+        }
+
+        /// <summary>
+        /// Partial edit result if ModelState is valid, otherwise simple JSON result for success
+        /// </summary>
+        /// <param name="invoiceModel">Invoice model to be saved.</param>
+        [HttpPost, ValidateInput(false)]
+        public ActionResult EditInvoice(InvoiceDataModel invoiceModel)
+        {
+            if (!ModelState.IsValid)
+                return PartialView("~/Views/Appendices/_InvoiceEditPartial.cshtml", invoiceModel);
+
+            if (invoiceModel.Id == -1)
+            {
+                var invoice = invoiceModel.ToInvoice();
+
+                _appendixService.InsertInvoice(invoice);
+            }
+            else
+            {
+                var invoice = _appendixService.GetInvoiceById(invoiceModel.Id);
+
+                invoice.CustomNumber = invoiceModel.CustomNumber.Value;
+                invoice.Date = invoiceModel.DateTime.Value;
+                invoice.Value = invoiceModel.Value.Value;
+
+                _appendixService.UpdateInvoice(invoice);
+            }
+
+            return new JsonResult
+            {
+                Data = "success"
+            };
+        }
+
+        /// <summary>
+        /// Simple JSON result for deleting a specific invoice
+        /// </summary>
+        /// <param name="id">Invoice id.</param>
+        [HttpPost]
+        public ActionResult DeleteInvoice(int id)
+        {
+            var invoice = _appendixService.GetInvoiceById(id);
+
+            if (invoice != null)
+                _appendixService.DeleteInvoice(invoice);
+
+            return new JsonResult
+            {
+                Data = "success"
+            };
+        }
+
+        #endregion
+
         #region Claims
 
         /// <summary>

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

@@ -88,6 +88,9 @@ namespace GreenTree.Nachtragsmanagement.Web.Extensions
                 });
             });
 
+            //if (_userContext == null)
+            //    _userContext = Core.CommonHelper.UserContext();
+
             if (_userContext.CurrentUser.HasFunction("Site-Sites-Edit"))
             {
                 s.Columns.Add(column =>

+ 3 - 0
GreenTree.Nachtragsmanagement.Web/Global.asax.cs

@@ -25,6 +25,7 @@ using GreenTree.Nachtragsmanagement.Core.Domain.Site;
 using GreenTree.Nachtragsmanagement.Core.Domain.Appendix;
 using GreenTree.Nachtragsmanagement.Services.Appendix;
 using GreenTree.Nachtragsmanagement.Services.Site;
+using GreenTree.Nachtragsmanagement.Core.Plugins;
 
 namespace GreenTree.Nachtragsmanagement.Web
 {
@@ -58,6 +59,8 @@ namespace GreenTree.Nachtragsmanagement.Web
             DevExpress.Web.ASPxWebControl.CallbackError += Application_Error;
 
             GenerateTestData();
+
+            var notificationPlugins = Singleton<IContainer>.Instance.Resolve<IEnumerable<INotificationPlugin>>();
         }
 
         protected void Application_Error(object sender, EventArgs e) 

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

@@ -52,6 +52,12 @@
     <Reference Include="Autofac, Version=4.0.1.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
       <HintPath>..\packages\Autofac.4.0.1\lib\net45\Autofac.dll</HintPath>
     </Reference>
+    <Reference Include="Common.Logging, Version=3.3.1.0, Culture=neutral, PublicKeyToken=af08829b84f0328e, processorArchitecture=MSIL">
+      <HintPath>..\packages\Common.Logging.3.3.1\lib\net40\Common.Logging.dll</HintPath>
+    </Reference>
+    <Reference Include="Common.Logging.Core, Version=3.3.1.0, Culture=neutral, PublicKeyToken=af08829b84f0328e, processorArchitecture=MSIL">
+      <HintPath>..\packages\Common.Logging.Core.3.3.1\lib\net40\Common.Logging.Core.dll</HintPath>
+    </Reference>
     <Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
       <HintPath>..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll</HintPath>
     </Reference>
@@ -72,6 +78,9 @@
     <Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
       <HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
     </Reference>
+    <Reference Include="Quartz, Version=2.6.0.0, Culture=neutral, PublicKeyToken=f6b8c98a402cc8a4, processorArchitecture=MSIL">
+      <HintPath>..\packages\Quartz.2.6.0\lib\net40\Quartz.dll</HintPath>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="System.Data" />
     <Reference Include="System.Drawing" />
@@ -215,6 +224,9 @@
     <Content Include="Scripts\knockout-3.3.0.debug.js" />
     <Content Include="Scripts\knockout-3.3.0.js" />
     <Content Include="packages.config" />
+    <None Include="job_scheduling_data_2_0.xsd">
+      <SubType>Designer</SubType>
+    </None>
     <None Include="Properties\PublishProfiles\greentreestudios.pubxml" />
     <None Include="Scripts\jquery.validate-vsdoc.js" />
     <Content Include="Scripts\modernizr-2.6.2.js" />
@@ -282,6 +294,8 @@
     <Content Include="Views\Appendices\_StateEditPartial.cshtml" />
     <Content Include="Views\Appendices\_StateListPartial.cshtml" />
     <Content Include="Views\Shared\DataEditorTemplates\_CategoriesComboBox.cshtml" />
+    <Content Include="Views\Appendices\_InvoiceEditPartial.cshtml" />
+    <Content Include="Views\Appendices\_InvoiceListPartial.cshtml" />
     <None Include="Web.Debug.config">
       <DependentUpon>Web.config</DependentUpon>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -297,6 +311,9 @@
     <Compile Include="App_Start\FunctionConfig.cs" />
     <Compile Include="App_Start\RouteConfig.cs" />
     <Compile Include="App_Start\WebApiConfig.cs" />
+    <Compile Include="Scheduling\AppendixNotificationPlugin.cs" />
+    <Compile Include="Scheduling\JobScheduler.cs" />
+    <Compile Include="Scheduling\JobWorker.cs" />
     <Compile Include="Controllers\AdminController.cs" />
     <Compile Include="Controllers\AppendixController.cs" />
     <Compile Include="Controllers\AuthController.cs" />
@@ -317,6 +334,7 @@
     <Compile Include="Models\Appendix\AppendixDataModel.cs" />
     <Compile Include="Models\Appendix\CategoryDataModel.cs" />
     <Compile Include="Models\Appendix\CategoryValueDataModel.cs" />
+    <Compile Include="Models\Appendix\InvoiceDataModel.cs" />
     <Compile Include="Models\Appendix\IRequireCategoryDataModel.cs" />
     <Compile Include="Models\Appendix\IRequireStateDataModel.cs" />
     <Compile Include="Models\Appendix\StateDataModel.cs" />
@@ -345,6 +363,7 @@
     <Compile Include="Models\Deviation\DisturbanceValueDataModel.cs" />
     <Compile Include="Validation\Appendix\AppendixDataModelValidator.cs" />
     <Compile Include="Validation\Appendix\CategoryDataModelValidator.cs" />
+    <Compile Include="Validation\Appendix\InvoiceDataModelValidator.cs" />
     <Compile Include="Validation\Appendix\StateDataModelValidator.cs" />
     <Compile Include="Validation\Deviation\KindDataModelValidator.cs" />
     <Compile Include="Validation\Deviation\DisturbanceDataModelValidator.cs" />

+ 9 - 1
GreenTree.Nachtragsmanagement.Web/Models/Appendix/AppendixDataModel.cs

@@ -45,6 +45,9 @@ namespace GreenTree.Nachtragsmanagement.Web.Models.Appendix
                     return String.Join(", ", CategoryValueEntities.Select(d => d.Description));
             }
         }
+        public string InvoiceDescription { get; set; }
+        public ICollection<InvoiceDataModel> Invoices { get; set; }
+
 
         public AppendixDataModel()
         {
@@ -52,6 +55,7 @@ namespace GreenTree.Nachtragsmanagement.Web.Models.Appendix
             DeviationDescriptions = new List<string>();
             CategoryEntities = new List<string>();
             CategoryValueEntities = new List<CategoryValueDataModel>();
+            Invoices = new List<InvoiceDataModel>();
         }
 
         public static AppendixDataModel FromAppendix(Core.Domain.Appendix.Appendix appendixEntity, bool newWhenIsNull)
@@ -130,7 +134,11 @@ namespace GreenTree.Nachtragsmanagement.Web.Models.Appendix
                 DeviationDescription = 
                     String.Join(", ",
                         appendixEntity.Deviations
-                            .Select(d => d.CustomNumber))
+                            .Select(d => d.CustomNumber)),
+                Invoices =
+                    appendixEntity.Invoices
+                        .Select(i => InvoiceDataModel.FromInvoice(i, false))
+                        .ToList()
             };
 
             foreach (var disturbance in model.CategoryValueEntities)

+ 49 - 0
GreenTree.Nachtragsmanagement.Web/Models/Appendix/InvoiceDataModel.cs

@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+
+namespace GreenTree.Nachtragsmanagement.Web.Models.Appendix
+{
+    public class InvoiceDataModel
+    {
+        public int Id { get; set; }
+        public int? CustomNumber { get; set; }
+        public decimal? Value { get; set; }
+        public DateTime? DateTime { get; set; }
+        public int? AppendixId { get; set; }
+
+        public static InvoiceDataModel FromInvoice(Core.Domain.Invoice.Invoice invoiceEntity, bool newWhenIsNull)
+        {
+            if (invoiceEntity == null && newWhenIsNull)
+                return new InvoiceDataModel
+                {
+                    Id = -1
+                };
+
+            if (invoiceEntity == null && !newWhenIsNull)
+                throw new ArgumentNullException("invoiceEntity", "Cannot create InvoiceDataModel from NULL invoice entity.");
+
+            return new InvoiceDataModel
+            {
+                Id = invoiceEntity.Id,
+                AppendixId = invoiceEntity.AppendixId,
+                CustomNumber = invoiceEntity.CustomNumber,
+                DateTime = invoiceEntity.Date,
+                Value = invoiceEntity.Value
+            };
+        }
+
+        public Core.Domain.Invoice.Invoice ToInvoice()
+        {
+            return new Core.Domain.Invoice.Invoice
+            {
+                Id = this.Id,
+                AppendixId = this.AppendixId.Value,
+                CustomNumber = this.CustomNumber.Value,
+                Date = this.DateTime.Value,
+                Value = this.Value.Value
+            };
+        }
+    }
+}

+ 87 - 0
GreenTree.Nachtragsmanagement.Web/Scheduling/AppendixNotificationPlugin.cs

@@ -0,0 +1,87 @@
+using GreenTree.Nachtragsmanagement.Core.Plugins;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+using GreenTree.Nachtragsmanagement.Core.Domain.Misc;
+using GreenTree.Nachtragsmanagement.Services.User;
+
+namespace GreenTree.Nachtragsmanagement.Web.Scheduling
+{
+    public class AppendixNotificationPlugin : INotificationPlugin
+    {
+        #region Services
+
+        private readonly IUserService _userService;
+
+        #endregion
+
+        #region Properties
+
+        /// <summary>
+        /// Id
+        /// </summary>
+        public Guid Id
+        {
+            get
+            {
+                return Guid.Parse("E99CA4A1-B3A9-4AA6-BBAD-9254CEED0A45");
+            }
+        }
+
+        /// <summary>
+        /// System name
+        /// </summary>
+        public string SystemName
+        {
+            get
+            {
+                return "GreenTree.Nachtragsmanagement.AppendixNotificationPlugin";
+            }
+        }
+
+        /// <summary>
+        /// Displayed name
+        /// </summary>
+        public string Name
+        {
+            get
+            {
+                return "Nachtragsbenachrichtigung";
+            }
+        }
+
+        /// <summary>
+        /// Further description on how this plugin works
+        /// </summary>
+        public string Description
+        {
+            get
+            {
+                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.";
+            }
+        } 
+        #endregion
+
+        /// <summary>
+        /// Initializes a new instance of the AppendixNotificationPlugin class
+        /// </summary>
+        public AppendixNotificationPlugin(
+            IUserService userService)
+        {
+            _userService = userService;
+        }
+
+        /// <summary>
+        /// Process all mail notifications registered for that plugin
+        /// </summary>
+        /// <param name="mailNotifications">The notifications which shall be generated.</param>
+        public void ProcessNotifications(IEnumerable<MailNotification> mailNotifications)
+        {
+
+        }
+    }
+}

+ 12 - 0
GreenTree.Nachtragsmanagement.Web/Scheduling/JobScheduler.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+
+namespace GreenTree.Nachtragsmanagement.Web.Classes
+{
+    public class JobScheduler
+    {
+
+    }
+}

+ 12 - 0
GreenTree.Nachtragsmanagement.Web/Scheduling/JobWorker.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+
+namespace GreenTree.Nachtragsmanagement.Web.Classes
+{
+    public class JobWorker
+    {
+
+    }
+}

+ 28 - 0
GreenTree.Nachtragsmanagement.Web/Validation/Appendix/InvoiceDataModelValidator.cs

@@ -0,0 +1,28 @@
+using FluentValidation;
+using GreenTree.Nachtragsmanagement.Web.Models.Appendix;
+using GreenTree.Nachtragsmanagement.Web.Models.Deviation;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+
+namespace GreenTree.Nachtragsmanagement.Web.Validation.Appendix
+{
+    public class InvoiceDataModelValidator : AbstractValidator<InvoiceDataModel>
+    {
+        public InvoiceDataModelValidator()
+        {
+            RuleFor(m => m.CustomNumber)
+                .NotEmpty()
+                    .WithMessage("Rechnungsnummer wird benötigt");
+
+            RuleFor(m => m.DateTime)
+                .NotEmpty()
+                    .WithMessage("Rechnungsdatum wird benötigt");
+
+            RuleFor(m => m.Value)
+                .NotEmpty()
+                    .WithMessage("Rechnungswert wird benötigt");
+        }
+    }
+}

+ 1 - 0
GreenTree.Nachtragsmanagement.Web/Validation/AppendixValidatorFactory.cs

@@ -27,6 +27,7 @@ namespace GreenTree.Nachtragsmanagement.Web.Validation
             validators.Add(typeof(IValidator<DisturbanceDataModel>), new DisturbanceDataModelValidator());
             validators.Add(typeof(IValidator<KindDataModel>), new KindDataModelValidator());
             validators.Add(typeof(IValidator<AppendixDataModel>), new AppendixDataModelValidator());
+            validators.Add(typeof(IValidator<InvoiceDataModel>), new InvoiceDataModelValidator());
             validators.Add(typeof(IValidator<StateDataModel>), new StateDataModelValidator());
             validators.Add(typeof(IValidator<CategoryDataModel>), new CategoryDataModelValidator());
             validators.Add(typeof(IValidator<SiteDataModel>), new SiteDataModelValidator());

+ 95 - 13
GreenTree.Nachtragsmanagement.Web/Views/Appendices/_AppendixEditPartial.cshtml

@@ -76,6 +76,49 @@
 			});
 		}
 
+		function editInvoice(id) {
+			if (!id) return;
+			$.ajax({
+				url: '@Url.Action("EditInvoice", "Appendix")',
+				data: { AppendixId: @Model.Id, Id: id },
+				success: function (response) {
+					setTimeout(function () {
+						$(".invoiceEditContainer").remove();
+						$("body").append(response);
+						parent.addCustomEventListener('InvoiceDataCallbackEventReceiver', function () {
+							devListBoxInvoices.PerformCallback();
+						});
+					}, 200);
+				},
+				error: function () {
+					 alert("error occured");
+				}
+			});
+		}
+
+		function deleteInvoice(id) {
+			$.ajax({
+				type: "POST",
+				url: '@Url.Action("DeleteInvoice", "Appendix")',
+				data: { Id: id },
+				success: function (response) {
+					setTimeout(function () {
+						devListBoxInvoices.PerformCallback();
+					}, 200);
+				},
+				error: function () {
+					 alert("error occured");
+				}
+			});
+		}
+
+		function setInvoiceDropDownTest() {
+			var textArray = $(".invoiceText").map(function () {
+				return $(this).text();
+			}).get();
+			devDropDownListInvoices.SetText(textArray.join(textSeparator));
+		}
+
 		function setDropDownText() {
 			var allItems = CategoryEntities.GetItemCount();
 			var text = "";
@@ -83,7 +126,7 @@
 				if (i == allItems - 1) {
 					text += JSON.parse(CategoryEntities.GetItem(i).value).Description;
 				} else {
-					text += JSON.parse(CategoryEntities.GetItem(i).value).Description + ", ";
+					text += JSON.parse(CategoryEntities.GetItem(i).value).Description + textSeparator;
 				}
 			}
 			devDropDownListCategoryValues.SetText(text);
@@ -118,6 +161,17 @@
 		}
 	</script>
 
+	<style>
+		.devHyperLinkEditInvoice {
+			margin: 0 4px;
+			text-decoration: none;
+		}
+
+		.devHyperLinkEditInvoice:hover {
+			text-decoration: underline;
+		}
+	</style>
+
 	@Html.Partial("~/Views/Appendices/_CategoryValueEditPartial.cshtml")
 
 	@Html.DevExpress().PopupControl(s =>
@@ -285,16 +339,6 @@
 			}
 			ViewContext.Writer.Write("</div>");
 
-
-
-
-
-			//ViewContext.Writer.Write("fdoüdshjpüdgfjhüdgfjhpüdgfjhüpodgfjhpoüdgfjhüpodgfjhüpofjdgdfgh");
-
-
-
-
-
 			ViewContext.Writer.Write("<div class='inlineModelPropertyContainer'>");
 			{
 				ViewContext.Writer.Write("<div class='inlineModelProperty' style='width: 40%'>");
@@ -325,14 +369,14 @@
 
 			ViewContext.Writer.Write("<div class='inlineModelPropertyContainer'>");
 			{
-				ViewContext.Writer.Write("<div class='inlineModelProperty' style='width: 35%'>");
+				ViewContext.Writer.Write("<div class='inlineModelProperty' style='width: 40%'>");
 				{
 					ViewContext.Writer.Write(Html.CustomLabelFor(m => m.StateId, "NT-Status:"));
 					ViewContext.Writer.Write(Html.ValidationMessageFor(m => m.StateId).ToHtmlString());
 
 					Session.Add("AppendixStatesComboBoxSettings", new Action<ComboBoxSettings>(a =>
 					{
-						a.Width = new Unit(95, UnitType.Percentage);
+						a.Width = new Unit(96, UnitType.Percentage);
 						a.Properties.ValueType = typeof(int);
 						a.Properties.ValueField = "Id";
 						a.Properties.TextField = "Description";
@@ -364,6 +408,44 @@
 					}
 				}
 				ViewContext.Writer.Write("</div>");
+
+				ViewContext.Writer.Write("<div class='inlineModelProperty' style='width: 60%'>");
+				{
+					ViewContext.Writer.Write(Html.CustomLabelFor(m => m.Invoices, "Rechnungen:"));
+
+					if (Model.Id == -1)
+					{
+						ViewContext.Writer.Write("<span style=\"margin-left: 8px; color: red\">Nachtrag muss erst gespeichert werden!</span>");
+					}
+
+					Html.DevExpress().DropDownEdit(t =>
+					{
+						t.Name = "devDropDownListInvoices";
+						t.Width = new Unit(100, UnitType.Percentage);
+						t.ClientEnabled = Model.Id != -1;
+
+						if (Model.Invoices != null && Model.Invoices.Any())
+							t.Text = String.Join(", ",
+								Model.Invoices
+									.Select(d => d.CustomNumber));
+
+						t.SetDropDownWindowTemplateContent(l =>
+						{
+							Html.RenderPartial("~/Views/Appendices/_InvoiceListPartial.cshtml", Model);
+						});
+					}).Render();
+
+					Html.DevExpress().HyperLink(t =>
+					{
+						t.Name = "devHyperLinkInvoiceAdd";
+						t.Properties.Text = "Hinzufügen";
+						t.NavigateUrl = "#";
+						t.ClientEnabled = Model.Id != -1;
+						t.Properties.ClientSideEvents.Click = "function (s, e) { editInvoice(-1); }";
+						t.Style.Add("line-height", "24px");
+					}).Render();
+				}
+				ViewContext.Writer.Write("</div>");
 			}
 			ViewContext.Writer.Write("</div>");
 

+ 98 - 0
GreenTree.Nachtragsmanagement.Web/Views/Appendices/_InvoiceEditPartial.cshtml

@@ -0,0 +1,98 @@
+@using GreenTree.Nachtragsmanagement.Web.Extensions
+
+@model GreenTree.Nachtragsmanagement.Web.Models.Appendix.InvoiceDataModel
+
+<div class="invoiceEditContainer">
+
+	<script>
+		function saveInvoice() {
+			var form = $("#invoiceEditForm");
+			$(form).submit(function (e) {
+				$.ajax({
+					type: "POST",
+					url: '@Url.Action("EditInvoice", "Appendix")',
+					data: form.serialize(),
+					success: function (response) {
+						setTimeout(function () {
+							$(".invoiceEditContainer").remove();
+							if (response == "success") {
+								parent.callCustomEventListener('InvoiceDataCallbackEventReceiver');
+							} else {
+								$("body").append(response);
+							}
+						}, 200);
+					}
+				});
+				e.preventDefault();
+			});
+			form.submit();
+		}
+	</script>
+
+	@Html.DevExpress().PopupControl(s =>
+{
+	s.Name = "devPopupControlEditInvoice";
+
+	if (Model.Id == -1)
+		s.HeaderText = "Neue Rechnung erstellen";
+	else
+		s.HeaderText = "\"" + Model.CustomNumber + "\" bearbeiten";
+
+	s.Modal = true;
+	s.Width = new Unit(300, UnitType.Pixel);
+	s.CloseAction = CloseAction.CloseButton;
+	s.PopupHorizontalAlign = PopupHorizontalAlign.WindowCenter;
+	s.PopupVerticalAlign = PopupVerticalAlign.WindowCenter;
+	s.AllowDragging = false;
+	s.AllowResize = false;
+	s.ShowFooter = false;
+	s.ShowOnPageLoad = true;
+	s.SetContent(() =>
+	{
+		using (Html.BeginForm("EditInvoice", "Appendix", FormMethod.Post, new { id = "invoiceEditForm" }))
+		{
+			ViewContext.Writer.Write("<div class='editFormWrapper'>");
+
+			ViewContext.Writer.Write("<input type=\"hidden\" value=\"" + Model.Id + "\" id=\"Id\" name=\"Id\" />");
+			ViewContext.Writer.Write("<input type=\"hidden\" value=\"" + Model.AppendixId + "\" id=\"AppendixId\" name=\"AppendixId\" />");
+
+			ViewContext.Writer.Write(Html.CustomLabelFor(m => m.CustomNumber, "Rechnungsnummer:"));
+			ViewContext.Writer.Write(Html.ValidationMessageFor(m => m.CustomNumber).ToHtmlString());
+			Html.DevExpress().TextBoxFor(m => m.CustomNumber, t =>
+			{
+				t.Width = new Unit(100, UnitType.Percentage);
+			}).Render();
+
+			ViewContext.Writer.Write(Html.CustomLabelFor(m => m.DateTime, "Rechnungsdatum:"));
+			ViewContext.Writer.Write(Html.ValidationMessageFor(m => m.DateTime).ToHtmlString());
+			Html.DevExpress().DateEditFor(m => m.DateTime, t =>
+			{
+				t.Width = new Unit(100, UnitType.Percentage);
+			}).Render();
+
+			ViewContext.Writer.Write(Html.CustomLabelFor(m => m.Value, "Rechnungswert:"));
+			ViewContext.Writer.Write(Html.ValidationMessageFor(m => m.Value).ToHtmlString());
+			Html.DevExpress().SpinEditFor(m => m.Value, t =>
+			{
+				t.Width = new Unit(100, UnitType.Percentage);
+				t.Properties.DecimalPlaces = 2;
+				t.Properties.NumberType = SpinEditNumberType.Float;
+				t.Properties.DisplayFormatString = "c2";
+			}).Render();
+
+			ViewContext.Writer.Write("</div>");
+
+			Html.RenderPartial(
+				"~/Views/Shared/_PopupButtonPanel.cshtml",
+				new GreenTree.Nachtragsmanagement.Web.Models.Global.PopupModel
+				{
+					PopupName = "devPopupControlEditInvoice",
+					AcceptFunction = "function (s, e) { saveInvoice(); }"
+				}
+			);
+		}
+	});
+	s.Styles.Content.Paddings.Padding = new Unit(0);
+	s.Styles.ModalBackground.Opacity = 0;
+}).GetHtml()
+</div>

+ 29 - 0
GreenTree.Nachtragsmanagement.Web/Views/Appendices/_InvoiceListPartial.cshtml

@@ -0,0 +1,29 @@
+@model GreenTree.Nachtragsmanagement.Web.Models.Appendix.AppendixDataModel
+
+@{ 
+	var userContext = GreenTree.Nachtragsmanagement.Core.CommonHelper.UserContext();
+}
+
+@Html.DevExpress().ListBox(lb =>
+{
+	lb.Name = "devListBoxInvoices";
+	lb.Width = new Unit(100, UnitType.Percentage);
+	lb.Properties.TextField = "CustomNumber";
+	lb.Properties.ValueField = "Id";
+	lb.Properties.ValueType = typeof(int);
+	lb.Properties.EnableClientSideAPI = true;
+	lb.EncodeHtml = false;
+	lb.CallbackRouteValues = new { Controller = "Appendix", Action = "PartialInvoices", appendixId = Model.Id };
+	lb.SetItemTemplateContent(i =>
+	{
+		var id = Convert.ToInt32(DataBinder.Eval(i.DataItem, "Id"));
+		var customNumber = DataBinder.Eval(i.DataItem, "CustomNumber").ToString();
+
+		ViewContext.Writer.Write("<span class=\"invoiceText\">" + customNumber + "</span>");
+		ViewContext.Writer.Write(
+			"<a class=\"devHyperLinkEditInvoice\" style=\"float: right\" href=\"#\" onclick='editInvoice(" + id + ")'>Bearbeiten</a>" +
+			"<a class=\"devHyperLinkEditInvoice\" style=\"float: right\" href=\"#\" onclick='deleteInvoice(" + id + ")'>Löschen</a>");
+	});
+	lb.ControlStyle.Border.BorderStyle = BorderStyle.None;
+	lb.Properties.ClientSideEvents.EndCallback = "function (s, e) { setInvoiceDropDownTest(); }";
+}).BindList(Model.Invoices).GetHtml()

+ 364 - 0
GreenTree.Nachtragsmanagement.Web/job_scheduling_data_2_0.xsd

@@ -0,0 +1,364 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+           xmlns="http://quartznet.sourceforge.net/JobSchedulingData"
+           targetNamespace="http://quartznet.sourceforge.net/JobSchedulingData"
+           elementFormDefault="qualified"
+           version="2.0">
+
+  <xs:element name="job-scheduling-data">
+    <xs:annotation>
+      <xs:documentation>Root level node</xs:documentation>
+    </xs:annotation>
+    <xs:complexType>
+      <xs:sequence maxOccurs="unbounded">
+        <xs:element name="pre-processing-commands" type="pre-processing-commandsType" minOccurs="0" maxOccurs="1">
+          <xs:annotation>
+            <xs:documentation>Commands to be executed before scheduling the jobs and triggers in this file.</xs:documentation>
+          </xs:annotation>
+        </xs:element>
+        <xs:element name="processing-directives" type="processing-directivesType" minOccurs="0" maxOccurs="1">
+          <xs:annotation>
+            <xs:documentation>Directives to be followed while scheduling the jobs and triggers in this file.</xs:documentation>
+          </xs:annotation>
+        </xs:element>
+        <xs:element name="schedule" minOccurs="0" maxOccurs="unbounded">
+          <xs:complexType>
+            <xs:sequence maxOccurs="unbounded">
+              <xs:element name="job" type="job-detailType" minOccurs="0" maxOccurs="unbounded" />
+              <xs:element name="trigger" type="triggerType" minOccurs="0" maxOccurs="unbounded" />
+            </xs:sequence>
+          </xs:complexType>
+        </xs:element>
+      </xs:sequence>
+      <xs:attribute name="version" type="xs:string">
+        <xs:annotation>
+          <xs:documentation>Version of the XML Schema instance</xs:documentation>
+        </xs:annotation>
+      </xs:attribute>
+    </xs:complexType>
+  </xs:element>
+
+  <xs:complexType name="pre-processing-commandsType">
+    <xs:sequence maxOccurs="unbounded">
+      <xs:element name="delete-jobs-in-group" type="xs:string" minOccurs="0" maxOccurs="unbounded">
+        <xs:annotation>
+          <xs:documentation>Delete all jobs, if any, in the identified group. "*" can be used to identify all groups. Will also result in deleting all triggers related to the jobs.</xs:documentation>
+        </xs:annotation>
+      </xs:element>
+      <xs:element name="delete-triggers-in-group" type="xs:string" minOccurs="0" maxOccurs="unbounded">
+        <xs:annotation>
+          <xs:documentation>Delete all triggers, if any, in the identified group. "*" can be used to identify all groups. Will also result in deletion of related jobs that are non-durable.</xs:documentation>
+        </xs:annotation>
+      </xs:element>
+      <xs:element name="delete-job" minOccurs="0" maxOccurs="unbounded">
+        <xs:annotation>
+          <xs:documentation>Delete the identified job if it exists (will also result in deleting all triggers related to it).</xs:documentation>
+        </xs:annotation>
+        <xs:complexType>
+          <xs:sequence>
+            <xs:element name="name" type="xs:string" />
+            <xs:element name="group" type="xs:string" minOccurs="0" />
+          </xs:sequence>
+        </xs:complexType>
+      </xs:element>
+      <xs:element name="delete-trigger" minOccurs="0" maxOccurs="unbounded">
+        <xs:annotation>
+          <xs:documentation>Delete the identified trigger if it exists (will also result in deletion of related jobs that are non-durable).</xs:documentation>
+        </xs:annotation>
+        <xs:complexType>
+          <xs:sequence>
+            <xs:element name="name" type="xs:string" />
+            <xs:element name="group" type="xs:string" minOccurs="0" />
+          </xs:sequence>
+        </xs:complexType>
+      </xs:element>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="processing-directivesType">
+    <xs:sequence>
+      <xs:element name="overwrite-existing-data" type="xs:boolean" minOccurs="0" default="true">
+        <xs:annotation>
+          <xs:documentation>Whether the existing scheduling data (with same identifiers) will be overwritten. If false, and ignore-duplicates is not false, and jobs or triggers with the same names already exist as those in the file, an error will occur.</xs:documentation>
+        </xs:annotation>
+      </xs:element>
+      <xs:element name="ignore-duplicates" type="xs:boolean" minOccurs="0" default="false">
+        <xs:annotation>
+          <xs:documentation>If true (and overwrite-existing-data is false) then any job/triggers encountered in this file that have names that already exist in the scheduler will be ignored, and no error will be produced.</xs:documentation>
+        </xs:annotation>
+      </xs:element>
+      <xs:element name="schedule-trigger-relative-to-replaced-trigger" type="xs:boolean" minOccurs="0" default="false">
+        <xs:annotation>
+          <xs:documentation>If true trigger's start time is calculated based on earlier run time instead of fixed value. Trigger's start time must be undefined for this to work.</xs:documentation>
+        </xs:annotation>
+      </xs:element>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="job-detailType">
+    <xs:annotation>
+      <xs:documentation>Define a JobDetail</xs:documentation>
+    </xs:annotation>
+    <xs:sequence>
+      <xs:element name="name" type="xs:string" />
+      <xs:element name="group" type="xs:string" minOccurs="0" />
+      <xs:element name="description" type="xs:string" minOccurs="0" />
+      <xs:element name="job-type" type="xs:string" />
+      <xs:sequence minOccurs="0">
+        <xs:element name="durable" type="xs:boolean" />
+        <xs:element name="recover" type="xs:boolean" />
+      </xs:sequence>
+      <xs:element name="job-data-map" type="job-data-mapType" minOccurs="0" />
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="job-data-mapType">
+    <xs:annotation>
+      <xs:documentation>Define a JobDataMap</xs:documentation>
+    </xs:annotation>
+    <xs:sequence minOccurs="0" maxOccurs="unbounded">
+      <xs:element name="entry" type="entryType" />
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="entryType">
+    <xs:annotation>
+      <xs:documentation>Define a JobDataMap entry</xs:documentation>
+    </xs:annotation>
+    <xs:sequence>
+      <xs:element name="key" type="xs:string" />
+      <xs:element name="value" type="xs:string" />
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="triggerType">
+    <xs:annotation>
+      <xs:documentation>Define a Trigger</xs:documentation>
+    </xs:annotation>
+    <xs:choice>
+      <xs:element name="simple" type="simpleTriggerType" />
+      <xs:element name="cron" type="cronTriggerType" />
+      <xs:element name="calendar-interval" type="calendarIntervalTriggerType" />
+    </xs:choice>
+  </xs:complexType>
+
+  <xs:complexType name="abstractTriggerType" abstract="true">
+    <xs:annotation>
+      <xs:documentation>Common Trigger definitions</xs:documentation>
+    </xs:annotation>
+    <xs:sequence>
+      <xs:element name="name" type="xs:string" />
+      <xs:element name="group" type="xs:string" minOccurs="0" />
+      <xs:element name="description" type="xs:string" minOccurs="0" />
+      <xs:element name="job-name" type="xs:string" />
+      <xs:element name="job-group" type="xs:string" minOccurs="0" />
+      <xs:element name="priority" type="xs:nonNegativeInteger" minOccurs="0" />
+      <xs:element name="calendar-name" type="xs:string" minOccurs="0" />
+      <xs:element name="job-data-map" type="job-data-mapType" minOccurs="0" />
+      <xs:sequence minOccurs="0">
+        <xs:choice>
+          <xs:element name="start-time" type="xs:dateTime" />
+          <xs:element name="start-time-seconds-in-future" type="xs:nonNegativeInteger" />
+        </xs:choice>
+        <xs:element name="end-time" type="xs:dateTime" minOccurs="0" />
+      </xs:sequence>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="simpleTriggerType">
+    <xs:annotation>
+      <xs:documentation>Define a SimpleTrigger</xs:documentation>
+    </xs:annotation>
+    <xs:complexContent>
+      <xs:extension base="abstractTriggerType">
+        <xs:sequence>
+          <xs:element name="misfire-instruction" type="simple-trigger-misfire-instructionType" minOccurs="0" />
+          <xs:sequence minOccurs="0">
+            <xs:element name="repeat-count" type="repeat-countType" />
+            <xs:element name="repeat-interval" type="xs:nonNegativeInteger" />
+          </xs:sequence>
+        </xs:sequence>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+
+  <xs:complexType name="cronTriggerType">
+    <xs:annotation>
+      <xs:documentation>Define a CronTrigger</xs:documentation>
+    </xs:annotation>
+    <xs:complexContent>
+      <xs:extension base="abstractTriggerType">
+        <xs:sequence>
+          <xs:element name="misfire-instruction" type="cron-trigger-misfire-instructionType" minOccurs="0" />
+          <xs:element name="cron-expression" type="cron-expressionType" />
+          <xs:element name="time-zone" type="xs:string" minOccurs="0" />
+        </xs:sequence>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+
+  <xs:complexType name="calendarIntervalTriggerType">
+    <xs:annotation>
+      <xs:documentation>Define a DateIntervalTrigger</xs:documentation>
+    </xs:annotation>
+    <xs:complexContent>
+      <xs:extension base="abstractTriggerType">
+        <xs:sequence>
+          <xs:element name="misfire-instruction" type="date-interval-trigger-misfire-instructionType" minOccurs="0" />
+          <xs:element name="repeat-interval" type="xs:nonNegativeInteger" />
+          <xs:element name="repeat-interval-unit" type="interval-unitType" />
+        </xs:sequence>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+
+  <xs:simpleType name="cron-expressionType">
+    <xs:annotation>
+      <xs:documentation>
+        Cron expression (see JavaDoc for examples)
+
+        Special thanks to Chris Thatcher (thatcher@butterfly.net) for the regular expression!
+
+        Regular expressions are not my strong point but I believe this is complete,
+        with the caveat that order for expressions like 3-0 is not legal but will pass,
+        and month and day names must be capitalized.
+        If you want to examine the correctness look for the [\s] to denote the
+        seperation of individual regular expressions. This is how I break them up visually
+        to examine them:
+
+        SECONDS:
+        (
+        ((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)
+        | (([\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))
+        | ([\?])
+        | ([\*])
+        ) [\s]
+        MINUTES:
+        (
+        ((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)
+        | (([\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))
+        | ([\?])
+        | ([\*])
+        ) [\s]
+        HOURS:
+        (
+        ((([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?,)*([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?)
+        | (([\*]|[0-9]|[0-1][0-9]|[2][0-3])/([0-9]|[0-1][0-9]|[2][0-3]))
+        | ([\?])
+        | ([\*])
+        ) [\s]
+        DAY OF MONTH:
+        (
+        ((([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?,)*([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?(C)?)
+        | (([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])/([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(C)?)
+        | (L(-[0-9])?)
+        | (L(-[1-2][0-9])?)
+        | (L(-[3][0-1])?)
+        | (LW)
+        | ([1-9]W)
+        | ([1-3][0-9]W)
+        | ([\?])
+        | ([\*])
+        )[\s]
+        MONTH:
+        (
+        ((([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?,)*([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?)
+        | (([1-9]|0[1-9]|1[0-2])/([1-9]|0[1-9]|1[0-2]))
+        | (((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?,)*(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)
+        | ((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)/(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))
+        | ([\?])
+        | ([\*])
+        )[\s]
+        DAY OF WEEK:
+        (
+        (([1-7](-([1-7]))?,)*([1-7])(-([1-7]))?)
+        | ([1-7]/([1-7]))
+        | (((MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?,)*(MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?(C)?)
+        | ((MON|TUE|WED|THU|FRI|SAT|SUN)/(MON|TUE|WED|THU|FRI|SAT|SUN)(C)?)
+        | (([1-7]|(MON|TUE|WED|THU|FRI|SAT|SUN))(L|LW)?)
+        | (([1-7]|MON|TUE|WED|THU|FRI|SAT|SUN)#([1-7])?)
+        | ([\?])
+        | ([\*])
+        )
+        YEAR (OPTIONAL):
+        (
+        [\s]?
+        ([\*])?
+        | ((19[7-9][0-9])|(20[0-9][0-9]))?
+        | (((19[7-9][0-9])|(20[0-9][0-9]))/((19[7-9][0-9])|(20[0-9][0-9])))?
+        | ((((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?,)*((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?)?
+        )
+      </xs:documentation>
+    </xs:annotation>
+    <xs:restriction base="xs:string">
+      <xs:pattern
+        value="(((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)|(([\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))|([\?])|([\*]))[\s](((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)|(([\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))|([\?])|([\*]))[\s](((([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?,)*([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?)|(([\*]|[0-9]|[0-1][0-9]|[2][0-3])/([0-9]|[0-1][0-9]|[2][0-3]))|([\?])|([\*]))[\s](((([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?,)*([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?(C)?)|(([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])/([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(C)?)|(L(-[0-9])?)|(L(-[1-2][0-9])?)|(L(-[3][0-1])?)|(LW)|([1-9]W)|([1-3][0-9]W)|([\?])|([\*]))[\s](((([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?,)*([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?)|(([1-9]|0[1-9]|1[0-2])/([1-9]|0[1-9]|1[0-2]))|(((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?,)*(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)|((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)/(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))|([\?])|([\*]))[\s]((([1-7](-([1-7]))?,)*([1-7])(-([1-7]))?)|([1-7]/([1-7]))|(((MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?,)*(MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?(C)?)|((MON|TUE|WED|THU|FRI|SAT|SUN)/(MON|TUE|WED|THU|FRI|SAT|SUN)(C)?)|(([1-7]|(MON|TUE|WED|THU|FRI|SAT|SUN))?(L|LW)?)|(([1-7]|MON|TUE|WED|THU|FRI|SAT|SUN)#([1-7])?)|([\?])|([\*]))([\s]?(([\*])?|(19[7-9][0-9])|(20[0-9][0-9]))?| (((19[7-9][0-9])|(20[0-9][0-9]))/((19[7-9][0-9])|(20[0-9][0-9])))?| ((((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?,)*((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?)?)" />
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="repeat-countType">
+    <xs:annotation>
+      <xs:documentation>Number of times to repeat the Trigger (-1 for indefinite)</xs:documentation>
+    </xs:annotation>
+    <xs:restriction base="xs:integer">
+      <xs:minInclusive value="-1" />
+    </xs:restriction>
+  </xs:simpleType>
+
+
+  <xs:simpleType name="simple-trigger-misfire-instructionType">
+    <xs:annotation>
+      <xs:documentation>Simple Trigger Misfire Instructions</xs:documentation>
+    </xs:annotation>
+    <xs:restriction base="xs:string">
+      <xs:pattern value="SmartPolicy" />
+      <xs:pattern value="RescheduleNextWithExistingCount" />
+      <xs:pattern value="RescheduleNextWithRemainingCount" />
+      <xs:pattern value="RescheduleNowWithExistingRepeatCount" />
+      <xs:pattern value="RescheduleNowWithRemainingRepeatCount" />
+      <xs:pattern value="FireNow" />
+      <xs:pattern value="IgnoreMisfirePolicy" />
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="cron-trigger-misfire-instructionType">
+    <xs:annotation>
+      <xs:documentation>Cron Trigger Misfire Instructions</xs:documentation>
+    </xs:annotation>
+    <xs:restriction base="xs:string">
+      <xs:pattern value="SmartPolicy" />
+      <xs:pattern value="DoNothing" />
+      <xs:pattern value="FireOnceNow" />
+      <xs:pattern value="IgnoreMisfirePolicy" />
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="date-interval-trigger-misfire-instructionType">
+    <xs:annotation>
+      <xs:documentation>Date Interval Trigger Misfire Instructions</xs:documentation>
+    </xs:annotation>
+    <xs:restriction base="xs:string">
+      <xs:pattern value="SmartPolicy" />
+      <xs:pattern value="DoNothing" />
+      <xs:pattern value="FireOnceNow" />
+      <xs:pattern value="IgnoreMisfirePolicy" />
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="interval-unitType">
+    <xs:annotation>
+      <xs:documentation>Interval Units</xs:documentation>
+    </xs:annotation>
+    <xs:restriction base="xs:string">
+      <xs:pattern value="Day" />
+      <xs:pattern value="Hour" />
+      <xs:pattern value="Minute" />
+      <xs:pattern value="Month" />
+      <xs:pattern value="Second" />
+      <xs:pattern value="Week" />
+      <xs:pattern value="Year" />
+    </xs:restriction>
+  </xs:simpleType>
+
+</xs:schema>

+ 3 - 0
GreenTree.Nachtragsmanagement.Web/packages.config

@@ -2,6 +2,8 @@
 <packages>
   <package id="Antlr" version="3.4.1.9004" targetFramework="net452" />
   <package id="Autofac" version="4.0.1" targetFramework="net452" />
+  <package id="Common.Logging" version="3.3.1" targetFramework="net452" />
+  <package id="Common.Logging.Core" version="3.3.1" targetFramework="net452" />
   <package id="EntityFramework" version="6.1.3" targetFramework="net452" />
   <package id="FluentValidation" version="7.1.1" targetFramework="net452" />
   <package id="FluentValidation.Mvc5" version="7.1.1" targetFramework="net452" />
@@ -19,5 +21,6 @@
   <package id="Microsoft.AspNet.WebPages.de" version="3.2.3" targetFramework="net452" />
   <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net452" />
   <package id="Newtonsoft.Json" version="10.0.3" targetFramework="net452" />
+  <package id="Quartz" version="2.6.0" targetFramework="net452" />
   <package id="WebGrease" version="1.5.2" targetFramework="net452" />
 </packages>