diff --git a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.ar.resx b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.ar.resx
index c586e122e..c7ff6f421 100644
--- a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.ar.resx
+++ b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.ar.resx
@@ -10,6 +10,7 @@
أضيف في
إضافة نوع مخزون
تعديل المخزون
+ يجب تقديم تعديل مخزون غير صفري (العدد/الكمية).
الكمية
الدفعة
الرقم التسلسلي للدفعة
@@ -18,6 +19,7 @@
تعديل نوع المخزون
للعناصر التي لا تنتهي صلاحيتها
المجموعة
+ المخزون حسب الوحدة
سجل المخزون
أنواع المخزون
تنتهي صلاحية العنصر بعد (أيام)
diff --git a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.de.resx b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.de.resx
index 7823251f0..87a0ffa4c 100644
--- a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.de.resx
+++ b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.de.resx
@@ -79,6 +79,9 @@
Inventar anpassen
+
+ Sie müssen eine von null abweichende Bestandsanpassung angeben (Anzahl/Menge).
+
Menge
@@ -112,6 +115,9 @@
Gruppe
+
+ Inventar nach Einheit
+
Inventarverlauf
diff --git a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.en.resx b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.en.resx
index 9e24daa7c..2487990f5 100644
--- a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.en.resx
+++ b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.en.resx
@@ -138,6 +138,9 @@
Adjust Inventory
+
+ You must supply a non-zero inventory adjustment (count/amount).
+
Amount
@@ -171,6 +174,9 @@
Group
+
+ Inventory by Unit
+
Inventory History
diff --git a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.es.resx b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.es.resx
index 21d93aada..d1d7195c5 100644
--- a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.es.resx
+++ b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.es.resx
@@ -79,6 +79,9 @@
Ajustar inventario
+
+ Debe proporcionar un ajuste de inventario distinto de cero (cantidad).
+
Cantidad
@@ -112,6 +115,9 @@
Grupo
+
+ Inventario por unidad
+
Historial de inventario
diff --git a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.fr.resx b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.fr.resx
index 4a775861a..9aa9b25b4 100644
--- a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.fr.resx
+++ b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.fr.resx
@@ -79,6 +79,9 @@
Ajuster l'inventaire
+
+ Vous devez fournir un ajustement d'inventaire non nul (nombre/quantité).
+
Quantité
@@ -112,6 +115,9 @@
Groupe
+
+ Inventaire par unité
+
Historique de l'inventaire
diff --git a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.it.resx b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.it.resx
index e958e7029..29a8066bb 100644
--- a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.it.resx
+++ b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.it.resx
@@ -79,6 +79,9 @@
Rettifica inventario
+
+ È necessario fornire una rettifica di inventario diversa da zero (conteggio/quantità).
+
Quantità
@@ -112,6 +115,9 @@
Gruppo
+
+ Inventario per unità
+
Cronologia inventario
diff --git a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.pl.resx b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.pl.resx
index 23744ce85..2fa89b58b 100644
--- a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.pl.resx
+++ b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.pl.resx
@@ -79,6 +79,9 @@
Dostosuj inwentarz
+
+ Musisz podać niezerową korektę stanu magazynowego (liczba/ilość).
+
Ilość
@@ -112,6 +115,9 @@
Grupa
+
+ Inwentarz według jednostki
+
Historia inwentarza
diff --git a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.sv.resx b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.sv.resx
index 8a60609a7..498d90fa6 100644
--- a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.sv.resx
+++ b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.sv.resx
@@ -79,6 +79,9 @@
Justera lager
+
+ Du måste ange en lagerjustering som inte är noll (antal/mängd).
+
Antal
@@ -112,6 +115,9 @@
Grupp
+
+ Inventering per enhet
+
Lagerhistorik
diff --git a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.uk.resx b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.uk.resx
index 1d4362a0d..7ef52a19e 100644
--- a/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.uk.resx
+++ b/Core/Resgrid.Localization/Areas/User/Inventory/Inventory.uk.resx
@@ -79,6 +79,9 @@
Коригувати інвентар
+
+ Ви повинні вказати ненульове коригування запасів (кількість).
+
Кількість
@@ -112,6 +115,9 @@
Група
+
+ Інвентар за підрозділом
+
Історія інвентарю
diff --git a/Core/Resgrid.Model/Providers/IAuditEventProvider.cs b/Core/Resgrid.Model/Providers/IAuditEventProvider.cs
deleted file mode 100644
index 1f2d80b8d..000000000
--- a/Core/Resgrid.Model/Providers/IAuditEventProvider.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using Resgrid.Model.Events;
-using System.Threading.Tasks;
-
-namespace Resgrid.Model.Providers
-{
- public interface IAuditEventProvider
- {
- Task EnqueueAuditEventAsync(AuditEvent auditEvent);
- }
-}
diff --git a/Core/Resgrid.Model/Repositories/IAuditLogsRepository.cs b/Core/Resgrid.Model/Repositories/IAuditLogsRepository.cs
index 175d4424b..6f4ccd100 100644
--- a/Core/Resgrid.Model/Repositories/IAuditLogsRepository.cs
+++ b/Core/Resgrid.Model/Repositories/IAuditLogsRepository.cs
@@ -1,4 +1,8 @@
-namespace Resgrid.Model.Repositories
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Resgrid.Model.Repositories
{
///
/// Interface IAuditLogsRepository
@@ -7,5 +11,16 @@
///
public interface IAuditLogsRepository: IRepository
{
+ ///
+ /// Gets a date-ranged, optionally type-filtered, paged set of audit logs for a department.
+ ///
+ /// The department identifier.
+ /// Inclusive lower bound on LoggedOn (UTC).
+ /// Exclusive upper bound on LoggedOn (UTC).
+ /// Optional LogType filter; when null all types are returned.
+ /// 1-based page number.
+ /// Page size.
+ /// Task<IEnumerable<AuditLog>>.
+ Task> GetAuditLogsForDepartmentPagedAsync(int departmentId, DateTime startDate, DateTime endDate, int? logType, int page, int pageSize);
}
}
diff --git a/Core/Resgrid.Model/Repositories/IPaymentProviderEventsRepository.cs b/Core/Resgrid.Model/Repositories/IPaymentProviderEventsRepository.cs
index f68b044fe..4864ac702 100644
--- a/Core/Resgrid.Model/Repositories/IPaymentProviderEventsRepository.cs
+++ b/Core/Resgrid.Model/Repositories/IPaymentProviderEventsRepository.cs
@@ -1,4 +1,7 @@
-namespace Resgrid.Model.Repositories
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Resgrid.Model.Repositories
{
///
/// Interface IPaymentProviderEventsRepository
@@ -7,5 +10,11 @@
///
public interface IPaymentProviderEventsRepository: IRepository
{
+ ///
+ /// Gets all provider events for a payment-provider customer id (e.g. a Stripe customer), newest first.
+ ///
+ /// The payment-provider customer identifier.
+ /// Task<IEnumerable<PaymentProviderEvent>>.
+ Task> GetByCustomerIdAsync(string customerId);
}
}
diff --git a/Core/Resgrid.Model/Repositories/ISystemAuditsRepository.cs b/Core/Resgrid.Model/Repositories/ISystemAuditsRepository.cs
index a512e3ea6..127a49417 100644
--- a/Core/Resgrid.Model/Repositories/ISystemAuditsRepository.cs
+++ b/Core/Resgrid.Model/Repositories/ISystemAuditsRepository.cs
@@ -1,4 +1,8 @@
-namespace Resgrid.Model.Repositories
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Resgrid.Model.Repositories
{
///
/// Interface ISystemAuditsRepository
@@ -7,5 +11,26 @@
///
public interface ISystemAuditsRepository : IRepository
{
+ ///
+ /// Gets a date-ranged, paged set of system audits for a user (e.g. a login timeline).
+ ///
+ /// The user identifier.
+ /// Inclusive lower bound on LoggedOn (UTC).
+ /// Exclusive upper bound on LoggedOn (UTC).
+ /// 1-based page number.
+ /// Page size.
+ /// Task<IEnumerable<SystemAudit>>.
+ Task> GetByUserIdPagedAsync(string userId, DateTime startDate, DateTime endDate, int page, int pageSize);
+
+ ///
+ /// Gets a date-ranged, paged set of system audits for a department.
+ ///
+ /// The department identifier.
+ /// Inclusive lower bound on LoggedOn (UTC).
+ /// Exclusive upper bound on LoggedOn (UTC).
+ /// 1-based page number.
+ /// Page size.
+ /// Task<IEnumerable<SystemAudit>>.
+ Task> GetByDepartmentIdPagedAsync(int departmentId, DateTime startDate, DateTime endDate, int page, int pageSize);
}
}
diff --git a/Core/Resgrid.Model/Services/IAuditEventService.cs b/Core/Resgrid.Model/Services/IAuditEventService.cs
deleted file mode 100644
index 76d997a0f..000000000
--- a/Core/Resgrid.Model/Services/IAuditEventService.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Resgrid.Model.Services
-{
- public interface IAuditEventService
- {
- }
-}
diff --git a/Core/Resgrid.Model/Services/IAuditService.cs b/Core/Resgrid.Model/Services/IAuditService.cs
index 382e3f2ef..ce78beca0 100644
--- a/Core/Resgrid.Model/Services/IAuditService.cs
+++ b/Core/Resgrid.Model/Services/IAuditService.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -10,5 +11,16 @@ public interface IAuditService
Task> GetAllAuditLogsForDepartmentAsync(int departmentId);
string GetAuditLogTypeString(AuditLogTypes logType);
Task GetAuditLogByIdAsync(int auditLogId);
+
+ ///
+ /// Gets a date-ranged, optionally type-filtered, paged set of audit logs for a department.
+ ///
+ /// The department identifier.
+ /// Inclusive lower bound on LoggedOn (UTC).
+ /// Exclusive upper bound on LoggedOn (UTC).
+ /// Optional LogType filter; when null all types are returned.
+ /// 1-based page number.
+ /// Page size.
+ Task> GetAuditLogsForDepartmentPagedAsync(int departmentId, DateTime startDate, DateTime endDate, AuditLogTypes? logType, int page, int pageSize);
}
}
diff --git a/Core/Resgrid.Services/AuditEventService.cs b/Core/Resgrid.Services/AuditEventService.cs
deleted file mode 100644
index 2da4664d2..000000000
--- a/Core/Resgrid.Services/AuditEventService.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System;
-using Resgrid.Model.Events;
-using Resgrid.Model.Services;
-using Resgrid.Model.Providers;
-
-namespace Resgrid.Services
-{
- public class AuditEventService : IAuditEventService
- {
- private readonly IEventAggregator _eventAggregator;
- private static IAuditEventProvider _auditEventProvider;
-
- public AuditEventService(IEventAggregator eventAggregator, IAuditEventProvider auditEventProvider)
- {
- _eventAggregator = eventAggregator;
- _auditEventProvider = auditEventProvider;
-
- _eventAggregator.AddListener(auditEventHandler);
- }
-
- private Action auditEventHandler = async delegate(AuditEvent message)
- {
- await _auditEventProvider.EnqueueAuditEventAsync(message);
- };
- }
-}
diff --git a/Core/Resgrid.Services/AuditService.cs b/Core/Resgrid.Services/AuditService.cs
index 43d1bcb44..4a4c34e35 100644
--- a/Core/Resgrid.Services/AuditService.cs
+++ b/Core/Resgrid.Services/AuditService.cs
@@ -37,6 +37,12 @@ public async Task> GetAllAuditLogsForDepartmentAsync(int departme
return logs.ToList();
}
+ public async Task> GetAuditLogsForDepartmentPagedAsync(int departmentId, DateTime startDate, DateTime endDate, AuditLogTypes? logType, int page, int pageSize)
+ {
+ var logs = await _auditLogsRepository.GetAuditLogsForDepartmentPagedAsync(departmentId, startDate, endDate, (int?)logType, page, pageSize);
+ return logs.ToList();
+ }
+
public string GetAuditLogTypeString(AuditLogTypes logType)
{
switch (logType)
diff --git a/Core/Resgrid.Services/InventoryService.cs b/Core/Resgrid.Services/InventoryService.cs
index 89e833272..fd9daab96 100644
--- a/Core/Resgrid.Services/InventoryService.cs
+++ b/Core/Resgrid.Services/InventoryService.cs
@@ -13,12 +13,14 @@ public class InventoryService: IInventoryService
private readonly IInventoryTypesRepository _inventoryTypesRepository;
private readonly IInventoryRepository _inventoryRepository;
private readonly IDepartmentGroupsService _departmentGroupsService;
+ private readonly IUnitsService _unitsService;
- public InventoryService(IInventoryTypesRepository inventoryTypesRepository, IInventoryRepository inventoryRepository, IDepartmentGroupsService departmentGroupsService)
+ public InventoryService(IInventoryTypesRepository inventoryTypesRepository, IInventoryRepository inventoryRepository, IDepartmentGroupsService departmentGroupsService, IUnitsService unitsService)
{
_inventoryTypesRepository = inventoryTypesRepository;
_inventoryRepository = inventoryRepository;
_departmentGroupsService = departmentGroupsService;
+ _unitsService = unitsService;
}
public async Task GetTypeByIdAsync(int typeId)
@@ -38,6 +40,9 @@ public async Task GetInventoryByIdAsync(int inventoryId)
if (inventory != null && inventory.GroupId > 0)
inventory.Group = await _departmentGroupsService.GetGroupByIdAsync(inventory.GroupId);
+ if (inventory != null && inventory.UnitId.HasValue && inventory.UnitId.Value > 0)
+ inventory.Unit = await _unitsService.GetUnitByIdAsync(inventory.UnitId.Value);
+
return inventory;
}
@@ -65,9 +70,14 @@ public async Task> GetAllTransactionsForDepartmentAsync(int depa
{
var inventories = await _inventoryRepository.GetAllInventoriesByDepartmentIdAsync(departmentId);
+ var units = await _unitsService.GetUnitsForDepartmentAsync(departmentId);
+
foreach (var inventory in inventories)
{
inventory.Group = await _departmentGroupsService.GetGroupByIdAsync(inventory.GroupId);
+
+ if (inventory.UnitId.HasValue && inventory.UnitId.Value > 0)
+ inventory.Unit = units?.FirstOrDefault(x => x.UnitId == inventory.UnitId.Value);
}
return inventories.ToList();
diff --git a/Providers/Resgrid.Providers.Bus/AuditEventProvider.cs b/Providers/Resgrid.Providers.Bus/AuditEventProvider.cs
deleted file mode 100644
index e4aa86f89..000000000
--- a/Providers/Resgrid.Providers.Bus/AuditEventProvider.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Resgrid.Framework;
-using Resgrid.Model;
-using Resgrid.Model.Events;
-using Resgrid.Model.Providers;
-using Resgrid.Providers.Bus.Rabbit;
-
-namespace Resgrid.Providers.Bus
-{
- public class AuditEventProvider : IAuditEventProvider
- {
- private readonly RabbitOutboundQueueProvider _rabbitOutboundQueueProvider;
-
- public AuditEventProvider()
- {
- _rabbitOutboundQueueProvider = new RabbitOutboundQueueProvider();
- }
-
- public async Task EnqueueAuditEventAsync(AuditEvent auditEvent)
- {
- if (Config.SystemBehaviorConfig.ServiceBusType == Config.ServiceBusTypes.Rabbit)
- {
- _rabbitOutboundQueueProvider.EnqueueAuditEvent(auditEvent);
- return true;
- }
-
- return false;
- }
- }
-}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/AuditLogsRepository.cs b/Repositories/Resgrid.Repositories.DataRepository/AuditLogsRepository.cs
index 7df0b335b..561f94762 100644
--- a/Repositories/Resgrid.Repositories.DataRepository/AuditLogsRepository.cs
+++ b/Repositories/Resgrid.Repositories.DataRepository/AuditLogsRepository.cs
@@ -1,8 +1,15 @@
-using Resgrid.Model;
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Threading.Tasks;
+using Dapper;
+using Resgrid.Framework;
+using Resgrid.Model;
using Resgrid.Model.Repositories;
using Resgrid.Model.Repositories.Connection;
using Resgrid.Model.Repositories.Queries;
using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Queries.AuditLogs;
namespace Resgrid.Repositories.DataRepository
{
@@ -21,5 +28,59 @@ public AuditLogsRepository(IConnectionProvider connectionProvider, SqlConfigurat
_queryFactory = queryFactory;
_unitOfWork = unitOfWork;
}
+
+ public async Task> GetAuditLogsForDepartmentPagedAsync(int departmentId, DateTime startDate, DateTime endDate, int? logType, int page, int pageSize)
+ {
+ try
+ {
+ var selectFunction = new Func>>(async x =>
+ {
+ var dynamicParameters = new DynamicParametersExtension();
+ dynamicParameters.Add("DepartmentId", departmentId);
+ dynamicParameters.Add("StartDate", startDate);
+ dynamicParameters.Add("EndDate", endDate);
+ dynamicParameters.Add("Offset", (page - 1) * pageSize);
+ dynamicParameters.Add("PageSize", pageSize);
+
+ string query;
+ if (logType.HasValue)
+ {
+ dynamicParameters.Add("LogType", logType.Value);
+ query = _queryFactory.GetQuery();
+ }
+ else
+ {
+ query = _queryFactory.GetQuery();
+ }
+
+ return await x.QueryAsync(sql: query,
+ param: dynamicParameters,
+ transaction: _unitOfWork.Transaction);
+ });
+
+ DbConnection conn = null;
+ if (_unitOfWork?.Connection == null)
+ {
+ using (conn = _connectionProvider.Create())
+ {
+ await conn.OpenAsync();
+
+ return await selectFunction(conn);
+ }
+ }
+ else
+ {
+ conn = _unitOfWork.CreateOrGetConnection();
+
+ return await selectFunction(conn);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex, extraMessage: $"GetAuditLogsForDepartmentPagedAsync DepartmentId: {departmentId}");
+
+ throw;
+ }
+ }
}
}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/PaymentProviderEventsRepository.cs b/Repositories/Resgrid.Repositories.DataRepository/PaymentProviderEventsRepository.cs
index 035a07cf2..ff6bac888 100644
--- a/Repositories/Resgrid.Repositories.DataRepository/PaymentProviderEventsRepository.cs
+++ b/Repositories/Resgrid.Repositories.DataRepository/PaymentProviderEventsRepository.cs
@@ -1,8 +1,15 @@
-using Resgrid.Model;
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Threading.Tasks;
+using Dapper;
+using Resgrid.Framework;
+using Resgrid.Model;
using Resgrid.Model.Repositories;
using Resgrid.Model.Repositories.Connection;
using Resgrid.Model.Repositories.Queries;
using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Queries.Payments;
namespace Resgrid.Repositories.DataRepository
{
@@ -21,5 +28,46 @@ public PaymentProviderEventsRepository(IConnectionProvider connectionProvider, S
_queryFactory = queryFactory;
_unitOfWork = unitOfWork;
}
+
+ public async Task> GetByCustomerIdAsync(string customerId)
+ {
+ try
+ {
+ var selectFunction = new Func>>(async x =>
+ {
+ var dynamicParameters = new DynamicParametersExtension();
+ dynamicParameters.Add("CustomerId", customerId);
+
+ var query = _queryFactory.GetQuery();
+
+ return await x.QueryAsync(sql: query,
+ param: dynamicParameters,
+ transaction: _unitOfWork.Transaction);
+ });
+
+ DbConnection conn = null;
+ if (_unitOfWork?.Connection == null)
+ {
+ using (conn = _connectionProvider.Create())
+ {
+ await conn.OpenAsync();
+
+ return await selectFunction(conn);
+ }
+ }
+ else
+ {
+ conn = _unitOfWork.CreateOrGetConnection();
+
+ return await selectFunction(conn);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex);
+
+ throw;
+ }
+ }
}
}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/AuditLogs/SelectAuditLogsForDepartmentByTypePagedQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/AuditLogs/SelectAuditLogsForDepartmentByTypePagedQuery.cs
new file mode 100644
index 000000000..42c8eb5e3
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/AuditLogs/SelectAuditLogsForDepartmentByTypePagedQuery.cs
@@ -0,0 +1,23 @@
+using Resgrid.Config;
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+
+namespace Resgrid.Repositories.DataRepository.Queries.AuditLogs
+{
+ public class SelectAuditLogsForDepartmentByTypePagedQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectAuditLogsForDepartmentByTypePagedQuery(SqlConfiguration sqlConfiguration) => _sqlConfiguration = sqlConfiguration;
+
+ public string GetQuery()
+ {
+ if (DataConfig.DatabaseType == DatabaseTypes.Postgres)
+ return $"SELECT * FROM {_sqlConfiguration.SchemaName}.auditlogs WHERE departmentid = {_sqlConfiguration.ParameterNotation}DepartmentId AND logtype = {_sqlConfiguration.ParameterNotation}LogType AND loggedon >= {_sqlConfiguration.ParameterNotation}StartDate AND loggedon < {_sqlConfiguration.ParameterNotation}EndDate ORDER BY loggedon DESC LIMIT {_sqlConfiguration.ParameterNotation}PageSize OFFSET {_sqlConfiguration.ParameterNotation}Offset";
+
+ return $"SELECT * FROM {_sqlConfiguration.SchemaName}.[AuditLogs] WHERE [DepartmentId] = {_sqlConfiguration.ParameterNotation}DepartmentId AND [LogType] = {_sqlConfiguration.ParameterNotation}LogType AND [LoggedOn] >= {_sqlConfiguration.ParameterNotation}StartDate AND [LoggedOn] < {_sqlConfiguration.ParameterNotation}EndDate ORDER BY [LoggedOn] DESC OFFSET {_sqlConfiguration.ParameterNotation}Offset ROWS FETCH NEXT {_sqlConfiguration.ParameterNotation}PageSize ROWS ONLY";
+ }
+
+ public string GetQuery() where TEntity : class, IEntity => GetQuery();
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/AuditLogs/SelectAuditLogsForDepartmentPagedQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/AuditLogs/SelectAuditLogsForDepartmentPagedQuery.cs
new file mode 100644
index 000000000..fa74c0d86
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/AuditLogs/SelectAuditLogsForDepartmentPagedQuery.cs
@@ -0,0 +1,23 @@
+using Resgrid.Config;
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+
+namespace Resgrid.Repositories.DataRepository.Queries.AuditLogs
+{
+ public class SelectAuditLogsForDepartmentPagedQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectAuditLogsForDepartmentPagedQuery(SqlConfiguration sqlConfiguration) => _sqlConfiguration = sqlConfiguration;
+
+ public string GetQuery()
+ {
+ if (DataConfig.DatabaseType == DatabaseTypes.Postgres)
+ return $"SELECT * FROM {_sqlConfiguration.SchemaName}.auditlogs WHERE departmentid = {_sqlConfiguration.ParameterNotation}DepartmentId AND loggedon >= {_sqlConfiguration.ParameterNotation}StartDate AND loggedon < {_sqlConfiguration.ParameterNotation}EndDate ORDER BY loggedon DESC LIMIT {_sqlConfiguration.ParameterNotation}PageSize OFFSET {_sqlConfiguration.ParameterNotation}Offset";
+
+ return $"SELECT * FROM {_sqlConfiguration.SchemaName}.[AuditLogs] WHERE [DepartmentId] = {_sqlConfiguration.ParameterNotation}DepartmentId AND [LoggedOn] >= {_sqlConfiguration.ParameterNotation}StartDate AND [LoggedOn] < {_sqlConfiguration.ParameterNotation}EndDate ORDER BY [LoggedOn] DESC OFFSET {_sqlConfiguration.ParameterNotation}Offset ROWS FETCH NEXT {_sqlConfiguration.ParameterNotation}PageSize ROWS ONLY";
+ }
+
+ public string GetQuery() where TEntity : class, IEntity => GetQuery();
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/Payments/SelectPaymentProviderEventsByCustomerIdQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/Payments/SelectPaymentProviderEventsByCustomerIdQuery.cs
new file mode 100644
index 000000000..c7dae34a7
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/Payments/SelectPaymentProviderEventsByCustomerIdQuery.cs
@@ -0,0 +1,23 @@
+using Resgrid.Config;
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+
+namespace Resgrid.Repositories.DataRepository.Queries.Payments
+{
+ public class SelectPaymentProviderEventsByCustomerIdQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectPaymentProviderEventsByCustomerIdQuery(SqlConfiguration sqlConfiguration) => _sqlConfiguration = sqlConfiguration;
+
+ public string GetQuery()
+ {
+ if (DataConfig.DatabaseType == DatabaseTypes.Postgres)
+ return $"SELECT * FROM {_sqlConfiguration.SchemaName}.paymentproviderevents WHERE customerid = {_sqlConfiguration.ParameterNotation}CustomerId ORDER BY recievedon DESC";
+
+ return $"SELECT * FROM {_sqlConfiguration.SchemaName}.[PaymentProviderEvents] WHERE [CustomerId] = {_sqlConfiguration.ParameterNotation}CustomerId ORDER BY [RecievedOn] DESC";
+ }
+
+ public string GetQuery() where TEntity : class, IEntity => GetQuery();
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/SystemAudits/SelectSystemAuditsByDepartmentIdPagedQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/SystemAudits/SelectSystemAuditsByDepartmentIdPagedQuery.cs
new file mode 100644
index 000000000..239124a19
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/SystemAudits/SelectSystemAuditsByDepartmentIdPagedQuery.cs
@@ -0,0 +1,23 @@
+using Resgrid.Config;
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+
+namespace Resgrid.Repositories.DataRepository.Queries.SystemAudits
+{
+ public class SelectSystemAuditsByDepartmentIdPagedQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectSystemAuditsByDepartmentIdPagedQuery(SqlConfiguration sqlConfiguration) => _sqlConfiguration = sqlConfiguration;
+
+ public string GetQuery()
+ {
+ if (DataConfig.DatabaseType == DatabaseTypes.Postgres)
+ return $"SELECT * FROM {_sqlConfiguration.SchemaName}.systemaudits WHERE departmentid = {_sqlConfiguration.ParameterNotation}DepartmentId AND loggedon >= {_sqlConfiguration.ParameterNotation}StartDate AND loggedon < {_sqlConfiguration.ParameterNotation}EndDate ORDER BY loggedon DESC LIMIT {_sqlConfiguration.ParameterNotation}PageSize OFFSET {_sqlConfiguration.ParameterNotation}Offset";
+
+ return $"SELECT * FROM {_sqlConfiguration.SchemaName}.[SystemAudits] WHERE [DepartmentId] = {_sqlConfiguration.ParameterNotation}DepartmentId AND [LoggedOn] >= {_sqlConfiguration.ParameterNotation}StartDate AND [LoggedOn] < {_sqlConfiguration.ParameterNotation}EndDate ORDER BY [LoggedOn] DESC OFFSET {_sqlConfiguration.ParameterNotation}Offset ROWS FETCH NEXT {_sqlConfiguration.ParameterNotation}PageSize ROWS ONLY";
+ }
+
+ public string GetQuery() where TEntity : class, IEntity => GetQuery();
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/SystemAudits/SelectSystemAuditsByUserIdPagedQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/SystemAudits/SelectSystemAuditsByUserIdPagedQuery.cs
new file mode 100644
index 000000000..79d27bd90
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/SystemAudits/SelectSystemAuditsByUserIdPagedQuery.cs
@@ -0,0 +1,23 @@
+using Resgrid.Config;
+using Resgrid.Model;
+using Resgrid.Model.Repositories.Queries.Contracts;
+using Resgrid.Repositories.DataRepository.Configs;
+
+namespace Resgrid.Repositories.DataRepository.Queries.SystemAudits
+{
+ public class SelectSystemAuditsByUserIdPagedQuery : ISelectQuery
+ {
+ private readonly SqlConfiguration _sqlConfiguration;
+ public SelectSystemAuditsByUserIdPagedQuery(SqlConfiguration sqlConfiguration) => _sqlConfiguration = sqlConfiguration;
+
+ public string GetQuery()
+ {
+ if (DataConfig.DatabaseType == DatabaseTypes.Postgres)
+ return $"SELECT * FROM {_sqlConfiguration.SchemaName}.systemaudits WHERE userid = {_sqlConfiguration.ParameterNotation}UserId AND loggedon >= {_sqlConfiguration.ParameterNotation}StartDate AND loggedon < {_sqlConfiguration.ParameterNotation}EndDate ORDER BY loggedon DESC LIMIT {_sqlConfiguration.ParameterNotation}PageSize OFFSET {_sqlConfiguration.ParameterNotation}Offset";
+
+ return $"SELECT * FROM {_sqlConfiguration.SchemaName}.[SystemAudits] WHERE [UserId] = {_sqlConfiguration.ParameterNotation}UserId AND [LoggedOn] >= {_sqlConfiguration.ParameterNotation}StartDate AND [LoggedOn] < {_sqlConfiguration.ParameterNotation}EndDate ORDER BY [LoggedOn] DESC OFFSET {_sqlConfiguration.ParameterNotation}Offset ROWS FETCH NEXT {_sqlConfiguration.ParameterNotation}PageSize ROWS ONLY";
+ }
+
+ public string GetQuery() where TEntity : class, IEntity => GetQuery();
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.DataRepository/SystemAuditRepository.cs b/Repositories/Resgrid.Repositories.DataRepository/SystemAuditRepository.cs
index f23ac1984..4aca22109 100644
--- a/Repositories/Resgrid.Repositories.DataRepository/SystemAuditRepository.cs
+++ b/Repositories/Resgrid.Repositories.DataRepository/SystemAuditRepository.cs
@@ -1,8 +1,15 @@
-using Resgrid.Model;
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Threading.Tasks;
+using Dapper;
+using Resgrid.Framework;
+using Resgrid.Model;
using Resgrid.Model.Repositories;
using Resgrid.Model.Repositories.Connection;
using Resgrid.Model.Repositories.Queries;
using Resgrid.Repositories.DataRepository.Configs;
+using Resgrid.Repositories.DataRepository.Queries.SystemAudits;
namespace Resgrid.Repositories.DataRepository
{
@@ -21,5 +28,95 @@ public SystemAuditsRepository(IConnectionProvider connectionProvider, SqlConfigu
_queryFactory = queryFactory;
_unitOfWork = unitOfWork;
}
+
+ public async Task> GetByUserIdPagedAsync(string userId, DateTime startDate, DateTime endDate, int page, int pageSize)
+ {
+ try
+ {
+ var selectFunction = new Func>>(async x =>
+ {
+ var dynamicParameters = new DynamicParametersExtension();
+ dynamicParameters.Add("UserId", userId);
+ dynamicParameters.Add("StartDate", startDate);
+ dynamicParameters.Add("EndDate", endDate);
+ dynamicParameters.Add("Offset", (page - 1) * pageSize);
+ dynamicParameters.Add("PageSize", pageSize);
+
+ var query = _queryFactory.GetQuery();
+
+ return await x.QueryAsync(sql: query,
+ param: dynamicParameters,
+ transaction: _unitOfWork.Transaction);
+ });
+
+ DbConnection conn = null;
+ if (_unitOfWork?.Connection == null)
+ {
+ using (conn = _connectionProvider.Create())
+ {
+ await conn.OpenAsync();
+
+ return await selectFunction(conn);
+ }
+ }
+ else
+ {
+ conn = _unitOfWork.CreateOrGetConnection();
+
+ return await selectFunction(conn);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex, extraMessage: $"GetByUserIdPagedAsync UserId: {userId}");
+
+ throw;
+ }
+ }
+
+ public async Task> GetByDepartmentIdPagedAsync(int departmentId, DateTime startDate, DateTime endDate, int page, int pageSize)
+ {
+ try
+ {
+ var selectFunction = new Func>>(async x =>
+ {
+ var dynamicParameters = new DynamicParametersExtension();
+ dynamicParameters.Add("DepartmentId", departmentId);
+ dynamicParameters.Add("StartDate", startDate);
+ dynamicParameters.Add("EndDate", endDate);
+ dynamicParameters.Add("Offset", (page - 1) * pageSize);
+ dynamicParameters.Add("PageSize", pageSize);
+
+ var query = _queryFactory.GetQuery();
+
+ return await x.QueryAsync(sql: query,
+ param: dynamicParameters,
+ transaction: _unitOfWork.Transaction);
+ });
+
+ DbConnection conn = null;
+ if (_unitOfWork?.Connection == null)
+ {
+ using (conn = _connectionProvider.Create())
+ {
+ await conn.OpenAsync();
+
+ return await selectFunction(conn);
+ }
+ }
+ else
+ {
+ conn = _unitOfWork.CreateOrGetConnection();
+
+ return await selectFunction(conn);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logging.LogException(ex, extraMessage: $"GetByDepartmentIdPagedAsync DepartmentId: {departmentId}");
+
+ throw;
+ }
+ }
}
}
diff --git a/Web/Resgrid.Web.Services/Controllers/v4/ContactVerificationController.cs b/Web/Resgrid.Web.Services/Controllers/v4/ContactVerificationController.cs
index a58ccdfc7..943c1ce3f 100644
--- a/Web/Resgrid.Web.Services/Controllers/v4/ContactVerificationController.cs
+++ b/Web/Resgrid.Web.Services/Controllers/v4/ContactVerificationController.cs
@@ -4,6 +4,7 @@
using Microsoft.AspNetCore.Mvc;
using Resgrid.Model;
using Resgrid.Model.Services;
+using Resgrid.Web.Services.Helpers;
using Resgrid.Web.Services.Models.v4.ContactVerification;
namespace Resgrid.Web.Services.Controllers.v4
@@ -87,7 +88,9 @@ public async Task> ConfirmVerificati
if (!ModelState.IsValid)
return BadRequest();
- string ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString();
+ // Use the X-Forwarded-For aware helper so the audit log records the real client IP
+ // rather than the reverse-proxy / load-balancer address.
+ string ipAddress = IpAddressHelper.GetRequestIP(Request, true);
bool confirmed = await _contactVerificationService.ConfirmVerificationCodeAsync(
UserId, DepartmentId, model.Type, model.Code, ipAddress, cancellationToken);
diff --git a/Web/Resgrid.Web/Areas/User/Controllers/HomeController.cs b/Web/Resgrid.Web/Areas/User/Controllers/HomeController.cs
index fd7cde02d..576d0c283 100644
--- a/Web/Resgrid.Web/Areas/User/Controllers/HomeController.cs
+++ b/Web/Resgrid.Web/Areas/User/Controllers/HomeController.cs
@@ -1076,7 +1076,9 @@ public async Task ConfirmContactVerificationCode([FromBody] Confi
if (request == null || string.IsNullOrWhiteSpace(request.Code))
return BadRequest();
- string ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString();
+ // Use the X-Forwarded-For aware helper so the audit log records the real client IP
+ // rather than the reverse-proxy / load-balancer address.
+ string ipAddress = IpAddressHelper.GetRequestIP(Request, true);
bool confirmed = await _contactVerificationService.ConfirmVerificationCodeAsync(UserId, DepartmentId, request.Type, request.Code, ipAddress, cancellationToken);
return Json(new { success = confirmed });
diff --git a/Web/Resgrid.Web/Areas/User/Controllers/InventoryController.cs b/Web/Resgrid.Web/Areas/User/Controllers/InventoryController.cs
index f5a30f4fe..a852983fc 100644
--- a/Web/Resgrid.Web/Areas/User/Controllers/InventoryController.cs
+++ b/Web/Resgrid.Web/Areas/User/Controllers/InventoryController.cs
@@ -3,6 +3,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Localization;
using Resgrid.Model;
using Resgrid.Model.Services;
using Resgrid.Web.Areas.User.Models.Inventory;
@@ -21,14 +22,19 @@ public class InventoryController : SecureBaseController
private readonly IUnitsService _unitsService;
private readonly IDepartmentsService _departmentsService;
private readonly IUserProfileService _userProfileService;
+ private readonly IStringLocalizer _localizer;
+ private readonly IStringLocalizer _commonLocalizer;
- public InventoryController(IInventoryService inventoryService, IDepartmentGroupsService departmentGroupsService, IUnitsService unitsService, IDepartmentsService departmentsService, IUserProfileService userProfileService)
+ public InventoryController(IInventoryService inventoryService, IDepartmentGroupsService departmentGroupsService, IUnitsService unitsService, IDepartmentsService departmentsService, IUserProfileService userProfileService,
+ IStringLocalizer localizer, IStringLocalizer commonLocalizer)
{
_inventoryService = inventoryService;
_departmentGroupsService = departmentGroupsService;
_unitsService = unitsService;
_departmentsService = departmentsService;
_userProfileService = userProfileService;
+ _localizer = localizer;
+ _commonLocalizer = commonLocalizer;
}
#endregion Private Members and Constructors
@@ -71,7 +77,7 @@ public async Task Adjust()
public async Task Adjust(AdjustView model)
{
if (model.Inventory.Amount == 0)
- ModelState.AddModelError("Inventory.Amount", "You must supply a non-zero inventory adjustment (count/amount).");
+ ModelState.AddModelError("Inventory.Amount", _localizer["AdjustmentAmountRequired"]);
if (ModelState.IsValid)
{
@@ -114,7 +120,7 @@ public async Task ViewEntry(int inventoryId)
if (profile != null)
model.Name = profile.FullName.AsFirstNameLastName;
else
- model.Name = "Unknown";
+ model.Name = _commonLocalizer["Unknown"];
return View(model);
}
@@ -230,7 +236,7 @@ public async Task GetCombinedInventoryList()
if (item.Unit != null)
inventory.Unit = item.Unit.Name;
else
- inventory.Unit = "No Unit";
+ inventory.Unit = _localizer["NoUnit"];
inventory.Count = item.Amount;
@@ -264,19 +270,19 @@ public async Task GetInventoryList()
if (item.Unit != null)
inventory.Unit = item.Unit.Name;
else
- inventory.Unit = "No Unit";
+ inventory.Unit = _localizer["NoUnit"];
if (item.Group != null)
inventory.Group = item.Group.Name;
else
- inventory.Group = "No Group";
+ inventory.Group = _localizer["NoGroup"];
var name = names.FirstOrDefault(x => x.UserId == item.AddedByUserId);
if (name != null)
inventory.UserName = name.Name;
else
- inventory.UserName = "Unknown";
+ inventory.UserName = _commonLocalizer["Unknown"];
inventoryJson.Add(inventory);
@@ -284,5 +290,55 @@ public async Task GetInventoryList()
return Json(inventoryJson);
}
+
+ [Authorize(Policy = ResgridResources.Inventory_View)]
+ public async Task ByUnit()
+ {
+ return View();
+ }
+
+ [HttpGet]
+ [Authorize(Policy = ResgridResources.Inventory_View)]
+ public async Task GetInventoryByUnitList()
+ {
+ List inventoryJson = new List();
+
+ var department = await _departmentsService.GetDepartmentByIdAsync(DepartmentId);
+ var items = await _inventoryService.GetAllTransactionsForDepartmentAsync(DepartmentId);
+ var names = await _departmentsService.GetAllPersonnelNamesForDepartmentAsync(DepartmentId);
+
+ foreach (var item in items.Where(x => x.UnitId.HasValue && x.UnitId.Value > 0)
+ .OrderBy(x => x.Unit != null ? x.Unit.Name : string.Empty)
+ .ThenBy(x => x.Type != null ? x.Type.Type : string.Empty))
+ {
+ var inventory = new InventoryJson();
+ inventory.InventoryId = item.InventoryId;
+ inventory.Type = item.Type.Type;
+ inventory.Amount = item.Amount;
+ inventory.Batch = item.Batch;
+ inventory.Timestamp = item.TimeStamp.FormatForDepartment(department);
+
+ if (item.Unit != null)
+ inventory.Unit = item.Unit.Name;
+ else
+ inventory.Unit = _localizer["NoUnit"];
+
+ if (item.Group != null)
+ inventory.Group = item.Group.Name;
+ else
+ inventory.Group = _localizer["NoGroup"];
+
+ var name = names.FirstOrDefault(x => x.UserId == item.AddedByUserId);
+
+ if (name != null)
+ inventory.UserName = name.Name;
+ else
+ inventory.UserName = _commonLocalizer["Unknown"];
+
+ inventoryJson.Add(inventory);
+ }
+
+ return Json(inventoryJson);
+ }
}
}
diff --git a/Web/Resgrid.Web/Areas/User/Controllers/ShiftsController.cs b/Web/Resgrid.Web/Areas/User/Controllers/ShiftsController.cs
index 673748322..f8e144022 100644
--- a/Web/Resgrid.Web/Areas/User/Controllers/ShiftsController.cs
+++ b/Web/Resgrid.Web/Areas/User/Controllers/ShiftsController.cs
@@ -960,21 +960,21 @@ public async Task GetShiftCalendarItems()
DateTime startResult;
if (!String.IsNullOrWhiteSpace(shift.StartTime) && DateTime.TryParse(shift.StartTime, out startResult))
{
- item.Start = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, startResult.Hour, startResult.Minute, 0, DateTimeKind.Local);
+ item.Start = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, startResult.Hour, startResult.Minute, 0, DateTimeKind.Unspecified);
}
else
{
- item.Start = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, 0, 0, 0, DateTimeKind.Local);
+ item.Start = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, 0, 0, 0, DateTimeKind.Unspecified);
}
DateTime endResult;
if (!String.IsNullOrWhiteSpace(shift.EndTime) && DateTime.TryParse(shift.EndTime, out endResult))
{
- item.End = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, endResult.Hour, endResult.Minute, 0, DateTimeKind.Local);
+ item.End = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, endResult.Hour, endResult.Minute, 0, DateTimeKind.Unspecified);
}
else
{
- item.End = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, 23, 59, 59, DateTimeKind.Local);
+ item.End = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, 23, 59, 59, DateTimeKind.Unspecified);
}
item.Color = shift.Color;
@@ -1109,21 +1109,21 @@ public async Task GetShiftCalendarItemsForShift(int shiftId)
DateTime startResult;
if (!String.IsNullOrWhiteSpace(shift.StartTime) && DateTime.TryParse(shift.StartTime, out startResult))
{
- item.Start = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, startResult.Hour, startResult.Minute, 0, DateTimeKind.Local);
+ item.Start = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, startResult.Hour, startResult.Minute, 0, DateTimeKind.Unspecified);
}
else
{
- item.Start = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, 0, 0, 0, DateTimeKind.Local);
+ item.Start = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, 0, 0, 0, DateTimeKind.Unspecified);
}
DateTime endResult;
if (!String.IsNullOrWhiteSpace(shift.EndTime) && DateTime.TryParse(shift.EndTime, out endResult))
{
- item.End = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, endResult.Hour, endResult.Minute, 0, DateTimeKind.Local);
+ item.End = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, endResult.Hour, endResult.Minute, 0, DateTimeKind.Unspecified);
}
else
{
- item.End = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, 23, 59, 59, DateTimeKind.Local);
+ item.End = new DateTime(day.Day.Year, day.Day.Month, day.Day.Day, 23, 59, 59, DateTimeKind.Unspecified);
}
item.Title = shift.Name;
diff --git a/Web/Resgrid.Web/Areas/User/Views/Inventory/ByUnit.cshtml b/Web/Resgrid.Web/Areas/User/Views/Inventory/ByUnit.cshtml
new file mode 100644
index 000000000..d8bb96f1d
--- /dev/null
+++ b/Web/Resgrid.Web/Areas/User/Views/Inventory/ByUnit.cshtml
@@ -0,0 +1,60 @@
+@using Resgrid.Model
+@using Resgrid.Web.Helpers
+@inject IStringLocalizer localizer
+@{
+ ViewBag.Title = "Resgrid | " + localizer["InventoryByUnitHeader"];
+ Layout = "~/Areas/User/Views/Shared/_UserLayout.cshtml";
+}
+
+
+
+
+
+
+
+@section Scripts
+{
+
+
+}
diff --git a/Web/Resgrid.Web/Areas/User/Views/Inventory/History.cshtml b/Web/Resgrid.Web/Areas/User/Views/Inventory/History.cshtml
index 9d9c1a71d..3e74fc94c 100644
--- a/Web/Resgrid.Web/Areas/User/Views/Inventory/History.cshtml
+++ b/Web/Resgrid.Web/Areas/User/Views/Inventory/History.cshtml
@@ -49,6 +49,7 @@
type: '@Html.Raw(commonLocalizer["Type"].Value)',
amount: '@Html.Raw(localizer["Amount"].Value)',
group: '@Html.Raw(localizer["Group"].Value)',
+ unit: '@Html.Raw(localizer["Unit"].Value)',
batch: '@Html.Raw(localizer["Batch"].Value)',
timestamp: '@Html.Raw(commonLocalizer["Timestamp"].Value)',
addedBy: '@Html.Raw(localizer["AddedBy"].Value)',
diff --git a/Web/Resgrid.Web/Areas/User/Views/Inventory/Index.cshtml b/Web/Resgrid.Web/Areas/User/Views/Inventory/Index.cshtml
index 2907553e1..802e2d11f 100644
--- a/Web/Resgrid.Web/Areas/User/Views/Inventory/Index.cshtml
+++ b/Web/Resgrid.Web/Areas/User/Views/Inventory/Index.cshtml
@@ -31,6 +31,7 @@
@localizer["AdjustInventory"]
}
@localizer["ViewHistory"]
+ @localizer["InventoryByUnitHeader"]
@if (ClaimsAuthorizationHelper.IsUserDepartmentAdmin())
{
@localizer["ManageTypes"]
diff --git a/Web/Resgrid.Web/wwwroot/_references.js b/Web/Resgrid.Web/wwwroot/_references.js
index 2d2c7d476..d891e448a 100644
--- a/Web/Resgrid.Web/wwwroot/_references.js
+++ b/Web/Resgrid.Web/wwwroot/_references.js
@@ -665,6 +665,7 @@
///
///
///
+///
///
///
///
diff --git a/Web/Resgrid.Web/wwwroot/js/app/internal/inventory/resgrid.inventory.byunit.js b/Web/Resgrid.Web/wwwroot/js/app/internal/inventory/resgrid.inventory.byunit.js
new file mode 100644
index 000000000..0a93dfc69
--- /dev/null
+++ b/Web/Resgrid.Web/wwwroot/js/app/internal/inventory/resgrid.inventory.byunit.js
@@ -0,0 +1,36 @@
+var resgrid;
+(function (resgrid) {
+ var inventory;
+ (function (inventory) {
+ var byunit;
+ (function (byunit) {
+ $(document).ready(function () {
+ resgrid.common.analytics.track('Inventory - By Unit');
+ var strings = typeof inventoryByUnitStrings !== 'undefined' ? inventoryByUnitStrings : {
+ unit: 'Unit', type: 'Type', amount: 'Amount', group: 'Group', batch: 'Batch',
+ timestamp: 'Timestamp', addedBy: 'Added By', actions: 'Actions', view: 'View'
+ };
+ $("#inventoryByUnitList").DataTable({
+ ajax: { url: resgrid.absoluteBaseUrl + '/User/Inventory/GetInventoryByUnitList', dataSrc: '' },
+ pageLength: 50,
+ order: [[0, 'asc']],
+ columns: [
+ { data: 'Unit', title: strings.unit },
+ { data: 'Type', title: strings.type },
+ { data: 'Amount', title: strings.amount },
+ { data: 'Group', title: strings.group },
+ { data: 'Batch', title: strings.batch },
+ { data: 'Timestamp', title: strings.timestamp },
+ { data: 'UserName', title: strings.addedBy },
+ {
+ data: 'InventoryId', title: strings.actions, orderable: false,
+ render: function (data) {
+ return '' + strings.view + '';
+ }
+ }
+ ]
+ });
+ });
+ })(byunit = inventory.byunit || (inventory.byunit = {}));
+ })(inventory = resgrid.inventory || (resgrid.inventory = {}));
+})(resgrid || (resgrid = {}));
diff --git a/Web/Resgrid.Web/wwwroot/js/app/internal/inventory/resgrid.inventory.history.js b/Web/Resgrid.Web/wwwroot/js/app/internal/inventory/resgrid.inventory.history.js
index 5a65a29ee..a559bb2eb 100644
--- a/Web/Resgrid.Web/wwwroot/js/app/internal/inventory/resgrid.inventory.history.js
+++ b/Web/Resgrid.Web/wwwroot/js/app/internal/inventory/resgrid.inventory.history.js
@@ -7,7 +7,7 @@ var resgrid;
$(document).ready(function () {
resgrid.common.analytics.track('Inventory - History');
var strings = typeof inventoryHistoryStrings !== 'undefined' ? inventoryHistoryStrings : {
- type: 'Type', amount: 'Amount', group: 'Group', batch: 'Batch',
+ type: 'Type', amount: 'Amount', group: 'Group', unit: 'Unit', batch: 'Batch',
timestamp: 'Timestamp', addedBy: 'Added By', actions: 'Actions', view: 'View'
};
$("#inventoryList").DataTable({
@@ -17,6 +17,7 @@ var resgrid;
{ data: 'Type', title: strings.type },
{ data: 'Amount', title: strings.amount },
{ data: 'Group', title: strings.group },
+ { data: 'Unit', title: strings.unit },
{ data: 'Batch', title: strings.batch },
{ data: 'Timestamp', title: strings.timestamp },
{ data: 'UserName', title: strings.addedBy },
diff --git a/Workers/Resgrid.Workers.Framework/Logic/AuditQueueLogic.cs b/Workers/Resgrid.Workers.Framework/Logic/AuditQueueLogic.cs
index c3f82e181..417ac8db6 100644
--- a/Workers/Resgrid.Workers.Framework/Logic/AuditQueueLogic.cs
+++ b/Workers/Resgrid.Workers.Framework/Logic/AuditQueueLogic.cs
@@ -778,14 +778,24 @@ public class AuditQueueLogic
} // end switch
+ // Fallback so no audit event is ever silently dropped. Any AuditLogTypes value that
+ // doesn't have an explicit case above (e.g. newly added types) would otherwise leave
+ // Message empty and never be persisted. Build a generic, human-readable message
+ // instead of discarding the event.
+ if (String.IsNullOrWhiteSpace(auditLog.Message))
+ {
+ var auditService = Bootstrapper.GetKernel().Resolve();
+ var actor = profile?.FullName?.AsFirstNameLastName;
+ var action = auditService.GetAuditLogTypeString(auditEvent.Type);
+
+ auditLog.Message = String.IsNullOrWhiteSpace(actor) ? action : $"{actor} - {action}";
+ }
+
if (String.IsNullOrWhiteSpace(auditLog.Data))
auditLog.Data = "No Data";
- if (!String.IsNullOrWhiteSpace(auditLog.Message))
- {
- auditLog.LoggedOn = DateTime.UtcNow;
- await auditLogsRepository.SaveOrUpdateAsync(auditLog, cancellationToken);
- }
+ auditLog.LoggedOn = DateTime.UtcNow;
+ await auditLogsRepository.SaveOrUpdateAsync(auditLog, cancellationToken);
}
catch (Exception ex)
{