From 1943895eec154e6749a2a54b19a8426bc8c6eb60 Mon Sep 17 00:00:00 2001 From: "chris.watts90@outlook.com" Date: Wed, 11 Sep 2019 17:37:26 +0100 Subject: [PATCH] Add support for card reader to provide its own date time. #103 --- .../Interfaces/IRepository.cs | 230 +- .../SQLiteRepository/SQLiteRepository.cs | 1870 +++++++++-------- .../WindowsDataCenter/CardData.cs | 15 +- 3 files changed, 1066 insertions(+), 1049 deletions(-) diff --git a/DataCenter_Windows/WindowsDataCenter/Interfaces/IRepository.cs b/DataCenter_Windows/WindowsDataCenter/Interfaces/IRepository.cs index e5e4f59..63402ab 100644 --- a/DataCenter_Windows/WindowsDataCenter/Interfaces/IRepository.cs +++ b/DataCenter_Windows/WindowsDataCenter/Interfaces/IRepository.cs @@ -1,114 +1,116 @@ -using System; -using System.Collections.Generic; - -namespace Interfaces -{ - public interface IRepository - { - /// - /// Get a list of Users - /// - /// - /// Returns with full list of users, - /// plus a total user count. Pagination options are supported. - /// - UserList GetUsers(int pageNumber = -1, int pageSize = -1, int groupId = -1); - /// - /// Search the user list for the following string - /// - /// string to search the user store for. - /// - /// Returns with full list of users, - /// plus a total user count. Pagination options are supported. - /// - UserList Search(string searchParam); - /// - /// Get details about a single user in the system base on their Id - /// - /// - /// integer data type, the Id of the User to get details about. - /// - /// object with full details, - /// including full - /// - User GetUser(int id); - /// - /// Get a list of the timelogs available for the specified user - /// for the current Calendar Week - /// - /// - /// integer data type, the Id of the user to get time logs for - /// - /// - /// with nested objects - /// for the current calendar week - /// - TimeLogList GetTimeLogs(int userId); - /// - /// Get a list of the timelogs available for the specified user - /// for the specified calendar week - /// - /// - /// integer data type, the Id of the user to get time logs for - /// - /// - /// datetime data type, the date to receive time logs for (will scope out to calendar week). - /// - /// - /// with nested objects - /// for the current calendar week - /// - TimeLogList GetTimeLogs(int userId, DateTime selectedDate); - /// - /// Get a full list of Identifiers which are not associated to a user. - /// - /// - /// with nested list - /// - IdentifierList GetUnassignedIdentifierList(); - /// - /// Remove all unassigned identifiers from the system. - /// - void ClearUnassignedIdentifiers(); - - /// - /// Update a user in the system with the new values. - /// - /// - /// If the user object passed does not exist, it will be created. - /// - /// - /// object detailing the new properties to assign to the user. - /// The Id Field should not be changed, or should be -1 for new users. - /// - /// - /// int - ref param, value will be the UserId of the inserted or updated user - /// - /// - /// to indicate procedure status. - /// - OperationResponse UpdateUser(User user, out int userId); - - /// - /// Create a new TimeLog Event in the repository. - /// - /// - /// object with the Unique Id triggering the event - /// - /// The resultant Id of the inserted TimeLog - /// - /// to indicate procedure status. - /// - LogEventResponse LogEventTime(Identifier identifier, out int logId); - - OperationResponse CreateGroup(Group group, out int groupId); - List GetGroups(int userId = -1); - Group GetGroup(int groupId); - OperationResponse UpdateGroup(Group group); - OperationResponse DeleteGroup(int groupId); - - OperationResponse DeleteLog(TimeLog log); - OperationResponse CreateLog(TimeLog log); - OperationResponse UpdateLog(TimeLog log); - } -} +using System; +using System.Collections.Generic; + +namespace Interfaces +{ + public interface IRepository + { + /// + /// Get a list of Users + /// + /// + /// Returns with full list of users, + /// plus a total user count. Pagination options are supported. + /// + UserList GetUsers(int pageNumber = -1, int pageSize = -1, int groupId = -1); + /// + /// Search the user list for the following string + /// + /// string to search the user store for. + /// + /// Returns with full list of users, + /// plus a total user count. Pagination options are supported. + /// + UserList Search(string searchParam); + /// + /// Get details about a single user in the system base on their Id + /// + /// + /// integer data type, the Id of the User to get details about. + /// + /// object with full details, + /// including full + /// + User GetUser(int id); + /// + /// Get a list of the timelogs available for the specified user + /// for the current Calendar Week + /// + /// + /// integer data type, the Id of the user to get time logs for + /// + /// + /// with nested objects + /// for the current calendar week + /// + TimeLogList GetTimeLogs(int userId); + /// + /// Get a list of the timelogs available for the specified user + /// for the specified calendar week + /// + /// + /// integer data type, the Id of the user to get time logs for + /// + /// + /// datetime data type, the date to receive time logs for (will scope out to calendar week). + /// + /// + /// with nested objects + /// for the current calendar week + /// + TimeLogList GetTimeLogs(int userId, DateTime selectedDate); + /// + /// Get a full list of Identifiers which are not associated to a user. + /// + /// + /// with nested list + /// + IdentifierList GetUnassignedIdentifierList(); + /// + /// Remove all unassigned identifiers from the system. + /// + void ClearUnassignedIdentifiers(); + + /// + /// Update a user in the system with the new values. + /// + /// + /// If the user object passed does not exist, it will be created. + /// + /// + /// object detailing the new properties to assign to the user. + /// The Id Field should not be changed, or should be -1 for new users. + /// + /// + /// int - ref param, value will be the UserId of the inserted or updated user + /// + /// + /// to indicate procedure status. + /// + OperationResponse UpdateUser(User user, out int userId); + + /// + /// Create a new TimeLog Event in the repository. + /// + /// + /// object with the Unique Id triggering the event + /// + /// The resultant Id of the inserted TimeLog + /// Optional - To set the log time of the swipe. + /// Particularly useful for buffering logs in nodes if they cannot contact server for whatever reason to minimise data loss + /// + /// to indicate procedure status. + /// + LogEventResponse LogEventTime(Identifier identifier, out int logId, DateTime logTime = default(DateTime)); + + OperationResponse CreateGroup(Group group, out int groupId); + List GetGroups(int userId = -1); + Group GetGroup(int groupId); + OperationResponse UpdateGroup(Group group); + OperationResponse DeleteGroup(int groupId); + + OperationResponse DeleteLog(TimeLog log); + OperationResponse CreateLog(TimeLog log); + OperationResponse UpdateLog(TimeLog log); + } +} diff --git a/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteRepository.cs b/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteRepository.cs index 89c510b..d260a81 100644 --- a/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteRepository.cs +++ b/DataCenter_Windows/WindowsDataCenter/SQLiteRepository/SQLiteRepository.cs @@ -1,929 +1,941 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Reflection; -using Interfaces; -using SQLite.Net; -using SQLite.Net.Platform.Win32; -using SQLiteRepository.Properties; - -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(); - _connection.CreateTable(); - _connection.CreateTable(); - _connection.CreateTable(); - _connection.CreateTable(); - _connection.CreateTable(); - _logger.Trace("Initialised SQLite Repository"); - _logger.Trace("Checking For Upgrades"); - CheckForDbUpgrade(); - } - - private void CheckForDbUpgrade() - { - var data = _connection.Query("select * from DbVersion"); - if (!data.Any()) - { - //Pre-Upgrade database, need upgrading - _logger.Trace("Pre version 0.2 RC database found, performing update.."); - ExecuteUpgradeFromVersion("0.1"); //execute 0.2 upgrade scripts onwards. - } - else - { - var installedVersion = new Version(data.First().VersionNumber); - var currentVersion = new Version(AssemblyInfo.ASSEMBLY_VERSION); - if (currentVersion.CompareTo(installedVersion) > 0) //greater than 0 - current version is newer - { - _logger.Trace("Installed Database Version: {0} is older than current version {1}"); - ExecuteUpgradeFromVersion(installedVersion.ToString()); - } - } - } - - private void ExecuteUpgradeFromVersion(string installedVersion) - { - const string PREFIX = "SQLiteRepository.UpgradeScripts."; - var instVers = new Version(installedVersion); - //so we have established that each script for upgrade will be .sql, so now start at lowest version and work up! - var assembly = Assembly.GetExecutingAssembly(); - var upgradeScripts = assembly.GetManifestResourceNames() - .Where(x=>x.StartsWith(PREFIX)) - .OrderBy(x=>new Version(Path.GetFileNameWithoutExtension(x.Replace(PREFIX, "")))) - .Where(x => - { - var scriptVersion = new Version(Path.GetFileNameWithoutExtension(x.Replace(PREFIX, ""))).CompareTo(instVers); - return scriptVersion > 0; - } - ) - .ToList(); - //now have an ordered list of upgrade script files, so execute in order! - foreach (var upgradeScript in upgradeScripts) - { - using (var stream = assembly.GetManifestResourceStream(upgradeScript)) - using(var str = new StreamReader(stream)) - { - var script = str.ReadToEnd(); - _logger.Trace("Executing upgrade script with name: {0}", upgradeScript); - _connection.Execute(script); - } - } - SetDbVersion(AssemblyInfo.ASSEMBLY_VERSION); - } - - private void SetDbVersion(string vers) - { - _connection.DeleteAll(); - _connection.Insert(new DbVersion {VersionNumber = vers}); - _logger.Trace("Set Database version to: {0}", vers); - } - - public UserList GetUsers(int pageNumber = -1, int pageSize = -1, int groupId = -1) - { - var ret = new UserList(); - List users; - int userCount; - if (pageNumber != -1 && pageSize != -1) - { - users = _connection.Query(SQLiteProcedures.GET_ALL_USERS_PAGINATE, - pageSize, (pageNumber - 1) * pageSize); - userCount = _connection.ExecuteScalar(SQLiteProcedures.GET_TOTAL_USER_COUNT); - } - else if (groupId != -1) - { - users = - _connection.Query( - SQLiteProcedures.GET_ALL_USERS_BY_GROUP, - groupId); - userCount = users.Count; - } - else - { - users = _connection.Query(SQLiteProcedures.GET_ALL_USERS); - userCount = users.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)); - userObj.Groups = GetGroups(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; - } - - 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( - 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( - 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( - SQLiteProcedures.GET_USER_BY_ID, - id); - - if (!users.Any()) return ret; - - var user = users.First(); - ret = ChangeToUserObject(user); - ret.Groups = GetGroups(); - var usersGroups = GetGroups(user.Id); - foreach (var group in usersGroups) - { - ret.Groups.First(x => x.Id == group.Id).IsAssociatedToUser = true; - } - var cards = _connection.Query( - 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(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( - 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; - } - - public void ClearUnassignedIdentifiers() - { - _connection.Execute( - SQLiteProcedures.CLEAR_UNASSIGNED_CARDS, - Constants.UNASSIGNED_CARD_USER_ID); - } - - //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(); - - //Get a list of current associated identifiers, convert into a list of Identifier Objects.. - var currentCards = - _connection.Query(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( - 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( - SQLiteProcedures.UPDATE_USER_DETAILS, - 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( - SQLiteProcedures.UPDATE_CARD_USER_ID, - userId, cardId); - } - foreach (var card in cardsToRemove) - { - _connection.Query( - SQLiteProcedures.UPDATE_CARD_USER_ID, - -1, card); - } - #endregion - - #region Update Group Associations - - SetUserGroups(userId, user.Groups.Where(x => x.IsAssociatedToUser).ToList()); - - #endregion - - userIdResult = userId; - return ret; - } - - public LogEventResponse LogEventTime(Identifier identifier, out int logId) - { - var ret = new LogEventResponse(); - var cardIdQuery = _connection.Query( - 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. - ret.ProcessResponse=OperationResponse.SUCCESS; - ret.Direction = LogDirection.UNKNOWN; - return ret; - } - 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( - 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; - ret.ProcessResponse = OperationResponse.FAILED; - ret.Direction = LogDirection.UNKNOWN; - return ret; - } - } - - #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, - Source = LogSourceDb.IDENTIFIER - }; - - _connection.Insert(timeLog); - UpdateIdentifierLastUsed(timeLog.SwipeEventDateTime, timeLog.IdentifierId); - - logId = timeLog.Id; - ret.Direction = (LogDirection)(int)logDirection; - ret.ProcessResponse = OperationResponse.SUCCESS; - return ret; - } - - /*Groups*/ - //TODO: check group name can only be entered once. - public OperationResponse CreateGroup(Group group, out int groupId) - { - var groupDb = new GroupDb { GroupName = group.Name }; - var resp = _connection.Insert(groupDb); - groupId = groupDb.GroupId; - return OperationResponse.CREATED; - } - - public List GetGroups(int userId = -1) - { - var ret = new List(); - List query; - if (userId == -1) - { - query = _connection.Query("select gp.GroupId, gp.GroupName, " + - "sum(case when gp.GroupId = ujdb.GroupId_FK then 1 else 0 end) as AssignedUserCount " + - "from GroupDb gp " + - "left join UserGroupJoinDb ujdb " + - "on ujdb.GroupId_FK = gp.GroupId " + - "group by gp.GroupId"); - } - else - { - query = - _connection.Query( - "select gdb.GroupId, gdb.GroupName, gdb.AssignedUserCount" + - " from GroupDb gdb" + - " left join UserGroupJoinDb ujdb" + - " on gdb.GroupId = ujdb.GroupId_FK" + - " where ujdb.UserId_FK = ?", - userId); - } - foreach (var group in query) - { - ret.Add(new Group - { - Id = group.GroupId, - Name = group.GroupName, - UserCount = int.Parse(group.AssignedUserCount ?? "0") - }); - } - return ret; - } - - public Group GetGroup(int groupId) - { - var query = _connection.Query("select * from GroupDb where GroupId = ?", groupId); - if (query.Any()) - { - var group = query.First(); - return new Group - { - Id = group.GroupId, - Name = group.GroupName, - UserCount = 0 - }; - } - return new Group(); - } - - public OperationResponse UpdateGroup(Group group) - { - //TODO: I would probably prefer to do this manually.... - var resp = _connection.Query("update GroupDb set GroupName=? where GroupId=?", group.Name, group.Id); - - return OperationResponse.UPDATED; - } - - public OperationResponse DeleteGroup(int groupId) - { - _connection.Delete(groupId); - return OperationResponse.DELETED; - } - - public OperationResponse DeleteLog(TimeLog log) - { - var query = _connection.Query( - "select * from TimeLogDb where Id=?", log.Id); - - if (!query.Any()) - return OperationResponse.FAILED; - - UpdateExistingLogDirections(log); - - _connection.ExecuteScalar("delete from TimeLogDb where Id=?", log.Id); - - return OperationResponse.DELETED; - } - - public OperationResponse CreateLog(TimeLog log) - { - var calendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime); - var year = log.EventTime.Year; - log.CalendarWeek = calendarWeek; - log.Year = year; - var dbLog = new TimeLogDb - { - SwipeEventDateTime = log.EventTime, - Direction = (LogDirectionDb)(int)log.Direction, - Year = year, - CalendarWeek = calendarWeek, - Source = (LogSourceDb)(int)log.Source, - UserId_FK = log.UserId, - IdentifierId = ConvertSourceToIdentifierId(log.Source), - }; - #region update in/out directions for manual logs. - UpdateExistingLogDirections(log); - #endregion - //and now insert the new log. - _connection.Insert(dbLog); - return OperationResponse.CREATED; - } - - public OperationResponse UpdateLog(TimeLog log) - { - var query = _connection.Query( - "select * from TimeLogDb where Id=?", log.Id); - if(!query.Any()) - return OperationResponse.FAILED; - - if (log.CalendarWeek > 52 || log.CalendarWeek < 1) - { - log.CalendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime); - } - if (log.Year < 2017) - { - log.Year = log.EventTime.Year; - } - _connection.ExecuteScalar( - "update TimeLogDb set UserId_FK=?, Direction=?,SwipeEventDateTime=?,CalendarWeek=?,Year=?,Source=? where Id=?", - log.UserId, (LogDirectionDb) (int) log.Direction, log.EventTime, log.CalendarWeek, log.Year, - (LogSourceDb) (int) log.Source, log.Id); - - return OperationResponse.UPDATED; - } - - 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; - } - } - - private int ConvertSourceToIdentifierId(LogSource logSource) - { - switch (logSource) - { - case LogSource.UI: - return -100; - case LogSource.TRAYAPP: - return -200; - default: - return -10; - } - } - - private bool SetUserGroups(int userId, List groups) - { - var groupIds = GetGroupIds(groups.Select(x => x.Name).ToList()); - return SetUserGroups(userId, groupIds); - } - - private bool SetUserGroups(int userId, int[] groupIds) - { - //remove the existing user>group associations - _connection.Query("delete from UserGroupJoinDb where UserId_FK = ?", userId); - //add the new group associations. - _connection.InsertAll(groupIds.Select(x => new UserGroupJoinDb { GroupId_FK = x, UserId_FK = userId })); - return true; - } - - private int[] GetGroupIds(List groupNames) - { - var ret = new List(); - - foreach (var g in groupNames) - { - var query = _connection.Query("select GroupId from GroupDb where GroupName=?", g); - if (!query.Any()) continue; - var id = query.First(); - ret.Add(id.GroupId); - } - return ret.ToArray(); - } - - private List GetTimeLogList(int userId, int calendarWeek, int year) - { - var timeLogList = _connection.Query( - 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 - }).OrderBy(x=>x.EventTime.UtcDateTime).ToList(); - - var dict = new Dictionary(); - var logList = new List(); - - //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.OrderBy(x=>x.EventTime)) - { - 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.EventTime.UtcDateTime).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); - } - - /// - /// determines if the number is an odd or even value - /// - /// number to determine is odd or even - /// true - number is odd - private bool IsOdd(int value) - { - return value % 2 != 0; - } - - /// - /// Get the new direction for the user based on previous entry logs in the system. - /// - /// - /// 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") - /// - /// Id of the user to get the log direction of. - /// indicating what direction the new log is. - 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! - logDirection = InvertLogDirectionDb(lastEntry.Direction); - } - } - else - { - //assume its the first then! - logDirection = LogDirectionDb.IN; - } - } - return logDirection; - } - - private TimeLogDb GetLastTimeLog(int userId) - { - var lastEntry = _connection.Query( - 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(SQLiteProcedures.UPDATE_CARD_LAST_USED, - dt, - cardId); - } - - private List GetAssociatedIdentifiers(int userId) - { - var cards = _connection.Query( - SQLiteProcedures.GET_CARDS_BY_USER_ID, - userId); - var ret = new List(); - foreach (var card in cards) - { - ret.Add(new Identifier() - { - UniqueId = card.CardUId, - IsAssociatedToUser = true, - Id = card.Id - }); - } - return ret; - } - - /// - /// Get the calendar week of the year according to the ISO8601 standard (starts monday). - /// - /// the date to get the calendar week of. - /// the calendar week of the year in integer form (1-52) - 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); - } - - /// - /// Check whether the specified DateTime is from yesterday or older. - /// - /// the DateTime object to check is yesterday or older - /// true - is yesterday or older. - 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 - }; - } - - private void UpdateExistingLogDirections(TimeLog log) - { - //need to make this generic so that both create and delete will update the log directions.. but ARGH. - //if you want to use this as a delete, you would delete and call this, if you want to create, call this then create. - //should look at how to improve this method so that it will compensate for if the log hasnt been deleted yet, but ott? - var weekLogs = GetTimeLogList(log.UserId, log.CalendarWeek, log.Year); - - //Get the same logs of the day that the log has been entered for - var todaysLogs = weekLogs.FirstOrDefault(x => x.Day == log.EventTime.DayOfWeek); - if (todaysLogs != null) - { - //Get the days logs that are after the manually created date - var logs = todaysLogs.Logs.Where(x => x.EventTime.CompareTo(log.EventTime) >= 0).OrderBy(x => x.EventTime).ToList(); - //Update each log with the inverse progressively - var currentlogDirection = log.Direction; - for (var i = 0; i < logs.Count; i++) - { - logs[i].Direction = InvertLogDirection(currentlogDirection); - UpdateLog(logs[i]); - currentlogDirection = logs[i].Direction; - } - } - } - - private LogDirectionDb InvertLogDirectionDb(LogDirectionDb direction) - { - return (LogDirectionDb) (int) InvertLogDirection((LogDirection) (int) direction); - } - - private LogDirection InvertLogDirection(LogDirection direction) - { - switch (direction) - { - case LogDirection.IN: - return LogDirection.OUT; - case LogDirection.OUT: - return LogDirection.IN; - default: - return LogDirection.UNKNOWN; - } - } - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using Interfaces; +using SQLite.Net; +using SQLite.Net.Platform.Win32; +using SQLiteRepository.Properties; + +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(); + _connection.CreateTable(); + _connection.CreateTable(); + _connection.CreateTable(); + _connection.CreateTable(); + _connection.CreateTable(); + _logger.Trace("Initialised SQLite Repository"); + _logger.Trace("Checking For Upgrades"); + CheckForDbUpgrade(); + } + + private void CheckForDbUpgrade() + { + var data = _connection.Query("select * from DbVersion"); + if (!data.Any()) + { + //Pre-Upgrade database, need upgrading + _logger.Trace("Pre version 0.2 RC database found, performing update.."); + ExecuteUpgradeFromVersion("0.1"); //execute 0.2 upgrade scripts onwards. + } + else + { + var installedVersion = new Version(data.First().VersionNumber); + var currentVersion = new Version(AssemblyInfo.ASSEMBLY_VERSION); + if (currentVersion.CompareTo(installedVersion) > 0) //greater than 0 - current version is newer + { + _logger.Trace("Installed Database Version: {0} is older than current version {1}"); + ExecuteUpgradeFromVersion(installedVersion.ToString()); + } + } + } + + private void ExecuteUpgradeFromVersion(string installedVersion) + { + const string PREFIX = "SQLiteRepository.UpgradeScripts."; + var instVers = new Version(installedVersion); + //so we have established that each script for upgrade will be .sql, so now start at lowest version and work up! + var assembly = Assembly.GetExecutingAssembly(); + var upgradeScripts = assembly.GetManifestResourceNames() + .Where(x=>x.StartsWith(PREFIX)) + .OrderBy(x=>new Version(Path.GetFileNameWithoutExtension(x.Replace(PREFIX, "")))) + .Where(x => + { + var scriptVersion = new Version(Path.GetFileNameWithoutExtension(x.Replace(PREFIX, ""))).CompareTo(instVers); + return scriptVersion > 0; + } + ) + .ToList(); + //now have an ordered list of upgrade script files, so execute in order! + foreach (var upgradeScript in upgradeScripts) + { + using (var stream = assembly.GetManifestResourceStream(upgradeScript)) + using(var str = new StreamReader(stream)) + { + var script = str.ReadToEnd(); + _logger.Trace("Executing upgrade script with name: {0}", upgradeScript); + _connection.Execute(script); + } + } + SetDbVersion(AssemblyInfo.ASSEMBLY_VERSION); + } + + private void SetDbVersion(string vers) + { + _connection.DeleteAll(); + _connection.Insert(new DbVersion {VersionNumber = vers}); + _logger.Trace("Set Database version to: {0}", vers); + } + + public UserList GetUsers(int pageNumber = -1, int pageSize = -1, int groupId = -1) + { + var ret = new UserList(); + List users; + int userCount; + if (pageNumber != -1 && pageSize != -1) + { + users = _connection.Query(SQLiteProcedures.GET_ALL_USERS_PAGINATE, + pageSize, (pageNumber - 1) * pageSize); + userCount = _connection.ExecuteScalar(SQLiteProcedures.GET_TOTAL_USER_COUNT); + } + else if (groupId != -1) + { + users = + _connection.Query( + SQLiteProcedures.GET_ALL_USERS_BY_GROUP, + groupId); + userCount = users.Count; + } + else + { + users = _connection.Query(SQLiteProcedures.GET_ALL_USERS); + userCount = users.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)); + userObj.Groups = GetGroups(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; + } + + 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( + 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( + 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( + SQLiteProcedures.GET_USER_BY_ID, + id); + + if (!users.Any()) return ret; + + var user = users.First(); + ret = ChangeToUserObject(user); + ret.Groups = GetGroups(); + var usersGroups = GetGroups(user.Id); + foreach (var group in usersGroups) + { + ret.Groups.First(x => x.Id == group.Id).IsAssociatedToUser = true; + } + var cards = _connection.Query( + 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(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( + 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; + } + + public void ClearUnassignedIdentifiers() + { + _connection.Execute( + SQLiteProcedures.CLEAR_UNASSIGNED_CARDS, + Constants.UNASSIGNED_CARD_USER_ID); + } + + //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(); + + //Get a list of current associated identifiers, convert into a list of Identifier Objects.. + var currentCards = + _connection.Query(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( + 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( + SQLiteProcedures.UPDATE_USER_DETAILS, + 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( + SQLiteProcedures.UPDATE_CARD_USER_ID, + userId, cardId); + } + foreach (var card in cardsToRemove) + { + _connection.Query( + SQLiteProcedures.UPDATE_CARD_USER_ID, + -1, card); + } + #endregion + + #region Update Group Associations + + SetUserGroups(userId, user.Groups.Where(x => x.IsAssociatedToUser).ToList()); + + #endregion + + userIdResult = userId; + return ret; + } + + public LogEventResponse LogEventTime(Identifier identifier, out int logId, DateTime logTime = default(DateTime)) + { + #region Set the LogTime before we start querying anything. + if (logTime == default(DateTime)) + { + logTime = DateTime.UtcNow; + _logger.Debug("Using own log time: {0}", logTime.ToString("o")); + } + else + { + _logger.Debug("Using supplied log time: {0}", logTime.ToString("o")); + } + #endregion + + var ret = new LogEventResponse(); + var cardIdQuery = _connection.Query( + 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. + ret.ProcessResponse=OperationResponse.SUCCESS; + ret.Direction = LogDirection.UNKNOWN; + return ret; + } + 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( + 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; + ret.ProcessResponse = OperationResponse.FAILED; + ret.Direction = LogDirection.UNKNOWN; + return ret; + } + } + + #endregion + + #region Get the current time (for swiping). and calendar week/year to help recall the data. + + 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, + Source = LogSourceDb.IDENTIFIER + }; + + _connection.Insert(timeLog); + UpdateIdentifierLastUsed(timeLog.SwipeEventDateTime, timeLog.IdentifierId); + + logId = timeLog.Id; + ret.Direction = (LogDirection)(int)logDirection; + ret.ProcessResponse = OperationResponse.SUCCESS; + return ret; + } + + /*Groups*/ + //TODO: check group name can only be entered once. + public OperationResponse CreateGroup(Group group, out int groupId) + { + var groupDb = new GroupDb { GroupName = group.Name }; + var resp = _connection.Insert(groupDb); + groupId = groupDb.GroupId; + return OperationResponse.CREATED; + } + + public List GetGroups(int userId = -1) + { + var ret = new List(); + List query; + if (userId == -1) + { + query = _connection.Query("select gp.GroupId, gp.GroupName, " + + "sum(case when gp.GroupId = ujdb.GroupId_FK then 1 else 0 end) as AssignedUserCount " + + "from GroupDb gp " + + "left join UserGroupJoinDb ujdb " + + "on ujdb.GroupId_FK = gp.GroupId " + + "group by gp.GroupId"); + } + else + { + query = + _connection.Query( + "select gdb.GroupId, gdb.GroupName, gdb.AssignedUserCount" + + " from GroupDb gdb" + + " left join UserGroupJoinDb ujdb" + + " on gdb.GroupId = ujdb.GroupId_FK" + + " where ujdb.UserId_FK = ?", + userId); + } + foreach (var group in query) + { + ret.Add(new Group + { + Id = group.GroupId, + Name = group.GroupName, + UserCount = int.Parse(group.AssignedUserCount ?? "0") + }); + } + return ret; + } + + public Group GetGroup(int groupId) + { + var query = _connection.Query("select * from GroupDb where GroupId = ?", groupId); + if (query.Any()) + { + var group = query.First(); + return new Group + { + Id = group.GroupId, + Name = group.GroupName, + UserCount = 0 + }; + } + return new Group(); + } + + public OperationResponse UpdateGroup(Group group) + { + //TODO: I would probably prefer to do this manually.... + var resp = _connection.Query("update GroupDb set GroupName=? where GroupId=?", group.Name, group.Id); + + return OperationResponse.UPDATED; + } + + public OperationResponse DeleteGroup(int groupId) + { + _connection.Delete(groupId); + return OperationResponse.DELETED; + } + + public OperationResponse DeleteLog(TimeLog log) + { + var query = _connection.Query( + "select * from TimeLogDb where Id=?", log.Id); + + if (!query.Any()) + return OperationResponse.FAILED; + + UpdateExistingLogDirections(log); + + _connection.ExecuteScalar("delete from TimeLogDb where Id=?", log.Id); + + return OperationResponse.DELETED; + } + + public OperationResponse CreateLog(TimeLog log) + { + var calendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime); + var year = log.EventTime.Year; + log.CalendarWeek = calendarWeek; + log.Year = year; + var dbLog = new TimeLogDb + { + SwipeEventDateTime = log.EventTime, + Direction = (LogDirectionDb)(int)log.Direction, + Year = year, + CalendarWeek = calendarWeek, + Source = (LogSourceDb)(int)log.Source, + UserId_FK = log.UserId, + IdentifierId = ConvertSourceToIdentifierId(log.Source), + }; + #region update in/out directions for manual logs. + UpdateExistingLogDirections(log); + #endregion + //and now insert the new log. + _connection.Insert(dbLog); + return OperationResponse.CREATED; + } + + public OperationResponse UpdateLog(TimeLog log) + { + var query = _connection.Query( + "select * from TimeLogDb where Id=?", log.Id); + if(!query.Any()) + return OperationResponse.FAILED; + + if (log.CalendarWeek > 52 || log.CalendarWeek < 1) + { + log.CalendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime); + } + if (log.Year < 2017) + { + log.Year = log.EventTime.Year; + } + _connection.ExecuteScalar( + "update TimeLogDb set UserId_FK=?, Direction=?,SwipeEventDateTime=?,CalendarWeek=?,Year=?,Source=? where Id=?", + log.UserId, (LogDirectionDb) (int) log.Direction, log.EventTime, log.CalendarWeek, log.Year, + (LogSourceDb) (int) log.Source, log.Id); + + return OperationResponse.UPDATED; + } + + 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; + } + } + + private int ConvertSourceToIdentifierId(LogSource logSource) + { + switch (logSource) + { + case LogSource.UI: + return -100; + case LogSource.TRAYAPP: + return -200; + default: + return -10; + } + } + + private bool SetUserGroups(int userId, List groups) + { + var groupIds = GetGroupIds(groups.Select(x => x.Name).ToList()); + return SetUserGroups(userId, groupIds); + } + + private bool SetUserGroups(int userId, int[] groupIds) + { + //remove the existing user>group associations + _connection.Query("delete from UserGroupJoinDb where UserId_FK = ?", userId); + //add the new group associations. + _connection.InsertAll(groupIds.Select(x => new UserGroupJoinDb { GroupId_FK = x, UserId_FK = userId })); + return true; + } + + private int[] GetGroupIds(List groupNames) + { + var ret = new List(); + + foreach (var g in groupNames) + { + var query = _connection.Query("select GroupId from GroupDb where GroupName=?", g); + if (!query.Any()) continue; + var id = query.First(); + ret.Add(id.GroupId); + } + return ret.ToArray(); + } + + private List GetTimeLogList(int userId, int calendarWeek, int year) + { + var timeLogList = _connection.Query( + 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 + }).OrderBy(x=>x.EventTime.UtcDateTime).ToList(); + + var dict = new Dictionary(); + var logList = new List(); + + //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.OrderBy(x=>x.EventTime)) + { + 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.EventTime.UtcDateTime).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); + } + + /// + /// determines if the number is an odd or even value + /// + /// number to determine is odd or even + /// true - number is odd + private bool IsOdd(int value) + { + return value % 2 != 0; + } + + /// + /// Get the new direction for the user based on previous entry logs in the system. + /// + /// + /// 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") + /// + /// Id of the user to get the log direction of. + /// indicating what direction the new log is. + 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! + logDirection = InvertLogDirectionDb(lastEntry.Direction); + } + } + else + { + //assume its the first then! + logDirection = LogDirectionDb.IN; + } + } + return logDirection; + } + + private TimeLogDb GetLastTimeLog(int userId) + { + var lastEntry = _connection.Query( + 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(SQLiteProcedures.UPDATE_CARD_LAST_USED, + dt, + cardId); + } + + private List GetAssociatedIdentifiers(int userId) + { + var cards = _connection.Query( + SQLiteProcedures.GET_CARDS_BY_USER_ID, + userId); + var ret = new List(); + foreach (var card in cards) + { + ret.Add(new Identifier() + { + UniqueId = card.CardUId, + IsAssociatedToUser = true, + Id = card.Id + }); + } + return ret; + } + + /// + /// Get the calendar week of the year according to the ISO8601 standard (starts monday). + /// + /// the date to get the calendar week of. + /// the calendar week of the year in integer form (1-52) + 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); + } + + /// + /// Check whether the specified DateTime is from yesterday or older. + /// + /// the DateTime object to check is yesterday or older + /// true - is yesterday or older. + 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 + }; + } + + private void UpdateExistingLogDirections(TimeLog log) + { + //need to make this generic so that both create and delete will update the log directions.. but ARGH. + //if you want to use this as a delete, you would delete and call this, if you want to create, call this then create. + //should look at how to improve this method so that it will compensate for if the log hasnt been deleted yet, but ott? + var weekLogs = GetTimeLogList(log.UserId, log.CalendarWeek, log.Year); + + //Get the same logs of the day that the log has been entered for + var todaysLogs = weekLogs.FirstOrDefault(x => x.Day == log.EventTime.DayOfWeek); + if (todaysLogs != null) + { + //Get the days logs that are after the manually created date + var logs = todaysLogs.Logs.Where(x => x.EventTime.CompareTo(log.EventTime) >= 0).OrderBy(x => x.EventTime).ToList(); + //Update each log with the inverse progressively + var currentlogDirection = log.Direction; + for (var i = 0; i < logs.Count; i++) + { + logs[i].Direction = InvertLogDirection(currentlogDirection); + UpdateLog(logs[i]); + currentlogDirection = logs[i].Direction; + } + } + } + + private LogDirectionDb InvertLogDirectionDb(LogDirectionDb direction) + { + return (LogDirectionDb) (int) InvertLogDirection((LogDirection) (int) direction); + } + + private LogDirection InvertLogDirection(LogDirection direction) + { + switch (direction) + { + case LogDirection.IN: + return LogDirection.OUT; + case LogDirection.OUT: + return LogDirection.IN; + default: + return LogDirection.UNKNOWN; + } + } + } +} diff --git a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/CardData.cs b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/CardData.cs index eee79f0..a019bea 100644 --- a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/CardData.cs +++ b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/CardData.cs @@ -1,7 +1,10 @@ -namespace WindowsDataCenter -{ - public class CardData - { - public string CardUId { get; set; } - } +using System; + +namespace WindowsDataCenter +{ + public class CardData + { + public DateTime? UtcTimeStamp { get; set; } + public string CardUId { get; set; } + } } \ No newline at end of file