Преглед на файлове

Business model erstellt

Arne Diekmann преди 5 години
родител
ревизия
23bd8281f2
променени са 35 файла, в които са добавени 3319 реда и са изтрити 69 реда
  1. 3 2
      GreenTree.Strohrmann.ERP.Core/Domain/Business/Craft.cs
  2. 8 2
      GreenTree.Strohrmann.ERP.Core/Domain/Business/Customer.cs
  3. 3 2
      GreenTree.Strohrmann.ERP.Core/Domain/Business/Employee.cs
  4. 3 2
      GreenTree.Strohrmann.ERP.Core/Domain/Business/Material.cs
  5. 3 2
      GreenTree.Strohrmann.ERP.Core/Domain/Business/Supplier.cs
  6. 33 0
      GreenTree.Strohrmann.ERP.Core/Domain/Shared/TrackedEntity.cs
  7. 4 0
      GreenTree.Strohrmann.ERP.Domain/GreenTree.Strohrmann.ERP.Domain.csproj
  8. 618 0
      GreenTree.Strohrmann.ERP.Domain/Migrations/20200703132950_Tracking.Designer.cs
  9. 224 0
      GreenTree.Strohrmann.ERP.Domain/Migrations/20200703132950_Tracking.cs
  10. 72 3
      GreenTree.Strohrmann.ERP.Domain/Migrations/ERPDbContextModelSnapshot.cs
  11. 5 2
      GreenTree.Strohrmann.ERP.Domain/Model/Business/CraftMapping.cs
  12. 14 3
      GreenTree.Strohrmann.ERP.Domain/Model/Business/CustomerMapping.cs
  13. 5 2
      GreenTree.Strohrmann.ERP.Domain/Model/Business/EmployeeMapping.cs
  14. 5 2
      GreenTree.Strohrmann.ERP.Domain/Model/Business/MaterialMapping.cs
  15. 5 2
      GreenTree.Strohrmann.ERP.Domain/Model/Business/SupplierMapping.cs
  16. 28 0
      GreenTree.Strohrmann.ERP.Domain/Model/Shared/TrackedEntityMapping.cs
  17. 33 0
      GreenTree.Strohrmann.ERP.Services/Authorization/CookieAuthorizationService.cs
  18. 13 0
      GreenTree.Strohrmann.ERP.Services/Authorization/IAuthorizationService.cs
  19. 8 0
      GreenTree.Strohrmann.ERP.Web/Controllers/AccountController.cs
  20. 127 0
      GreenTree.Strohrmann.ERP.Web/Controllers/CustomerController.cs
  21. 0 4
      GreenTree.Strohrmann.ERP.Web/GreenTree.Strohrmann.ERP.Web.csproj
  22. 113 0
      GreenTree.Strohrmann.ERP.Web/Models/Business/CustomerModel.cs
  23. 62 0
      GreenTree.Strohrmann.ERP.Web/Models/Business/TaxModel.cs
  24. 18 4
      GreenTree.Strohrmann.ERP.Web/Startup.cs
  25. 8 3
      GreenTree.Strohrmann.ERP.Web/Validators/LoginValidator.cs
  26. 89 0
      GreenTree.Strohrmann.ERP.Web/Views/Customer/Create.cshtml
  27. 181 0
      GreenTree.Strohrmann.ERP.Web/Views/Customer/Index.cshtml
  28. 7 7
      GreenTree.Strohrmann.ERP.Web/Views/Rights/User/Index.cshtml
  29. 82 25
      GreenTree.Strohrmann.ERP.Web/Views/Shared/_Layout.cshtml
  30. 4 0
      GreenTree.Strohrmann.ERP.Web/libman.json
  31. 1 1
      GreenTree.Strohrmann.ERP.Web/wwwroot/css/simple-sidebar.css
  32. 36 1
      GreenTree.Strohrmann.ERP.Web/wwwroot/css/site.css
  33. BIN
      GreenTree.Strohrmann.ERP.Web/wwwroot/img/user_logo.png
  34. 1472 0
      GreenTree.Strohrmann.ERP.Web/wwwroot/lib/datatables-responsive/dataTables.responsive.js
  35. 32 0
      GreenTree.Strohrmann.ERP.Web/wwwroot/lib/datatables-responsive/dataTables.responsive.min.js

+ 3 - 2
GreenTree.Strohrmann.ERP.Core/Domain/Business/Craft.cs

@@ -1,10 +1,11 @@
-using System;
+using GreenTree.Strohrmann.ERP.Core.Domain.Shared;
+using System;
 using System.Collections.Generic;
 using System.Text;
 
 namespace GreenTree.Strohrmann.ERP.Core.Domain.Business
 {
-    public class Craft
+    public class Craft : TrackedEntity
     {
         /// <summary>
         /// Craft id

+ 8 - 2
GreenTree.Strohrmann.ERP.Core/Domain/Business/Customer.cs

@@ -1,10 +1,11 @@
-using System;
+using GreenTree.Strohrmann.ERP.Core.Domain.Shared;
+using System;
 using System.Collections.Generic;
 using System.Text;
 
 namespace GreenTree.Strohrmann.ERP.Core.Domain.Business
 {
-    public class Customer
+    public class Customer : TrackedEntity
     {
         /// <summary>
         /// Customer id
@@ -26,6 +27,11 @@ namespace GreenTree.Strohrmann.ERP.Core.Domain.Business
         /// </summary>
         public string CompanyName { get; set; }
 
+        /// <summary>
+        /// Customer business state
+        /// </summary>
+        public bool IsBusiness { get; set; }
+
         /// <summary>
         /// Customer address
         /// </summary>

+ 3 - 2
GreenTree.Strohrmann.ERP.Core/Domain/Business/Employee.cs

@@ -1,10 +1,11 @@
-using System;
+using GreenTree.Strohrmann.ERP.Core.Domain.Shared;
+using System;
 using System.Collections.Generic;
 using System.Text;
 
 namespace GreenTree.Strohrmann.ERP.Core.Domain.Business
 {
-    public class Employee
+    public class Employee : TrackedEntity
     {
         /// <summary>
         /// Employee Id

+ 3 - 2
GreenTree.Strohrmann.ERP.Core/Domain/Business/Material.cs

@@ -1,10 +1,11 @@
-using System;
+using GreenTree.Strohrmann.ERP.Core.Domain.Shared;
+using System;
 using System.Collections.Generic;
 using System.Text;
 
 namespace GreenTree.Strohrmann.ERP.Core.Domain.Business
 {
-    public class Material
+    public class Material : TrackedEntity
     {
         /// <summary>
         /// Material id

+ 3 - 2
GreenTree.Strohrmann.ERP.Core/Domain/Business/Supplier.cs

@@ -1,10 +1,11 @@
-using System;
+using GreenTree.Strohrmann.ERP.Core.Domain.Shared;
+using System;
 using System.Collections.Generic;
 using System.Text;
 
 namespace GreenTree.Strohrmann.ERP.Core.Domain.Business
 {
-    public class Supplier
+    public class Supplier : TrackedEntity
     {
         /// <summary>
         /// Supplier Id

+ 33 - 0
GreenTree.Strohrmann.ERP.Core/Domain/Shared/TrackedEntity.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace GreenTree.Strohrmann.ERP.Core.Domain.Shared
+{
+    public class TrackedEntity
+    {
+        #region Properties
+
+        /// <summary>
+        /// Tracked creation user
+        /// </summary>
+        public string CreatedBy { get; set; }
+
+        /// <summary>
+        /// Tracked creation datetime
+        /// </summary>
+        public DateTime CreatedOn { get; set; }
+
+        /// <summary>
+        /// Tracked changed user
+        /// </summary>
+        public string ChangedBy { get; set; }
+
+        /// <summary>
+        /// Tracked changed datetime
+        /// </summary>
+        public DateTime? ChangedOn { get; set; }
+
+        #endregion
+    }
+}

+ 4 - 0
GreenTree.Strohrmann.ERP.Domain/GreenTree.Strohrmann.ERP.Domain.csproj

@@ -28,4 +28,8 @@
     <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
   </ItemGroup>
 
+  <ItemGroup>
+    <Folder Include="Extension\" />
+  </ItemGroup>
+
 </Project>

+ 618 - 0
GreenTree.Strohrmann.ERP.Domain/Migrations/20200703132950_Tracking.Designer.cs

@@ -0,0 +1,618 @@
+// <auto-generated />
+using System;
+using GreenTree.Strohrmann.ERP.Domain.Model;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+namespace GreenTree.Strohrmann.ERP.Domain.Migrations
+{
+    [DbContext(typeof(ERPDbContext))]
+    [Migration("20200703132950_Tracking")]
+    partial class Tracking
+    {
+        protected override void BuildTargetModel(ModelBuilder modelBuilder)
+        {
+#pragma warning disable 612, 618
+            modelBuilder
+                .HasAnnotation("ProductVersion", "3.1.5")
+                .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.Craft", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int");
+
+                    b.Property<string>("ChangedBy")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime?>("ChangedOn")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<string>("CreatedBy")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime>("CreatedOn")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<DateTime>("CreationDate")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<int>("CustomerId")
+                        .HasColumnType("int");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("CustomerId");
+
+                    b.ToTable("Crafts");
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.CraftEmployee", b =>
+                {
+                    b.Property<int>("CraftId")
+                        .HasColumnType("int");
+
+                    b.Property<int>("EmployeeId")
+                        .HasColumnType("int");
+
+                    b.Property<decimal>("Amount")
+                        .HasColumnType("decimal(65,30)");
+
+                    b.HasKey("CraftId", "EmployeeId");
+
+                    b.HasIndex("EmployeeId");
+
+                    b.ToTable("CraftEmployees");
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.CraftMaterial", b =>
+                {
+                    b.Property<int>("CraftId")
+                        .HasColumnType("int");
+
+                    b.Property<int>("MaterialId")
+                        .HasColumnType("int");
+
+                    b.Property<decimal>("Amount")
+                        .HasColumnType("decimal(65,30)");
+
+                    b.HasKey("CraftId", "MaterialId");
+
+                    b.HasIndex("MaterialId");
+
+                    b.ToTable("CraftMaterials");
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.Customer", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int");
+
+                    b.Property<string>("Address")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("ChangedBy")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime?>("ChangedOn")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<string>("CompanyName")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("Country")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("CreatedBy")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime>("CreatedOn")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<string>("Firstname")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<bool>("IsBusiness")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("tinyint(1)")
+                        .HasDefaultValue(false);
+
+                    b.Property<string>("Lastname")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<int?>("TaxId")
+                        .HasColumnType("int");
+
+                    b.Property<string>("Town")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("ZipCode")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("TaxId");
+
+                    b.ToTable("Customers");
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.Employee", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int");
+
+                    b.Property<DateTime?>("Birthdate")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<string>("ChangedBy")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime?>("ChangedOn")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<string>("CreatedBy")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime>("CreatedOn")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<int>("EmployeeDegreeId")
+                        .HasColumnType("int");
+
+                    b.Property<string>("Firstname")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("Lastname")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("MailAddress")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("EmployeeDegreeId");
+
+                    b.ToTable("Employees");
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.EmployeeDegree", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<int>("Order")
+                        .HasColumnType("int");
+
+                    b.Property<decimal>("Value")
+                        .HasColumnType("decimal(65,30)");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("EmployeeDegrees");
+
+                    b.HasData(
+                        new
+                        {
+                            Id = 1,
+                            Name = "Meister",
+                            Order = 3,
+                            Value = 60m
+                        },
+                        new
+                        {
+                            Id = 2,
+                            Name = "Geselle",
+                            Order = 2,
+                            Value = 30m
+                        },
+                        new
+                        {
+                            Id = 3,
+                            Name = "Aushilfe",
+                            Order = 1,
+                            Value = 15m
+                        });
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.Material", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int");
+
+                    b.Property<string>("ChangedBy")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime?>("ChangedOn")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<string>("CreatedBy")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime>("CreatedOn")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<int>("DefaultUnitId")
+                        .HasColumnType("int");
+
+                    b.Property<decimal>("Depth")
+                        .HasColumnType("decimal(65,30)");
+
+                    b.Property<string>("Description")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<decimal>("Height")
+                        .HasColumnType("decimal(65,30)");
+
+                    b.Property<string>("ItemNumber")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<decimal>("NetValue")
+                        .HasColumnType("decimal(65,30)");
+
+                    b.Property<int>("SupplierId")
+                        .HasColumnType("int");
+
+                    b.Property<decimal>("Width")
+                        .HasColumnType("decimal(65,30)");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("DefaultUnitId");
+
+                    b.HasIndex("SupplierId");
+
+                    b.ToTable("Materials");
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.Supplier", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int");
+
+                    b.Property<string>("Address")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("ChangedBy")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime?>("ChangedOn")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<string>("Comment")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("Country")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("CreatedBy")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime>("CreatedOn")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<string>("Description")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("MailFirst")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("MailSecond")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("PhoneFirst")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("PhoneSecond")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("Town")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("ZipCode")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Suppliers");
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.Tax", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("ShortName")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<decimal>("Value")
+                        .HasColumnType("decimal(65,30)");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Taxes");
+
+                    b.HasData(
+                        new
+                        {
+                            Id = 1,
+                            Name = "Deutschland Umsatzsteuer",
+                            ShortName = "Umst. (19%)",
+                            Value = 0.19m
+                        },
+                        new
+                        {
+                            Id = 2,
+                            Name = "Deutschland Umsatzsteuer 2020",
+                            ShortName = "Umst. (16%) 2020",
+                            Value = 0.16m
+                        });
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.Unit", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int");
+
+                    b.Property<string>("Description")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("ShortName")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Units");
+
+                    b.HasData(
+                        new
+                        {
+                            Id = 1,
+                            Description = "Angabe in Einheit Meter",
+                            Name = "Meter",
+                            ShortName = "m"
+                        },
+                        new
+                        {
+                            Id = 2,
+                            Description = "Angabe in Einheit Quadratmeter",
+                            Name = "Quadratmeter",
+                            ShortName = "m²"
+                        },
+                        new
+                        {
+                            Id = 3,
+                            Description = "Angabe in Einheit Kubikmeter",
+                            Name = "Kubikmeter",
+                            ShortName = "m³"
+                        },
+                        new
+                        {
+                            Id = 4,
+                            Description = "Angabe in Einheit Liter",
+                            Name = "Liter",
+                            ShortName = "l"
+                        },
+                        new
+                        {
+                            Id = 5,
+                            Description = "Angabe in Stückzahl",
+                            Name = "Stück",
+                            ShortName = "Stck."
+                        },
+                        new
+                        {
+                            Id = 6,
+                            Description = "Angabe in Stückzahl",
+                            Name = "Kilogramm",
+                            ShortName = "Kg"
+                        },
+                        new
+                        {
+                            Id = 7,
+                            Description = "Angabe in Zeitstunden",
+                            Name = "Stunden",
+                            ShortName = "Std."
+                        });
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Rights.Policy", b =>
+                {
+                    b.Property<string>("Name")
+                        .HasColumnType("varchar(255) CHARACTER SET utf8mb4");
+
+                    b.HasKey("Name");
+
+                    b.ToTable("Policies");
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Rights.User", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int");
+
+                    b.Property<string>("Accountname")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<bool?>("Activated")
+                        .IsRequired()
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("tinyint(1)")
+                        .HasDefaultValue(true);
+
+                    b.Property<DateTime?>("Birthdate")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<string>("Forename")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("Lastname")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("MailAddress")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<string>("Password")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Users");
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Rights.UserPolicy", b =>
+                {
+                    b.Property<int>("UserId")
+                        .HasColumnType("int");
+
+                    b.Property<string>("PolicyName")
+                        .HasColumnType("varchar(255) CHARACTER SET utf8mb4");
+
+                    b.HasKey("UserId", "PolicyName");
+
+                    b.ToTable("UserPolicies");
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.Craft", b =>
+                {
+                    b.HasOne("GreenTree.Strohrmann.ERP.Core.Domain.Business.Customer", "Customer")
+                        .WithMany("Crafts")
+                        .HasForeignKey("CustomerId")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.CraftEmployee", b =>
+                {
+                    b.HasOne("GreenTree.Strohrmann.ERP.Core.Domain.Business.Craft", "Craft")
+                        .WithMany("CraftEmployees")
+                        .HasForeignKey("CraftId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("GreenTree.Strohrmann.ERP.Core.Domain.Business.Employee", "Employee")
+                        .WithMany()
+                        .HasForeignKey("EmployeeId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.CraftMaterial", b =>
+                {
+                    b.HasOne("GreenTree.Strohrmann.ERP.Core.Domain.Business.Craft", "Craft")
+                        .WithMany("CraftMaterials")
+                        .HasForeignKey("CraftId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("GreenTree.Strohrmann.ERP.Core.Domain.Business.Material", "Material")
+                        .WithMany()
+                        .HasForeignKey("MaterialId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.Customer", b =>
+                {
+                    b.HasOne("GreenTree.Strohrmann.ERP.Core.Domain.Business.Tax", "Tax")
+                        .WithMany("Customers")
+                        .HasForeignKey("TaxId")
+                        .OnDelete(DeleteBehavior.Restrict);
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.Employee", b =>
+                {
+                    b.HasOne("GreenTree.Strohrmann.ERP.Core.Domain.Business.EmployeeDegree", "EmployeeDegree")
+                        .WithMany("Employees")
+                        .HasForeignKey("EmployeeDegreeId")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.Material", b =>
+                {
+                    b.HasOne("GreenTree.Strohrmann.ERP.Core.Domain.Business.Unit", "DefaultUnit")
+                        .WithMany("Materials")
+                        .HasForeignKey("DefaultUnitId")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("GreenTree.Strohrmann.ERP.Core.Domain.Business.Supplier", "Supplier")
+                        .WithMany("Materials")
+                        .HasForeignKey("SupplierId")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Rights.UserPolicy", b =>
+                {
+                    b.HasOne("GreenTree.Strohrmann.ERP.Core.Domain.Rights.User", "User")
+                        .WithMany("Policies")
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}

+ 224 - 0
GreenTree.Strohrmann.ERP.Domain/Migrations/20200703132950_Tracking.cs

@@ -0,0 +1,224 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace GreenTree.Strohrmann.ERP.Domain.Migrations
+{
+    public partial class Tracking : Migration
+    {
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.AddColumn<string>(
+                name: "ChangedBy",
+                table: "Suppliers",
+                nullable: true);
+
+            migrationBuilder.AddColumn<DateTime>(
+                name: "ChangedOn",
+                table: "Suppliers",
+                nullable: true);
+
+            migrationBuilder.AddColumn<string>(
+                name: "CreatedBy",
+                table: "Suppliers",
+                nullable: false);
+
+            migrationBuilder.AddColumn<DateTime>(
+                name: "CreatedOn",
+                table: "Suppliers",
+                nullable: false,
+                defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
+
+            migrationBuilder.AddColumn<string>(
+                name: "ChangedBy",
+                table: "Materials",
+                nullable: true);
+
+            migrationBuilder.AddColumn<DateTime>(
+                name: "ChangedOn",
+                table: "Materials",
+                nullable: true);
+
+            migrationBuilder.AddColumn<string>(
+                name: "CreatedBy",
+                table: "Materials",
+                nullable: false);
+
+            migrationBuilder.AddColumn<DateTime>(
+                name: "CreatedOn",
+                table: "Materials",
+                nullable: false,
+                defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
+
+            migrationBuilder.AddColumn<string>(
+                name: "ChangedBy",
+                table: "Employees",
+                nullable: true);
+
+            migrationBuilder.AddColumn<DateTime>(
+                name: "ChangedOn",
+                table: "Employees",
+                nullable: true);
+
+            migrationBuilder.AddColumn<string>(
+                name: "CreatedBy",
+                table: "Employees",
+                nullable: false);
+
+            migrationBuilder.AddColumn<DateTime>(
+                name: "CreatedOn",
+                table: "Employees",
+                nullable: false,
+                defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
+
+            migrationBuilder.AlterColumn<int>(
+                name: "TaxId",
+                table: "Customers",
+                nullable: true,
+                oldClrType: typeof(int),
+                oldType: "int");
+
+            migrationBuilder.AddColumn<string>(
+                name: "ChangedBy",
+                table: "Customers",
+                nullable: true);
+
+            migrationBuilder.AddColumn<DateTime>(
+                name: "ChangedOn",
+                table: "Customers",
+                nullable: true);
+
+            migrationBuilder.AddColumn<string>(
+                name: "CreatedBy",
+                table: "Customers",
+                nullable: false);
+
+            migrationBuilder.AddColumn<DateTime>(
+                name: "CreatedOn",
+                table: "Customers",
+                nullable: false,
+                defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
+
+            migrationBuilder.AddColumn<bool>(
+                name: "IsBusiness",
+                table: "Customers",
+                nullable: false,
+                defaultValue: false);
+
+            migrationBuilder.AddColumn<string>(
+                name: "ChangedBy",
+                table: "Crafts",
+                nullable: true);
+
+            migrationBuilder.AddColumn<DateTime>(
+                name: "ChangedOn",
+                table: "Crafts",
+                nullable: true);
+
+            migrationBuilder.AddColumn<string>(
+                name: "CreatedBy",
+                table: "Crafts",
+                nullable: false);
+
+            migrationBuilder.AddColumn<DateTime>(
+                name: "CreatedOn",
+                table: "Crafts",
+                nullable: false,
+                defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
+        }
+
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropColumn(
+                name: "ChangedBy",
+                table: "Suppliers");
+
+            migrationBuilder.DropColumn(
+                name: "ChangedOn",
+                table: "Suppliers");
+
+            migrationBuilder.DropColumn(
+                name: "CreatedBy",
+                table: "Suppliers");
+
+            migrationBuilder.DropColumn(
+                name: "CreatedOn",
+                table: "Suppliers");
+
+            migrationBuilder.DropColumn(
+                name: "ChangedBy",
+                table: "Materials");
+
+            migrationBuilder.DropColumn(
+                name: "ChangedOn",
+                table: "Materials");
+
+            migrationBuilder.DropColumn(
+                name: "CreatedBy",
+                table: "Materials");
+
+            migrationBuilder.DropColumn(
+                name: "CreatedOn",
+                table: "Materials");
+
+            migrationBuilder.DropColumn(
+                name: "ChangedBy",
+                table: "Employees");
+
+            migrationBuilder.DropColumn(
+                name: "ChangedOn",
+                table: "Employees");
+
+            migrationBuilder.DropColumn(
+                name: "CreatedBy",
+                table: "Employees");
+
+            migrationBuilder.DropColumn(
+                name: "CreatedOn",
+                table: "Employees");
+
+            migrationBuilder.DropColumn(
+                name: "ChangedBy",
+                table: "Customers");
+
+            migrationBuilder.DropColumn(
+                name: "ChangedOn",
+                table: "Customers");
+
+            migrationBuilder.DropColumn(
+                name: "CreatedBy",
+                table: "Customers");
+
+            migrationBuilder.DropColumn(
+                name: "CreatedOn",
+                table: "Customers");
+
+            migrationBuilder.DropColumn(
+                name: "IsBusiness",
+                table: "Customers");
+
+            migrationBuilder.DropColumn(
+                name: "ChangedBy",
+                table: "Crafts");
+
+            migrationBuilder.DropColumn(
+                name: "ChangedOn",
+                table: "Crafts");
+
+            migrationBuilder.DropColumn(
+                name: "CreatedBy",
+                table: "Crafts");
+
+            migrationBuilder.DropColumn(
+                name: "CreatedOn",
+                table: "Crafts");
+
+            migrationBuilder.AlterColumn<int>(
+                name: "TaxId",
+                table: "Customers",
+                type: "int",
+                nullable: false,
+                oldClrType: typeof(int),
+                oldNullable: true);
+        }
+    }
+}

+ 72 - 3
GreenTree.Strohrmann.ERP.Domain/Migrations/ERPDbContextModelSnapshot.cs

@@ -23,6 +23,19 @@ namespace GreenTree.Strohrmann.ERP.Domain.Migrations
                         .ValueGeneratedOnAdd()
                         .HasColumnType("int");
 
+                    b.Property<string>("ChangedBy")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime?>("ChangedOn")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<string>("CreatedBy")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime>("CreatedOn")
+                        .HasColumnType("datetime(6)");
+
                     b.Property<DateTime>("CreationDate")
                         .HasColumnType("datetime(6)");
 
@@ -86,6 +99,12 @@ namespace GreenTree.Strohrmann.ERP.Domain.Migrations
                         .IsRequired()
                         .HasColumnType("longtext CHARACTER SET utf8mb4");
 
+                    b.Property<string>("ChangedBy")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime?>("ChangedOn")
+                        .HasColumnType("datetime(6)");
+
                     b.Property<string>("CompanyName")
                         .HasColumnType("longtext CHARACTER SET utf8mb4");
 
@@ -93,15 +112,27 @@ namespace GreenTree.Strohrmann.ERP.Domain.Migrations
                         .IsRequired()
                         .HasColumnType("longtext CHARACTER SET utf8mb4");
 
+                    b.Property<string>("CreatedBy")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime>("CreatedOn")
+                        .HasColumnType("datetime(6)");
+
                     b.Property<string>("Firstname")
                         .IsRequired()
                         .HasColumnType("longtext CHARACTER SET utf8mb4");
 
+                    b.Property<bool>("IsBusiness")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("tinyint(1)")
+                        .HasDefaultValue(false);
+
                     b.Property<string>("Lastname")
                         .IsRequired()
                         .HasColumnType("longtext CHARACTER SET utf8mb4");
 
-                    b.Property<int>("TaxId")
+                    b.Property<int?>("TaxId")
                         .HasColumnType("int");
 
                     b.Property<string>("Town")
@@ -128,6 +159,19 @@ namespace GreenTree.Strohrmann.ERP.Domain.Migrations
                     b.Property<DateTime?>("Birthdate")
                         .HasColumnType("datetime(6)");
 
+                    b.Property<string>("ChangedBy")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime?>("ChangedOn")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<string>("CreatedBy")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime>("CreatedOn")
+                        .HasColumnType("datetime(6)");
+
                     b.Property<int>("EmployeeDegreeId")
                         .HasColumnType("int");
 
@@ -199,6 +243,19 @@ namespace GreenTree.Strohrmann.ERP.Domain.Migrations
                         .ValueGeneratedOnAdd()
                         .HasColumnType("int");
 
+                    b.Property<string>("ChangedBy")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime?>("ChangedOn")
+                        .HasColumnType("datetime(6)");
+
+                    b.Property<string>("CreatedBy")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime>("CreatedOn")
+                        .HasColumnType("datetime(6)");
+
                     b.Property<int>("DefaultUnitId")
                         .HasColumnType("int");
 
@@ -248,6 +305,12 @@ namespace GreenTree.Strohrmann.ERP.Domain.Migrations
                         .IsRequired()
                         .HasColumnType("longtext CHARACTER SET utf8mb4");
 
+                    b.Property<string>("ChangedBy")
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime?>("ChangedOn")
+                        .HasColumnType("datetime(6)");
+
                     b.Property<string>("Comment")
                         .HasColumnType("longtext CHARACTER SET utf8mb4");
 
@@ -255,6 +318,13 @@ namespace GreenTree.Strohrmann.ERP.Domain.Migrations
                         .IsRequired()
                         .HasColumnType("longtext CHARACTER SET utf8mb4");
 
+                    b.Property<string>("CreatedBy")
+                        .IsRequired()
+                        .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+                    b.Property<DateTime>("CreatedOn")
+                        .HasColumnType("datetime(6)");
+
                     b.Property<string>("Description")
                         .HasColumnType("longtext CHARACTER SET utf8mb4");
 
@@ -505,8 +575,7 @@ namespace GreenTree.Strohrmann.ERP.Domain.Migrations
                     b.HasOne("GreenTree.Strohrmann.ERP.Core.Domain.Business.Tax", "Tax")
                         .WithMany("Customers")
                         .HasForeignKey("TaxId")
-                        .OnDelete(DeleteBehavior.Restrict)
-                        .IsRequired();
+                        .OnDelete(DeleteBehavior.Restrict);
                 });
 
             modelBuilder.Entity("GreenTree.Strohrmann.ERP.Core.Domain.Business.Employee", b =>

+ 5 - 2
GreenTree.Strohrmann.ERP.Domain/Model/Business/CraftMapping.cs

@@ -1,4 +1,5 @@
 using GreenTree.Strohrmann.ERP.Core.Domain.Business;
+using GreenTree.Strohrmann.ERP.Domain.Model.Shared;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.EntityFrameworkCore.Metadata.Builders;
 using System;
@@ -7,10 +8,12 @@ using System.Text;
 
 namespace GreenTree.Strohrmann.ERP.Domain.Model.Business
 {
-    public class CraftMapping : IEntityTypeConfiguration<Craft>
+    public class CraftMapping : TrackedEntityMapping<Craft>
     {
-        public void Configure(EntityTypeBuilder<Craft> builder)
+        public override void Configure(EntityTypeBuilder<Craft> builder)
         {
+            base.Configure(builder);
+
             builder.ToTable("Crafts");
 
             builder.HasKey(u => u.Id);

+ 14 - 3
GreenTree.Strohrmann.ERP.Domain/Model/Business/CustomerMapping.cs

@@ -1,4 +1,6 @@
 using GreenTree.Strohrmann.ERP.Core.Domain.Business;
+using GreenTree.Strohrmann.ERP.Core.Domain.Shared;
+using GreenTree.Strohrmann.ERP.Domain.Model.Shared;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.EntityFrameworkCore.Metadata.Builders;
 using System;
@@ -7,10 +9,12 @@ using System.Text;
 
 namespace GreenTree.Strohrmann.ERP.Domain.Model.Business
 {
-    public class CustomerMapping : IEntityTypeConfiguration<Customer>
+    public class CustomerMapping : TrackedEntityMapping<Customer>
     {
-        public void Configure(EntityTypeBuilder<Customer> builder)
+        public override void Configure(EntityTypeBuilder<Customer> builder)
         {
+            base.Configure(builder);
+
             builder.ToTable("Customers");
 
             builder.HasKey(u => u.Id);
@@ -24,6 +28,13 @@ namespace GreenTree.Strohrmann.ERP.Domain.Model.Business
             builder.Property(u => u.Lastname)
                 .IsRequired();
 
+            builder.Property(u => u.CompanyName)
+                .IsRequired(false);
+
+            builder.Property(u => u.IsBusiness)
+                .HasDefaultValue(false)
+                .IsRequired();
+
             builder.Property(u => u.Address)
                 .IsRequired();
 
@@ -38,7 +49,7 @@ namespace GreenTree.Strohrmann.ERP.Domain.Model.Business
 
             builder.HasOne(u => u.Tax)
                 .WithMany(d => d.Customers)
-                .IsRequired()
+                .IsRequired(false)
                 .OnDelete(DeleteBehavior.Restrict);
         }
     }

+ 5 - 2
GreenTree.Strohrmann.ERP.Domain/Model/Business/EmployeeMapping.cs

@@ -1,4 +1,5 @@
 using GreenTree.Strohrmann.ERP.Core.Domain.Business;
+using GreenTree.Strohrmann.ERP.Domain.Model.Shared;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.EntityFrameworkCore.Metadata.Builders;
 using System;
@@ -7,10 +8,12 @@ using System.Text;
 
 namespace GreenTree.Strohrmann.ERP.Domain.Model.Business
 {
-    public class EmployeeMapping : IEntityTypeConfiguration<Employee>
+    public class EmployeeMapping : TrackedEntityMapping<Employee>
     {
-        public void Configure(EntityTypeBuilder<Employee> builder)
+        public override void Configure(EntityTypeBuilder<Employee> builder)
         {
+            base.Configure(builder);
+
             builder.ToTable("Employees");
 
             builder.HasKey(u => u.Id);

+ 5 - 2
GreenTree.Strohrmann.ERP.Domain/Model/Business/MaterialMapping.cs

@@ -1,4 +1,5 @@
 using GreenTree.Strohrmann.ERP.Core.Domain.Business;
+using GreenTree.Strohrmann.ERP.Domain.Model.Shared;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.EntityFrameworkCore.Metadata.Builders;
 using System;
@@ -7,10 +8,12 @@ using System.Text;
 
 namespace GreenTree.Strohrmann.ERP.Domain.Model.Business
 {
-    public class MaterialMapping : IEntityTypeConfiguration<Material>
+    public class MaterialMapping : TrackedEntityMapping<Material>
     {
-        public void Configure(EntityTypeBuilder<Material> builder)
+        public override void Configure(EntityTypeBuilder<Material> builder)
         {
+            base.Configure(builder);
+
             builder.ToTable("Materials");
 
             builder.HasKey(u => u.Id);

+ 5 - 2
GreenTree.Strohrmann.ERP.Domain/Model/Business/SupplierMapping.cs

@@ -1,4 +1,5 @@
 using GreenTree.Strohrmann.ERP.Core.Domain.Business;
+using GreenTree.Strohrmann.ERP.Domain.Model.Shared;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.EntityFrameworkCore.Metadata.Builders;
 using System;
@@ -7,10 +8,12 @@ using System.Text;
 
 namespace GreenTree.Strohrmann.ERP.Domain.Model.Business
 {
-    public class SupplierMapping : IEntityTypeConfiguration<Supplier>
+    public class SupplierMapping : TrackedEntityMapping<Supplier>
     {
-        public void Configure(EntityTypeBuilder<Supplier> builder)
+        public override void Configure(EntityTypeBuilder<Supplier> builder)
         {
+            base.Configure(builder);
+
             builder.ToTable("Suppliers");
 
             builder.HasKey(u => u.Id);

+ 28 - 0
GreenTree.Strohrmann.ERP.Domain/Model/Shared/TrackedEntityMapping.cs

@@ -0,0 +1,28 @@
+using GreenTree.Strohrmann.ERP.Core.Domain.Shared;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace GreenTree.Strohrmann.ERP.Domain.Model.Shared
+{
+    public class TrackedEntityMapping<TEntity> : IEntityTypeConfiguration<TEntity>
+        where TEntity : TrackedEntity
+    {
+        public virtual void Configure(EntityTypeBuilder<TEntity> builder)
+        {
+            builder.Property(t => t.CreatedOn)
+                .IsRequired();
+
+            builder.Property(t => t.CreatedBy)
+                .IsRequired();
+
+            builder.Property(t => t.ChangedOn)
+                .IsRequired(false);
+
+            builder.Property(t => t.ChangedBy)
+                .IsRequired(false);
+        }
+    }
+}

+ 33 - 0
GreenTree.Strohrmann.ERP.Services/Authorization/CookieAuthorizationService.cs

@@ -30,6 +30,39 @@ namespace GreenTree.Strohrmann.ERP.Services.Authorization
                 .Any(c => c.Type == "Policy" && c.Value == policy);
         }
 
+        /// <summary>
+        /// Check wether the user has any policy regarding base data management
+        /// </summary>
+        /// <param name="identity">The user identity.</param>
+        public bool UserHasBasedataPolicy(IIdentity identity)
+        {
+            var claimsIdentity = identity as ClaimsIdentity;
+
+            if (claimsIdentity == null) return false;
+
+            return claimsIdentity.Claims
+                .Any(c => c.Type == "Policy" &&
+                         (c.Value.StartsWith("Customer") ||
+                          c.Value.StartsWith("Employee") ||
+                          c.Value.StartsWith("Material") ||
+                          c.Value.StartsWith("Supplier")));
+        }
+
+        /// <summary>
+        /// Check wether the user has any policy in a specific category
+        /// </summary>
+        /// <param name="identity">The user identity.</param>
+        /// <param name="category">The category (e.g. 'Customer').</param>
+        public bool UserHasCategoryPolicy(IIdentity identity, string category)
+        {
+            var claimsIdentity = identity as ClaimsIdentity;
+
+            if (claimsIdentity == null) return false;
+
+            return claimsIdentity.Claims
+                .Any(c => c.Type == "Policy" && c.Value.StartsWith(category));
+        }
+
         #endregion
     }
 }

+ 13 - 0
GreenTree.Strohrmann.ERP.Services/Authorization/IAuthorizationService.cs

@@ -14,5 +14,18 @@ namespace GreenTree.Strohrmann.ERP.Services.Authorization
         /// <param name="identity">The user identity.</param>
         /// <param name="policy">The policy to be checked.</param>
         public bool UserHasPolicy(IIdentity identity, string policy);
+
+        /// <summary>
+        /// Check wether the user has any policy regarding base data management
+        /// </summary>
+        /// <param name="identity">The user identity.</param>
+        public bool UserHasBasedataPolicy(IIdentity identity);
+
+        /// <summary>
+        /// Check wether the user has any policy in a specific category
+        /// </summary>
+        /// <param name="identity">The user identity.</param>
+        /// <param name="category">The category (e.g. 'Customer').</param>
+        public bool UserHasCategoryPolicy(IIdentity identity, string category);
     }
 }

+ 8 - 0
GreenTree.Strohrmann.ERP.Web/Controllers/AccountController.cs

@@ -94,6 +94,14 @@ namespace GreenTree.Strohrmann.ERP.Web.Controllers
             }
         }
 
+        // Logout View
+        public IActionResult Logout()
+        {
+            _authenticationService.SignOut();
+
+            return RedirectToAction(nameof(Login));
+        }
+
         #endregion
     }
 }

+ 127 - 0
GreenTree.Strohrmann.ERP.Web/Controllers/CustomerController.cs

@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using GreenTree.Strohrmann.ERP.Core.Domain.Business;
+using GreenTree.Strohrmann.ERP.Core.Helper;
+using GreenTree.Strohrmann.ERP.Domain.Model;
+using GreenTree.Strohrmann.ERP.Web.Models.Business;
+using GreenTree.Strohrmann.ERP.Web.Models.Rights.User;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace GreenTree.Strohrmann.ERP.Web.Controllers
+{
+    public class CustomerController : Controller
+    {
+        #region DI fields
+
+        // The global DbContext
+        private readonly ERPDbContext _eRPDbContext;
+
+        // The global user helper
+        private readonly IUserHelper _userHelper;
+
+        #endregion
+
+        #region Ctor
+
+        /// <summary>
+        /// Initializes a new instance of the CustomerController class
+        /// </summary>
+        /// <param name="eRPDbContext">Global DbContext.</param>
+        /// <param name="userHelper">Global user helper.</param>
+        public CustomerController(
+            ERPDbContext eRPDbContext,
+            IUserHelper userHelper)
+        {
+            _eRPDbContext = eRPDbContext;
+            _userHelper = userHelper;
+        }
+
+        #endregion
+
+        #region Actions
+
+        // GET: CustomerController
+        public ActionResult Index()
+        {
+            var customers = _eRPDbContext.Customers
+                .ToList()
+                .Select(c => new CustomerModel(c));
+
+            return View(customers);
+        }
+
+        // GET: CustomerController/Details/5
+        public ActionResult Details(int id)
+        {
+            return View();
+        }
+
+        // GET: CustomerController/Create
+        public ActionResult Create()
+        {
+            return View();
+        }
+
+        // POST: CustomerController/Create
+        [HttpPost]
+        [ValidateAntiForgeryToken]
+        public ActionResult Create(IFormCollection collection)
+        {
+            try
+            {
+                return RedirectToAction(nameof(Index));
+            }
+            catch
+            {
+                return View();
+            }
+        }
+
+        // GET: CustomerController/Edit/5
+        public ActionResult Edit(int id)
+        {
+            return View();
+        }
+
+        // POST: CustomerController/Edit/5
+        [HttpPost]
+        [ValidateAntiForgeryToken]
+        public ActionResult Edit(int id, IFormCollection collection)
+        {
+            try
+            {
+                return RedirectToAction(nameof(Index));
+            }
+            catch
+            {
+                return View();
+            }
+        }
+
+        // GET: CustomerController/Delete/5
+        public ActionResult Delete(int id)
+        {
+            return View();
+        }
+
+        // POST: CustomerController/Delete/5
+        [HttpPost]
+        [ValidateAntiForgeryToken]
+        public ActionResult Delete(int id, IFormCollection collection)
+        {
+            try
+            {
+                return RedirectToAction(nameof(Index));
+            }
+            catch
+            {
+                return View();
+            }
+        }
+
+        #endregion
+    }
+}

+ 0 - 4
GreenTree.Strohrmann.ERP.Web/GreenTree.Strohrmann.ERP.Web.csproj

@@ -22,10 +22,6 @@
     <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.3" />
   </ItemGroup>
 
-  <ItemGroup>
-    <Folder Include="wwwroot\img\" />
-  </ItemGroup>
-
   <ItemGroup>
     <ProjectReference Include="..\GreenTree.Strohrmann.ERP.Core\GreenTree.Strohrmann.ERP.Core.csproj" />
     <ProjectReference Include="..\GreenTree.Strohrmann.ERP.Domain\GreenTree.Strohrmann.ERP.Domain.csproj" />

+ 113 - 0
GreenTree.Strohrmann.ERP.Web/Models/Business/CustomerModel.cs

@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace GreenTree.Strohrmann.ERP.Web.Models.Business
+{
+    public class CustomerModel
+    {
+        #region Properties
+
+        /// <summary>
+        /// Customer id
+        /// </summary>
+        [Display(Name = "ID")]
+        public int Id { get; set; }
+
+        /// <summary>
+        /// Customer forename
+        /// </summary>
+        [Display(Name = "Vorname")]
+        public string Firstname { get; set; }
+
+        /// <summary>
+        /// Customer lastname
+        /// </summary>
+        [Display(Name = "Nachname")]
+        public string Lastname { get; set; }
+
+        /// <summary>
+        /// Customer company name
+        /// </summary>
+        [Display(Name = "Firmenname")]
+        public string CompanyName { get; set; }
+
+        /// <summary>
+        /// Customer business state
+        /// </summary>
+        [Display(Name = "Geschäftskunde")]
+        public bool IsBusiness { get; set; }
+
+        /// <summary>
+        /// Customer address
+        /// </summary>
+        [Display(Name = "Adresse")]
+        public string Address { get; set; }
+
+        /// <summary>
+        /// Customer town
+        /// </summary>
+        [Display(Name = "Stadt")]
+        public string Town { get; set; }
+
+        /// <summary>
+        /// Customer zip code
+        /// </summary>
+        [Display(Name = "Postleitzahl")]
+        public string ZipCode { get; set; }
+
+        /// <summary>
+        /// Customer country
+        /// </summary>
+        [Display(Name = "Land")]
+        public string Country { get; set; }
+
+        /// <summary>
+        /// Customer tax
+        /// </summary>
+        [Display(Name = "Steuersatz")]
+        public TaxModel Tax { get; set; }
+
+        /// <summary>
+        /// Customer crafts
+        /// </summary>
+        [Display(Name = "Gewerke")]
+        public string[] Crafts { get; set; }
+
+        #endregion
+
+        #region Ctor
+
+        /// <summary>
+        /// Initializes a new instance of the CustomerModel class
+        /// </summary>
+        public CustomerModel() { }
+
+        /// <summary>
+        /// Initializes a new instance of the CustomerModel class
+        /// </summary>
+        /// <param name="customer">Base customer entity.</param>
+        public CustomerModel(Core.Domain.Business.Customer customer)
+        {
+            if (customer == null) return;
+
+            Id = customer.Id;
+            Firstname = customer.Firstname;
+            Lastname = customer.Lastname;
+            CompanyName = customer.CompanyName;
+            IsBusiness = customer.IsBusiness;
+            Address = customer.Address;
+            Town = customer.Town;
+            ZipCode = customer.ZipCode;
+            Country = customer.Country;
+            Tax = new TaxModel(customer.Tax);
+            Crafts = customer.Crafts
+                .Select(c => c.Name)
+                .ToArray();
+        }
+
+        #endregion
+    }
+}

+ 62 - 0
GreenTree.Strohrmann.ERP.Web/Models/Business/TaxModel.cs

@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace GreenTree.Strohrmann.ERP.Web.Models.Business
+{
+    public class TaxModel
+    {
+        #region Properties
+
+        /// <summary>
+        /// Tax id
+        /// </summary>
+        [Display(Name = "ID")]
+        public int Id { get; set; }
+
+        /// <summary>
+        /// Tax name
+        /// </summary>
+        [Display(Name = "Name")]
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Tax short name
+        /// </summary>
+        [Display(Name = "Kurzname")]
+        public string ShortName { get; set; }
+
+        /// <summary>
+        /// Tax percentage value
+        /// </summary>
+        [Display(Name = "Steuersatz (in %)")]
+        public decimal Value { get; set; }
+
+        #endregion
+
+        #region Ctor
+
+        /// <summary>
+        /// Initializes a new instance of the TaxModel class
+        /// </summary>
+        public TaxModel() { }
+
+        /// <summary>
+        /// Initializes a new instance of the TaxModel class
+        /// </summary>
+        /// <param name="tax">Base tax entity.</param>
+        public TaxModel(Core.Domain.Business.Tax tax)
+        {
+            if (tax == null) return;
+
+            Id = tax.Id;
+            Name = tax.Name;
+            ShortName = tax.ShortName;
+            Value = tax.Value;
+        }
+
+        #endregion
+    }
+}

+ 18 - 4
GreenTree.Strohrmann.ERP.Web/Startup.cs

@@ -33,10 +33,24 @@ namespace GreenTree.Strohrmann.ERP.Web
         /// </summary>
         public static readonly string[] _availablePolicies =
         {
-            "ViewDashboard",
-            "ViewUser",
-            "ChangeUser",
-            "DeleteUser"
+            "User-View",
+            "User-Change",
+            "User-Delete",
+            "Craft-View",
+            "Craft-Change",
+            "Craft-Delete",
+            "Customer-View",
+            "Customer-Change",
+            "Customer-Delete",
+            "Employee-View",
+            "Employee-Change",
+            "Employee-Delete",
+            "Material-View",
+            "Material-Change",
+            "Material-Delete",
+            "Supplier-View",
+            "Supplier-Change",
+            "Supplier-Delete"
         };
 
         #endregion

+ 8 - 3
GreenTree.Strohrmann.ERP.Web/Validators/LoginValidator.cs

@@ -72,10 +72,15 @@ namespace GreenTree.Strohrmann.ERP.Web.Validators
 						return;
 					}
 
-					if (model.Username == administrationOptions.Administrator &&
-						_userHelper.HashString(p, false) == _administrationOptions.Password)
+					if (model.Username == administrationOptions.Administrator)
 					{
-						return;
+						if (_userHelper.HashString(p, false) == _administrationOptions.Password)
+							return;
+						else
+                        {
+							context.AddFailure("Kennwort falsch.");
+							return;
+						}
 					}
 
 					var user = _eRPDbContext.Users

+ 89 - 0
GreenTree.Strohrmann.ERP.Web/Views/Customer/Create.cshtml

@@ -0,0 +1,89 @@
+@model GreenTree.Strohrmann.ERP.Web.Models.Business.CustomerModel
+
+@{
+    ViewData["Title"] = "Neuer Kunde";
+}
+
+<h1>Kunden erstellen</h1>
+
+<h4>Neuer Kunde</h4>
+<hr />
+
+<form asp-action="Create">
+    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
+    <div class="card-deck">
+        <div class="card bg-light" style="max-width: 368px">
+            <div class="card-header pb-1">
+                <h6>Kundenname / -firma</h6>
+            </div>
+            <div class="card-body">
+                <div class="form-group">
+                    <label asp-for="Firstname" class="control-label"></label>
+                    <input asp-for="Firstname" class="form-control" />
+                    <span asp-validation-for="Firstname" class="text-danger"></span>
+                </div>
+                <div class="form-group">
+                    <label asp-for="Lastname" class="control-label"></label>
+                    <input asp-for="Lastname" class="form-control" />
+                    <span asp-validation-for="Lastname" class="text-danger"></span>
+                </div>
+                <div class="form-group">
+                    <label asp-for="CompanyName" class="control-label"></label>
+                    <input asp-for="CompanyName" class="form-control" />
+                    <span asp-validation-for="CompanyName" class="text-danger"></span>
+                </div>
+            </div>
+        </div>
+        <div class="card bg-light" style="max-width: 368px">
+            <div class="card-header pb-1">
+                <h6>Adressdaten</h6>
+            </div>
+            <div class="card-body">
+                <div class="form-group">
+                    <label asp-for="Address" class="control-label"></label>
+                    <input asp-for="Address" class="form-control" />
+                    <span asp-validation-for="Address" class="text-danger"></span>
+                </div>
+                <div class="form-group">
+                    <label asp-for="Town" class="control-label"></label>
+                    <input asp-for="Town" class="form-control" />
+                    <span asp-validation-for="Town" class="text-danger"></span>
+                </div>
+                <div class="form-group">
+                    <label asp-for="ZipCode" class="control-label"></label>
+                    <input asp-for="ZipCode" class="form-control" />
+                    <span asp-validation-for="ZipCode" class="text-danger"></span>
+                </div>
+                <div class="form-group">
+                    <label asp-for="Country" class="control-label"></label>
+                    <input asp-for="Country" class="form-control" />
+                    <span asp-validation-for="Country" class="text-danger"></span>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="card-deck mt-3">
+        <div class="card bg-light" style="max-width: 368px">
+            <div class="card-header pb-1">
+                <h6>Sonstige Daten</h6>
+            </div>
+            <div class="card-body">
+                <div class="form-group">
+                    <label asp-for="IsBusiness" class="control-label"></label>
+                    <select asp-for="IsBusiness" class="form-control">
+                        <option value="true">Ja</option>
+                        <option value="false">Nein</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="form-group mt-3">
+        <input type="submit" value="Erstellen" class="btn btn-primary" />
+    </div>
+</form>
+
+<div>
+    <a asp-action="Index">Zurück zur Liste</a>
+</div>
+

+ 181 - 0
GreenTree.Strohrmann.ERP.Web/Views/Customer/Index.cshtml

@@ -0,0 +1,181 @@
+@model IEnumerable<GreenTree.Strohrmann.ERP.Web.Models.Business.CustomerModel>
+
+@{
+    ViewData["Title"] = "Kundenliste";
+}
+
+<!-- Custom JavaScript -->
+<script>
+
+    $(document).ready(function () {
+        $("#customersTable").DataTable({
+            autoWidth: false,
+            paging: true,
+            pageLength: 50,
+            "bInfo": false,
+            columns: [
+                {
+                    data: "@Html.DisplayNameFor(model => model.Id)",
+                    orderable: true
+                },
+                {
+                    data: "@Html.DisplayNameFor(model => model.Firstname)",
+                    orderable: true
+                },
+                {
+                    data: "@Html.DisplayNameFor(model => model.Lastname)",
+                    orderable: true
+                },
+                {
+                    data: "@Html.DisplayNameFor(model => model.CompanyName)",
+                    orderable: true
+                },
+                {
+                    data: "@Html.DisplayNameFor(model => model.IsBusiness)",
+                    orderable: true
+                },
+                {
+                    data: "@Html.DisplayNameFor(model => model.Address)",
+                    orderable: true
+                },
+                {
+                    data: "@Html.DisplayNameFor(model => model.Town)",
+                    orderable: true
+                },
+                {
+                    data: "@Html.DisplayNameFor(model => model.ZipCode)",
+                    orderable: true
+                },
+                {
+                    data: "@Html.DisplayNameFor(model => model.Country)",
+                    orderable: true
+                },
+                {
+                    data: "Aktionen",
+                    orderable: false
+				}
+            ],
+            language: {
+                zeroRecords: "Keine Einträge gefunden",
+                search: "Suchen:",
+                info: "Zeige _START_ bis _END_ von _TOTAL_ Einträgen",
+                lengthMenu: "Zeige _MENU_ Einträge",
+                paginate: {
+                    first: "Erste",
+                    last: "Letzte",
+                    next: "Nächste",
+                    previous: "Vorige"
+				}
+			}
+        });
+    });
+
+    function showDeleteModal(id) {
+        if (!id) return;
+        $('#deleteModal').modal("show");
+        $("[name='id'").val(id);
+    }
+
+</script>
+
+<h1>Kundenliste</h1>
+
+<p>
+    <a asp-action="Create">Neuen Kunden erstellen</a>
+</p>
+
+<table id="customersTable" class="table responsive">
+    <thead>
+        <tr>
+            <th data-priority="1">
+                @Html.DisplayNameFor(model => model.Id)
+            </th>
+            <th data-priority="2">
+                @Html.DisplayNameFor(model => model.Firstname)
+            </th>
+            <th data-priority="3">
+                @Html.DisplayNameFor(model => model.Lastname)
+            </th>
+            <th data-priority="4">
+                @Html.DisplayNameFor(model => model.CompanyName)
+            </th>
+            <th>
+                @Html.DisplayNameFor(model => model.IsBusiness)
+            </th>
+            <th>
+                @Html.DisplayNameFor(model => model.Address)
+            </th>
+            <th>
+                @Html.DisplayNameFor(model => model.Town)
+            </th>
+            <th>
+                @Html.DisplayNameFor(model => model.ZipCode)
+            </th>
+            <th>
+                @Html.DisplayNameFor(model => model.Country)
+            </th>
+            <th></th>
+        </tr>
+    </thead>
+    <tbody>
+@foreach (var item in Model) {
+        <tr>
+            <td>
+                @Html.DisplayFor(modelItem => item.Id)
+            </td>
+            <td>
+                @Html.DisplayFor(modelItem => item.Firstname)
+            </td>
+            <td>
+                @Html.DisplayFor(modelItem => item.Lastname)
+            </td>
+            <td>
+                @Html.DisplayFor(modelItem => item.CompanyName)
+            </td>
+            <td>
+                @Html.YesNoBadgeFor(modelItem => item.IsBusiness)
+            </td>
+            <td>
+                @Html.DisplayFor(modelItem => item.Address)
+            </td>
+            <td>
+                @Html.DisplayFor(modelItem => item.Town)
+            </td>
+            <td>
+                @Html.DisplayFor(modelItem => item.ZipCode)
+            </td>
+            <td>
+                @Html.DisplayFor(modelItem => item.Country)
+            </td>
+            <td>
+                @Html.ActionLink("Bearbeiten", "Edit", new { id = item.Id }) |
+                @Html.ActionLink("Anzeigen", "Details", new { id = item.Id }) |
+                <a href="#" onclick="showDeleteModal(@item.Id)">Löschen</a>
+            </td>
+        </tr>
+}
+    </tbody>
+</table>
+
+<div id="deleteModal" class="modal fade" tabindex="-1" role="dialog">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">Kunde löschen</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <form asp-action="Delete">
+                <div class="modal-body">
+                    <p>Sind Sie sicher, dass Sie diesen Kunden löschen möchten?</p>
+                    <input name="id" type="hidden" />
+                </div>
+                <div class="modal-footer">
+                    <button type="submit" class="btn btn-primary">Ja</button>
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">Nein</button>
+                </div>
+            </form>
+        </div>
+    </div>
+</div>

+ 7 - 7
GreenTree.Strohrmann.ERP.Web/Views/Rights/User/Index.cshtml

@@ -75,25 +75,25 @@
     <thead>
         <tr>
             <th>
-                ID
+                @Html.DisplayNameFor(model => model.Id)
             </th>
             <th>
-                Kontoname
+                @Html.DisplayNameFor(model => model.Accountname)
             </th>
             <th>
-                Vorname
+                @Html.DisplayNameFor(model => model.Forename)
             </th>
             <th>
-                Nachname
+                @Html.DisplayNameFor(model => model.Lastname)
             </th>
             <th>
-                Geburtsdatum
+                @Html.DisplayNameFor(model => model.Birthdate)
             </th>
             <th>
-                Aktiviert
+                @Html.DisplayNameFor(model => model.Activated)
             </th>
             <th>
-                Rechte
+                @Html.DisplayNameFor(model => model.Policies)
             </th>
             <th>
                 Aktionen

+ 82 - 25
GreenTree.Strohrmann.ERP.Web/Views/Shared/_Layout.cshtml

@@ -1,10 +1,12 @@
 @using Microsoft.AspNetCore.Authorization
 
-@inject IAuthorizationService AuthorizationService
+@inject GreenTree.Strohrmann.ERP.Services.Authorization.AdministrationOptions AdministrationOptions
+@inject GreenTree.Strohrmann.ERP.Services.Authorization.IAuthorizationService AuthorizationService
 
 @{ 
-	//var isSuperAdministrator = (await AuthorizationService.AuthorizeAsync(User, "ChangeCompanyConfigs")).Succeeded;
-	var isSuperAdministrator = true;
+	var isAdministrator = User.Claims
+        .Any(c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier &&
+                  c.Value == AdministrationOptions.Administrator);
 }
 
 <!DOCTYPE html>
@@ -56,7 +58,7 @@
     <!-- DataTables JavaScript -->
     <script src="~/lib/datatables/js/jquery.dataTables.min.js"></script>
     <script src="~/lib/datatables/js/dataTables.bootstrap4.min.js"></script>
-
+    <script src="~/lib/datatables-responsive/dataTables.responsive.js"></script>
     <!-- application core JavaScript -->
     <script src="~/js/site.js" asp-append-version="true"></script>
 
@@ -70,43 +72,88 @@
             <div class="list-group list-group-flush">
                 <h5 class="list-group-item">Dashboard</h5>
                 <a asp-controller="Home" asp-action="Index" class="list-group-item list-group-item-action bg-light">
-                    <span class="fas fa-tachometer-alt"></span>
+                    <span class="fas fa-fw fa-tachometer-alt"></span>
                     <span>Übersicht</span>
                 </a>
-                @if (isSuperAdministrator) 
+                @if (isAdministrator) 
                 {
                     <h5 class="list-group-item">Administration</h5>
                     <a asp-controller="Rights" asp-action="Index" class="list-group-item list-group-item-action bg-light">
-                        <span class="fas fa-user-friends"></span>
+                        <span class="fas fa-fw fa-user-friends w-10"></span>
                         <span>Benutzerverwaltung</span>
                     </a>
                 }
+                @if (isAdministrator || AuthorizationService.UserHasBasedataPolicy(User.Identity))
+				{
+                    <h5 class="list-group-item">Stammdaten</h5>
+                    @if (isAdministrator || AuthorizationService.UserHasCategoryPolicy(User.Identity, "Customer"))
+					{
+                        <a asp-controller="Customer" asp-action="Index" class="list-group-item list-group-item-action bg-light">
+                            <span class="fas fa-fw fa-user-tag"></span>
+                            <span>Kunden</span>
+                        </a>
+					}
+                    @if (isAdministrator || AuthorizationService.UserHasCategoryPolicy(User.Identity, "Employee"))
+					{
+                        <a asp-controller="Employee" asp-action="Index" class="list-group-item list-group-item-action bg-light">
+                            <span class="fas fa-fw fa-people-carry"></span>
+                            <span>Mitarbeiter</span>
+                        </a>
+					}
+                    @if (isAdministrator || AuthorizationService.UserHasCategoryPolicy(User.Identity, "Material"))
+					{
+                        <a asp-controller="Material" asp-action="Index" class="list-group-item list-group-item-action bg-light">
+                            <span class="fas fa-fw fa-box"></span>
+                            <span>Material</span>
+                        </a>
+					}
+                    @if (isAdministrator || AuthorizationService.UserHasCategoryPolicy(User.Identity, "Supplier"))
+					{
+                        <a asp-controller="Supplier" asp-action="Index" class="list-group-item list-group-item-action bg-light">
+                            <span class="fas fa-fw fa-truck"></span>
+                            <span>Lieferant</span>
+                        </a>
+					}
+				}
+                @if (isAdministrator || AuthorizationService.UserHasCategoryPolicy(User.Identity, "Craft"))
+				{
+                    <h5 class="list-group-item">Operativ</h5>
+                    <a asp-controller="Craft" asp-action="Index" class="list-group-item list-group-item-action bg-light">
+                        <span class="fas fa-fw fa-warehouse"></span>
+                        <span>Gewerke</span>
+                    </a>
+				}
             </div>
         </div>
         <!-- /#sidebar-wrapper -->
 
         <!-- Page Content -->
         <div id="page-content-wrapper">
-
-            <nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
-                <button class="btn btn-primary" id="menu-toggle" title="Menü ein- / ausklappen">
-                    <span class="fas fa-th-list"></span>
-                </button>
-
-                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
-                    <span class="navbar-toggler-icon"></span>
-                </button>
-
-                <div class="collapse navbar-collapse" id="navbarSupportedContent">
-                    <ul class="navbar-nav ml-auto mt-2 mt-lg-0">
-                        <li>
-                            <img src="~/img/strohrmann_logo.png" title="Strohrmann" height="40" />
-                        </li>
-                    </ul>
+            <div id="contentHeadContainer" class="container-fluid pr-0">
+                <div class="row d-flex top-menu">
+                    <div class="mr-auto p-0">
+                        <nav class="navbar navbar-expand-lg navbar-light">
+                            <button class="btn btn-primary" id="menu-toggle" title="Menü ein- / ausklappen">
+                                <span class="fas fa-th-list"></span>
+                            </button>
+                        </nav>
+                    </div>
+                    <div class="d-none d-lg-inline p-2">
+                        <img class="align-middle mt-1" src="~/img/strohrmann_logo.png" title="Strohrmann" height="30" />
+                    </div>
+                    <div class="d-flex user-logo-container">
+                        <img class="align-self-center mr-1 ml-3" src="~/img/user_logo.png" title="@User.Identity.Name" height="40" />
+                        <span class="align-self-center mr-4 ml-1 text-white">
+                            <small>@User.Identity.Name</small>
+                        </span>
+                        <a class="btn btn-info btn-sm align-self-center mr-4" role="button" href="@Url.Action("Logout", "Account")">
+                            Ausloggen
+                        </a>
+                    </div>
                 </div>
-            </nav>
+            </div>
 
-            <div class="container-fluid">
+            <div id="contentBodyContainer" class="container-fluid overflow-auto">
                 @RenderBody()
             </div>
         </div>
@@ -117,10 +164,20 @@
 
     <!-- Menu Toggle Script -->
     <script type="text/javascript">
+
         $("#menu-toggle").click(function(e) {
             e.preventDefault();
             $("#wrapper").toggleClass("toggled");
         });
+
+        $(window).resize(function () {
+            $("#contentBodyContainer").height($(window).height() - $("#contentHeadContainer").height());
+        });
+
+        $(document).ready(function () {
+            $("#contentBodyContainer").height($(window).height() - $("#contentHeadContainer").height());
+        });
+
     </script>
 
 </body>

+ 4 - 0
GreenTree.Strohrmann.ERP.Web/libman.json

@@ -17,6 +17,10 @@
     {
       "library": "bootstrap-tokenfield@0.12.0",
       "destination": "wwwroot/lib/bootstrap-tokenfield/"
+    },
+    {
+      "library": "datatables-responsive@2.2.5",
+      "destination": "wwwroot/lib/datatables-responsive/"
     }
   ]
 }

+ 1 - 1
GreenTree.Strohrmann.ERP.Web/wwwroot/css/simple-sidebar.css

@@ -17,7 +17,7 @@ body {
 }
 
 #sidebar-wrapper .sidebar-heading {
-  padding: 0.875rem 1.25rem;
+  padding: 0.8rem 1.25rem;
   font-size: 1.2rem;
 }
 

+ 36 - 1
GreenTree.Strohrmann.ERP.Web/wwwroot/css/site.css

@@ -58,10 +58,13 @@ html {
   min-height: 100%;
 }
 
+/* General  */
+
 body {
   /* Margin bottom by footer height */
-/*  margin-bottom: 60px;*/
+  /*  margin-bottom: 60px;*/
 }
+
 .footer {
   position: absolute;
   bottom: 0;
@@ -70,6 +73,19 @@ body {
   line-height: 60px; /* Vertically center the text there */
 }
 
+.divider-v {
+    height: auto;
+    width: 2px;
+    background-color: #A0A0A0;
+    margin: auto 12px;
+    clear: both;
+}
+
+.top-menu {
+    border-bottom: 1px solid #dee2e6 !important;
+    background-color: #f8f9fa !important;
+}
+
 /* Custom view styles */
 
 .form-signin {
@@ -92,6 +108,7 @@ body {
 }
 
 /* Scrollbar */
+
 ::-webkit-scrollbar-track {
     -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
     background-color: #F5F5F5;
@@ -104,4 +121,22 @@ body {
 ::-webkit-scrollbar-thumb {
     -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
     background-color: #A0A0A0;
+}
+
+/* NavBar */
+
+.user-logo-container {
+    border-left-color: #F29000;
+    background-color: #F29000;
+    border-bottom-left-radius: 20px;
+    border-top-left-radius: 0;
+}
+
+.user-logo-content {
+    margin: auto 0;
+}
+
+.user-logo-container img {
+    width: 32px;
+    height: 32px;
 }

BIN
GreenTree.Strohrmann.ERP.Web/wwwroot/img/user_logo.png


+ 1472 - 0
GreenTree.Strohrmann.ERP.Web/wwwroot/lib/datatables-responsive/dataTables.responsive.js

@@ -0,0 +1,1472 @@
+/*! Responsive 2.2.5
+ * 2014-2020 SpryMedia Ltd - datatables.net/license
+ */
+
+/**
+ * @summary     Responsive
+ * @description Responsive tables plug-in for DataTables
+ * @version     2.2.5
+ * @file        dataTables.responsive.js
+ * @author      SpryMedia Ltd (www.sprymedia.co.uk)
+ * @contact     www.sprymedia.co.uk/contact
+ * @copyright   Copyright 2014-2020 SpryMedia Ltd.
+ *
+ * This source file is free software, available under the following license:
+ *   MIT license - http://datatables.net/license/mit
+ *
+ * This source file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
+ *
+ * For details please refer to: http://www.datatables.net
+ */
+(function( factory ){
+	if ( typeof define === 'function' && define.amd ) {
+		// AMD
+		define( ['jquery', 'datatables.net'], function ( $ ) {
+			return factory( $, window, document );
+		} );
+	}
+	else if ( typeof exports === 'object' ) {
+		// CommonJS
+		module.exports = function (root, $) {
+			if ( ! root ) {
+				root = window;
+			}
+
+			if ( ! $ || ! $.fn.dataTable ) {
+				$ = require('datatables.net')(root, $).$;
+			}
+
+			return factory( $, root, root.document );
+		};
+	}
+	else {
+		// Browser
+		factory( jQuery, window, document );
+	}
+}(function( $, window, document, undefined ) {
+'use strict';
+var DataTable = $.fn.dataTable;
+
+
+/**
+ * Responsive is a plug-in for the DataTables library that makes use of
+ * DataTables' ability to change the visibility of columns, changing the
+ * visibility of columns so the displayed columns fit into the table container.
+ * The end result is that complex tables will be dynamically adjusted to fit
+ * into the viewport, be it on a desktop, tablet or mobile browser.
+ *
+ * Responsive for DataTables has two modes of operation, which can used
+ * individually or combined:
+ *
+ * * Class name based control - columns assigned class names that match the
+ *   breakpoint logic can be shown / hidden as required for each breakpoint.
+ * * Automatic control - columns are automatically hidden when there is no
+ *   room left to display them. Columns removed from the right.
+ *
+ * In additional to column visibility control, Responsive also has built into
+ * options to use DataTables' child row display to show / hide the information
+ * from the table that has been hidden. There are also two modes of operation
+ * for this child row display:
+ *
+ * * Inline - when the control element that the user can use to show / hide
+ *   child rows is displayed inside the first column of the table.
+ * * Column - where a whole column is dedicated to be the show / hide control.
+ *
+ * Initialisation of Responsive is performed by:
+ *
+ * * Adding the class `responsive` or `dt-responsive` to the table. In this case
+ *   Responsive will automatically be initialised with the default configuration
+ *   options when the DataTable is created.
+ * * Using the `responsive` option in the DataTables configuration options. This
+ *   can also be used to specify the configuration options, or simply set to
+ *   `true` to use the defaults.
+ *
+ *  @class
+ *  @param {object} settings DataTables settings object for the host table
+ *  @param {object} [opts] Configuration options
+ *  @requires jQuery 1.7+
+ *  @requires DataTables 1.10.3+
+ *
+ *  @example
+ *      $('#example').DataTable( {
+ *        responsive: true
+ *      } );
+ *    } );
+ */
+var Responsive = function ( settings, opts ) {
+	// Sanity check that we are using DataTables 1.10 or newer
+	if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.10' ) ) {
+		throw 'DataTables Responsive requires DataTables 1.10.10 or newer';
+	}
+
+	this.s = {
+		dt: new DataTable.Api( settings ),
+		columns: [],
+		current: []
+	};
+
+	// Check if responsive has already been initialised on this table
+	if ( this.s.dt.settings()[0].responsive ) {
+		return;
+	}
+
+	// details is an object, but for simplicity the user can give it as a string
+	// or a boolean
+	if ( opts && typeof opts.details === 'string' ) {
+		opts.details = { type: opts.details };
+	}
+	else if ( opts && opts.details === false ) {
+		opts.details = { type: false };
+	}
+	else if ( opts && opts.details === true ) {
+		opts.details = { type: 'inline' };
+	}
+
+	this.c = $.extend( true, {}, Responsive.defaults, DataTable.defaults.responsive, opts );
+	settings.responsive = this;
+	this._constructor();
+};
+
+$.extend( Responsive.prototype, {
+	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+	 * Constructor
+	 */
+
+	/**
+	 * Initialise the Responsive instance
+	 *
+	 * @private
+	 */
+	_constructor: function ()
+	{
+		var that = this;
+		var dt = this.s.dt;
+		var dtPrivateSettings = dt.settings()[0];
+		var oldWindowWidth = $(window).innerWidth();
+
+		dt.settings()[0]._responsive = this;
+
+		// Use DataTables' throttle function to avoid processor thrashing on
+		// resize
+		$(window).on( 'resize.dtr orientationchange.dtr', DataTable.util.throttle( function () {
+			// iOS has a bug whereby resize can fire when only scrolling
+			// See: http://stackoverflow.com/questions/8898412
+			var width = $(window).innerWidth();
+
+			if ( width !== oldWindowWidth ) {
+				that._resize();
+				oldWindowWidth = width;
+			}
+		} ) );
+
+		// DataTables doesn't currently trigger an event when a row is added, so
+		// we need to hook into its private API to enforce the hidden rows when
+		// new data is added
+		dtPrivateSettings.oApi._fnCallbackReg( dtPrivateSettings, 'aoRowCreatedCallback', function (tr, data, idx) {
+			if ( $.inArray( false, that.s.current ) !== -1 ) {
+				$('>td, >th', tr).each( function ( i ) {
+					var idx = dt.column.index( 'toData', i );
+
+					if ( that.s.current[idx] === false ) {
+						$(this).css('display', 'none');
+					}
+				} );
+			}
+		} );
+
+		// Destroy event handler
+		dt.on( 'destroy.dtr', function () {
+			dt.off( '.dtr' );
+			$( dt.table().body() ).off( '.dtr' );
+			$(window).off( 'resize.dtr orientationchange.dtr' );
+			dt.cells('.dtr-control').nodes().to$().removeClass('dtr-control');
+
+			// Restore the columns that we've hidden
+			$.each( that.s.current, function ( i, val ) {
+				if ( val === false ) {
+					that._setColumnVis( i, true );
+				}
+			} );
+		} );
+
+		// Reorder the breakpoints array here in case they have been added out
+		// of order
+		this.c.breakpoints.sort( function (a, b) {
+			return a.width < b.width ? 1 :
+				a.width > b.width ? -1 : 0;
+		} );
+
+		this._classLogic();
+		this._resizeAuto();
+
+		// Details handler
+		var details = this.c.details;
+
+		if ( details.type !== false ) {
+			that._detailsInit();
+
+			// DataTables will trigger this event on every column it shows and
+			// hides individually
+			dt.on( 'column-visibility.dtr', function () {
+				// Use a small debounce to allow multiple columns to be set together
+				if ( that._timer ) {
+					clearTimeout( that._timer );
+				}
+
+				that._timer = setTimeout( function () {
+					that._timer = null;
+
+					that._classLogic();
+					that._resizeAuto();
+					that._resize();
+
+					that._redrawChildren();
+				}, 100 );
+			} );
+
+			// Redraw the details box on each draw which will happen if the data
+			// has changed. This is used until DataTables implements a native
+			// `updated` event for rows
+			dt.on( 'draw.dtr', function () {
+				that._redrawChildren();
+			} );
+
+			$(dt.table().node()).addClass( 'dtr-'+details.type );
+		}
+
+		dt.on( 'column-reorder.dtr', function (e, settings, details) {
+			that._classLogic();
+			that._resizeAuto();
+			that._resize(true);
+		} );
+
+		// Change in column sizes means we need to calc
+		dt.on( 'column-sizing.dtr', function () {
+			that._resizeAuto();
+			that._resize();
+		});
+
+		// On Ajax reload we want to reopen any child rows which are displayed
+		// by responsive
+		dt.on( 'preXhr.dtr', function () {
+			var rowIds = [];
+			dt.rows().every( function () {
+				if ( this.child.isShown() ) {
+					rowIds.push( this.id(true) );
+				}
+			} );
+
+			dt.one( 'draw.dtr', function () {
+				that._resizeAuto();
+				that._resize();
+
+				dt.rows( rowIds ).every( function () {
+					that._detailsDisplay( this, false );
+				} );
+			} );
+		});
+
+		dt
+			.on( 'draw.dtr', function () {
+				that._controlClass();
+			})
+			.on( 'init.dtr', function (e, settings, details) {
+				if ( e.namespace !== 'dt' ) {
+					return;
+				}
+
+				that._resizeAuto();
+				that._resize();
+
+				// If columns were hidden, then DataTables needs to adjust the
+				// column sizing
+				if ( $.inArray( false, that.s.current ) ) {
+					dt.columns.adjust();
+				}
+			} );
+
+		// First pass - draw the table for the current viewport size
+		this._resize();
+	},
+
+
+	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+	 * Private methods
+	 */
+
+	/**
+	 * Calculate the visibility for the columns in a table for a given
+	 * breakpoint. The result is pre-determined based on the class logic if
+	 * class names are used to control all columns, but the width of the table
+	 * is also used if there are columns which are to be automatically shown
+	 * and hidden.
+	 *
+	 * @param  {string} breakpoint Breakpoint name to use for the calculation
+	 * @return {array} Array of boolean values initiating the visibility of each
+	 *   column.
+	 *  @private
+	 */
+	_columnsVisiblity: function ( breakpoint )
+	{
+		var dt = this.s.dt;
+		var columns = this.s.columns;
+		var i, ien;
+
+		// Create an array that defines the column ordering based first on the
+		// column's priority, and secondly the column index. This allows the
+		// columns to be removed from the right if the priority matches
+		var order = columns
+			.map( function ( col, idx ) {
+				return {
+					columnIdx: idx,
+					priority: col.priority
+				};
+			} )
+			.sort( function ( a, b ) {
+				if ( a.priority !== b.priority ) {
+					return a.priority - b.priority;
+				}
+				return a.columnIdx - b.columnIdx;
+			} );
+
+		// Class logic - determine which columns are in this breakpoint based
+		// on the classes. If no class control (i.e. `auto`) then `-` is used
+		// to indicate this to the rest of the function
+		var display = $.map( columns, function ( col, i ) {
+			if ( dt.column(i).visible() === false ) {
+				return 'not-visible';
+			}
+			return col.auto && col.minWidth === null ?
+				false :
+				col.auto === true ?
+					'-' :
+					$.inArray( breakpoint, col.includeIn ) !== -1;
+		} );
+
+		// Auto column control - first pass: how much width is taken by the
+		// ones that must be included from the non-auto columns
+		var requiredWidth = 0;
+		for ( i=0, ien=display.length ; i<ien ; i++ ) {
+			if ( display[i] === true ) {
+				requiredWidth += columns[i].minWidth;
+			}
+		}
+
+		// Second pass, use up any remaining width for other columns. For
+		// scrolling tables we need to subtract the width of the scrollbar. It
+		// may not be requires which makes this sub-optimal, but it would
+		// require another full redraw to make complete use of those extra few
+		// pixels
+		var scrolling = dt.settings()[0].oScroll;
+		var bar = scrolling.sY || scrolling.sX ? scrolling.iBarWidth : 0;
+		var widthAvailable = dt.table().container().offsetWidth - bar;
+		var usedWidth = widthAvailable - requiredWidth;
+
+		// Control column needs to always be included. This makes it sub-
+		// optimal in terms of using the available with, but to stop layout
+		// thrashing or overflow. Also we need to account for the control column
+		// width first so we know how much width is available for the other
+		// columns, since the control column might not be the first one shown
+		for ( i=0, ien=display.length ; i<ien ; i++ ) {
+			if ( columns[i].control ) {
+				usedWidth -= columns[i].minWidth;
+			}
+		}
+
+		// Allow columns to be shown (counting by priority and then right to
+		// left) until we run out of room
+		var empty = false;
+		for ( i=0, ien=order.length ; i<ien ; i++ ) {
+			var colIdx = order[i].columnIdx;
+
+			if ( display[colIdx] === '-' && ! columns[colIdx].control && columns[colIdx].minWidth ) {
+				// Once we've found a column that won't fit we don't let any
+				// others display either, or columns might disappear in the
+				// middle of the table
+				if ( empty || usedWidth - columns[colIdx].minWidth < 0 ) {
+					empty = true;
+					display[colIdx] = false;
+				}
+				else {
+					display[colIdx] = true;
+				}
+
+				usedWidth -= columns[colIdx].minWidth;
+			}
+		}
+
+		// Determine if the 'control' column should be shown (if there is one).
+		// This is the case when there is a hidden column (that is not the
+		// control column). The two loops look inefficient here, but they are
+		// trivial and will fly through. We need to know the outcome from the
+		// first , before the action in the second can be taken
+		var showControl = false;
+
+		for ( i=0, ien=columns.length ; i<ien ; i++ ) {
+			if ( ! columns[i].control && ! columns[i].never && display[i] === false ) {
+				showControl = true;
+				break;
+			}
+		}
+
+		for ( i=0, ien=columns.length ; i<ien ; i++ ) {
+			if ( columns[i].control ) {
+				display[i] = showControl;
+			}
+
+			// Replace not visible string with false from the control column detection above
+			if ( display[i] === 'not-visible' ) {
+				display[i] = false;
+			}
+		}
+
+		// Finally we need to make sure that there is at least one column that
+		// is visible
+		if ( $.inArray( true, display ) === -1 ) {
+			display[0] = true;
+		}
+
+		return display;
+	},
+
+
+	/**
+	 * Create the internal `columns` array with information about the columns
+	 * for the table. This includes determining which breakpoints the column
+	 * will appear in, based upon class names in the column, which makes up the
+	 * vast majority of this method.
+	 *
+	 * @private
+	 */
+	_classLogic: function ()
+	{
+		var that = this;
+		var calc = {};
+		var breakpoints = this.c.breakpoints;
+		var dt = this.s.dt;
+		var columns = dt.columns().eq(0).map( function (i) {
+			var column = this.column(i);
+			var className = column.header().className;
+			var priority = dt.settings()[0].aoColumns[i].responsivePriority;
+			var dataPriority = column.header().getAttribute('data-priority');
+
+			if ( priority === undefined ) {
+				priority = dataPriority === undefined || dataPriority === null?
+					10000 :
+					dataPriority * 1;
+			}
+
+			return {
+				className: className,
+				includeIn: [],
+				auto:      false,
+				control:   false,
+				never:     className.match(/\bnever\b/) ? true : false,
+				priority:  priority
+			};
+		} );
+
+		// Simply add a breakpoint to `includeIn` array, ensuring that there are
+		// no duplicates
+		var add = function ( colIdx, name ) {
+			var includeIn = columns[ colIdx ].includeIn;
+
+			if ( $.inArray( name, includeIn ) === -1 ) {
+				includeIn.push( name );
+			}
+		};
+
+		var column = function ( colIdx, name, operator, matched ) {
+			var size, i, ien;
+
+			if ( ! operator ) {
+				columns[ colIdx ].includeIn.push( name );
+			}
+			else if ( operator === 'max-' ) {
+				// Add this breakpoint and all smaller
+				size = that._find( name ).width;
+
+				for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
+					if ( breakpoints[i].width <= size ) {
+						add( colIdx, breakpoints[i].name );
+					}
+				}
+			}
+			else if ( operator === 'min-' ) {
+				// Add this breakpoint and all larger
+				size = that._find( name ).width;
+
+				for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
+					if ( breakpoints[i].width >= size ) {
+						add( colIdx, breakpoints[i].name );
+					}
+				}
+			}
+			else if ( operator === 'not-' ) {
+				// Add all but this breakpoint
+				for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
+					if ( breakpoints[i].name.indexOf( matched ) === -1 ) {
+						add( colIdx, breakpoints[i].name );
+					}
+				}
+			}
+		};
+
+		// Loop over each column and determine if it has a responsive control
+		// class
+		columns.each( function ( col, i ) {
+			var classNames = col.className.split(' ');
+			var hasClass = false;
+
+			// Split the class name up so multiple rules can be applied if needed
+			for ( var k=0, ken=classNames.length ; k<ken ; k++ ) {
+				var className = $.trim( classNames[k] );
+
+				if ( className === 'all' ) {
+					// Include in all
+					hasClass = true;
+					col.includeIn = $.map( breakpoints, function (a) {
+						return a.name;
+					} );
+					return;
+				}
+				else if ( className === 'none' || col.never ) {
+					// Include in none (default) and no auto
+					hasClass = true;
+					return;
+				}
+				else if ( className === 'control' ) {
+					// Special column that is only visible, when one of the other
+					// columns is hidden. This is used for the details control
+					hasClass = true;
+					col.control = true;
+					return;
+				}
+
+				$.each( breakpoints, function ( j, breakpoint ) {
+					// Does this column have a class that matches this breakpoint?
+					var brokenPoint = breakpoint.name.split('-');
+					var re = new RegExp( '(min\\-|max\\-|not\\-)?('+brokenPoint[0]+')(\\-[_a-zA-Z0-9])?' );
+					var match = className.match( re );
+
+					if ( match ) {
+						hasClass = true;
+
+						if ( match[2] === brokenPoint[0] && match[3] === '-'+brokenPoint[1] ) {
+							// Class name matches breakpoint name fully
+							column( i, breakpoint.name, match[1], match[2]+match[3] );
+						}
+						else if ( match[2] === brokenPoint[0] && ! match[3] ) {
+							// Class name matched primary breakpoint name with no qualifier
+							column( i, breakpoint.name, match[1], match[2] );
+						}
+					}
+				} );
+			}
+
+			// If there was no control class, then automatic sizing is used
+			if ( ! hasClass ) {
+				col.auto = true;
+			}
+		} );
+
+		this.s.columns = columns;
+	},
+
+	/**
+	 * Update the cells to show the correct control class / button
+	 * @private
+	 */
+	_controlClass: function ()
+	{
+		if ( this.c.details.type === 'inline' ) {
+			var dt = this.s.dt;
+			var columnsVis = this.s.current;
+			var firstVisible = $.inArray(true, columnsVis);
+
+			// Remove from any cells which shouldn't have it
+			dt.cells(
+				null,
+				function(idx) {
+					return idx !== firstVisible;
+				},
+				{page: 'current'}
+			)
+				.nodes()
+				.to$()
+				.filter('.dtr-control')
+				.removeClass('dtr-control');
+
+			dt.cells(null, firstVisible, {page: 'current'})
+				.nodes()
+				.to$()
+				.addClass('dtr-control');
+		}
+	},
+
+	/**
+	 * Show the details for the child row
+	 *
+	 * @param  {DataTables.Api} row    API instance for the row
+	 * @param  {boolean}        update Update flag
+	 * @private
+	 */
+	_detailsDisplay: function ( row, update )
+	{
+		var that = this;
+		var dt = this.s.dt;
+		var details = this.c.details;
+
+		if ( details && details.type !== false ) {
+			var res = details.display( row, update, function () {
+				return details.renderer(
+					dt, row[0], that._detailsObj(row[0])
+				);
+			} );
+
+			if ( res === true || res === false ) {
+				$(dt.table().node()).triggerHandler( 'responsive-display.dt', [dt, row, res, update] );
+			}
+		}
+	},
+
+
+	/**
+	 * Initialisation for the details handler
+	 *
+	 * @private
+	 */
+	_detailsInit: function ()
+	{
+		var that    = this;
+		var dt      = this.s.dt;
+		var details = this.c.details;
+
+		// The inline type always uses the first child as the target
+		if ( details.type === 'inline' ) {
+			details.target = 'td.dtr-control, th.dtr-control';
+		}
+
+		// Keyboard accessibility
+		dt.on( 'draw.dtr', function () {
+			that._tabIndexes();
+		} );
+		that._tabIndexes(); // Initial draw has already happened
+
+		$( dt.table().body() ).on( 'keyup.dtr', 'td, th', function (e) {
+			if ( e.keyCode === 13 && $(this).data('dtr-keyboard') ) {
+				$(this).click();
+			}
+		} );
+
+		// type.target can be a string jQuery selector or a column index
+		var target   = details.target;
+		var selector = typeof target === 'string' ? target : 'td, th';
+
+		if ( target !== undefined || target !== null ) {
+			// Click handler to show / hide the details rows when they are available
+			$( dt.table().body() )
+				.on( 'click.dtr mousedown.dtr mouseup.dtr', selector, function (e) {
+					// If the table is not collapsed (i.e. there is no hidden columns)
+					// then take no action
+					if ( ! $(dt.table().node()).hasClass('collapsed' ) ) {
+						return;
+					}
+
+					// Check that the row is actually a DataTable's controlled node
+					if ( $.inArray( $(this).closest('tr').get(0), dt.rows().nodes().toArray() ) === -1 ) {
+						return;
+					}
+
+					// For column index, we determine if we should act or not in the
+					// handler - otherwise it is already okay
+					if ( typeof target === 'number' ) {
+						var targetIdx = target < 0 ?
+							dt.columns().eq(0).length + target :
+							target;
+
+						if ( dt.cell( this ).index().column !== targetIdx ) {
+							return;
+						}
+					}
+
+					// $().closest() includes itself in its check
+					var row = dt.row( $(this).closest('tr') );
+
+					// Check event type to do an action
+					if ( e.type === 'click' ) {
+						// The renderer is given as a function so the caller can execute it
+						// only when they need (i.e. if hiding there is no point is running
+						// the renderer)
+						that._detailsDisplay( row, false );
+					}
+					else if ( e.type === 'mousedown' ) {
+						// For mouse users, prevent the focus ring from showing
+						$(this).css('outline', 'none');
+					}
+					else if ( e.type === 'mouseup' ) {
+						// And then re-allow at the end of the click
+						$(this).trigger('blur').css('outline', '');
+					}
+				} );
+		}
+	},
+
+
+	/**
+	 * Get the details to pass to a renderer for a row
+	 * @param  {int} rowIdx Row index
+	 * @private
+	 */
+	_detailsObj: function ( rowIdx )
+	{
+		var that = this;
+		var dt = this.s.dt;
+
+		return $.map( this.s.columns, function( col, i ) {
+			// Never and control columns should not be passed to the renderer
+			if ( col.never || col.control ) {
+				return;
+			}
+
+			var dtCol = dt.settings()[0].aoColumns[ i ];
+
+			return {
+				className:   dtCol.sClass,
+				columnIndex: i,
+				data:        dt.cell( rowIdx, i ).render( that.c.orthogonal ),
+				hidden:      dt.column( i ).visible() && !that.s.current[ i ],
+				rowIndex:    rowIdx,
+				title:       dtCol.sTitle !== null ?
+					dtCol.sTitle :
+					$(dt.column(i).header()).text()
+			};
+		} );
+	},
+
+
+	/**
+	 * Find a breakpoint object from a name
+	 *
+	 * @param  {string} name Breakpoint name to find
+	 * @return {object}      Breakpoint description object
+	 * @private
+	 */
+	_find: function ( name )
+	{
+		var breakpoints = this.c.breakpoints;
+
+		for ( var i=0, ien=breakpoints.length ; i<ien ; i++ ) {
+			if ( breakpoints[i].name === name ) {
+				return breakpoints[i];
+			}
+		}
+	},
+
+
+	/**
+	 * Re-create the contents of the child rows as the display has changed in
+	 * some way.
+	 *
+	 * @private
+	 */
+	_redrawChildren: function ()
+	{
+		var that = this;
+		var dt = this.s.dt;
+
+		dt.rows( {page: 'current'} ).iterator( 'row', function ( settings, idx ) {
+			var row = dt.row( idx );
+
+			that._detailsDisplay( dt.row( idx ), true );
+		} );
+	},
+
+
+	/**
+	 * Alter the table display for a resized viewport. This involves first
+	 * determining what breakpoint the window currently is in, getting the
+	 * column visibilities to apply and then setting them.
+	 *
+	 * @param  {boolean} forceRedraw Force a redraw
+	 * @private
+	 */
+	_resize: function (forceRedraw)
+	{
+		var that = this;
+		var dt = this.s.dt;
+		var width = $(window).innerWidth();
+		var breakpoints = this.c.breakpoints;
+		var breakpoint = breakpoints[0].name;
+		var columns = this.s.columns;
+		var i, ien;
+		var oldVis = this.s.current.slice();
+
+		// Determine what breakpoint we are currently at
+		for ( i=breakpoints.length-1 ; i>=0 ; i-- ) {
+			if ( width <= breakpoints[i].width ) {
+				breakpoint = breakpoints[i].name;
+				break;
+			}
+		}
+		
+		// Show the columns for that break point
+		var columnsVis = this._columnsVisiblity( breakpoint );
+		this.s.current = columnsVis;
+
+		// Set the class before the column visibility is changed so event
+		// listeners know what the state is. Need to determine if there are
+		// any columns that are not visible but can be shown
+		var collapsedClass = false;
+	
+		for ( i=0, ien=columns.length ; i<ien ; i++ ) {
+			if ( columnsVis[i] === false && ! columns[i].never && ! columns[i].control && ! dt.column(i).visible() === false ) {
+				collapsedClass = true;
+				break;
+			}
+		}
+
+		$( dt.table().node() ).toggleClass( 'collapsed', collapsedClass );
+
+		var changed = false;
+		var visible = 0;
+
+		dt.columns().eq(0).each( function ( colIdx, i ) {
+			if ( columnsVis[i] === true ) {
+				visible++;
+			}
+
+			if ( forceRedraw || columnsVis[i] !== oldVis[i] ) {
+				changed = true;
+				that._setColumnVis( colIdx, columnsVis[i] );
+			}
+		} );
+
+		if ( changed ) {
+			this._redrawChildren();
+
+			// Inform listeners of the change
+			$(dt.table().node()).trigger( 'responsive-resize.dt', [dt, this.s.current] );
+
+			// If no records, update the "No records" display element
+			if ( dt.page.info().recordsDisplay === 0 ) {
+				$('td', dt.table().body()).eq(0).attr('colspan', visible);
+			}
+		}
+	},
+
+
+	/**
+	 * Determine the width of each column in the table so the auto column hiding
+	 * has that information to work with. This method is never going to be 100%
+	 * perfect since column widths can change slightly per page, but without
+	 * seriously compromising performance this is quite effective.
+	 *
+	 * @private
+	 */
+	_resizeAuto: function ()
+	{
+		var dt = this.s.dt;
+		var columns = this.s.columns;
+
+		// Are we allowed to do auto sizing?
+		if ( ! this.c.auto ) {
+			return;
+		}
+
+		// Are there any columns that actually need auto-sizing, or do they all
+		// have classes defined
+		if ( $.inArray( true, $.map( columns, function (c) { return c.auto; } ) ) === -1 ) {
+			return;
+		}
+
+		// Need to restore all children. They will be reinstated by a re-render
+		if ( ! $.isEmptyObject( _childNodeStore ) ) {
+			$.each( _childNodeStore, function ( key ) {
+				var idx = key.split('-');
+
+				_childNodesRestore( dt, idx[0]*1, idx[1]*1 );
+			} );
+		}
+
+		// Clone the table with the current data in it
+		var tableWidth   = dt.table().node().offsetWidth;
+		var columnWidths = dt.columns;
+		var clonedTable  = dt.table().node().cloneNode( false );
+		var clonedHeader = $( dt.table().header().cloneNode( false ) ).appendTo( clonedTable );
+		var clonedBody   = $( dt.table().body() ).clone( false, false ).empty().appendTo( clonedTable ); // use jQuery because of IE8
+
+		clonedTable.style.width = 'auto';
+
+		// Header
+		var headerCells = dt.columns()
+			.header()
+			.filter( function (idx) {
+				return dt.column(idx).visible();
+			} )
+			.to$()
+			.clone( false )
+			.css( 'display', 'table-cell' )
+			.css( 'width', 'auto' )
+			.css( 'min-width', 0 );
+
+		// Body rows - we don't need to take account of DataTables' column
+		// visibility since we implement our own here (hence the `display` set)
+		$(clonedBody)
+			.append( $(dt.rows( { page: 'current' } ).nodes()).clone( false ) )
+			.find( 'th, td' ).css( 'display', '' );
+
+		// Footer
+		var footer = dt.table().footer();
+		if ( footer ) {
+			var clonedFooter = $( footer.cloneNode( false ) ).appendTo( clonedTable );
+			var footerCells = dt.columns()
+				.footer()
+				.filter( function (idx) {
+					return dt.column(idx).visible();
+				} )
+				.to$()
+				.clone( false )
+				.css( 'display', 'table-cell' );
+
+			$('<tr/>')
+				.append( footerCells )
+				.appendTo( clonedFooter );
+		}
+
+		$('<tr/>')
+			.append( headerCells )
+			.appendTo( clonedHeader );
+
+		// In the inline case extra padding is applied to the first column to
+		// give space for the show / hide icon. We need to use this in the
+		// calculation
+		if ( this.c.details.type === 'inline' ) {
+			$(clonedTable).addClass( 'dtr-inline collapsed' );
+		}
+		
+		// It is unsafe to insert elements with the same name into the DOM
+		// multiple times. For example, cloning and inserting a checked radio
+		// clears the chcecked state of the original radio.
+		$( clonedTable ).find( '[name]' ).removeAttr( 'name' );
+
+		// A position absolute table would take the table out of the flow of
+		// our container element, bypassing the height and width (Scroller)
+		$( clonedTable ).css( 'position', 'relative' )
+		
+		var inserted = $('<div/>')
+			.css( {
+				width: 1,
+				height: 1,
+				overflow: 'hidden',
+				clear: 'both'
+			} )
+			.append( clonedTable );
+
+		inserted.insertBefore( dt.table().node() );
+
+		// The cloned header now contains the smallest that each column can be
+		headerCells.each( function (i) {
+			var idx = dt.column.index( 'fromVisible', i );
+			columns[ idx ].minWidth =  this.offsetWidth || 0;
+		} );
+
+		inserted.remove();
+	},
+
+	/**
+	 * Get the state of the current hidden columns - controlled by Responsive only
+	 */
+	_responsiveOnlyHidden: function ()
+	{
+		var dt = this.s.dt;
+
+		return $.map( this.s.current, function (v, i) {
+			// If the column is hidden by DataTables then it can't be hidden by
+			// Responsive!
+			if ( dt.column(i).visible() === false ) {
+				return true;
+			}
+			return v;
+		} );
+	},
+
+	/**
+	 * Set a column's visibility.
+	 *
+	 * We don't use DataTables' column visibility controls in order to ensure
+	 * that column visibility can Responsive can no-exist. Since only IE8+ is
+	 * supported (and all evergreen browsers of course) the control of the
+	 * display attribute works well.
+	 *
+	 * @param {integer} col      Column index
+	 * @param {boolean} showHide Show or hide (true or false)
+	 * @private
+	 */
+	_setColumnVis: function ( col, showHide )
+	{
+		var dt = this.s.dt;
+		var display = showHide ? '' : 'none'; // empty string will remove the attr
+
+		$( dt.column( col ).header() ).css( 'display', display );
+		$( dt.column( col ).footer() ).css( 'display', display );
+		dt.column( col ).nodes().to$().css( 'display', display );
+
+		// If the are child nodes stored, we might need to reinsert them
+		if ( ! $.isEmptyObject( _childNodeStore ) ) {
+			dt.cells( null, col ).indexes().each( function (idx) {
+				_childNodesRestore( dt, idx.row, idx.column );
+			} );
+		}
+	},
+
+
+	/**
+	 * Update the cell tab indexes for keyboard accessibility. This is called on
+	 * every table draw - that is potentially inefficient, but also the least
+	 * complex option given that column visibility can change on the fly. Its a
+	 * shame user-focus was removed from CSS 3 UI, as it would have solved this
+	 * issue with a single CSS statement.
+	 *
+	 * @private
+	 */
+	_tabIndexes: function ()
+	{
+		var dt = this.s.dt;
+		var cells = dt.cells( { page: 'current' } ).nodes().to$();
+		var ctx = dt.settings()[0];
+		var target = this.c.details.target;
+
+		cells.filter( '[data-dtr-keyboard]' ).removeData( '[data-dtr-keyboard]' );
+
+		if ( typeof target === 'number' ) {
+			dt.cells( null, target, { page: 'current' } ).nodes().to$()
+				.attr( 'tabIndex', ctx.iTabIndex )
+				.data( 'dtr-keyboard', 1 );
+		}
+		else {
+			// This is a bit of a hack - we need to limit the selected nodes to just
+			// those of this table
+			if ( target === 'td:first-child, th:first-child' ) {
+				target = '>td:first-child, >th:first-child';
+			}
+
+			$( target, dt.rows( { page: 'current' } ).nodes() )
+				.attr( 'tabIndex', ctx.iTabIndex )
+				.data( 'dtr-keyboard', 1 );
+		}
+	}
+} );
+
+
+/**
+ * List of default breakpoints. Each item in the array is an object with two
+ * properties:
+ *
+ * * `name` - the breakpoint name.
+ * * `width` - the breakpoint width
+ *
+ * @name Responsive.breakpoints
+ * @static
+ */
+Responsive.breakpoints = [
+	{ name: 'desktop',  width: Infinity },
+	{ name: 'tablet-l', width: 1024 },
+	{ name: 'tablet-p', width: 768 },
+	{ name: 'mobile-l', width: 480 },
+	{ name: 'mobile-p', width: 320 }
+];
+
+
+/**
+ * Display methods - functions which define how the hidden data should be shown
+ * in the table.
+ *
+ * @namespace
+ * @name Responsive.defaults
+ * @static
+ */
+Responsive.display = {
+	childRow: function ( row, update, render ) {
+		if ( update ) {
+			if ( $(row.node()).hasClass('parent') ) {
+				row.child( render(), 'child' ).show();
+
+				return true;
+			}
+		}
+		else {
+			if ( ! row.child.isShown()  ) {
+				row.child( render(), 'child' ).show();
+				$( row.node() ).addClass( 'parent' );
+
+				return true;
+			}
+			else {
+				row.child( false );
+				$( row.node() ).removeClass( 'parent' );
+
+				return false;
+			}
+		}
+	},
+
+	childRowImmediate: function ( row, update, render ) {
+		if ( (! update && row.child.isShown()) || ! row.responsive.hasHidden() ) {
+			// User interaction and the row is show, or nothing to show
+			row.child( false );
+			$( row.node() ).removeClass( 'parent' );
+
+			return false;
+		}
+		else {
+			// Display
+			row.child( render(), 'child' ).show();
+			$( row.node() ).addClass( 'parent' );
+
+			return true;
+		}
+	},
+
+	// This is a wrapper so the modal options for Bootstrap and jQuery UI can
+	// have options passed into them. This specific one doesn't need to be a
+	// function but it is for consistency in the `modal` name
+	modal: function ( options ) {
+		return function ( row, update, render ) {
+			if ( ! update ) {
+				// Show a modal
+				var close = function () {
+					modal.remove(); // will tidy events for us
+					$(document).off( 'keypress.dtr' );
+				};
+
+				var modal = $('<div class="dtr-modal"/>')
+					.append( $('<div class="dtr-modal-display"/>')
+						.append( $('<div class="dtr-modal-content"/>')
+							.append( render() )
+						)
+						.append( $('<div class="dtr-modal-close">&times;</div>' )
+							.click( function () {
+								close();
+							} )
+						)
+					)
+					.append( $('<div class="dtr-modal-background"/>')
+						.click( function () {
+							close();
+						} )
+					)
+					.appendTo( 'body' );
+
+				$(document).on( 'keyup.dtr', function (e) {
+					if ( e.keyCode === 27 ) {
+						e.stopPropagation();
+
+						close();
+					}
+				} );
+			}
+			else {
+				$('div.dtr-modal-content')
+					.empty()
+					.append( render() );
+			}
+
+			if ( options && options.header ) {
+				$('div.dtr-modal-content').prepend(
+					'<h2>'+options.header( row )+'</h2>'
+				);
+			}
+		};
+	}
+};
+
+
+var _childNodeStore = {};
+
+function _childNodes( dt, row, col ) {
+	var name = row+'-'+col;
+
+	if ( _childNodeStore[ name ] ) {
+		return _childNodeStore[ name ];
+	}
+
+	// https://jsperf.com/childnodes-array-slice-vs-loop
+	var nodes = [];
+	var children = dt.cell( row, col ).node().childNodes;
+	for ( var i=0, ien=children.length ; i<ien ; i++ ) {
+		nodes.push( children[i] );
+	}
+
+	_childNodeStore[ name ] = nodes;
+
+	return nodes;
+}
+
+function _childNodesRestore( dt, row, col ) {
+	var name = row+'-'+col;
+
+	if ( ! _childNodeStore[ name ] ) {
+		return;
+	}
+
+	var node = dt.cell( row, col ).node();
+	var store = _childNodeStore[ name ];
+	var parent = store[0].parentNode;
+	var parentChildren = parent.childNodes;
+	var a = [];
+
+	for ( var i=0, ien=parentChildren.length ; i<ien ; i++ ) {
+		a.push( parentChildren[i] );
+	}
+
+	for ( var j=0, jen=a.length ; j<jen ; j++ ) {
+		node.appendChild( a[j] );
+	}
+
+	_childNodeStore[ name ] = undefined;
+}
+
+
+/**
+ * Display methods - functions which define how the hidden data should be shown
+ * in the table.
+ *
+ * @namespace
+ * @name Responsive.defaults
+ * @static
+ */
+Responsive.renderer = {
+	listHiddenNodes: function () {
+		return function ( api, rowIdx, columns ) {
+			var ul = $('<ul data-dtr-index="'+rowIdx+'" class="dtr-details"/>');
+			var found = false;
+
+			var data = $.each( columns, function ( i, col ) {
+				if ( col.hidden ) {
+					var klass = col.className ?
+						'class="'+ col.className +'"' :
+						'';
+	
+					$(
+						'<li '+klass+' data-dtr-index="'+col.columnIndex+'" data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+
+							'<span class="dtr-title">'+
+								col.title+
+							'</span> '+
+						'</li>'
+					)
+						.append( $('<span class="dtr-data"/>').append( _childNodes( api, col.rowIndex, col.columnIndex ) ) )// api.cell( col.rowIndex, col.columnIndex ).node().childNodes ) )
+						.appendTo( ul );
+
+					found = true;
+				}
+			} );
+
+			return found ?
+				ul :
+				false;
+		};
+	},
+
+	listHidden: function () {
+		return function ( api, rowIdx, columns ) {
+			var data = $.map( columns, function ( col ) {
+				var klass = col.className ?
+					'class="'+ col.className +'"' :
+					'';
+
+				return col.hidden ?
+					'<li '+klass+' data-dtr-index="'+col.columnIndex+'" data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+
+						'<span class="dtr-title">'+
+							col.title+
+						'</span> '+
+						'<span class="dtr-data">'+
+							col.data+
+						'</span>'+
+					'</li>' :
+					'';
+			} ).join('');
+
+			return data ?
+				$('<ul data-dtr-index="'+rowIdx+'" class="dtr-details"/>').append( data ) :
+				false;
+		}
+	},
+
+	tableAll: function ( options ) {
+		options = $.extend( {
+			tableClass: ''
+		}, options );
+
+		return function ( api, rowIdx, columns ) {
+			var data = $.map( columns, function ( col ) {
+				var klass = col.className ?
+					'class="'+ col.className +'"' :
+					'';
+
+				return '<tr '+klass+' data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+
+						'<td>'+col.title+':'+'</td> '+
+						'<td>'+col.data+'</td>'+
+					'</tr>';
+			} ).join('');
+
+			return $('<table class="'+options.tableClass+' dtr-details" width="100%"/>').append( data );
+		}
+	}
+};
+
+/**
+ * Responsive default settings for initialisation
+ *
+ * @namespace
+ * @name Responsive.defaults
+ * @static
+ */
+Responsive.defaults = {
+	/**
+	 * List of breakpoints for the instance. Note that this means that each
+	 * instance can have its own breakpoints. Additionally, the breakpoints
+	 * cannot be changed once an instance has been creased.
+	 *
+	 * @type {Array}
+	 * @default Takes the value of `Responsive.breakpoints`
+	 */
+	breakpoints: Responsive.breakpoints,
+
+	/**
+	 * Enable / disable auto hiding calculations. It can help to increase
+	 * performance slightly if you disable this option, but all columns would
+	 * need to have breakpoint classes assigned to them
+	 *
+	 * @type {Boolean}
+	 * @default  `true`
+	 */
+	auto: true,
+
+	/**
+	 * Details control. If given as a string value, the `type` property of the
+	 * default object is set to that value, and the defaults used for the rest
+	 * of the object - this is for ease of implementation.
+	 *
+	 * The object consists of the following properties:
+	 *
+	 * * `display` - A function that is used to show and hide the hidden details
+	 * * `renderer` - function that is called for display of the child row data.
+	 *   The default function will show the data from the hidden columns
+	 * * `target` - Used as the selector for what objects to attach the child
+	 *   open / close to
+	 * * `type` - `false` to disable the details display, `inline` or `column`
+	 *   for the two control types
+	 *
+	 * @type {Object|string}
+	 */
+	details: {
+		display: Responsive.display.childRow,
+
+		renderer: Responsive.renderer.listHidden(),
+
+		target: 0,
+
+		type: 'inline'
+	},
+
+	/**
+	 * Orthogonal data request option. This is used to define the data type
+	 * requested when Responsive gets the data to show in the child row.
+	 *
+	 * @type {String}
+	 */
+	orthogonal: 'display'
+};
+
+
+/*
+ * API
+ */
+var Api = $.fn.dataTable.Api;
+
+// Doesn't do anything - work around for a bug in DT... Not documented
+Api.register( 'responsive()', function () {
+	return this;
+} );
+
+Api.register( 'responsive.index()', function ( li ) {
+	li = $(li);
+
+	return {
+		column: li.data('dtr-index'),
+		row:    li.parent().data('dtr-index')
+	};
+} );
+
+Api.register( 'responsive.rebuild()', function () {
+	return this.iterator( 'table', function ( ctx ) {
+		if ( ctx._responsive ) {
+			ctx._responsive._classLogic();
+		}
+	} );
+} );
+
+Api.register( 'responsive.recalc()', function () {
+	return this.iterator( 'table', function ( ctx ) {
+		if ( ctx._responsive ) {
+			ctx._responsive._resizeAuto();
+			ctx._responsive._resize();
+		}
+	} );
+} );
+
+Api.register( 'responsive.hasHidden()', function () {
+	var ctx = this.context[0];
+
+	return ctx._responsive ?
+		$.inArray( false, ctx._responsive._responsiveOnlyHidden() ) !== -1 :
+		false;
+} );
+
+Api.registerPlural( 'columns().responsiveHidden()', 'column().responsiveHidden()', function () {
+	return this.iterator( 'column', function ( settings, column ) {
+		return settings._responsive ?
+			settings._responsive._responsiveOnlyHidden()[ column ] :
+			false;
+	}, 1 );
+} );
+
+
+/**
+ * Version information
+ *
+ * @name Responsive.version
+ * @static
+ */
+Responsive.version = '2.2.5';
+
+
+$.fn.dataTable.Responsive = Responsive;
+$.fn.DataTable.Responsive = Responsive;
+
+// Attach a listener to the document which listens for DataTables initialisation
+// events so we can automatically initialise
+$(document).on( 'preInit.dt.dtr', function (e, settings, json) {
+	if ( e.namespace !== 'dt' ) {
+		return;
+	}
+
+	if ( $(settings.nTable).hasClass( 'responsive' ) ||
+		 $(settings.nTable).hasClass( 'dt-responsive' ) ||
+		 settings.oInit.responsive ||
+		 DataTable.defaults.responsive
+	) {
+		var init = settings.oInit.responsive;
+
+		if ( init !== false ) {
+			new Responsive( settings, $.isPlainObject( init ) ? init : {}  );
+		}
+	}
+} );
+
+
+return Responsive;
+}));

+ 32 - 0
GreenTree.Strohrmann.ERP.Web/wwwroot/lib/datatables-responsive/dataTables.responsive.min.js

@@ -0,0 +1,32 @@
+/*!
+ Responsive 2.2.5
+ 2014-2020 SpryMedia Ltd - datatables.net/license
+*/
+(function(d){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(m){return d(m,window,document)}):"object"===typeof exports?module.exports=function(m,l){m||(m=window);if(!l||!l.fn.dataTable)l=require("datatables.net")(m,l).$;return d(l,m,m.document)}:d(jQuery,window,document)})(function(d,m,l,q){function t(a,b,c){var e=b+"-"+c;if(n[e])return n[e];for(var d=[],a=a.cell(b,c).node().childNodes,b=0,c=a.length;b<c;b++)d.push(a[b]);return n[e]=d}function r(a,b,c){var e=b+
+"-"+c;if(n[e]){for(var a=a.cell(b,c).node(),c=n[e][0].parentNode.childNodes,b=[],d=0,g=c.length;d<g;d++)b.push(c[d]);c=0;for(d=b.length;c<d;c++)a.appendChild(b[c]);n[e]=q}}var o=d.fn.dataTable,i=function(a,b){if(!o.versionCheck||!o.versionCheck("1.10.10"))throw"DataTables Responsive requires DataTables 1.10.10 or newer";this.s={dt:new o.Api(a),columns:[],current:[]};this.s.dt.settings()[0].responsive||(b&&"string"===typeof b.details?b.details={type:b.details}:b&&!1===b.details?b.details={type:!1}:
+b&&!0===b.details&&(b.details={type:"inline"}),this.c=d.extend(!0,{},i.defaults,o.defaults.responsive,b),a.responsive=this,this._constructor())};d.extend(i.prototype,{_constructor:function(){var a=this,b=this.s.dt,c=b.settings()[0],e=d(m).innerWidth();b.settings()[0]._responsive=this;d(m).on("resize.dtr orientationchange.dtr",o.util.throttle(function(){var b=d(m).innerWidth();b!==e&&(a._resize(),e=b)}));c.oApi._fnCallbackReg(c,"aoRowCreatedCallback",function(c){-1!==d.inArray(!1,a.s.current)&&d(">td, >th",
+c).each(function(c){c=b.column.index("toData",c);!1===a.s.current[c]&&d(this).css("display","none")})});b.on("destroy.dtr",function(){b.off(".dtr");d(b.table().body()).off(".dtr");d(m).off("resize.dtr orientationchange.dtr");b.cells(".dtr-control").nodes().to$().removeClass("dtr-control");d.each(a.s.current,function(b,c){!1===c&&a._setColumnVis(b,!0)})});this.c.breakpoints.sort(function(a,b){return a.width<b.width?1:a.width>b.width?-1:0});this._classLogic();this._resizeAuto();c=this.c.details;!1!==
+c.type&&(a._detailsInit(),b.on("column-visibility.dtr",function(){a._timer&&clearTimeout(a._timer);a._timer=setTimeout(function(){a._timer=null;a._classLogic();a._resizeAuto();a._resize();a._redrawChildren()},100)}),b.on("draw.dtr",function(){a._redrawChildren()}),d(b.table().node()).addClass("dtr-"+c.type));b.on("column-reorder.dtr",function(){a._classLogic();a._resizeAuto();a._resize(true)});b.on("column-sizing.dtr",function(){a._resizeAuto();a._resize()});b.on("preXhr.dtr",function(){var c=[];
+b.rows().every(function(){this.child.isShown()&&c.push(this.id(true))});b.one("draw.dtr",function(){a._resizeAuto();a._resize();b.rows(c).every(function(){a._detailsDisplay(this,false)})})});b.on("draw.dtr",function(){a._controlClass()}).on("init.dtr",function(c){if(c.namespace==="dt"){a._resizeAuto();a._resize();d.inArray(false,a.s.current)&&b.columns.adjust()}});this._resize()},_columnsVisiblity:function(a){var b=this.s.dt,c=this.s.columns,e,f,g=c.map(function(a,b){return{columnIdx:b,priority:a.priority}}).sort(function(a,
+b){return a.priority!==b.priority?a.priority-b.priority:a.columnIdx-b.columnIdx}),j=d.map(c,function(c,e){return!1===b.column(e).visible()?"not-visible":c.auto&&null===c.minWidth?!1:!0===c.auto?"-":-1!==d.inArray(a,c.includeIn)}),h=0;e=0;for(f=j.length;e<f;e++)!0===j[e]&&(h+=c[e].minWidth);e=b.settings()[0].oScroll;e=e.sY||e.sX?e.iBarWidth:0;h=b.table().container().offsetWidth-e-h;e=0;for(f=j.length;e<f;e++)c[e].control&&(h-=c[e].minWidth);var s=!1;e=0;for(f=g.length;e<f;e++){var k=g[e].columnIdx;
+"-"===j[k]&&(!c[k].control&&c[k].minWidth)&&(s||0>h-c[k].minWidth?(s=!0,j[k]=!1):j[k]=!0,h-=c[k].minWidth)}g=!1;e=0;for(f=c.length;e<f;e++)if(!c[e].control&&!c[e].never&&!1===j[e]){g=!0;break}e=0;for(f=c.length;e<f;e++)c[e].control&&(j[e]=g),"not-visible"===j[e]&&(j[e]=!1);-1===d.inArray(!0,j)&&(j[0]=!0);return j},_classLogic:function(){var a=this,b=this.c.breakpoints,c=this.s.dt,e=c.columns().eq(0).map(function(a){var b=this.column(a),e=b.header().className,a=c.settings()[0].aoColumns[a].responsivePriority,
+b=b.header().getAttribute("data-priority");a===q&&(a=b===q||null===b?1E4:1*b);return{className:e,includeIn:[],auto:!1,control:!1,never:e.match(/\bnever\b/)?!0:!1,priority:a}}),f=function(a,b){var c=e[a].includeIn;-1===d.inArray(b,c)&&c.push(b)},g=function(c,d,g,k){if(g)if("max-"===g){k=a._find(d).width;d=0;for(g=b.length;d<g;d++)b[d].width<=k&&f(c,b[d].name)}else if("min-"===g){k=a._find(d).width;d=0;for(g=b.length;d<g;d++)b[d].width>=k&&f(c,b[d].name)}else{if("not-"===g){d=0;for(g=b.length;d<g;d++)-1===
+b[d].name.indexOf(k)&&f(c,b[d].name)}}else e[c].includeIn.push(d)};e.each(function(a,c){for(var e=a.className.split(" "),f=!1,i=0,m=e.length;i<m;i++){var l=d.trim(e[i]);if("all"===l){f=!0;a.includeIn=d.map(b,function(a){return a.name});return}if("none"===l||a.never){f=!0;return}if("control"===l){f=!0;a.control=!0;return}d.each(b,function(a,b){var d=b.name.split("-"),e=l.match(RegExp("(min\\-|max\\-|not\\-)?("+d[0]+")(\\-[_a-zA-Z0-9])?"));e&&(f=!0,e[2]===d[0]&&e[3]==="-"+d[1]?g(c,b.name,e[1],e[2]+
+e[3]):e[2]===d[0]&&!e[3]&&g(c,b.name,e[1],e[2]))})}f||(a.auto=!0)});this.s.columns=e},_controlClass:function(){if("inline"===this.c.details.type){var a=this.s.dt,b=d.inArray(!0,this.s.current);a.cells(null,function(a){return a!==b},{page:"current"}).nodes().to$().filter(".dtr-control").removeClass("dtr-control");a.cells(null,b,{page:"current"}).nodes().to$().addClass("dtr-control")}},_detailsDisplay:function(a,b){var c=this,e=this.s.dt,f=this.c.details;if(f&&!1!==f.type){var g=f.display(a,b,function(){return f.renderer(e,
+a[0],c._detailsObj(a[0]))});(!0===g||!1===g)&&d(e.table().node()).triggerHandler("responsive-display.dt",[e,a,g,b])}},_detailsInit:function(){var a=this,b=this.s.dt,c=this.c.details;"inline"===c.type&&(c.target="td.dtr-control, th.dtr-control");b.on("draw.dtr",function(){a._tabIndexes()});a._tabIndexes();d(b.table().body()).on("keyup.dtr","td, th",function(a){a.keyCode===13&&d(this).data("dtr-keyboard")&&d(this).click()});var e=c.target;if(e!==q||null!==e)d(b.table().body()).on("click.dtr mousedown.dtr mouseup.dtr",
+"string"===typeof e?e:"td, th",function(c){if(d(b.table().node()).hasClass("collapsed")&&d.inArray(d(this).closest("tr").get(0),b.rows().nodes().toArray())!==-1){if(typeof e==="number"){var g=e<0?b.columns().eq(0).length+e:e;if(b.cell(this).index().column!==g)return}g=b.row(d(this).closest("tr"));c.type==="click"?a._detailsDisplay(g,false):c.type==="mousedown"?d(this).css("outline","none"):c.type==="mouseup"&&d(this).trigger("blur").css("outline","")}})},_detailsObj:function(a){var b=this,c=this.s.dt;
+return d.map(this.s.columns,function(e,f){if(!e.never&&!e.control){var g=c.settings()[0].aoColumns[f];return{className:g.sClass,columnIndex:f,data:c.cell(a,f).render(b.c.orthogonal),hidden:c.column(f).visible()&&!b.s.current[f],rowIndex:a,title:null!==g.sTitle?g.sTitle:d(c.column(f).header()).text()}}})},_find:function(a){for(var b=this.c.breakpoints,c=0,d=b.length;c<d;c++)if(b[c].name===a)return b[c]},_redrawChildren:function(){var a=this,b=this.s.dt;b.rows({page:"current"}).iterator("row",function(c,
+d){b.row(d);a._detailsDisplay(b.row(d),!0)})},_resize:function(a){var b=this,c=this.s.dt,e=d(m).innerWidth(),f=this.c.breakpoints,g=f[0].name,j=this.s.columns,h,i=this.s.current.slice();for(h=f.length-1;0<=h;h--)if(e<=f[h].width){g=f[h].name;break}var k=this._columnsVisiblity(g);this.s.current=k;f=!1;h=0;for(e=j.length;h<e;h++)if(!1===k[h]&&!j[h].never&&!j[h].control&&!1===!c.column(h).visible()){f=!0;break}d(c.table().node()).toggleClass("collapsed",f);var l=!1,n=0;c.columns().eq(0).each(function(c,
+d){!0===k[d]&&n++;if(a||k[d]!==i[d])l=!0,b._setColumnVis(c,k[d])});l&&(this._redrawChildren(),d(c.table().node()).trigger("responsive-resize.dt",[c,this.s.current]),0===c.page.info().recordsDisplay&&d("td",c.table().body()).eq(0).attr("colspan",n))},_resizeAuto:function(){var a=this.s.dt,b=this.s.columns;if(this.c.auto&&-1!==d.inArray(!0,d.map(b,function(a){return a.auto}))){d.isEmptyObject(n)||d.each(n,function(b){b=b.split("-");r(a,1*b[0],1*b[1])});a.table().node();var c=a.table().node().cloneNode(!1),
+e=d(a.table().header().cloneNode(!1)).appendTo(c),f=d(a.table().body()).clone(!1,!1).empty().appendTo(c);c.style.width="auto";var g=a.columns().header().filter(function(b){return a.column(b).visible()}).to$().clone(!1).css("display","table-cell").css("width","auto").css("min-width",0);d(f).append(d(a.rows({page:"current"}).nodes()).clone(!1)).find("th, td").css("display","");if(f=a.table().footer()){var f=d(f.cloneNode(!1)).appendTo(c),j=a.columns().footer().filter(function(b){return a.column(b).visible()}).to$().clone(!1).css("display",
+"table-cell");d("<tr/>").append(j).appendTo(f)}d("<tr/>").append(g).appendTo(e);"inline"===this.c.details.type&&d(c).addClass("dtr-inline collapsed");d(c).find("[name]").removeAttr("name");d(c).css("position","relative");c=d("<div/>").css({width:1,height:1,overflow:"hidden",clear:"both"}).append(c);c.insertBefore(a.table().node());g.each(function(d){d=a.column.index("fromVisible",d);b[d].minWidth=this.offsetWidth||0});c.remove()}},_responsiveOnlyHidden:function(){var a=this.s.dt;return d.map(this.s.current,
+function(b,d){return!1===a.column(d).visible()?!0:b})},_setColumnVis:function(a,b){var c=this.s.dt,e=b?"":"none";d(c.column(a).header()).css("display",e);d(c.column(a).footer()).css("display",e);c.column(a).nodes().to$().css("display",e);d.isEmptyObject(n)||c.cells(null,a).indexes().each(function(a){r(c,a.row,a.column)})},_tabIndexes:function(){var a=this.s.dt,b=a.cells({page:"current"}).nodes().to$(),c=a.settings()[0],e=this.c.details.target;b.filter("[data-dtr-keyboard]").removeData("[data-dtr-keyboard]");
+"number"===typeof e?a.cells(null,e,{page:"current"}).nodes().to$().attr("tabIndex",c.iTabIndex).data("dtr-keyboard",1):("td:first-child, th:first-child"===e&&(e=">td:first-child, >th:first-child"),d(e,a.rows({page:"current"}).nodes()).attr("tabIndex",c.iTabIndex).data("dtr-keyboard",1))}});i.breakpoints=[{name:"desktop",width:Infinity},{name:"tablet-l",width:1024},{name:"tablet-p",width:768},{name:"mobile-l",width:480},{name:"mobile-p",width:320}];i.display={childRow:function(a,b,c){if(b){if(d(a.node()).hasClass("parent"))return a.child(c(),
+"child").show(),!0}else{if(a.child.isShown())return a.child(!1),d(a.node()).removeClass("parent"),!1;a.child(c(),"child").show();d(a.node()).addClass("parent");return!0}},childRowImmediate:function(a,b,c){if(!b&&a.child.isShown()||!a.responsive.hasHidden())return a.child(!1),d(a.node()).removeClass("parent"),!1;a.child(c(),"child").show();d(a.node()).addClass("parent");return!0},modal:function(a){return function(b,c,e){if(c)d("div.dtr-modal-content").empty().append(e());else{var f=function(){g.remove();
+d(l).off("keypress.dtr")},g=d('<div class="dtr-modal"/>').append(d('<div class="dtr-modal-display"/>').append(d('<div class="dtr-modal-content"/>').append(e())).append(d('<div class="dtr-modal-close">&times;</div>').click(function(){f()}))).append(d('<div class="dtr-modal-background"/>').click(function(){f()})).appendTo("body");d(l).on("keyup.dtr",function(a){27===a.keyCode&&(a.stopPropagation(),f())})}a&&a.header&&d("div.dtr-modal-content").prepend("<h2>"+a.header(b)+"</h2>")}}};var n={};i.renderer=
+{listHiddenNodes:function(){return function(a,b,c){var e=d('<ul data-dtr-index="'+b+'" class="dtr-details"/>'),f=!1;d.each(c,function(b,c){c.hidden&&(d("<li "+(c.className?'class="'+c.className+'"':"")+' data-dtr-index="'+c.columnIndex+'" data-dt-row="'+c.rowIndex+'" data-dt-column="'+c.columnIndex+'"><span class="dtr-title">'+c.title+"</span> </li>").append(d('<span class="dtr-data"/>').append(t(a,c.rowIndex,c.columnIndex))).appendTo(e),f=!0)});return f?e:!1}},listHidden:function(){return function(a,
+b,c){return(a=d.map(c,function(a){var b=a.className?'class="'+a.className+'"':"";return a.hidden?"<li "+b+' data-dtr-index="'+a.columnIndex+'" data-dt-row="'+a.rowIndex+'" data-dt-column="'+a.columnIndex+'"><span class="dtr-title">'+a.title+'</span> <span class="dtr-data">'+a.data+"</span></li>":""}).join(""))?d('<ul data-dtr-index="'+b+'" class="dtr-details"/>').append(a):!1}},tableAll:function(a){a=d.extend({tableClass:""},a);return function(b,c,e){b=d.map(e,function(a){return"<tr "+(a.className?
+'class="'+a.className+'"':"")+' data-dt-row="'+a.rowIndex+'" data-dt-column="'+a.columnIndex+'"><td>'+a.title+":</td> <td>"+a.data+"</td></tr>"}).join("");return d('<table class="'+a.tableClass+' dtr-details" width="100%"/>').append(b)}}};i.defaults={breakpoints:i.breakpoints,auto:!0,details:{display:i.display.childRow,renderer:i.renderer.listHidden(),target:0,type:"inline"},orthogonal:"display"};var p=d.fn.dataTable.Api;p.register("responsive()",function(){return this});p.register("responsive.index()",
+function(a){a=d(a);return{column:a.data("dtr-index"),row:a.parent().data("dtr-index")}});p.register("responsive.rebuild()",function(){return this.iterator("table",function(a){a._responsive&&a._responsive._classLogic()})});p.register("responsive.recalc()",function(){return this.iterator("table",function(a){a._responsive&&(a._responsive._resizeAuto(),a._responsive._resize())})});p.register("responsive.hasHidden()",function(){var a=this.context[0];return a._responsive?-1!==d.inArray(!1,a._responsive._responsiveOnlyHidden()):
+!1});p.registerPlural("columns().responsiveHidden()","column().responsiveHidden()",function(){return this.iterator("column",function(a,b){return a._responsive?a._responsive._responsiveOnlyHidden()[b]:!1},1)});i.version="2.2.5";d.fn.dataTable.Responsive=i;d.fn.DataTable.Responsive=i;d(l).on("preInit.dt.dtr",function(a,b){if("dt"===a.namespace&&(d(b.nTable).hasClass("responsive")||d(b.nTable).hasClass("dt-responsive")||b.oInit.responsive||o.defaults.responsive)){var c=b.oInit.responsive;!1!==c&&new i(b,
+d.isPlainObject(c)?c:{})}});return i});