diff --git a/Library/EmployeeState.cs b/Library/EmployeeState.cs index 713c40b..a5fc30b 100644 --- a/Library/EmployeeState.cs +++ b/Library/EmployeeState.cs @@ -2,7 +2,7 @@ public enum EmployeeState { - WORKING, + OUT, + IN, BREAK, - AWAY, } \ No newline at end of file diff --git a/Library/Server.cs b/Library/Server.cs index bbb6d70..88a0366 100644 --- a/Library/Server.cs +++ b/Library/Server.cs @@ -193,7 +193,7 @@ public class Server return Task.FromException(new Exception("Failed to get logged in employee.")); } - public Task clockIn(Employee employee) + public Task ClockIn(Employee? employee = null) { var commandResult = ExecuteCommandAsync("clock in").Result; if (commandResult == "success") @@ -202,7 +202,7 @@ public class Server } return Task.FromException(new Exception("Failed to clock in.")); } - public Task clockOut(Employee employee) + public Task ClockOut(Employee? employee = null) { var commandResult = ExecuteCommandAsync("clock out").Result; if (commandResult == "success") @@ -214,7 +214,7 @@ public class Server return Task.FromException(new Exception("Failed to clock out.")); } } - public Task clockBreak(Employee employee) + public Task ClockBreak(Employee? employee = null) { var commandResult = ExecuteCommandAsync("clock break").Result; if (commandResult == "success") diff --git a/Server/BackendServer.cs b/Server/BackendServer.cs index 3a702fe..bd08066 100644 --- a/Server/BackendServer.cs +++ b/Server/BackendServer.cs @@ -43,7 +43,7 @@ public class BackendServer : DX86.TcpServer JsonMessage? jsonMessage; if (message.StartsWith("{") && message.EndsWith("}")) { - this.SendMessageAsync(client, "Command received\n"); + //this.SendMessageAsync(client, "Command received\n"); try { jsonMessage = JsonSerializer.Deserialize(message); diff --git a/Server/Commands/CommandLibrary.cs b/Server/Commands/CommandLibrary.cs index 2570015..6c8344d 100644 --- a/Server/Commands/CommandLibrary.cs +++ b/Server/Commands/CommandLibrary.cs @@ -170,6 +170,123 @@ public class CommandLibrary return self.ToJson(); } + [Command("clock")] + public static string ClockCommand(string[] args, TcpClient? client, TcpServer? socket) + { + // 1) Argument check + if (args.Length < 1) + throw new CommandException("Missing argument: usage is clock "); + + string desired = args[0].ToLowerInvariant(); + int desiredState; + switch (desired) + { + case "in": + desiredState = 1; + break; + case "out": + desiredState = 0; + break; + case "break": + desiredState = 2; + break; + default: + throw new CommandException("Invalid argument. Use in, out, or break."); + } + + // 2) Verify client/socket + if (client == null || socket == null) + throw new CommandException("No client connection detected."); + + if (!socket.LoggedInClients.TryGetValue(client, out var employeeCode)) + throw new CommandException("User not logged in."); + + // 3) Look up the employee by Code to get Id and current state + var empParams = new Dictionary { { "Code", employeeCode } }; + string empResultJson = Program.mySql.Get("employees", empParams); + + using var empDoc = JsonDocument.Parse(empResultJson); + bool empError = empDoc.RootElement.GetProperty("error").GetBoolean(); + if (empError) + { + string dbMsg = empDoc.RootElement.GetProperty("data").GetString() ?? "Unknown DB error"; + Program.messageSender.Error($"[ClockCommand] DB error when looking up employee: {dbMsg}"); + throw new CommandException("Internal error while fetching employee data."); + } + + var empArray = empDoc.RootElement.GetProperty("data"); + if (empArray.GetArrayLength() == 0) + throw new CommandException("Logged-in user not found in database."); + + var empRow = empArray[0]; + int employeeId = empRow.GetProperty("Id").GetInt32(); + int currentState = empRow.GetProperty("EmployeeState").GetInt32(); + + // 4) Validate state transition + if (currentState == desiredState) + throw new CommandException($"Already {(desiredState == 0 ? "OUT" : desiredState == 1 ? "IN" : "on BREAK")}."); + + bool valid = false; + switch (currentState) + { + case 0: // OUT → only IN + valid = (desiredState == 1); + break; + case 1: // IN → OUT or BREAK + valid = (desiredState == 0 || desiredState == 2); + break; + case 2: // BREAK → only IN + valid = (desiredState == 1); + break; + } + if (!valid) + throw new CommandException("Invalid transition from current status."); + + // 5) Update employees.EmployeeState to desiredState + var updateData = new Dictionary + { + { "EmployeeState", desiredState } + }; + var whereData = new Dictionary + { + { "Id", employeeId } + }; + string updateJson = Program.mySql.Update("employees", updateData, whereData); + + using (var updateDoc = JsonDocument.Parse(updateJson)) + { + bool updError = updateDoc.RootElement.GetProperty("error").GetBoolean(); + if (updError) + { + string dbMsg = updateDoc.RootElement.GetProperty("data").GetString() ?? "Unknown DB error"; + Program.messageSender.Error($"[ClockCommand] DB error when updating state: {dbMsg}"); + throw new CommandException("Internal error while updating status."); + } + } + + // 6) Insert into employee_state_history + var historyData = new Dictionary + { + { "EmployeeId", employeeId }, + { "NewState", desiredState } + }; + string historyJson = Program.mySql.Insert("employee_state_history", historyData); + + using (var histDoc = JsonDocument.Parse(historyJson)) + { + bool histError = histDoc.RootElement.GetProperty("error").GetBoolean(); + if (histError) + { + string dbMsg = histDoc.RootElement.GetProperty("data").GetString() ?? "Unknown DB error"; + Program.messageSender.Error($"[ClockCommand] DB error when logging history: {dbMsg}"); + throw new CommandException("Internal error while logging status change."); + } + } + + // 7) Return success + return "success"; + } + #endregion #region Administration Commands diff --git a/Server/Program.cs b/Server/Program.cs index 0206b79..26d629d 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -31,7 +31,8 @@ namespace Server; ServerLoop(); } - private static MySQL LoadOrInitDatabase() + #region MySQL Initialization + private static MySQL LoadOrInitDatabase() { var cfg = DbConfig.Load(); if (cfg != null) @@ -54,107 +55,143 @@ namespace Server; return InitializeDatabase(username, password, server, port, database); } } - - /// - /// Creates a new MySQL connection, runs the CREATE TABLE DDL, - /// saves credentials in config.json, and returns the MySQL instance. - /// -private static MySQL InitializeDatabase( - string username, - string password, - string server, - string port, - string database -) -{ - // 1) Instantiate MySQL (will exit on failure) - var db = new MySQL(username, password, server, port, database, messageSender); - string createEmployeesTable = @" - CREATE TABLE IF NOT EXISTS `employees` ( - `Id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, - `Code` VARCHAR(50) NOT NULL, - `Surname` VARCHAR(100) NOT NULL, - `Forename` VARCHAR(100) NOT NULL, - `Email` VARCHAR(150) NOT NULL, - `Phone` VARCHAR(50), - `Street` VARCHAR(200), - `City` VARCHAR(100), - `Postcode` VARCHAR(20), - `Country` VARCHAR(100), - `Department` VARCHAR(100), - `Position` VARCHAR(100), - `EmployeeState` INT NOT NULL - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - "; - - messageSender.Log("[Program] Creating or verifying 'employees' table…"); - var employeesResult = db.ExecuteNonQuery(createEmployeesTable); - try - { - using var doc = JsonDocument.Parse(employeesResult); - bool empError = doc.RootElement.GetProperty("error").GetBoolean(); - if (empError) + /// + /// Creates a new MySQL connection, runs the CREATE TABLE DDL, + /// saves credentials in config.json, and returns the MySQL instance. + /// + private static MySQL InitializeDatabase( + string username, + string password, + string server, + string port, + string database + ) { - messageSender.Error("[Program] Failed to create/verify 'employees' table: " + employeesResult); - Environment.Exit(1); + // 1) Instantiate MySQL (will exit on failure) + var db = new MySQL(username, password, server, port, database, messageSender); + string createEmployeesTable = @" + CREATE TABLE IF NOT EXISTS `employees` ( + `Id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `Code` VARCHAR(50) NOT NULL, + `Surname` VARCHAR(100) NOT NULL, + `Forename` VARCHAR(100) NOT NULL, + `Email` VARCHAR(150) NOT NULL, + `Phone` VARCHAR(50), + `Street` VARCHAR(200), + `City` VARCHAR(100), + `Postcode` VARCHAR(20), + `Country` VARCHAR(100), + `Department` VARCHAR(100), + `Position` VARCHAR(100), + `EmployeeState` INT NOT NULL + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + "; + + messageSender.Log("[Program] Creating or verifying 'employees' table…"); + var employeesResult = db.ExecuteNonQuery(createEmployeesTable); + try + { + using var doc = JsonDocument.Parse(employeesResult); + bool empError = doc.RootElement.GetProperty("error").GetBoolean(); + if (empError) + { + messageSender.Error("[Program] Failed to create/verify 'employees' table: " + employeesResult); + Environment.Exit(1); + } + } + catch (Exception ex) + { + messageSender.Error("[Program] Could not parse employees CREATE TABLE response: " + ex.Message); + messageSender.Error("[Program] Raw response: " + employeesResult); + Environment.Exit(1); + } + messageSender.Log("[Program] 'employees' table is ready."); + + // 2b) Create a new table for storing 4-digit PINs linked to EmployeeId + string createPinsTable = @" + CREATE TABLE IF NOT EXISTS `employee_pins` ( + `PinId` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `EmployeeId` INT NOT NULL, + `PinCode` CHAR(4) NOT NULL, + FOREIGN KEY (`EmployeeId`) REFERENCES `employees`(`Id`) + ON DELETE CASCADE + ON UPDATE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + "; + + messageSender.Log("[Program] Creating or verifying 'employee_pins' table…"); + var pinsResult = db.ExecuteNonQuery(createPinsTable); + try + { + using var doc2 = JsonDocument.Parse(pinsResult); + bool pinError = doc2.RootElement.GetProperty("error").GetBoolean(); + if (pinError) + { + messageSender.Error("[Program] Failed to create/verify 'employee_pins' table: " + pinsResult); + Environment.Exit(1); + } + } + catch (Exception ex) + { + messageSender.Error("[Program] Could not parse employee_pins CREATE TABLE response: " + ex.Message); + messageSender.Error("[Program] Raw response: " + pinsResult); + Environment.Exit(1); + } + messageSender.Log("[Program] 'employee_pins' table is ready."); + + // 3) Neue Tabelle für Status‐Historie anlegen: + string createStateHistoryTable = @" + CREATE TABLE IF NOT EXISTS `employee_state_history` ( + `HistoryId` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `EmployeeId` INT NOT NULL, + `NewState` INT NOT NULL, + `ChangeTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (`EmployeeId`) + REFERENCES `employees`(`Id`) + ON DELETE CASCADE + ON UPDATE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + "; + + Program.messageSender.Log("[Program] Ensuring 'employee_state_history' table exists…"); + var historyResultJson = db.ExecuteNonQuery(createStateHistoryTable); + try + { + using var doc = JsonDocument.Parse(historyResultJson); + bool histError = doc.RootElement.GetProperty("error").GetBoolean(); + if (histError) + { + string dbMsg = doc.RootElement.GetProperty("data").GetString() ?? "Unknown DB error"; + Program.messageSender.Error("[Program] Failed to create 'employee_state_history' table: " + dbMsg); + Environment.Exit(1); + } + } + catch (Exception ex) + { + Program.messageSender.Error("[Program] Could not parse state history CREATE TABLE response: " + ex.Message); + Program.messageSender.Error("[Program] Raw response: " + historyResultJson); + Environment.Exit(1); + } + Program.messageSender.Log("[Program] 'employee_state_history' table is ready."); + + // … (any further tables) … + + // 3) Save the new credentials (including port) into config.json: + var newCfg = new DbConfig + { + Username = username, + Password = password, + Server = server, + Port = port, + Database = database + }; + newCfg.Save(); + messageSender.Log("[Program] Saved DB configuration to config.json."); + + return db; } - } - catch (Exception ex) - { - messageSender.Error("[Program] Could not parse employees CREATE TABLE response: " + ex.Message); - messageSender.Error("[Program] Raw response: " + employeesResult); - Environment.Exit(1); - } - messageSender.Log("[Program] 'employees' table is ready."); - // 2b) Create a new table for storing 4-digit PINs linked to EmployeeId - string createPinsTable = @" - CREATE TABLE IF NOT EXISTS `employee_pins` ( - `PinId` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, - `EmployeeId` INT NOT NULL, - `PinCode` CHAR(4) NOT NULL, - FOREIGN KEY (`EmployeeId`) REFERENCES `employees`(`Id`) - ON DELETE CASCADE - ON UPDATE CASCADE - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - "; - - messageSender.Log("[Program] Creating or verifying 'employee_pins' table…"); - var pinsResult = db.ExecuteNonQuery(createPinsTable); - try - { - using var doc2 = JsonDocument.Parse(pinsResult); - bool pinError = doc2.RootElement.GetProperty("error").GetBoolean(); - if (pinError) - { - messageSender.Error("[Program] Failed to create/verify 'employee_pins' table: " + pinsResult); - Environment.Exit(1); - } - } - catch (Exception ex) - { - messageSender.Error("[Program] Could not parse employee_pins CREATE TABLE response: " + ex.Message); - messageSender.Error("[Program] Raw response: " + pinsResult); - Environment.Exit(1); - } - messageSender.Log("[Program] 'employee_pins' table is ready."); - - // … (any further tables) … - - // 3) Save the new credentials (including port) into config.json: - var newCfg = new DbConfig - { - Username = username, - Password = password, - Server = server, - Port = port, - Database = database - }; - newCfg.Save(); - messageSender.Log("[Program] Saved DB configuration to config.json."); - - return db; -} + #endregion private static void ServerLoop() { diff --git a/TestClient/Program.cs b/TestClient/Program.cs index a7a76aa..f1c1a02 100644 --- a/TestClient/Program.cs +++ b/TestClient/Program.cs @@ -81,6 +81,27 @@ public class Tester $"Forename: {employee.Forename}", }); } + else if (command == "clock in") + { + string response = await server.ClockIn(); + // split response into string[] at \n + var responseLines = response.Split('\n').ToList(); + await messageBox.ShowAsync("Command Result: clock in", responseLines); + } + else if (command == "clock out") + { + string response = await server.ClockOut(); + // split response into string[] at \n + var responseLines = response.Split('\n').ToList(); + await messageBox.ShowAsync("Command Result: clock out", responseLines); + } + else if (command == "clock break") + { + string response = await server.ClockBreak(); + // split response into string[] at \n + var responseLines = response.Split('\n').ToList(); + await messageBox.ShowAsync("Command Result: clock break", responseLines); + } } catch (Exception ex) {