Production Approvals,
sql client refactoring
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
namespace LaaProductionWeb.Data.Interfaces
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public interface IDbContext
|
||||
{
|
||||
IEnumerable<T> AsEnumerable<T>(string query, Action<ParameterCollection> parameters, Func<ISqlReader, T> reader);
|
||||
|
||||
IEnumerable<T> AsEnumerable<T>(string query, Func<ISqlReader, T> reader);
|
||||
|
||||
int ExecuteNonQuery(string query, Action<ParameterCollection> parameters);
|
||||
|
||||
T FirstOrDefault<T>(string query, Action<ParameterCollection> parameters, Func<ISqlReader, T> reader);
|
||||
|
||||
T ScalarTransaction<T>(string query, Action<ParameterCollection> parameters);
|
||||
|
||||
bool Transaction(string query, Action<ParameterCollection> parameters);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace LaaProductionWeb.Data.Interfaces
|
||||
{
|
||||
public interface IParameterCollection
|
||||
{
|
||||
IParameterCollection Add(string name, object value);
|
||||
|
||||
IParameterCollection AddFile(string name, byte[] fileContent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace LaaProductionWeb.Data.Interfaces
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
|
||||
public interface ISqlClient
|
||||
{
|
||||
int ExecuteNonQuery(string query, Action<SqlParameters> parameters, SqlInfoMessageEventHandler errorCallback = null);
|
||||
|
||||
IEnumerable<T> ExecuteReader<T>(string query, Func<ISqlReader, T> reader, SqlInfoMessageEventHandler errorCallback = null);
|
||||
|
||||
IEnumerable<T> ExecuteReader<T>(string query, Action<SqlParameters> parameters, Func<ISqlReader, T> reader, SqlInfoMessageEventHandler errorCallback = null);
|
||||
|
||||
T ExecuteScalar<T>(string query, Action<SqlParameters> parameters, SqlInfoMessageEventHandler errorCallback = null);
|
||||
|
||||
T FirstOrDefault<T>(string query, Func<ISqlReader, T> reader, SqlInfoMessageEventHandler errorCallback = null);
|
||||
|
||||
T FirstOrDefault<T>(string query, Action<SqlParameters> parameters, Func<ISqlReader, T> reader, SqlInfoMessageEventHandler errorCallback = null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace LaaProductionWeb.Data.Interfaces
|
||||
{
|
||||
public interface ISqlParameters
|
||||
{
|
||||
ISqlParameters Add(string name, object value);
|
||||
|
||||
ISqlParameters AddFile(string name, byte[] fileContent);
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@
|
||||
{
|
||||
public interface ISqlReader
|
||||
{
|
||||
byte[] GetBytes(int index);
|
||||
byte[] GetBytes(int index = -1);
|
||||
|
||||
string GetString(int index);
|
||||
string GetString(int index = -1);
|
||||
|
||||
T GetValue<T>(int index);
|
||||
T GetValue<T>(int index = -1);
|
||||
}
|
||||
}
|
||||
@@ -56,12 +56,14 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="DbContext.cs" />
|
||||
<Compile Include="DbContextExtensions.cs" />
|
||||
<Compile Include="Interfaces\IDbContext.cs" />
|
||||
<Compile Include="Interfaces\IParameterCollection.cs" />
|
||||
<Compile Include="Repositories\Interfaces\IRepository[T].cs" />
|
||||
<Compile Include="Repositories\Repository[T].cs" />
|
||||
<Compile Include="SqlClient.cs" />
|
||||
<Compile Include="SqlClientExtensions.cs" />
|
||||
<Compile Include="Interfaces\ISqlClient.cs" />
|
||||
<Compile Include="Interfaces\ISqlParameters.cs" />
|
||||
<Compile Include="Interfaces\ISqlReader.cs" />
|
||||
<Compile Include="ParameterCollection.cs" />
|
||||
<Compile Include="SqlParameters.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SqlReader.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace LaaProductionWeb.Data.Repositories.Interfaces
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public interface IRepository<T>
|
||||
{
|
||||
int Create(string query, object parameters);
|
||||
|
||||
int Delete(string query, object parameters);
|
||||
|
||||
IEnumerable<T> First(string query, object parameters);
|
||||
|
||||
T Read(string query, object parameters);
|
||||
|
||||
int Update(string query, object parameters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
namespace LaaProductionWeb.Data.Repositories
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
using LaaProductionWeb.Data.Interfaces;
|
||||
using LaaProductionWeb.Data.Repositories.Interfaces;
|
||||
|
||||
public abstract class Repository<T> : IRepository<T>
|
||||
{
|
||||
private readonly ISqlClient sqlClient;
|
||||
|
||||
protected Repository(ISqlClient sqlClient)
|
||||
=> this.sqlClient = sqlClient;
|
||||
|
||||
public int Create(string query, object parameters)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public int Delete(string query, object parameters)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public IEnumerable<T> First(string query, object parameters)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public T Read(string query, object parameters)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public int Update(string query, object parameters)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+75
-86
@@ -6,77 +6,121 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
|
||||
public class DbContext : IDbContext
|
||||
|
||||
public class SqlClient : ISqlClient
|
||||
{
|
||||
private readonly string connectionString;
|
||||
|
||||
public DbContext(string connectionString)
|
||||
public SqlClient(string connectionString)
|
||||
=> this.connectionString = connectionString;
|
||||
|
||||
public IEnumerable<T> AsEnumerable<T>(string query, Action<ParameterCollection> parameters, Func<ISqlReader, T> reader)
|
||||
public int ExecuteNonQuery(string query, Action<SqlParameters> parameters, SqlInfoMessageEventHandler errorCallback = null)
|
||||
{
|
||||
var rowsAffected = 0;
|
||||
|
||||
using (var sqlConnection = new SqlConnection(this.connectionString))
|
||||
{
|
||||
sqlConnection.OpenWithErrorHandling();
|
||||
sqlConnection.OpenWithErrorHandling(errorCallback);
|
||||
|
||||
using (var sqlCommand = sqlConnection.CreateCommand())
|
||||
{
|
||||
sqlCommand.CommandText = query;
|
||||
|
||||
parameters(sqlCommand.Parameters);
|
||||
|
||||
using (SqlReader sqlReader = sqlCommand.ExecuteReader(CommandBehavior.SequentialAccess))
|
||||
{
|
||||
while (sqlReader.Read())
|
||||
{
|
||||
yield return reader(sqlReader);
|
||||
}
|
||||
}
|
||||
sqlCommand.CommandText = query;
|
||||
rowsAffected = sqlCommand.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
return rowsAffected;
|
||||
}
|
||||
|
||||
public IEnumerable<T> AsEnumerable<T>(string query, Func<ISqlReader, T> reader)
|
||||
public IEnumerable<T> ExecuteReader<T>(string query, Func<ISqlReader, T> output, SqlInfoMessageEventHandler errorCallback = null)
|
||||
{
|
||||
using (var sqlConnection = new SqlConnection(this.connectionString))
|
||||
{
|
||||
sqlConnection.OpenWithErrorHandling();
|
||||
sqlConnection.OpenWithErrorHandling(errorCallback);
|
||||
|
||||
using (var sqlCommand = sqlConnection.CreateCommand())
|
||||
{
|
||||
sqlCommand.CommandText = query;
|
||||
|
||||
using (SqlReader sqlReader = sqlCommand.ExecuteReader(CommandBehavior.SequentialAccess))
|
||||
using (SqlReader sqlReader = sqlCommand.ExecuteReader())
|
||||
{
|
||||
while (sqlReader.Read())
|
||||
{
|
||||
yield return reader(sqlReader);
|
||||
yield return output(sqlReader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T FirstOrDefault<T>(string query, Action<ParameterCollection> parameters, Func<ISqlReader, T> reader)
|
||||
public IEnumerable<T> ExecuteReader<T>(string query, Action<SqlParameters> parameters, Func<ISqlReader, T> output, SqlInfoMessageEventHandler errorCallback = null)
|
||||
{
|
||||
using (var sqlConnection = new SqlConnection(this.connectionString))
|
||||
{
|
||||
sqlConnection.OpenWithErrorHandling(errorCallback);
|
||||
|
||||
using (var sqlCommand = sqlConnection.CreateCommand())
|
||||
{
|
||||
parameters(sqlCommand.Parameters);
|
||||
|
||||
sqlCommand.CommandText = query;
|
||||
|
||||
using (SqlReader sqlReader = sqlCommand.ExecuteReader())
|
||||
{
|
||||
while (sqlReader.Read())
|
||||
{
|
||||
yield return output(sqlReader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T ExecuteScalar<T>(string query, Action<SqlParameters> parameters, SqlInfoMessageEventHandler errorCallback = null)
|
||||
{
|
||||
var scalarResult = default(T);
|
||||
|
||||
using (var sqlConnection = new SqlConnection(this.connectionString))
|
||||
{
|
||||
sqlConnection.OpenWithErrorHandling(errorCallback);
|
||||
|
||||
using (var sqlCommand = sqlConnection.CreateCommand())
|
||||
{
|
||||
parameters(sqlCommand.Parameters);
|
||||
|
||||
sqlCommand.CommandText = query;
|
||||
|
||||
var scalarObject = sqlCommand.ExecuteScalar();
|
||||
|
||||
if (scalarObject is T scalarValue)
|
||||
{
|
||||
scalarResult = scalarValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scalarResult;
|
||||
}
|
||||
|
||||
public T FirstOrDefault<T>(string query, Func<ISqlReader, T> output, SqlInfoMessageEventHandler errorCallback = null)
|
||||
{
|
||||
var firstOrDefaultResult = default(T);
|
||||
|
||||
using (var sqlConnection = new SqlConnection(this.connectionString))
|
||||
{
|
||||
sqlConnection.OpenWithErrorHandling();
|
||||
sqlConnection.OpenWithErrorHandling(errorCallback);
|
||||
|
||||
using (var sqlCommand = sqlConnection.CreateCommand())
|
||||
{
|
||||
sqlCommand.CommandText = query;
|
||||
|
||||
parameters(sqlCommand.Parameters);
|
||||
|
||||
using (SqlReader sqlReader = sqlCommand.ExecuteReader(CommandBehavior.SequentialAccess))
|
||||
{
|
||||
if (sqlReader.Read())
|
||||
{
|
||||
firstOrDefaultResult = reader(sqlReader);
|
||||
firstOrDefaultResult = output(sqlReader);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,86 +129,31 @@
|
||||
return firstOrDefaultResult;
|
||||
}
|
||||
|
||||
public int ExecuteNonQuery(string query, Action<ParameterCollection> parameters)
|
||||
public T FirstOrDefault<T>(string query, Action<SqlParameters> parameters, Func<ISqlReader, T> output, SqlInfoMessageEventHandler errorCallback = null)
|
||||
{
|
||||
var rowsAffected = 0;
|
||||
var firstOrDefaultResult = default(T);
|
||||
|
||||
using (var sqlConnection = new SqlConnection(this.connectionString))
|
||||
{
|
||||
sqlConnection.OpenWithErrorHandling();
|
||||
sqlConnection.OpenWithErrorHandling(errorCallback);
|
||||
|
||||
using (var sqlCommand = sqlConnection.CreateCommand())
|
||||
{
|
||||
sqlCommand.CommandText = query;
|
||||
|
||||
parameters(sqlCommand.Parameters);
|
||||
|
||||
rowsAffected = sqlCommand.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
sqlCommand.CommandText = query;
|
||||
|
||||
return rowsAffected;
|
||||
}
|
||||
|
||||
public T ScalarTransaction<T>(string query, Action<ParameterCollection> parameters)
|
||||
{
|
||||
var scalarResult = default(T);
|
||||
|
||||
using (var sqlConnection = new SqlConnection(this.connectionString))
|
||||
{
|
||||
sqlConnection.OpenWithErrorHandling();
|
||||
|
||||
using (var sqlTransaction = sqlConnection.BeginTransaction())
|
||||
{
|
||||
using (var sqlCommand = sqlConnection.CreateCommand())
|
||||
using (SqlReader sqlReader = sqlCommand.ExecuteReader(CommandBehavior.SequentialAccess))
|
||||
{
|
||||
sqlCommand.Transaction = sqlTransaction;
|
||||
sqlCommand.CommandText = query;
|
||||
|
||||
parameters(sqlCommand.Parameters);
|
||||
|
||||
var scalarObject = sqlCommand.ExecuteScalar();
|
||||
|
||||
if (scalarObject is T scalarValue)
|
||||
if (sqlReader.Read())
|
||||
{
|
||||
scalarResult = scalarValue;
|
||||
firstOrDefaultResult = output(sqlReader);
|
||||
}
|
||||
}
|
||||
|
||||
sqlTransaction.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
return scalarResult;
|
||||
}
|
||||
|
||||
public bool Transaction(string query, Action<ParameterCollection> 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(sqlCommand.Parameters);
|
||||
sqlCommand.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
if (canCommitTransaction)
|
||||
{
|
||||
sqlTransaction.Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return canCommitTransaction;
|
||||
return firstOrDefaultResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -7,11 +7,11 @@
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
|
||||
public static class DbContextExtensions
|
||||
public static class SqlClientExtensions
|
||||
{
|
||||
public static IServiceCollection AddDbContext(this IServiceCollection services, string connectionString)
|
||||
{
|
||||
services.AddTransient<IDbContext>(_ => new DbContext(connectionString));
|
||||
services.AddTransient<ISqlClient>(_ => new SqlClient(connectionString));
|
||||
|
||||
return services;
|
||||
}
|
||||
+6
-6
@@ -6,21 +6,21 @@
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
|
||||
public class ParameterCollection : IParameterCollection
|
||||
public class SqlParameters : ISqlParameters
|
||||
{
|
||||
private readonly SqlParameterCollection parameters;
|
||||
|
||||
private ParameterCollection(SqlParameterCollection parameters)
|
||||
private SqlParameters(SqlParameterCollection parameters)
|
||||
=> this.parameters = parameters;
|
||||
|
||||
public IParameterCollection Add(string name, object value)
|
||||
public ISqlParameters Add(string name, object value)
|
||||
{
|
||||
this.parameters.Add(new SqlParameter(name, value ?? DBNull.Value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IParameterCollection AddFile(string name, byte[] fileContent)
|
||||
public ISqlParameters AddFile(string name, byte[] fileContent)
|
||||
{
|
||||
var sqlParameter = new SqlParameter(name, SqlDbType.VarBinary);
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
return this;
|
||||
}
|
||||
|
||||
public static implicit operator ParameterCollection(SqlParameterCollection sqlParameterCollection)
|
||||
=> new ParameterCollection(sqlParameterCollection);
|
||||
public static implicit operator SqlParameters(SqlParameterCollection sqlParameterCollection)
|
||||
=> new SqlParameters(sqlParameterCollection);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@
|
||||
{
|
||||
private readonly SqlDataReader sqlDataReader;
|
||||
|
||||
private int column;
|
||||
|
||||
private SqlReader(SqlDataReader sqlDataReader)
|
||||
=> this.sqlDataReader = sqlDataReader;
|
||||
|
||||
@@ -18,9 +20,10 @@
|
||||
this.sqlDataReader.Dispose();
|
||||
}
|
||||
|
||||
public byte[] GetBytes(int index)
|
||||
public byte[] GetBytes(int index = -1)
|
||||
{
|
||||
var bytes = default(byte[]);
|
||||
index = Math.Max(index, this.column++);
|
||||
|
||||
if (!this.sqlDataReader.IsDBNull(index))
|
||||
{
|
||||
@@ -30,8 +33,10 @@
|
||||
return bytes ?? Array.Empty<byte>();
|
||||
}
|
||||
|
||||
public string GetString(int index)
|
||||
public string GetString(int index = -1)
|
||||
{
|
||||
index = Math.Max(index, this.column++);
|
||||
|
||||
if (!this.sqlDataReader.IsDBNull(index))
|
||||
{
|
||||
return $"{this.sqlDataReader.GetValue(index)}";
|
||||
@@ -40,8 +45,10 @@
|
||||
return default(string);
|
||||
}
|
||||
|
||||
public T GetValue<T>(int index)
|
||||
public T GetValue<T>(int index = -1)
|
||||
{
|
||||
index = Math.Max(index, this.column++);
|
||||
|
||||
if (!this.sqlDataReader.IsDBNull(index))
|
||||
{
|
||||
if (this.sqlDataReader.GetValue(index) is T value)
|
||||
@@ -54,7 +61,11 @@
|
||||
}
|
||||
|
||||
internal bool Read()
|
||||
=> this.sqlDataReader.Read();
|
||||
{
|
||||
this.column = 0;
|
||||
|
||||
return this.sqlDataReader.Read();
|
||||
}
|
||||
|
||||
public static implicit operator SqlReader(SqlDataReader sqlDataReader)
|
||||
=> new SqlReader(sqlDataReader);
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
public class AccountService : IAccountService
|
||||
{
|
||||
private readonly IDbContext dbContext;
|
||||
private readonly ISqlClient dbContext;
|
||||
|
||||
public AccountService(IDbContext dbContext)
|
||||
public AccountService(ISqlClient dbContext)
|
||||
=> this.dbContext = dbContext;
|
||||
|
||||
public WindowsUser GetOrCreateWindowsUser(string primarySID, string domainName, string userName)
|
||||
@@ -81,7 +81,7 @@
|
||||
}) ?? new UserRole();
|
||||
|
||||
public IEnumerable<UserRole> GetUserRoles(int userId)
|
||||
=> this.dbContext.AsEnumerable($@"
|
||||
=> this.dbContext.ExecuteReader($@"
|
||||
SELECT [Account].[Roles].[RoleId]
|
||||
, [Account].[Roles].[RoleName]
|
||||
, [Account].[Roles].[DisplayName]
|
||||
@@ -99,7 +99,7 @@
|
||||
});
|
||||
|
||||
public List<UserPermissions> GetUsersWithPendingRoles()
|
||||
=> this.dbContext.AsEnumerable($@"
|
||||
=> this.dbContext.ExecuteReader($@"
|
||||
SELECT [Account].[Users].[UserId]
|
||||
, [Account].[Users].[FirstName] + ' ' + [Account].[Users].[LastName]
|
||||
, [Account].[Roles].[RoleId]
|
||||
@@ -140,7 +140,7 @@
|
||||
|
||||
public void RequestPermissions(PermissionsModel model)
|
||||
{
|
||||
this.dbContext.Transaction($@"
|
||||
this.dbContext.ExecuteNonQuery($@"
|
||||
INSERT INTO [Auftrag].[Account].[Users] (
|
||||
[UserId]
|
||||
, [FirstName]
|
||||
@@ -153,7 +153,7 @@
|
||||
.Add(nameof(model.FirstName), model.FirstName)
|
||||
.Add(nameof(model.LastName), model.LastName));
|
||||
|
||||
model.Role.Pending = this.dbContext.Transaction($@"
|
||||
model.Role.Pending = this.dbContext.ExecuteNonQuery($@"
|
||||
INSERT INTO [Auftrag].[Account].[UsersRoles] (
|
||||
[UserId]
|
||||
, [RoleId])
|
||||
@@ -161,7 +161,7 @@
|
||||
, @{nameof(model.Role.RoleId)})",
|
||||
parameters => parameters
|
||||
.Add(nameof(model.UserId), model.UserId)
|
||||
.Add(nameof(model.Role.RoleId), model.Role.RoleId));
|
||||
.Add(nameof(model.Role.RoleId), model.Role.RoleId)) == 1;
|
||||
}
|
||||
|
||||
public void SaveUserPermissions(IEnumerable<UserPermissions> usersPermissions)
|
||||
@@ -170,7 +170,7 @@
|
||||
{
|
||||
foreach (var role in user.Roles)
|
||||
{
|
||||
this.dbContext.Transaction(
|
||||
this.dbContext.ExecuteNonQuery(
|
||||
role.Deleted
|
||||
? $@"DELETE FROM [Account].[UsersRoles]
|
||||
WHERE [Account].[UsersRoles].[UserId] = @{nameof(user.UserId)}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
namespace LaaProductionWeb.Services
|
||||
{
|
||||
using LaaProductionWeb.Data.Interfaces;
|
||||
using LaaProductionWeb.Services.Interfaces;
|
||||
using LaaProductionWeb.Services.Models;
|
||||
|
||||
public class ApprovalsService : IApprovalsService
|
||||
{
|
||||
private readonly ISqlClient sqlClient;
|
||||
|
||||
public ApprovalsService(ISqlClient sqlClient)
|
||||
=> this.sqlClient = sqlClient;
|
||||
|
||||
public Result Add(ProductionApproval approval)
|
||||
{
|
||||
var message = string.Empty;
|
||||
var rowsAffected = this.sqlClient.ExecuteNonQuery($@"
|
||||
INSERT INTO [ProductionSkipAndLifeTimeInfo] (
|
||||
[OrderNumber]
|
||||
, [PcbId]
|
||||
, [SkipRadioParametersCheck]
|
||||
, [OverrideFwCheck]
|
||||
, [OverrideLutCheck]
|
||||
, [RequiredLifeTimeYearsAssembly]
|
||||
, [RequiredLifeTimeYearsShipping]
|
||||
, [KitronProductionDate]
|
||||
, [MaxDrainedBatteryLoadPercent]
|
||||
, [MaxStorageMonths]
|
||||
, [ApprovalDate]
|
||||
, [ApproverName]
|
||||
, [ApprovalComment])
|
||||
VALUES (@{nameof(approval.OrderNr)}
|
||||
, @{nameof(approval.PcbId)}
|
||||
, @{nameof(approval.SkipRadioCheck)}
|
||||
, @{nameof(approval.OverrideFwCheck)}
|
||||
, @{nameof(approval.OverrideLutCheck)}
|
||||
, @{nameof(approval.RequiredLifeTimeYearsAssembly)}
|
||||
, @{nameof(approval.RequiredLifeTimeYearsShipping)}
|
||||
, @{nameof(approval.KitronProductionDate)}
|
||||
, @{nameof(approval.MaxDrainedBatteryLoadPercent)}
|
||||
, @{nameof(approval.MaxStorageMonths)}
|
||||
, @{nameof(approval.ApprovalDate)}
|
||||
, @{nameof(approval.ApproverName)}
|
||||
, @{nameof(approval.ApprovalComment)})",
|
||||
parameters => parameters
|
||||
.Add(nameof(approval.OrderNr), approval.OrderNr)
|
||||
.Add(nameof(approval.PcbId), approval.PcbId)
|
||||
.Add(nameof(approval.SkipRadioCheck), approval.SkipRadioCheck)
|
||||
.Add(nameof(approval.OverrideFwCheck), approval.OverrideFwCheck)
|
||||
.Add(nameof(approval.OverrideLutCheck), approval.OverrideLutCheck)
|
||||
.Add(nameof(approval.RequiredLifeTimeYearsAssembly), approval.RequiredLifeTimeYearsAssembly)
|
||||
.Add(nameof(approval.RequiredLifeTimeYearsShipping), approval.RequiredLifeTimeYearsShipping)
|
||||
.Add(nameof(approval.KitronProductionDate), approval.KitronProductionDate)
|
||||
.Add(nameof(approval.MaxDrainedBatteryLoadPercent), approval.MaxDrainedBatteryLoadPercent)
|
||||
.Add(nameof(approval.MaxStorageMonths), approval.MaxStorageMonths)
|
||||
.Add(nameof(approval.ApprovalDate), approval.ApprovalDate)
|
||||
.Add(nameof(approval.ApproverName), approval.ApproverName)
|
||||
.Add(nameof(approval.ApprovalComment), approval.ApprovalComment),
|
||||
errorCallback: (s, args) => message = args.Message);
|
||||
|
||||
Result result = rowsAffected == 1;
|
||||
|
||||
if (result)
|
||||
{
|
||||
result.WithMessage("Die Genehmigung wurde erfolgreich hinzugefügt.");
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
result.WithMessage("Die Genehmigung wurde nicht hinzugefügt.");
|
||||
}
|
||||
else
|
||||
{
|
||||
result.WithMessage(message);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace LaaProductionWeb.Services.Interfaces
|
||||
{
|
||||
using LaaProductionWeb.Services.Models;
|
||||
|
||||
public interface IApprovalsService : ITransient
|
||||
{
|
||||
Result Add(ProductionApproval approval);
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,9 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AccountService.cs" />
|
||||
<Compile Include="ApprovalsService.cs" />
|
||||
<Compile Include="Interfaces\IAccountService.cs" />
|
||||
<Compile Include="Interfaces\IApprovalsService.cs" />
|
||||
<Compile Include="Interfaces\IOrdersService.cs" />
|
||||
<Compile Include="Interfaces\IProtocolService.cs" />
|
||||
<Compile Include="Interfaces\IReportService.cs" />
|
||||
@@ -72,6 +74,7 @@
|
||||
<Compile Include="Models\HeliumReportModelExtensions.cs" />
|
||||
<Compile Include="Models\OrderIdentity.cs" />
|
||||
<Compile Include="Models\PalletPosition.cs" />
|
||||
<Compile Include="Models\ProductionApproval.cs" />
|
||||
<Compile Include="Models\RoleModel.cs" />
|
||||
<Compile Include="Models\UserRole.cs" />
|
||||
<Compile Include="Models\UserPermissions.cs" />
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace LaaProductionWeb.Services.Models
|
||||
{
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
public class ProductionApproval
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public int? OrderNr { get; set; }
|
||||
|
||||
public int? PcbId { get; set; }
|
||||
|
||||
public bool SkipRadioCheck { get; set; }
|
||||
|
||||
public int? OverrideFwCheck { get; set; }
|
||||
|
||||
public int? OverrideLutCheck { get; set; }
|
||||
|
||||
public int? RequiredLifeTimeYearsAssembly { get; set; }
|
||||
|
||||
public int? RequiredLifeTimeYearsShipping { get; set; }
|
||||
|
||||
public DateTime? KitronProductionDate { get; set; }
|
||||
|
||||
public double? MaxDrainedBatteryLoadPercent { get; set; }
|
||||
|
||||
public int? MaxStorageMonths { get; set; }
|
||||
|
||||
public DateTime ApprovalDate { get; set; }
|
||||
|
||||
public string ApproverName { get; set; }
|
||||
|
||||
[Required]
|
||||
[MinLength(10)]
|
||||
public string ApprovalComment { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
namespace LaaProductionWeb.Services.Models
|
||||
{
|
||||
public struct Result
|
||||
public class Result
|
||||
{
|
||||
private readonly bool succeeded;
|
||||
private readonly string message;
|
||||
|
||||
private string message;
|
||||
|
||||
private Result(bool succeeded = true)
|
||||
{
|
||||
@@ -17,6 +18,9 @@
|
||||
this.succeeded = string.IsNullOrWhiteSpace(message);
|
||||
}
|
||||
|
||||
public void WithMessage(string message)
|
||||
=> this.message = message;
|
||||
|
||||
public override string ToString()
|
||||
=> this.message;
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
|
||||
public class OrdersService : IOrdersService
|
||||
{
|
||||
private readonly IDbContext dbContext;
|
||||
private readonly ISqlClient dbContext;
|
||||
|
||||
public OrdersService(IDbContext dbContext)
|
||||
public OrdersService(ISqlClient dbContext)
|
||||
=> this.dbContext = dbContext;
|
||||
|
||||
public OrdersFoundModel FindOrders(string search)
|
||||
{
|
||||
var ordersFound = this.dbContext.AsEnumerable(
|
||||
var ordersFound = this.dbContext.ExecuteReader(
|
||||
query: $@"
|
||||
SELECT DISTINCT
|
||||
[AlleAuftragPositionen].[AuftragNr] -- 0
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
public OrderPositions FindPositions(string orderNr)
|
||||
{
|
||||
var positionsFound = this.dbContext.AsEnumerable(
|
||||
var positionsFound = this.dbContext.ExecuteReader(
|
||||
query: $@"
|
||||
SELECT DISTINCT
|
||||
[AlleAuftragPositionen].[PositionNr] -- 0
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
|
||||
public class ProtocolService : IProtocolService
|
||||
{
|
||||
private readonly IDbContext dbContext;
|
||||
private readonly ISqlClient dbContext;
|
||||
|
||||
public ProtocolService(IDbContext dbContext)
|
||||
public ProtocolService(ISqlClient dbContext)
|
||||
=> this.dbContext = dbContext;
|
||||
|
||||
public bool DeleteFile(int fileId)
|
||||
=> this.dbContext.ScalarTransaction<int>($@"
|
||||
=> this.dbContext.ExecuteScalar<int>($@"
|
||||
DELETE FROM [File]
|
||||
WHERE [FileId] = @{nameof(fileId)};
|
||||
DELETE FROM [FilesOrders]
|
||||
@@ -85,7 +85,7 @@
|
||||
}
|
||||
|
||||
public IEnumerable<KeyValuePair<int, string>> FindOrdersById(int orderno)
|
||||
=> this.dbContext.AsEnumerable($@"
|
||||
=> this.dbContext.ExecuteReader($@"
|
||||
SELECT DISTINCT
|
||||
[APG].[AuftragNr]
|
||||
, [K].[Name]
|
||||
@@ -104,7 +104,7 @@
|
||||
value: reader.GetString(1)));
|
||||
|
||||
public IEnumerable<KeyValuePair<int, string>> FindOrdersByClient(string clientName)
|
||||
=> this.dbContext.AsEnumerable(
|
||||
=> this.dbContext.ExecuteReader(
|
||||
query: $@"
|
||||
SELECT DISTINCT
|
||||
[APG].[AuftragNr]
|
||||
@@ -124,7 +124,7 @@
|
||||
value: reader.GetString(1)));
|
||||
|
||||
public IEnumerable<TestStatus> LoadTestStatus(int orderId)
|
||||
=> this.dbContext.AsEnumerable(
|
||||
=> this.dbContext.ExecuteReader(
|
||||
query: $@"
|
||||
SELECT DISTINCT
|
||||
[APS].[PositionNr] AS [PosNr] -- 0
|
||||
@@ -165,7 +165,7 @@
|
||||
|
||||
public bool OrderCompleted(int orderId)
|
||||
{
|
||||
var sumResults = this.dbContext.AsEnumerable($@"
|
||||
var sumResults = this.dbContext.ExecuteReader($@"
|
||||
SELECT CAST(CASE WHEN ISNULL([CPT].[CordonelPressureTest_Valid], 1) = 1 THEN 0 ELSE 1 END AS INT)
|
||||
+ CAST(CASE WHEN ISNULL([DP].[Dicht], 1) = 1 THEN 0 ELSE 1 END AS INT)
|
||||
+ ISNULL(MAX([APS].[StatusFertigung]) - 30, 0)
|
||||
@@ -199,7 +199,7 @@
|
||||
var fileName = $"{clientId}_{orderId}.pdf";
|
||||
var fileContent = this.GetBuffer(fileStream);
|
||||
|
||||
return this.dbContext.ScalarTransaction<int>(
|
||||
return this.dbContext.ExecuteScalar<int>(
|
||||
query: $@"
|
||||
INSERT INTO [dbo].[File] (
|
||||
[FileName]
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
public class ReportService : IReportService
|
||||
{
|
||||
private readonly IDbContext dbContext;
|
||||
private readonly ISqlClient dbContext;
|
||||
|
||||
public ReportService(IDbContext dbContext)
|
||||
public ReportService(ISqlClient dbContext)
|
||||
=> this.dbContext = dbContext;
|
||||
|
||||
public HeliumReportModel LoadHeliumPressureReport(HeliumReportModel model = null)
|
||||
@@ -25,7 +25,7 @@
|
||||
var where = model.Filter.Where();
|
||||
var orderBy = model.Filter.OrderBy();
|
||||
|
||||
model.Items = this.dbContext.AsEnumerable($@"
|
||||
model.Items = this.dbContext.ExecuteReader($@"
|
||||
SELECT [PT].[CordonelPressureTest_FertigungsAuftragsNr] AS [FertigungNr] -- 0 Filter
|
||||
, [POS].[AuftragNr] AS [AuftragNr] -- 1 Filter
|
||||
, [POS].[PositionNr] AS [PositionNr] -- 2 Filter
|
||||
@@ -94,7 +94,7 @@
|
||||
}
|
||||
|
||||
public IEnumerable<HeliumPressurePoint> LoadHeliumPressureResults(int testId)
|
||||
=> this.dbContext.AsEnumerable($@"
|
||||
=> this.dbContext.ExecuteReader($@"
|
||||
SELECT [CordonelPressureTestTestPoints_Id]
|
||||
, [CordonelPressureTest_TestPointNr]
|
||||
, CAST([CordonelPressureTest_TestPointResult] AS NUMERIC(9, 8))
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
|
||||
public class ShipmentsService : IShipmentsService
|
||||
{
|
||||
private readonly IDbContext dbContext;
|
||||
private readonly ISqlClient dbContext;
|
||||
|
||||
public ShipmentsService(IDbContext dbContext)
|
||||
public ShipmentsService(ISqlClient dbContext)
|
||||
=> this.dbContext = dbContext;
|
||||
|
||||
public OrderScanModel DeletePalletEntry(int id)
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
public byte[] LoadBatchBuffer(OrderScanModel model)
|
||||
{
|
||||
var palletLoad = this.dbContext.AsEnumerable(
|
||||
var palletLoad = this.dbContext.ExecuteReader(
|
||||
query: $@"
|
||||
SELECT [PalettenNr]
|
||||
, [SerienNr]
|
||||
@@ -76,7 +76,7 @@
|
||||
}
|
||||
|
||||
public IEnumerable<SerialNr> LoadBatchItems(OrderScanModel model)
|
||||
=> this.dbContext.AsEnumerable(
|
||||
=> this.dbContext.ExecuteReader(
|
||||
query: $@"
|
||||
SELECT DISTINCT
|
||||
[PalettenScan].[ID] -- 0
|
||||
@@ -156,7 +156,7 @@
|
||||
}
|
||||
|
||||
private IEnumerable<PalletPosition> LoadBatchPositions(OrderScanModel model, string orderNr, string palNr)
|
||||
=> this.dbContext.AsEnumerable($@"
|
||||
=> this.dbContext.ExecuteReader($@"
|
||||
SELECT DISTINCT
|
||||
[Auftrag].[PositionNr]
|
||||
, COUNT(1)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
namespace LaaProductionWeb.Controllers
|
||||
{
|
||||
using LaaProductionWeb.Services.Interfaces;
|
||||
using LaaProductionWeb.Services.Models;
|
||||
|
||||
using System;
|
||||
using System.Web.Mvc;
|
||||
|
||||
[Authorize]
|
||||
public class ApprovalsController : Controller
|
||||
{
|
||||
private readonly IApprovalsService approvals;
|
||||
|
||||
public ApprovalsController(IApprovalsService approvals)
|
||||
=> this.approvals = approvals;
|
||||
|
||||
[HttpGet]
|
||||
public ActionResult Add()
|
||||
=> this.View(new ProductionApproval
|
||||
{
|
||||
ApproverName = this.User.Identity.Name,
|
||||
ApprovalDate = DateTime.Now
|
||||
});
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult Add(ProductionApproval approval)
|
||||
{
|
||||
if (this.ModelState.IsValid)
|
||||
{
|
||||
approval.ApprovalDate = DateTime.Now;
|
||||
approval.ApproverName = this.User.Identity.Name;
|
||||
|
||||
var addResult = this.approvals.Add(approval);
|
||||
|
||||
if (addResult)
|
||||
{
|
||||
this.TempData["Success"] = (string)addResult;
|
||||
|
||||
return this.RedirectToAction(nameof(this.Add));
|
||||
}
|
||||
|
||||
this.TempData["Error"] = (string)addResult;
|
||||
}
|
||||
|
||||
return this.View(approval);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,6 +143,7 @@
|
||||
<Compile Include="App_Start\ServicesConfig.cs" />
|
||||
<Compile Include="App_Start\WebApiConfig.cs" />
|
||||
<Compile Include="Controllers\AccountController.cs" />
|
||||
<Compile Include="Controllers\ApprovalsController.cs" />
|
||||
<Compile Include="Controllers\HomeController.cs" />
|
||||
<Compile Include="Controllers\ProtocolController.cs" />
|
||||
<Compile Include="Controllers\ReportController.cs" />
|
||||
@@ -183,6 +184,7 @@
|
||||
<Content Include="Views\Report\Results.cshtml" />
|
||||
<Content Include="Views\Report\DateFilter.cshtml" />
|
||||
<None Include="Properties\PublishProfiles\FolderProfile.pubxml" />
|
||||
<Content Include="Views\Approvals\Add.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="App_Content\font\fonts\bootstrap-icons.woff" />
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
@model LaaProductionWeb.Services.Models.ProductionApproval
|
||||
|
||||
<form action="/Approvals/Add" method="post" autocomplete="off">
|
||||
<div class="container my-3">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>Neue Genehmigung erstellen</h4>
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating mb-3">
|
||||
@this.Html.TextBoxFor(x => x.OrderNr, new { @class = "form-control", placeholder = "Creator" })
|
||||
@this.Html.LabelFor(x => x.OrderNr, labelText: "FertigungsNr")
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating mb-3">
|
||||
@this.Html.TextBoxFor(x => x.PcbId, new { @class = "form-control", placeholder = "Creator" })
|
||||
@this.Html.LabelFor(x => x.PcbId, labelText: "PcbId")
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating mb-3">
|
||||
@this.Html.TextBoxFor(x => x.OverrideFwCheck, new { @class = "form-control", placeholder = "Creator" })
|
||||
@this.Html.LabelFor(x => x.OverrideFwCheck, labelText: "OverrideFwCheck")
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating mb-3">
|
||||
@this.Html.TextBoxFor(x => x.OverrideLutCheck, new { @class = "form-control", placeholder = "Creator" })
|
||||
@this.Html.LabelFor(x => x.OverrideLutCheck, labelText: "OverrideLutCheck")
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating mb-3">
|
||||
@this.Html.TextBoxFor(x => x.RequiredLifeTimeYearsAssembly, new { @class = "form-control", placeholder = "Creator" })
|
||||
@this.Html.LabelFor(x => x.RequiredLifeTimeYearsAssembly, labelText: "RequiredLifeTimeYearsAssembly")
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating mb-3">
|
||||
@this.Html.TextBoxFor(x => x.RequiredLifeTimeYearsShipping, new { @class = "form-control", placeholder = "Creator" })
|
||||
@this.Html.LabelFor(x => x.RequiredLifeTimeYearsShipping, labelText: "RequiredLifeTimeYearsShipping")
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch pt-3">
|
||||
@this.Html.CheckBoxFor(x => x.SkipRadioCheck, new { @class = "form-check-input", type = "checkbox", role = "switch", @checked = (this.Model.SkipRadioCheck ? "checked" : null) })
|
||||
@this.Html.LabelFor(x => x.SkipRadioCheck, labelText: "Überprüfung der Funkparameter überspringen", htmlAttributes: new { @class = "form-check-label" })
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating mb-3">
|
||||
@this.Html.TextBoxFor(x => x.KitronProductionDate, new { @class = "form-control", placeholder = "Creator", type = "date" })
|
||||
@this.Html.LabelFor(x => x.KitronProductionDate, labelText: "KitronProductionDate")
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating mb-3">
|
||||
@this.Html.TextBoxFor(x => x.MaxDrainedBatteryLoadPercent, new { @class = "form-control", placeholder = "Creator" })
|
||||
@this.Html.LabelFor(x => x.RequiredLifeTimeYearsShipping, labelText: "MaxDrainedBatteryLoadPercent")
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating mb-3">
|
||||
@this.Html.TextBoxFor(x => x.MaxStorageMonths, new { @class = "form-control", placeholder = "Creator" })
|
||||
@this.Html.LabelFor(x => x.RequiredLifeTimeYearsShipping, labelText: "MaxStorageMonths")
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating mb-3">
|
||||
@this.Html.TextBoxFor(x => x.ApproverName, new { @class = "form-control bg-light border-light", placeholder = "Creator", @readonly = "readonly" })
|
||||
@this.Html.LabelFor(x => x.ApproverName, labelText: "Name des Genehmigers")
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating mb-3">
|
||||
@this.Html.TextBoxFor(x => x.ApprovalDate, new { @class = "form-control bg-light border-light", placeholder = "Creator", @readonly = "readonly" })
|
||||
@this.Html.LabelFor(x => x.ApprovalDate, labelText: "Datum der Genehmigung")
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="form-floating mb-3">
|
||||
@this.Html.TextAreaFor(x => x.ApprovalComment, new { @class = "form-control", placeholder = "Creator", style = "height: 210px" })
|
||||
@this.Html.LabelFor(x => x.ApprovalComment, labelText: "Genehmigungskommentar")
|
||||
@this.Html.ValidationMessageFor(x => x.ApprovalComment, string.Empty, new { @class = "small text-danger" })
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col d-flex">
|
||||
<input type="submit" class="btn btn-primary py-3 px-5 me-3" value="Neue Genehmigung erstellen" />
|
||||
@if (this.TempData.TryGetValue("Success", out object message))
|
||||
{
|
||||
<div class="toast fade show position-relative align-items-center text-bg-success border-0 ms-auto" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header">
|
||||
<i class="bi-bold bi-check text-success me-2 fs-4"></i>
|
||||
<strong class="me-auto text-success">@message</strong>
|
||||
<small>Just now</small>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else if (this.TempData.TryGetValue("Error", out object error))
|
||||
{
|
||||
<div class="toast fade show position-relative align-items-center text-bg-danger border-0 ms-auto" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header">
|
||||
<i class="bi-bold bi-x text-danger me-2 fs-4"></i>
|
||||
<strong class="me-auto text-danger">@error</strong>
|
||||
<small>Just now</small>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-smallcaps" href="/Report">[He] Report</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-smallcaps" href="/Approvals/Add">Production Approval</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
|
||||
Reference in New Issue
Block a user