diff --git a/LaaProductionWeb/LaaProductionWeb.Data/DbContext.cs b/LaaProductionWeb/LaaProductionWeb.Data/DbContext.cs index ef9012ab..deec32c4 100644 --- a/LaaProductionWeb/LaaProductionWeb.Data/DbContext.cs +++ b/LaaProductionWeb/LaaProductionWeb.Data/DbContext.cs @@ -36,6 +36,27 @@ } } + public IEnumerable AsEnumerable(string query, Func reader) + { + using (var sqlConnection = new SqlConnection(this.connectionString)) + { + sqlConnection.OpenWithErrorHandling(); + + using (var sqlCommand = sqlConnection.CreateCommand()) + { + sqlCommand.CommandText = query; + + using (var sqlDataReader = sqlCommand.ExecuteReader()) + { + while (sqlDataReader.Read()) + { + yield return reader(new SqlReader(sqlDataReader)); + } + } + } + } + } + public T FirstOrDefault(string query, Action parameters, Func reader) { var firstOrDefaultResult = default(T); @@ -94,5 +115,34 @@ return scalarResult; } + + public bool Transaction(string query, Action parameters) + { + var canCommitTransaction = true; + + using (var sqlConnection = new SqlConnection(this.connectionString)) + { + sqlConnection.OpenWithErrorHandling((sender, args) => canCommitTransaction = false); + + using (var sqlTransaction = sqlConnection.BeginTransaction()) + { + using (var sqlCommand = sqlConnection.CreateCommand()) + { + sqlCommand.Transaction = sqlTransaction; + sqlCommand.CommandText = query; + + parameters(new ParameterCollection(sqlCommand.Parameters)); + sqlCommand.ExecuteNonQuery(); + } + + if (canCommitTransaction) + { + sqlTransaction.Commit(); + } + } + } + + return canCommitTransaction; + } } } diff --git a/LaaProductionWeb/LaaProductionWeb.Data/DbContextExtensions.cs b/LaaProductionWeb/LaaProductionWeb.Data/DbContextExtensions.cs index fd61a718..d5d94c28 100644 --- a/LaaProductionWeb/LaaProductionWeb.Data/DbContextExtensions.cs +++ b/LaaProductionWeb/LaaProductionWeb.Data/DbContextExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; + using System; using System.Data.SqlClient; using System.Diagnostics; @@ -16,11 +17,16 @@ return services; } - internal static void OpenWithErrorHandling(this SqlConnection sqlConnection) + internal static void OpenWithErrorHandling(this SqlConnection sqlConnection, SqlInfoMessageEventHandler messageHandler = null) { sqlConnection.FireInfoMessageEventOnUserErrors = true; sqlConnection.InfoMessage += SqlConnectionInfoMessage; + if (messageHandler != null) + { + sqlConnection.InfoMessage += messageHandler; + } + sqlConnection.Open(); } diff --git a/LaaProductionWeb/LaaProductionWeb.Data/Interfaces/IDbContext.cs b/LaaProductionWeb/LaaProductionWeb.Data/Interfaces/IDbContext.cs index 20ea5496..51c7040e 100644 --- a/LaaProductionWeb/LaaProductionWeb.Data/Interfaces/IDbContext.cs +++ b/LaaProductionWeb/LaaProductionWeb.Data/Interfaces/IDbContext.cs @@ -7,8 +7,12 @@ { IEnumerable AsEnumerable(string query, Action parameters, Func reader); + IEnumerable AsEnumerable(string query, Func reader); + T FirstOrDefault(string query, Action parameters, Func reader); T ScalarTransaction(string query, Action parameters); + + bool Transaction(string query, Action parameters); } } diff --git a/LaaProductionWeb/LaaProductionWeb.Services/AccountService.cs b/LaaProductionWeb/LaaProductionWeb.Services/AccountService.cs new file mode 100644 index 00000000..6cfc6327 --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb.Services/AccountService.cs @@ -0,0 +1,190 @@ +namespace LaaProductionWeb.Services +{ + using LaaProductionWeb.Data.Interfaces; + using LaaProductionWeb.Services.Interfaces; + using LaaProductionWeb.Services.Models; + + using System.Collections.Generic; + using System.Linq; + + public class AccountService : IAccountService + { + private readonly IDbContext dbContext; + + public AccountService(IDbContext dbContext) + => this.dbContext = dbContext; + + public WindowsUser GetOrCreateWindowsUser(string primarySID, string domainName, string userName) + { + var user = this.dbContext.FirstOrDefault($@" + DECLARE @userId INT; + + SELECT @userId = [Account].[WindowsUsers].[UserId] + FROM [Account].[WindowsUsers] + WHERE [Account].[WindowsUsers].[PrimarySID] = @{nameof(primarySID)} + + IF @userId IS NULL + INSERT INTO [Account].[WindowsUsers] ( + [PrimarySID] + , [DomainName] + , [UserName]) + OUTPUT [Inserted].[UserId] + VALUES (@{nameof(primarySID)} + , @{nameof(domainName)} + , @{nameof(userName)}) + ELSE SELECT @userId", + parameters => parameters + .Add(nameof(primarySID), primarySID) + .Add(nameof(domainName), domainName) + .Add(nameof(userName), userName), + reader => new WindowsUser + { + UserId = reader.GetValue(0) + }) + ?? new WindowsUser(); + + user.Roles = this + .GetUserRoles(user.UserId) + .Select(x => x.RoleName); + + return user; + } + + public PermissionsModel GetPermissionModelForUser(int userId, string role) + { + var roleModel = this.GetRoleForUser(role); + + return new PermissionsModel + { + UserId = userId, + Role = roleModel + }; + } + + public UserRole GetRoleForUser(string role) + => this.dbContext.FirstOrDefault($@" + SELECT [Account].[Roles].[RoleId] + , [Account].[Roles].[RoleName] + , [Account].[Roles].[DisplayName] + , [Account].[UsersRoles].[Approved] + FROM [Account].[Roles] + LEFT JOIN [Account].[UsersRoles] + ON [Account].[UsersRoles].[RoleId] = [Account].[Roles].[RoleId] + WHERE [Account].[Roles].[RoleName] = @{nameof(role)}", + parameters => parameters.Add(nameof(role), role), + reader => new UserRole + { + RoleId = reader.GetValue(0), + RoleName = reader.GetString(1), + DisplayName = reader.GetString(2), + Pending = reader.GetValue(3), + }) ?? new UserRole(); + + public IEnumerable GetUserRoles(int userId) + => this.dbContext.AsEnumerable($@" + SELECT [Account].[Roles].[RoleId] + , [Account].[Roles].[RoleName] + , [Account].[Roles].[DisplayName] + FROM [Account].[Roles] + JOIN [Account].[UsersRoles] + ON [Account].[UsersRoles].[RoleId] = [Account].[Roles].[RoleId] + AND [Account].[UsersRoles].[UserId] = @{nameof(userId)} + AND [Account].[UsersRoles].[Approved] = 1", + parameters => parameters.Add(nameof(userId), userId), + reader => new UserRole + { + RoleId = reader.GetValue(0), + RoleName = reader.GetString(1), + DisplayName = reader.GetString(2), + }); + + public List GetUsersWithPendingRoles() + => this.dbContext.AsEnumerable($@" + SELECT [Account].[Users].[UserId] + , [Account].[Users].[FirstName] + ' ' + [Account].[Users].[LastName] + , [Account].[Roles].[RoleId] + , [Account].[Roles].[RoleName] + , [Account].[UsersRoles].[Approved] + FROM [Account].[UsersRoles] + JOIN [Account].[Roles] ON [Account].[Roles].[RoleId] = [Account].[UsersRoles].[RoleId] + JOIN [Account].[Users] ON [Account].[Users].[UserId] = [Account].[UsersRoles].[UserId] + WHERE [Account].[UsersRoles].[Approved] = 0", + reader => new + { + UserId = reader.GetValue(0), + DisplayName = reader.GetString(1), + RoleId = reader.GetValue(2), + RoleName = reader.GetString(3), + Approved = reader.GetValue(4) + }) + .GroupBy(x => new UserPermissions + { + UserId = x.UserId, + DisplayName = x.DisplayName + }, UserPermissionsComparer.Current) + .Select(x => new UserPermissions + { + UserId = x.Key.UserId, + DisplayName = x.Key.DisplayName, + Roles = x + .AsEnumerable() + .Select(r => new RoleModel + { + RoleId = r.RoleId, + RoleName = r.RoleName, + Approved = r.Approved + }) + .ToList() + }) + .ToList(); + + public void RequestPermissions(PermissionsModel model) + { + this.dbContext.Transaction($@" + INSERT INTO [Auftrag].[Account].[Users] ( + [UserId] + , [FirstName] + , [LastName]) + VALUES (@{nameof(model.UserId)} + , @{nameof(model.FirstName)} + , @{nameof(model.LastName)})", + parameters => parameters + .Add(nameof(model.UserId), model.UserId) + .Add(nameof(model.FirstName), model.FirstName) + .Add(nameof(model.LastName), model.LastName)); + + model.Role.Pending = this.dbContext.Transaction($@" + INSERT INTO [Auftrag].[Account].[UsersRoles] ( + [UserId] + , [RoleId]) + VALUES (@{nameof(model.UserId)} + , @{nameof(model.Role.RoleId)})", + parameters => parameters + .Add(nameof(model.UserId), model.UserId) + .Add(nameof(model.Role.RoleId), model.Role.RoleId)); + } + + public void SaveUserPermissions(IEnumerable usersPermissions) + { + foreach (var user in usersPermissions) + { + foreach (var role in user.Roles) + { + this.dbContext.Transaction( + role.Deleted + ? $@"DELETE FROM [Account].[UsersRoles] + WHERE [Account].[UsersRoles].[UserId] = @{nameof(user.UserId)} + AND [Account].[UsersRoles].[RoleId] = @{nameof(role.RoleId)}" + : $@"UPDATE [Account].[UsersRoles] + SET [Approved] = @{nameof(role.Approved)} + WHERE [Account].[UsersRoles].[UserId] = @{nameof(user.UserId)} + AND [Account].[UsersRoles].[RoleId] = @{nameof(role.RoleId)}", + parameters => parameters + .Add(nameof(user.UserId), user.UserId) + .Add(nameof(role.RoleId), role.RoleId) + .Add(nameof(role.Approved), role.Approved)); + } + } + } + } +} diff --git a/LaaProductionWeb/LaaProductionWeb.Services/Interfaces/IAccountService.cs b/LaaProductionWeb/LaaProductionWeb.Services/Interfaces/IAccountService.cs new file mode 100644 index 00000000..81293be3 --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb.Services/Interfaces/IAccountService.cs @@ -0,0 +1,23 @@ +namespace LaaProductionWeb.Services.Interfaces +{ + using LaaProductionWeb.Services.Models; + + using System.Collections.Generic; + + public interface IAccountService : ITransient + { + WindowsUser GetOrCreateWindowsUser(string primarySID, string domainName, string userName); + + PermissionsModel GetPermissionModelForUser(int userId, string role); + + IEnumerable GetUserRoles(int userId); + + List GetUsersWithPendingRoles(); + + UserRole GetRoleForUser(string role); + + void RequestPermissions(PermissionsModel model); + + void SaveUserPermissions(IEnumerable usersPermissions); + } +} diff --git a/LaaProductionWeb/LaaProductionWeb.Services/Interfaces/IProtocolService.cs b/LaaProductionWeb/LaaProductionWeb.Services/Interfaces/IProtocolService.cs index efc0b9ee..08f50374 100644 --- a/LaaProductionWeb/LaaProductionWeb.Services/Interfaces/IProtocolService.cs +++ b/LaaProductionWeb/LaaProductionWeb.Services/Interfaces/IProtocolService.cs @@ -7,6 +7,8 @@ public interface IProtocolService : ITransient { + bool DeleteFile(int fileId); + string FileContent(int? fileId); OrderClientModel FindOrder(int orderId); diff --git a/LaaProductionWeb/LaaProductionWeb.Services/LaaProductionWeb.Services.csproj b/LaaProductionWeb/LaaProductionWeb.Services/LaaProductionWeb.Services.csproj index 3bc38777..9fc94991 100644 --- a/LaaProductionWeb/LaaProductionWeb.Services/LaaProductionWeb.Services.csproj +++ b/LaaProductionWeb/LaaProductionWeb.Services/LaaProductionWeb.Services.csproj @@ -57,10 +57,17 @@ + + + + + + + @@ -69,6 +76,7 @@ + diff --git a/LaaProductionWeb/LaaProductionWeb.Services/Models/BatchPrintModel.cs b/LaaProductionWeb/LaaProductionWeb.Services/Models/BatchPrintModel.cs index 60a288ef..ea652345 100644 --- a/LaaProductionWeb/LaaProductionWeb.Services/Models/BatchPrintModel.cs +++ b/LaaProductionWeb/LaaProductionWeb.Services/Models/BatchPrintModel.cs @@ -19,14 +19,14 @@ public int ProductionOrderNr => this.batchModel.ProductionOrderNr; - public int PositionNr - => this.batchModel.PositionNr; + public IEnumerable Positions + => this.batchModel.Positions; public int TotlalItemsCount => this.batchModel.TotlalItemsCount; - public string ItemsType - => this.batchModel.ItemsType; + public int ItemsCount + => this.batchModel.ItemsCount; public SerialNr[] Items => this.batchModel diff --git a/LaaProductionWeb/LaaProductionWeb.Services/Models/PalletPosition.cs b/LaaProductionWeb/LaaProductionWeb.Services/Models/PalletPosition.cs new file mode 100644 index 00000000..c1f8dee0 --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb.Services/Models/PalletPosition.cs @@ -0,0 +1,11 @@ +namespace LaaProductionWeb.Services.Models +{ + public class PalletPosition + { + public int PositionNr { get; set; } + + public string Description { get; set; } + + public int ItemsCount { get; set; } + } +} diff --git a/LaaProductionWeb/LaaProductionWeb.Services/Models/PalletsBatchModel.cs b/LaaProductionWeb/LaaProductionWeb.Services/Models/PalletsBatchModel.cs index 993527a2..adc8eea8 100644 --- a/LaaProductionWeb/LaaProductionWeb.Services/Models/PalletsBatchModel.cs +++ b/LaaProductionWeb/LaaProductionWeb.Services/Models/PalletsBatchModel.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Linq; public class PalletsBatchModel { @@ -11,15 +12,17 @@ public int ProductionOrderNr { get; set; } - public int PositionNr { get; set; } - public int TotlalItemsCount { get; set; } - public string ItemsType { get; set; } - public string AdditionalText { get; set; } + public int ItemsCount + => this.Positions.Sum(x => x.ItemsCount); + public IEnumerable Items { get; set; } = Array.Empty(); + + public IEnumerable Positions { get; set; } + = Array.Empty(); } } \ No newline at end of file diff --git a/LaaProductionWeb/LaaProductionWeb.Services/Models/PermissionsModel.cs b/LaaProductionWeb/LaaProductionWeb.Services/Models/PermissionsModel.cs new file mode 100644 index 00000000..b6d4c08b --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb.Services/Models/PermissionsModel.cs @@ -0,0 +1,19 @@ +namespace LaaProductionWeb.Services.Models +{ + using System.ComponentModel.DataAnnotations; + + public class PermissionsModel + { + public int UserId { get; set; } + + [Display(Name = "Vorname")] + [Required(ErrorMessage = "Die Vorname ist erforderlich.")] + public string FirstName { get; set; } + + [Display(Name = "Name")] + [Required(ErrorMessage = "Die Name ist erforderlich.")] + public string LastName { get; set; } + + public UserRole Role { get; set; } + } +} diff --git a/LaaProductionWeb/LaaProductionWeb.Services/Models/RoleModel.cs b/LaaProductionWeb/LaaProductionWeb.Services/Models/RoleModel.cs new file mode 100644 index 00000000..798cc9a3 --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb.Services/Models/RoleModel.cs @@ -0,0 +1,13 @@ +namespace LaaProductionWeb.Services.Models +{ + public class RoleModel + { + public int RoleId { get; set; } + + public string RoleName { get; set; } + + public bool Approved { get; set; } + + public bool Deleted { get; set; } + } +} diff --git a/LaaProductionWeb/LaaProductionWeb.Services/Models/SerialNr.cs b/LaaProductionWeb/LaaProductionWeb.Services/Models/SerialNr.cs index 99594ad1..8532caa5 100644 --- a/LaaProductionWeb/LaaProductionWeb.Services/Models/SerialNr.cs +++ b/LaaProductionWeb/LaaProductionWeb.Services/Models/SerialNr.cs @@ -16,6 +16,8 @@ public int Nr { get; set; } + public int PositionNr { get; set; } + public int? PalletNr { get; set; } public string ClientNr { get; set; } diff --git a/LaaProductionWeb/LaaProductionWeb.Services/Models/ShipmentModel.cs b/LaaProductionWeb/LaaProductionWeb.Services/Models/ShipmentModel.cs index 6eb51d33..bb748b08 100644 --- a/LaaProductionWeb/LaaProductionWeb.Services/Models/ShipmentModel.cs +++ b/LaaProductionWeb/LaaProductionWeb.Services/Models/ShipmentModel.cs @@ -28,7 +28,7 @@ public int? PositionNr { get; set; } public bool HasOrder - => this.orderModel != null; + => this.orderModel != null && this.orderModel.OrderNr > 0; public OrderModel OrderModel => this.orderModel diff --git a/LaaProductionWeb/LaaProductionWeb.Services/Models/UserPermissions.cs b/LaaProductionWeb/LaaProductionWeb.Services/Models/UserPermissions.cs new file mode 100644 index 00000000..21530007 --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb.Services/Models/UserPermissions.cs @@ -0,0 +1,25 @@ +namespace LaaProductionWeb.Services.Models +{ + using System.Collections.Generic; + + public class UserPermissions + { + public int UserId { get; set; } + + public string DisplayName { get; set; } + + public List Roles { get; set; } + } + + public class UserPermissionsComparer : IEqualityComparer + { + public bool Equals(UserPermissions x, UserPermissions y) + => x?.UserId == y?.UserId; + + public int GetHashCode(UserPermissions obj) + => obj?.UserId ?? default(int); + + public static UserPermissionsComparer Current + = new UserPermissionsComparer(); + } +} diff --git a/LaaProductionWeb/LaaProductionWeb.Services/Models/UserRole.cs b/LaaProductionWeb/LaaProductionWeb.Services/Models/UserRole.cs new file mode 100644 index 00000000..73b74ec2 --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb.Services/Models/UserRole.cs @@ -0,0 +1,13 @@ +namespace LaaProductionWeb.Services.Models +{ + public class UserRole + { + public int RoleId { get; set; } + + public string RoleName { get; set; } + + public string DisplayName { get; set; } + + public bool? Pending { get; set; } + } +} diff --git a/LaaProductionWeb/LaaProductionWeb.Services/Models/WindowsUser.cs b/LaaProductionWeb/LaaProductionWeb.Services/Models/WindowsUser.cs new file mode 100644 index 00000000..e7d50ade --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb.Services/Models/WindowsUser.cs @@ -0,0 +1,12 @@ +namespace LaaProductionWeb.Services.Models +{ + using System; + using System.Collections.Generic; + + public class WindowsUser + { + public int UserId { get; set; } = -1; + + public IEnumerable Roles { get; set; } = Array.Empty(); + } +} diff --git a/LaaProductionWeb/LaaProductionWeb.Services/ProtocolService.cs b/LaaProductionWeb/LaaProductionWeb.Services/ProtocolService.cs index 688cf760..f1303d68 100644 --- a/LaaProductionWeb/LaaProductionWeb.Services/ProtocolService.cs +++ b/LaaProductionWeb/LaaProductionWeb.Services/ProtocolService.cs @@ -15,6 +15,15 @@ public ProtocolService(IDbContext dbContext) => this.dbContext = dbContext; + public bool DeleteFile(int fileId) + => this.dbContext.ScalarTransaction($@" + DELETE FROM [File] + WHERE [FileId] = @{nameof(fileId)}; + DELETE FROM [FilesOrders] + OUTPUT [Deleted].[FileId] + WHERE [FileId] = @{nameof(fileId)};", + parameters: parameters => parameters.Add(nameof(fileId), fileId)) > 0; + public string FileContent(int? fileId) { var fileContent = this.dbContext.FirstOrDefault( @@ -23,13 +32,14 @@ FROM [dbo].[File] WHERE [dbo].[File].[FileId] = @{nameof(fileId)}", parameters: parameters => parameters.Add(nameof(fileId), fileId), - reader: reader => reader.GetBytes(0)); + reader: reader => reader.GetBytes(0)) ?? Array.Empty(); return string.Format("data:application/pdf;base64,{0}", Convert.ToBase64String(fileContent)); } public OrderClientModel FindOrder(int orderId) - => this.dbContext.FirstOrDefault( + { + var orderClientModel = this.dbContext.FirstOrDefault( query: $@" SELECT DISTINCT [APG].[AuftragNr] @@ -57,7 +67,21 @@ ClientId = reader.GetValue(1), ClientName = reader.GetString(2), Positions = reader.GetString(3).Trim().Trim(',') - }) ?? new OrderClientModel(); + }); + + if (orderClientModel != null && orderId > 0) + { + orderClientModel.FileId = this.dbContext.FirstOrDefault($@" + SELECT [FileId] + FROM [FilesOrders] + WHERE [OrderId] = @{nameof(orderId)} + AND [Usage] = 'PROTOCOL'", + parameters: parameters => parameters.Add(nameof(orderId), orderId), + reader: reader => reader.GetValue(0)); + } + + return orderClientModel ?? new OrderClientModel(); + } public IEnumerable> FindOrdersById(int orderno) => this.dbContext.AsEnumerable($@" diff --git a/LaaProductionWeb/LaaProductionWeb.Services/ShipmentsService.cs b/LaaProductionWeb/LaaProductionWeb.Services/ShipmentsService.cs index 06f48f53..a94e305f 100644 --- a/LaaProductionWeb/LaaProductionWeb.Services/ShipmentsService.cs +++ b/LaaProductionWeb/LaaProductionWeb.Services/ShipmentsService.cs @@ -83,18 +83,17 @@ , REPLACE(RTRIM(LTRIM([AuftragPositionSerienNr].[KundeneigeneSerienNr])), ' ', '') -- 2 , [PalettenScan].[PalettenNr] -- 3 , CAST(CASE WHEN [PalettenScan].[ID] IS NULL THEN 0 ELSE 1 END AS BIT) -- 4 + , [PalettenScan].[PositionNr] -- 5 FROM [AuftragPositionSerienNr] LEFT JOIN [PalettenScan] ON [PalettenScan].[AuftragNr] = [AuftragPositionSerienNr].[AuftragNr] AND [PalettenScan].[PositionNr] = [AuftragPositionSerienNr].[PositionNr] AND [PalettenScan].[SerienNr] = REPLACE(RTRIM(LTRIM([AuftragPositionSerienNr].[KundeneigeneSerienNr])), ' ', '') WHERE [AuftragPositionSerienNr].[AuftragNr] = @{nameof(model.OrderNr)} - AND [AuftragPositionSerienNr].[PositionNr] = @{nameof(model.PositionNr)} AND ([PalettenScan].[PalettenNr] = @{nameof(model.PalletNr)} OR [PalettenScan].[PalettenNr] IS NULL)", parameters: parameters => parameters .Add(nameof(model.OrderNr), model.OrderNr) - .Add(nameof(model.PositionNr), model.PositionNr) .Add(nameof(model.PalletNr), model.PalletNr), reader: reader => new SerialNr { @@ -102,30 +101,30 @@ Nr = reader.GetValue(1), ClientNr = reader.GetString(2), PalletNr = reader.GetValue(3), - IsScaned = reader.GetValue(4) + IsScaned = reader.GetValue(4), + PositionNr = reader.GetValue(5), }) .OrderByDescending(x => x.IsScaned) .ThenBy(x => x.ClientNr); public PalletsBatchModel LoadBatchModel(OrderScanModel model) { + const string orderNr = nameof(model.OrderNr); + const string posNr = nameof(model.PositionNr); + const string palNr = nameof(model.PalletNr); + var batchModel = this.dbContext.FirstOrDefault( query: $@" SELECT TOP 1 [Auftrag].[AuftragNr] -- 0 , [Auftrag].[FertigungsauftragNr] -- 1 - , [Auftrag].[PositionNr] -- 2 - , [Auftrag].[Menge] -- 3 - , [Kunde].[Name] -- 4 - , [IdentNr].[Typ] -- 5 - , [IdentNr].[Typzusatz] -- 6 - , [IdentNr].[Nennweite] -- 7 - , [Auftrag].[ZusatzText] -- 8 + , [Auftrag].[Menge] -- 2 + , [Kunde].[Name] -- 3 + , [Auftrag].[ZusatzText] -- 4 FROM [AlleAuftragPositionen] AS [Auftrag] JOIN [Kunde] ON [Kunde].[KundenNr] = [Auftrag].[KundenNr] - JOIN [IdentNr] ON [IdentNr].[IdentNr] = [Auftrag].[IdentNr] - WHERE [Auftrag].[AuftragNr] = @{nameof(model.OrderNr)} - AND [Auftrag].[PositionNr] = @{nameof(model.PositionNr)} + WHERE [Auftrag].[AuftragNr] = @{orderNr} + AND [Auftrag].[PositionNr] = @{posNr} ORDER BY [Auftrag].[FertigungsauftragNr] DESC", parameters: parameters => parameters .Add(nameof(model.OrderNr), model.OrderNr) @@ -134,26 +133,59 @@ { OrderNr = reader.GetValue(0), ProductionOrderNr = reader.GetValue(1), - PositionNr = reader.GetValue(2), - TotlalItemsCount = reader.GetValue(3), - ClientName = reader.GetValue(4), - ItemsType = string.Join(" ", new[] - { - reader.GetString(5), - reader.GetString(6), - reader.GetString(7), - }.Where(x => !string.IsNullOrWhiteSpace(x))), - AdditionalText = reader.GetValue(8), - }) ?? new PalletsBatchModel(); + TotlalItemsCount = reader.GetValue(2), + ClientName = reader.GetValue(3), + AdditionalText = reader.GetValue(4), + }) + ?? new PalletsBatchModel(); if (batchModel.OrderNr > 0) { batchModel.Items = this.LoadBatchItems(model); + batchModel.Positions = this.LoadBatchPositions(model, orderNr, palNr); } return batchModel; } + private IEnumerable LoadBatchPositions(OrderScanModel model, string orderNr, string palNr) + => this.dbContext.AsEnumerable($@" + SELECT DISTINCT + [Auftrag].[PositionNr] + , COUNT(1) + , [Identnr].[Typ] + , [Identnr].[Typzusatz] + , [Identnr].[Nennweite] + FROM [Identnr] + JOIN [AlleAuftragPositionen] AS [Auftrag] + ON [Auftrag].[IdentNr] = [Identnr].[IdentNr] + JOIN [PalettenScan] + ON [PalettenScan].[AuftragNr] = [Auftrag].[AuftragNr] + AND [PalettenScan].[PositionNr] = [Auftrag].[PositionNr] + AND [PalettenScan].[PalettenNr] = @{palNr} + WHERE [Auftrag].[AuftragNr] = @{orderNr} + GROUP BY [Auftrag].[PositionNr] + , [Identnr].[Typ] + , [Identnr].[Typzusatz] + , [Identnr].[Nennweite] + ORDER BY [Auftrag].[PositionNr] ", + parameters => parameters + .Add(orderNr, model.OrderNr) + .Add(palNr, model.PalletNr), + positionReader => new PalletPosition + { + PositionNr = positionReader.GetValue(0), + ItemsCount = positionReader.GetValue(1), + Description = string + .Join(" ", new[] + { + positionReader.GetString(2), + positionReader.GetString(3), + positionReader.GetString(4), + } + .Where(x => !string.IsNullOrWhiteSpace(x))) + }); + public Result UpdateBatchModel(OrderScanModel model) { if (!string.IsNullOrWhiteSpace(model.SerialNr)) diff --git a/LaaProductionWeb/LaaProductionWeb/App_Content/css/styles.css b/LaaProductionWeb/LaaProductionWeb/App_Content/css/styles.css index 4e9add2e..9e9ecbf8 100644 --- a/LaaProductionWeb/LaaProductionWeb/App_Content/css/styles.css +++ b/LaaProductionWeb/LaaProductionWeb/App_Content/css/styles.css @@ -117,4 +117,12 @@ table.table table.table { .text-teal { color: var(--bs-teal); +} + +.btn.btn-link:hover { + text-decoration: underline !important; +} + +.bg-whitesmoke { + background-color: whitesmoke !important } \ No newline at end of file diff --git a/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/AllowedRolesAttribute.cs b/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/AllowedRolesAttribute.cs new file mode 100644 index 00000000..fa8e2908 --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/AllowedRolesAttribute.cs @@ -0,0 +1,23 @@ +namespace LaaProductionWeb.App_Infrastructure +{ + using System.Security.Claims; + using System.Web.Mvc; + + public class AllowedRolesAttribute : AuthorizeAttribute + { + public AllowedRolesAttribute(params string[] roles) + => this.Roles = string.Join(",", roles); + + protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) + { + if (filterContext.HttpContext.User is ClaimsPrincipal claimsPrincipal) + { + filterContext.Result = new RedirectResult($"/Account/Authorize?role={this.Roles}"); + } + else + { + base.HandleUnauthorizedRequest(filterContext); + } + } + } +} \ No newline at end of file diff --git a/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/AuthorizationFilter.cs b/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/AuthorizationFilter.cs new file mode 100644 index 00000000..a93c74bf --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/AuthorizationFilter.cs @@ -0,0 +1,48 @@ +namespace LaaProductionWeb.App_Infrastructure +{ + using LaaProductionWeb.Services.Interfaces; + + using System; + using System.Collections.Generic; + using System.Linq; + using System.Security.Claims; + using System.Web.Mvc; + + public class AuthorizationFilter : IAuthorizationFilter + { + public void OnAuthorization(AuthorizationContext filterContext) + { + if (filterContext.HttpContext.User is ClaimsPrincipal claimsPrincipal) + { + this.GetWindowsUser(claimsPrincipal, out string primarySID, out string domainName, out string userName); + + var accountService = ServiceProvider.Current.GetService(); + var windowsUser = accountService.GetOrCreateWindowsUser(primarySID, domainName, userName); + var claims = new List { new Claim(ClaimTypes.NameIdentifier, $"{windowsUser.UserId}") }; + + foreach (var role in windowsUser.Roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var claimsIdentity = new ClaimsIdentity(claims, nameof(ClaimsPrincipal), ClaimTypes.Name, ClaimTypes.Role); + + claimsPrincipal.AddIdentity(claimsIdentity); + } + } + + private void GetWindowsUser(ClaimsPrincipal claimsPrincipal, out string primarySID, out string domainName, out string userName) + { + var nameTokens = claimsPrincipal + .FindFirst(ClaimTypes.Name) + ?.Value + ?.ToLower() + ?.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries) + ?? Array.Empty(); + + primarySID = claimsPrincipal.FindFirst(ClaimTypes.PrimarySid)?.Value; + domainName = nameTokens.FirstOrDefault(); + userName = nameTokens.LastOrDefault(); + } + } +} \ No newline at end of file diff --git a/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/ControllersExtensions.cs b/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/ControllersExtensions.cs index abccdf6d..417f3e13 100644 --- a/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/ControllersExtensions.cs +++ b/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/ControllersExtensions.cs @@ -4,6 +4,8 @@ using System; using System.Linq; + using System.Security.Claims; + using System.Security.Principal; using System.Web.Mvc; public static class ControllersExtensions @@ -32,5 +34,25 @@ return serviceProvider; } + + public static int NameIdentifier(this IPrincipal principal) + { + if (principal is ClaimsPrincipal claimsPrincipal) + { + var nameIdentifier = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value; + + if (int.TryParse($"{nameIdentifier}", out int userId)) + { + return userId; + } + } + + return -1; + } + + public static T NameIdentifier(this ViewContext viewContext) + { + return default(T); + } } } \ No newline at end of file diff --git a/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/ExceptionFilter.cs b/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/ExceptionFilter.cs new file mode 100644 index 00000000..a38a1d6a --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/ExceptionFilter.cs @@ -0,0 +1,12 @@ +namespace LaaProductionWeb.App_Infrastructure +{ + using System.Web.Mvc; + + public class ExceptionFilter : IExceptionFilter + { + public void OnException(ExceptionContext filterContext) + { + filterContext.Result = new RedirectResult("/"); + } + } +} \ No newline at end of file diff --git a/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/ServiceProvider.cs b/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/ServiceProvider.cs new file mode 100644 index 00000000..d0488c18 --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb/App_Infrastructure/ServiceProvider.cs @@ -0,0 +1,39 @@ +namespace LaaProductionWeb.App_Infrastructure +{ + using Microsoft.Extensions.DependencyInjection; + + using System; + + public class ServiceProvider + { + private readonly IServiceProvider services; + + private ServiceProvider(IServiceProvider services) + => this.services = services; + + public T GetService() where T : class + { + if (this.services.GetService() is T service) + { + return service; + } + + return default(T); + } + + public static ServiceProvider Current { get; private set; } + + internal static void SetServiceProvider(IServiceProvider serviceProvider) + => Current = new ServiceProvider(serviceProvider); + } + + public static class ServiceProviderExtensions + { + public static IServiceProvider RegisterServiceProvider(this IServiceProvider serviceProvider) + { + ServiceProvider.SetServiceProvider(serviceProvider); + + return serviceProvider; + } + } +} \ No newline at end of file diff --git a/LaaProductionWeb/LaaProductionWeb/App_Start/FilterConfig.cs b/LaaProductionWeb/LaaProductionWeb/App_Start/FilterConfig.cs index 1d166153..30ca7e69 100644 --- a/LaaProductionWeb/LaaProductionWeb/App_Start/FilterConfig.cs +++ b/LaaProductionWeb/LaaProductionWeb/App_Start/FilterConfig.cs @@ -1,12 +1,15 @@ namespace LaaProductionWeb { + using LaaProductionWeb.App_Infrastructure; + using System.Web.Mvc; public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { - filters.Add(new HandleErrorAttribute()); + filters.Add(new ExceptionFilter()); + filters.Add(new AuthorizationFilter()); } } } diff --git a/LaaProductionWeb/LaaProductionWeb/App_Start/ServicesConfig.cs b/LaaProductionWeb/LaaProductionWeb/App_Start/ServicesConfig.cs index 9edd7e26..d203e145 100644 --- a/LaaProductionWeb/LaaProductionWeb/App_Start/ServicesConfig.cs +++ b/LaaProductionWeb/LaaProductionWeb/App_Start/ServicesConfig.cs @@ -12,7 +12,8 @@ => new ServiceCollection() .ConfigureServices() .BuildServiceProvider() - .BuildControllerFactory(); + .BuildControllerFactory() + .RegisterServiceProvider(); static IServiceCollection ConfigureServices(this IServiceCollection services) { diff --git a/LaaProductionWeb/LaaProductionWeb/Controllers/AccountController.cs b/LaaProductionWeb/LaaProductionWeb/Controllers/AccountController.cs new file mode 100644 index 00000000..e4aca18b --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb/Controllers/AccountController.cs @@ -0,0 +1,61 @@ +namespace LaaProductionWeb.Controllers +{ + using LaaProductionWeb.App_Infrastructure; + using LaaProductionWeb.Services.Interfaces; + using LaaProductionWeb.Services.Models; + + using System.Collections.Generic; + using System.Web.Mvc; + + [Authorize] + public class AccountController : Controller + { + private readonly IAccountService accountService; + + public AccountController(IAccountService accountService) + => this.accountService = accountService; + + [HttpGet] + public ActionResult Authorize(string role) + { + var userId = this.User.NameIdentifier(); + var permissionsModel = this.accountService + .GetPermissionModelForUser(userId, role); + + return this.View(model: permissionsModel); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult Authorize(PermissionsModel model) + { + if (this.ModelState.IsValid) + { + this.accountService.RequestPermissions(model); + } + + return this.View(model); + } + + [HttpGet] + [AllowedRoles("Admin")] + public ActionResult Permissions() + { + var model = this.accountService.GetUsersWithPendingRoles(); + + return this.View(model); + } + + [HttpPost] + [AllowedRoles("Admin")] + [ValidateAntiForgeryToken] + public ActionResult Permissions(List model) + { + this.accountService.SaveUserPermissions(model); + + model = this.accountService.GetUsersWithPendingRoles(); + + return this.View(model); + } + } +} \ No newline at end of file diff --git a/LaaProductionWeb/LaaProductionWeb/Controllers/ProtocolController.cs b/LaaProductionWeb/LaaProductionWeb/Controllers/ProtocolController.cs index 27cebcb2..295d01ad 100644 --- a/LaaProductionWeb/LaaProductionWeb/Controllers/ProtocolController.cs +++ b/LaaProductionWeb/LaaProductionWeb/Controllers/ProtocolController.cs @@ -1,11 +1,12 @@ namespace LaaProductionWeb.Controllers { + using LaaProductionWeb.App_Infrastructure; using LaaProductionWeb.Services.Interfaces; using LaaProductionWeb.Services.Models; using System.Web.Mvc; - [Authorize] + [AllowedRoles("Protocol")] public class ProtocolController : Controller { public const string FilePDF = nameof(FilePDF); @@ -20,6 +21,11 @@ { var orderClientModel = this.protocolService.FindOrder(orderId); + if (orderClientModel.FileId > 0) + { + return this.RedirectToAction(nameof(Document), orderClientModel); + } + return this.View(orderClientModel); } @@ -28,7 +34,11 @@ { var files = this.Request.Files; - if (files.Count > 0) + if (model.OrderId is null || model.OrderId <= 0) + { + this.TempData[FilePDF] = $"Bitte Auftrag auswählen."; + } + else if (files.Count > 0) { var file = this.Request.Files.Get(0); @@ -66,6 +76,15 @@ return this.View(model); } + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult DeleteFile(int fileId, int orderId) + { + this.protocolService.DeleteFile(fileId); + + return this.RedirectToAction(nameof(this.Index), new { orderId }); + } + public ActionResult Clients(string search = null) { var clientsOrders = this.protocolService.FindOrdersByClient(search); diff --git a/LaaProductionWeb/LaaProductionWeb/Controllers/ShipmentsController.cs b/LaaProductionWeb/LaaProductionWeb/Controllers/ShipmentsController.cs index c993bbc7..7c754e60 100644 --- a/LaaProductionWeb/LaaProductionWeb/Controllers/ShipmentsController.cs +++ b/LaaProductionWeb/LaaProductionWeb/Controllers/ShipmentsController.cs @@ -1,5 +1,6 @@ namespace LaaProductionWeb.Controllers { + using LaaProductionWeb.App_Infrastructure; using LaaProductionWeb.Services.Interfaces; using LaaProductionWeb.Services.Models; @@ -12,7 +13,7 @@ using SystemFile = System.IO.File; - [Authorize] + [AllowedRoles("Shipment")] public class ShipmentsController : Controller { private readonly IShipmentsService shipmentsService; diff --git a/LaaProductionWeb/LaaProductionWeb/LaaProductionWeb.csproj b/LaaProductionWeb/LaaProductionWeb/LaaProductionWeb.csproj index 5a789874..6649aecc 100644 --- a/LaaProductionWeb/LaaProductionWeb/LaaProductionWeb.csproj +++ b/LaaProductionWeb/LaaProductionWeb/LaaProductionWeb.csproj @@ -131,13 +131,18 @@ + + + + + @@ -168,6 +173,9 @@ + + + @@ -198,6 +206,7 @@ LaaProductionWeb.Services + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/LaaProductionWeb/LaaProductionWeb/Views/Account/Authorize.cshtml b/LaaProductionWeb/LaaProductionWeb/Views/Account/Authorize.cshtml new file mode 100644 index 00000000..62a158e7 --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb/Views/Account/Authorize.cshtml @@ -0,0 +1,35 @@ +@model PermissionsModel + +@using LaaProductionWeb.Services.Models + +
+ @if (this.Model.Role.Pending is null) + { +
+
+ @this.Html.AntiForgeryToken() + @this.Html.HiddenFor(x => x.UserId) + @this.Html.Partial(nameof(UserRole), this.Model.Role, new ViewDataDictionary { { "namePattern", $"{nameof(this.Model.Role)}.{{0}}" } }) +
Ihnen fehlt die Berechtigung – '@this.Model.Role.DisplayName'.
+
+ @this.Html.TextBoxFor(x => x.FirstName, new { @class = "form-control", placeholder = "Vorname" }) + @this.Html.LabelFor(x => x.FirstName, new { @class = "text-secondary" }) + @this.Html.ValidationMessageFor(x => x.FirstName, string.Empty, new { @class = "small text-danger" }) +
+
+ @this.Html.TextBoxFor(x => x.LastName, new { @class = "form-control", placeholder = "Vorname" }) + @this.Html.LabelFor(x => x.LastName, new { @class = "text-secondary" }) + @this.Html.ValidationMessageFor(x => x.LastName, string.Empty, new { @class = "small text-danger" }) +
+ +
+
+ } + else + { +
+

+
Ihre anfrage für die berechtigung – '@this.Model.Role.DisplayName' ist in bearbeitung ...
+
+ } +
diff --git a/LaaProductionWeb/LaaProductionWeb/Views/Account/Permissions.cshtml b/LaaProductionWeb/LaaProductionWeb/Views/Account/Permissions.cshtml new file mode 100644 index 00000000..38f3b306 --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb/Views/Account/Permissions.cshtml @@ -0,0 +1,60 @@ +@model List + +@if (this.Model.Any()) +{ +
+ @this.Html.AntiForgeryToken() +
    + @{ + var usersCount = this.Model.Count; + + for (int userIndex = 0; userIndex < usersCount; userIndex++) + { + var user = this.Model[userIndex]; + var hiddenUserId = $"[{userIndex}].{nameof(user.UserId)}"; + var hiddenUserName = $"[{userIndex}].{nameof(user.DisplayName)}"; + +
  • + + +
    @user.DisplayName
    +
  • +
  • + + + + + + + + + + + @{ + var rolesCount = user.Roles.Count; + + for (int roleIndex = 0; roleIndex < rolesCount; roleIndex++) + { + var role = user.Roles[roleIndex]; + var hiddenRoleId = $"[{userIndex}].{nameof(user.Roles)}[{roleIndex}].{nameof(role.RoleId)}"; + var hiddenRoleName = $"[{userIndex}].{nameof(user.Roles)}[{roleIndex}].{nameof(role.RoleName)}"; + var roleApproved = $"[{userIndex}].{nameof(user.Roles)}[{roleIndex}].{nameof(role.Approved)}"; + var roleDeleted = $"[{userIndex}].{nameof(user.Roles)}[{roleIndex}].{nameof(role.Deleted)}"; + + + + + + + + } + } + +
    Role IdRole NameIs ApprovedIs Deleted
    @role.RoleId @role.RoleName @this.Html.CheckBox(roleApproved, role.Approved, new { @class = "form-check-input" })@this.Html.CheckBox(roleDeleted, role.Deleted, new { @class = "form-check-input" })
    +
  • + } + } +
+ +
+} \ No newline at end of file diff --git a/LaaProductionWeb/LaaProductionWeb/Views/Account/UserRole.cshtml b/LaaProductionWeb/LaaProductionWeb/Views/Account/UserRole.cshtml new file mode 100644 index 00000000..bb3077f7 --- /dev/null +++ b/LaaProductionWeb/LaaProductionWeb/Views/Account/UserRole.cshtml @@ -0,0 +1,14 @@ +@model LaaProductionWeb.Services.Models.UserRole + +@{ + var namePattern = "{0}"; + + if (this.ViewData.TryGetValue(nameof(namePattern), out object value)) + { + namePattern = $"{value}"; + } +} + +@this.Html.Hidden(string.Format(namePattern, nameof(this.Model.RoleId)), this.Model.RoleId) +@this.Html.Hidden(string.Format(namePattern, nameof(this.Model.RoleName)), this.Model.RoleName) +@this.Html.Hidden(string.Format(namePattern, nameof(this.Model.DisplayName)), this.Model.DisplayName) diff --git a/LaaProductionWeb/LaaProductionWeb/Views/Protocol/Body.cshtml b/LaaProductionWeb/LaaProductionWeb/Views/Protocol/Body.cshtml index 2c0d2686..92153bba 100644 --- a/LaaProductionWeb/LaaProductionWeb/Views/Protocol/Body.cshtml +++ b/LaaProductionWeb/LaaProductionWeb/Views/Protocol/Body.cshtml @@ -53,7 +53,7 @@ { @item.PosNr - @item.SerialNr + @item.SerialNr diff --git a/LaaProductionWeb/LaaProductionWeb/Views/Protocol/Document.cshtml b/LaaProductionWeb/LaaProductionWeb/Views/Protocol/Document.cshtml index 9960ae8f..9ed590a7 100644 --- a/LaaProductionWeb/LaaProductionWeb/Views/Protocol/Document.cshtml +++ b/LaaProductionWeb/LaaProductionWeb/Views/Protocol/Document.cshtml @@ -2,12 +2,13 @@ @using LaaProductionWeb.Services.Models -
+ + @this.Html.AntiForgeryToken() @this.Html.HiddenFor(x => x.FileId) @this.Html.HiddenFor(x => x.OrderId) - @this.Html.HiddenFor(x => x.ClientId) - @this.Html.HiddenFor(x => x.ClientName) - @this.Html.HiddenFor(x => x.Positions) +
diff --git a/LaaProductionWeb/LaaProductionWeb/Views/Protocol/Index.cshtml b/LaaProductionWeb/LaaProductionWeb/Views/Protocol/Index.cshtml index 91f67f69..9c2ac57a 100644 --- a/LaaProductionWeb/LaaProductionWeb/Views/Protocol/Index.cshtml +++ b/LaaProductionWeb/LaaProductionWeb/Views/Protocol/Index.cshtml @@ -13,7 +13,7 @@ - +