updated LogEventTime method to update the last used time in the db. changed LogEventTime method to not store timelogs against unassigned cards. updated GetUnassignedIdentifierList method to populate the Identifier LastUsed property. #56
611 lines
22 KiB
C#
611 lines
22 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using Interfaces;
|
|
using SQLite.Net;
|
|
using SQLite.Net.Platform.Win32;
|
|
|
|
namespace SQLiteRepository
|
|
{
|
|
public class SQLiteRepository : IRepository
|
|
{
|
|
private readonly SQLiteConnection _connection;
|
|
private readonly ILogger _logger;
|
|
private string _path = "flexitimedb.db";
|
|
|
|
public SQLiteRepository(ILogger logger)
|
|
{
|
|
_path =
|
|
new Uri(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().CodeBase), "flexitimedb.db"))
|
|
.LocalPath;
|
|
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
|
_logger = logger;
|
|
|
|
_connection = new SQLiteConnection(new SQLitePlatformWin32(), _path);
|
|
|
|
_connection.CreateTable<CardUniqueId>();
|
|
_connection.CreateTable<UserIdentity>();
|
|
_connection.CreateTable<TimeLogDb>();
|
|
_logger.Trace("Initialised SQLite Repository");
|
|
}
|
|
|
|
public UserList GetUsers(int pageNumber = -1, int pageSize = -1)
|
|
{
|
|
var ret = new UserList();
|
|
List<UserIdentity> users;
|
|
int userCount;
|
|
if (pageNumber == -1 && pageSize == -1)
|
|
{
|
|
users = _connection.Query<UserIdentity>(SQLiteProcedures.GET_ALL_USERS);
|
|
userCount = users.Count;
|
|
}
|
|
else
|
|
{
|
|
users = _connection.Query<UserIdentity>(SQLiteProcedures.GET_ALL_USERS_PAGINATE,
|
|
pageSize, (pageNumber - 1) * pageSize);
|
|
userCount = _connection.ExecuteScalar<int>(SQLiteProcedures.GET_TOTAL_USER_COUNT);
|
|
}
|
|
|
|
if (!users.Any())
|
|
{
|
|
if (pageNumber == -1 && pageSize == -1)
|
|
{
|
|
ret.PageNumber = 1;
|
|
ret.PageSize = 20;
|
|
}
|
|
else
|
|
{
|
|
ret.PageNumber = pageNumber;
|
|
ret.PageSize = pageSize;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
foreach (var user in users)
|
|
{
|
|
var userObj = ChangeToUserObject(user);
|
|
|
|
userObj.AssociatedIdentifiers = GetAssociatedIdentifiers(user.Id);
|
|
userObj.State = GetUserState(GetLogDirection(user.Id));
|
|
userObj.LastEventDateTime = GetLastLogDateTime(GetLastTimeLog(user.Id));
|
|
ret.Users.Add(userObj);
|
|
}
|
|
if (pageNumber == -1 && pageSize == -1)
|
|
{
|
|
ret.PageSize = 10;
|
|
ret.PageNumber = 1;
|
|
}
|
|
else
|
|
{
|
|
ret.PageSize = pageSize;
|
|
ret.PageNumber = pageNumber;
|
|
}
|
|
ret.TotalUserCount = userCount;
|
|
return ret;
|
|
}
|
|
|
|
private DateTime GetLastLogDateTime(TimeLogDb timeLog)
|
|
{
|
|
if (timeLog != null)
|
|
{
|
|
return timeLog.SwipeEventDateTime.DateTime;
|
|
}
|
|
return DateTime.MinValue;
|
|
}
|
|
|
|
private bool GetUserState(LogDirectionDb logDirection)
|
|
{
|
|
switch (logDirection)
|
|
{
|
|
case LogDirectionDb.OUT:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public UserList Search(string searchParam)
|
|
{
|
|
_logger.Trace("Searching SQLite database for the term: {0}", searchParam);
|
|
var ret = new UserList();
|
|
searchParam = string.Format("%{0}%", searchParam);
|
|
var users = _connection.Query<UserIdentity>(
|
|
SQLiteProcedures.SEARCH_USER_LIST,
|
|
searchParam, searchParam);
|
|
|
|
_logger.Trace("Got {0} results for term: {1}", users.Count, searchParam);
|
|
if (!users.Any())
|
|
{
|
|
ret.PageNumber = 1;
|
|
ret.PageSize = 20;
|
|
return ret;
|
|
}
|
|
|
|
foreach (var user in users)
|
|
{
|
|
var userObj = ChangeToUserObject(user);
|
|
var cards = _connection.Query<CardUniqueId>(
|
|
SQLiteProcedures.GET_CARDS_BY_USER_ID,
|
|
user.Id);
|
|
|
|
foreach (var card in cards)
|
|
{
|
|
userObj.AssociatedIdentifiers.Add(new Identifier()
|
|
{
|
|
UniqueId = card.CardUId,
|
|
IsAssociatedToUser = true,
|
|
Id = card.Id
|
|
});
|
|
}
|
|
userObj.State = GetUserState(GetLogDirection(user.Id));
|
|
ret.Users.Add(userObj);
|
|
}
|
|
//TODO: figure out paging here. - should there be any?
|
|
ret.PageSize = 20;
|
|
ret.PageNumber = 1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
public User GetUser(int id)
|
|
{
|
|
var ret = new User();
|
|
|
|
try
|
|
{
|
|
var users = _connection.Query<UserIdentity>(
|
|
SQLiteProcedures.GET_USER_BY_ID,
|
|
id);
|
|
|
|
if (!users.Any()) return ret;
|
|
|
|
var user = users.First();
|
|
ret = ChangeToUserObject(user);
|
|
var cards = _connection.Query<CardUniqueId>(
|
|
SQLiteProcedures.GET_CARDS_BY_USER_ID,
|
|
user.Id);
|
|
|
|
foreach (var card in cards)
|
|
{
|
|
ret.AssociatedIdentifiers.Add(new Identifier { UniqueId = card.CardUId, IsAssociatedToUser = true, Id = card.Id });
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Error(ex, "Error in GetUser with Id: {0}", id);
|
|
ret.UserId = id;
|
|
ret.FirstName = ret.LastName = string.Empty;
|
|
ret.HoursPerWeek = -1.0f;
|
|
ret.IsContractor = false;
|
|
ret.AssociatedIdentifiers.Clear();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
public TimeLogList GetTimeLogs(int userId)
|
|
{
|
|
return GetTimeLogs(userId, DateTime.UtcNow);
|
|
}
|
|
|
|
public TimeLogList GetTimeLogs(int userId, DateTime selectedDate)
|
|
{
|
|
var ret = new TimeLogList { SelectedDate = selectedDate.Date };
|
|
var calendarWeek = GetIso8601CalendarWeek(selectedDate);
|
|
ret.CalendarWeek = calendarWeek;
|
|
|
|
try
|
|
{
|
|
ret.TimeLogs = GetTimeLogList(userId, calendarWeek, selectedDate.Year);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Error(ex, "Error in GetTimeLogs with Id: {0} and selected date: {1}, Exception: {2}", userId, selectedDate, ex);
|
|
}
|
|
|
|
try
|
|
{
|
|
ret.HoursPerWeekMinutes = GetUserContractedHours(userId) * 60.0f;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Error(ex, "Error in GetUserContracterHours with Id: {0} and selected date: {1}, Exception: {2}", userId, selectedDate, ex);
|
|
}
|
|
|
|
ret.UserInformation = GetUser(userId);
|
|
return ret;
|
|
}
|
|
|
|
private float GetUserContractedHours(int userId)
|
|
{
|
|
var hoursQuery = _connection.Query<UserIdentity>(SQLiteProcedures.GET_USER_CONTRACTED_HOURS, userId);
|
|
if (hoursQuery.Any())
|
|
{
|
|
return hoursQuery.First().HoursPerWeek;
|
|
}
|
|
return -1.0f;
|
|
}
|
|
|
|
public IdentifierList GetUnassignedIdentifierList()
|
|
{
|
|
var ret = new IdentifierList();
|
|
var cardQuery = _connection.Query<CardUniqueId>(
|
|
SQLiteProcedures.GET_UNASSIGNED_CARD_LIST,
|
|
Constants.UNASSIGNED_CARD_USER_ID);
|
|
|
|
foreach (var card in cardQuery)
|
|
{
|
|
ret.data.Add(new Identifier
|
|
{
|
|
Id = card.Id,
|
|
IsAssociatedToUser = card.UserId_FK != Constants.UNASSIGNED_CARD_USER_ID,
|
|
UniqueId = card.CardUId,
|
|
LastUsed = card.LastUsed.DateTime
|
|
});
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
//TODO: Check time logs table on update to ensure associated cards/unique identifiers are removed/added as appropriate.
|
|
public OperationResponse UpdateUser(User user, out int userIdResult)
|
|
{
|
|
//if(user.UserId <=0) return OperationResponse.FAILED;
|
|
|
|
var ret = OperationResponse.NONE;
|
|
var cardIds = new List<int>();
|
|
|
|
//Get a list of current associated identifiers, convert into a list of Identifier Objects..
|
|
var currentCards =
|
|
_connection.Query<CardUniqueId>(SQLiteProcedures.GET_CARDS_BY_USER_ID, user.UserId)
|
|
.Select(x => new Identifier { Id = x.Id, IsAssociatedToUser = true, UniqueId = x.CardUId });
|
|
var cardsToRemove = currentCards.Except(user.AssociatedIdentifiers).Select(x => x.Id).ToList();
|
|
|
|
#region GetUnique Identifiers
|
|
foreach (var card in user.AssociatedIdentifiers)
|
|
{
|
|
var existingCard = _connection.Query<CardUniqueId>(
|
|
SQLiteProcedures.GET_CARDS_BY_UNIQUE_ID, card.UniqueId);
|
|
|
|
if (!existingCard.Any())
|
|
{
|
|
var cardInsert = new CardUniqueId { CardUId = card.UniqueId, UserId_FK = -1 };
|
|
_connection.Insert(cardInsert);
|
|
cardIds.Add(cardInsert.Id);
|
|
if (ret < OperationResponse.CREATED)
|
|
ret = OperationResponse.CREATED; //only change it if my status supercedes.
|
|
}
|
|
else
|
|
{
|
|
cardIds.Add(existingCard.First().Id);
|
|
if (ret < OperationResponse.UPDATED)
|
|
ret = OperationResponse.UPDATED;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Update/Create User
|
|
int userId;
|
|
|
|
if (user.UserId != -1)
|
|
{
|
|
//edit..
|
|
_connection.Query<UserIdentity>(
|
|
"update UserIdentity set FirstName=?, LastName=?, HoursPerWeek=?,IsContractor=? where Id=?",
|
|
user.FirstName,
|
|
user.LastName,
|
|
user.HoursPerWeek,
|
|
user.IsContractor,
|
|
user.UserId
|
|
);
|
|
userId = user.UserId;
|
|
}
|
|
else
|
|
{
|
|
var userInsert = new UserIdentity
|
|
{
|
|
FirstName = user.FirstName,
|
|
LastName = user.LastName,
|
|
HoursPerWeek = user.HoursPerWeek,
|
|
IsContractor = user.IsContractor
|
|
};
|
|
_connection.Insert(userInsert);
|
|
userId = userInsert.Id;
|
|
if (ret < OperationResponse.CREATED)
|
|
ret = OperationResponse.CREATED;
|
|
}
|
|
#endregion
|
|
|
|
#region Update Card/Unique Id entries.
|
|
foreach (var cardId in cardIds)
|
|
{
|
|
_connection.Query<CardUniqueId>(
|
|
SQLiteProcedures.UPDATE_CARD_USER_ID,
|
|
userId, cardId);
|
|
}
|
|
foreach (var card in cardsToRemove)
|
|
{
|
|
_connection.Query<CardUniqueId>(
|
|
SQLiteProcedures.UPDATE_CARD_USER_ID,
|
|
-1, card);
|
|
}
|
|
#endregion
|
|
|
|
userIdResult = userId;
|
|
return ret;
|
|
}
|
|
|
|
public OperationResponse LogEventTime(Identifier identifier, out int logId)
|
|
{
|
|
var cardIdQuery = _connection.Query<CardUniqueId>(
|
|
SQLiteProcedures.GET_CARDS_BY_UNIQUE_ID,
|
|
identifier.UniqueId);
|
|
|
|
#region Get/Insert the PK Id Identifier to associate the time log to.
|
|
var ident = new CardUniqueId();
|
|
if (!cardIdQuery.Any())
|
|
{
|
|
//new card, create it!
|
|
ident.UserId_FK = -1;
|
|
ident.CardUId = identifier.UniqueId;
|
|
_connection.Insert(ident);
|
|
UpdateIdentifierLastUsed(DateTimeOffset.UtcNow, ident.Id);
|
|
logId = -1;
|
|
//dont try to log any timelogs against this card, as it is unassigned to a user.
|
|
return OperationResponse.SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
//TODO: log when more than one comes back. should NEVER happen but....
|
|
ident = cardIdQuery.First();
|
|
}
|
|
#endregion
|
|
|
|
// Get The User Direction (are they going in or out)?
|
|
var logDirection = GetLogDirection(ident.UserId_FK);
|
|
|
|
#region Check the user hasnt registered an event in the last few minutes..
|
|
|
|
if (ident.UserId_FK!=-1)
|
|
{ //only check log gap if the card is associated to a user
|
|
var hysteresisThresholdMinutes = Convert.ToInt32(ConfigurationHandler.ConfigurationHandler.GetConfiguration("SwipeTimeGap") ?? "3");
|
|
var threshold = DateTime.UtcNow.AddMinutes(0 - hysteresisThresholdMinutes);
|
|
var logs = _connection.Query<TimeLogDb>(
|
|
SQLiteProcedures.GET_LOGS_IN_LAST_X_MINUTES,
|
|
threshold.Ticks, ident.UserId_FK);
|
|
_logger.Trace("Checking last swipe event gap");
|
|
if (logs.Any())
|
|
{
|
|
_logger.Error("Not logging event for user id: {0}, logged event within TimeGap Threshold of {1}", ident.UserId_FK, threshold);
|
|
logId = -1;
|
|
return OperationResponse.FAILED;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Get the current time (for swiping). and calendar week/year to help recall the data.
|
|
var logTime = DateTime.UtcNow;
|
|
var calendarWeek = GetIso8601CalendarWeek(logTime);
|
|
var year = logTime.Year;
|
|
#endregion
|
|
|
|
var timeLog = new TimeLogDb
|
|
{
|
|
SwipeEventDateTime = DateTime.UtcNow,
|
|
UserId_FK = ident.UserId_FK,
|
|
IdentifierId = ident.Id,
|
|
Direction = logDirection,
|
|
Year = year,
|
|
CalendarWeek = calendarWeek
|
|
};
|
|
|
|
_connection.Insert(timeLog);
|
|
UpdateIdentifierLastUsed(timeLog.SwipeEventDateTime, timeLog.IdentifierId);
|
|
|
|
logId = timeLog.Id;
|
|
|
|
return OperationResponse.SUCCESS;
|
|
}
|
|
|
|
private List<DailyLogs> GetTimeLogList(int userId, int calendarWeek, int year)
|
|
{
|
|
var timeLogList = _connection.Query<TimeLogDb>(
|
|
SQLiteProcedures.GET_TIMELOGS,
|
|
userId, calendarWeek, year);
|
|
|
|
var timeLogs = timeLogList.Select(x => new TimeLog
|
|
{
|
|
Id = x.Id,
|
|
CalendarWeek = x.CalendarWeek,
|
|
Direction = (LogDirection)x.Direction,
|
|
IdentifierId = x.IdentifierId,
|
|
EventTime = x.SwipeEventDateTime,
|
|
UserId = x.UserId_FK,
|
|
Year = x.Year
|
|
}).ToList();
|
|
|
|
var dict = new Dictionary<DayOfWeek, DailyLogs>();
|
|
var logList = new List<DailyLogs>();
|
|
|
|
//make sure each day of the week is accounted for in the dictionary.
|
|
foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek)))
|
|
{
|
|
dict.Add(day, new DailyLogs());
|
|
}
|
|
|
|
//add the logs to the respective day of the week.
|
|
foreach (var log in timeLogs)
|
|
{
|
|
dict[log.EventTime.DayOfWeek].Logs.Add(log);
|
|
}
|
|
var logGroups = timeLogs.GroupBy(x => x.EventTime.DayOfWeek);
|
|
foreach (var group in logGroups)
|
|
{
|
|
var groupLogs = group.ToList();
|
|
var dailyLog = new DailyLogs
|
|
{
|
|
Logs = groupLogs,
|
|
Day = group.Key,
|
|
DayOfWeek = group.Key.ToString()
|
|
};
|
|
dailyLog.DailyTotal = CalculateDailyTotal(dailyLog);
|
|
logList.Add(dailyLog);
|
|
}
|
|
foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek)))
|
|
{
|
|
if (logList.Any(x => x.Day == day)) continue;
|
|
var dailyLog = new DailyLogs {Day = day, DayOfWeek = day.ToString()};
|
|
logList.Add(dailyLog);
|
|
}
|
|
|
|
foreach (var dailyCollection in dict)
|
|
{
|
|
dailyCollection.Value.DailyTotal = CalculateDailyTotal(dailyCollection.Value);
|
|
}
|
|
|
|
return logList.OrderBy(x => ((int) x.Day + 6)%7).ToList();
|
|
}
|
|
|
|
private double CalculateDailyTotal(DailyLogs dailyLogs)
|
|
{
|
|
var totalInTime = TimeSpan.FromSeconds(0);
|
|
var logs = dailyLogs.Logs.OrderBy(x => x.Id).ToArray();
|
|
var totalCalcMax = IsOdd(logs.Length) ? logs.Length - 1 : logs.Length;
|
|
for (int i = 0; i < totalCalcMax; i += 2)
|
|
{
|
|
totalInTime += (logs[i + 1].EventTime - logs[i].EventTime);
|
|
}
|
|
return Math.Round(totalInTime.TotalMinutes, 2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// determines if the number is an odd or even value
|
|
/// </summary>
|
|
/// <param name="value">number to determine is odd or even</param>
|
|
/// <returns>true - number is odd</returns>
|
|
private bool IsOdd(int value)
|
|
{
|
|
return value % 2 != 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the new direction for the user based on previous entry logs in the system.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If the user has not logged in today, the direction will be In.
|
|
/// If the user has logged in already today, the direction will be the opposite of the last
|
|
/// recorded log direction. ("out" if "in", "in" if "out")
|
|
/// </remarks>
|
|
/// <param name="userId">Id of the user to get the log direction of.</param>
|
|
/// <returns><see cref="LogDirectionDb"/> indicating what direction the new log is.</returns>
|
|
private LogDirectionDb GetLogDirection(int userId)
|
|
{
|
|
var logDirection = LogDirectionDb.UNKNOWN;
|
|
if (userId != -1)
|
|
{
|
|
var lastEntry = GetLastTimeLog(userId);
|
|
if (lastEntry != null)
|
|
{
|
|
// See if the datetime retrieved is yesterday. If yesterday, logDirection = true (in)
|
|
if (IsLogDateTimeYesterdayOrOlder(lastEntry.SwipeEventDateTime.DateTime))
|
|
{
|
|
logDirection = LogDirectionDb.IN;
|
|
}
|
|
else
|
|
{
|
|
// we have a time log from today already, so just do the opposite of what we last did!
|
|
if (lastEntry.Direction == LogDirectionDb.IN)
|
|
logDirection = LogDirectionDb.OUT;
|
|
else if (lastEntry.Direction == LogDirectionDb.OUT)
|
|
logDirection = LogDirectionDb.IN;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//assume its the first then!
|
|
logDirection = LogDirectionDb.IN;
|
|
}
|
|
}
|
|
return logDirection;
|
|
}
|
|
|
|
private TimeLogDb GetLastTimeLog(int userId)
|
|
{
|
|
var lastEntry = _connection.Query<TimeLogDb>(
|
|
SQLiteProcedures.GET_LAST_TIMELOG_DIRECTION,
|
|
userId);
|
|
if (lastEntry.Any())
|
|
{
|
|
return lastEntry.First();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void UpdateIdentifierLastUsed(DateTimeOffset dt, int cardId)
|
|
{
|
|
var res = _connection.ExecuteScalar<CardUniqueId>("update CardUniqueId set LastUsed = ? where Id = ?",
|
|
dt,
|
|
cardId);
|
|
}
|
|
|
|
private List<Identifier> GetAssociatedIdentifiers(int userId)
|
|
{
|
|
var cards = _connection.Query<CardUniqueId>(
|
|
SQLiteProcedures.GET_CARDS_BY_USER_ID,
|
|
userId);
|
|
var ret = new List<Identifier>();
|
|
foreach (var card in cards)
|
|
{
|
|
ret.Add(new Identifier()
|
|
{
|
|
UniqueId = card.CardUId,
|
|
IsAssociatedToUser = true,
|
|
Id = card.Id
|
|
});
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the calendar week of the year according to the ISO8601 standard (starts monday).
|
|
/// </summary>
|
|
/// <param name="date">the date to get the calendar week of.</param>
|
|
/// <returns>the calendar week of the year in integer form (1-52)</returns>
|
|
private int GetIso8601CalendarWeek(DateTime date)
|
|
{
|
|
var day = CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(date);
|
|
if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday)
|
|
{
|
|
date = date.AddDays(3);
|
|
}
|
|
return CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(date, CalendarWeekRule.FirstFourDayWeek,
|
|
DayOfWeek.Monday);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check whether the specified DateTime is from yesterday or older.
|
|
/// </summary>
|
|
/// <param name="dt">the DateTime object to check is yesterday or older</param>
|
|
/// <returns>true - is yesterday or older.</returns>
|
|
private bool IsLogDateTimeYesterdayOrOlder(DateTime dt)
|
|
{
|
|
return dt.Date.CompareTo(DateTime.Today.Date) < 0;
|
|
}
|
|
|
|
private User ChangeToUserObject(UserIdentity user)
|
|
{
|
|
return new User
|
|
{
|
|
UserId = user.Id,
|
|
FirstName = user.FirstName,
|
|
LastName = user.LastName,
|
|
HoursPerWeek = user.HoursPerWeek,
|
|
IsContractor = user.IsContractor
|
|
};
|
|
}
|
|
}
|
|
}
|