using System; using System.Collections.Generic; using MySql.Data.MySqlClient; namespace HikvisionAttendanceService.Data; internal sealed class MySqlAttendanceMachineRepository : IAttendanceMachineRepository { private readonly IHrmsDbConnectionFactory _factory; public MySqlAttendanceMachineRepository(IHrmsDbConnectionFactory factory) { _factory = factory; } public List GetActiveMachines(string? machineTypeFilter, out string error) { error = ""; var rows = new List(); if (!_factory.TryBuildConnectionString(out var cs, out error)) return rows; try { using var conn = new MySqlConnection(cs); conn.Open(); string sql = "SELECT machine_id,machine_ip,port_number,machine_name,site_id,machine_status,machine_type,status,last_sync_date,total_users " + "FROM attendance_machine WHERE (machine_status='active' OR machine_status='1' OR machine_status=1)"; if (!string.IsNullOrWhiteSpace(machineTypeFilter)) sql += " AND machine_type=@machineType"; using var cmd = new MySqlCommand(sql, conn); if (!string.IsNullOrWhiteSpace(machineTypeFilter)) cmd.Parameters.AddWithValue("@machineType", machineTypeFilter); using var rd = cmd.ExecuteReader(); while (rd.Read()) { rows.Add(new AttendanceMachineRow { MachineId = rd["machine_id"]?.ToString() ?? "", MachineIp = rd["machine_ip"]?.ToString() ?? "", PortNumber = rd["port_number"] == DBNull.Value ? 8000 : Convert.ToInt32(rd["port_number"]), MachineName = rd["machine_name"]?.ToString() ?? "", SiteId = rd["site_id"] == DBNull.Value ? (int?)null : Convert.ToInt32(rd["site_id"]), MachineStatus = rd["machine_status"]?.ToString() ?? "", MachineType = rd["machine_type"]?.ToString() ?? "", Status = rd["status"]?.ToString() ?? "", LastSyncDate = rd["last_sync_date"] == DBNull.Value ? (DateTime?)null : Convert.ToDateTime(rd["last_sync_date"]), TotalUsers = rd["total_users"] == DBNull.Value ? 0 : Convert.ToInt32(rd["total_users"]) }); } } catch (Exception ex) { error = ex.Message; } return rows; } public void UpdateMachineSyncState(string machineIp, string status, DateTime? lastSyncDate, int? totalUsers, out string error) { error = ""; if (!_factory.TryBuildConnectionString(out var cs, out error)) return; try { using var conn = new MySqlConnection(cs); conn.Open(); using var cmd = new MySqlCommand( "UPDATE attendance_machine SET status=@status,last_sync_date=COALESCE(@last_sync_date,last_sync_date),total_users=COALESCE(@total_users,total_users) WHERE machine_ip=@machine_ip", conn); cmd.Parameters.AddWithValue("@status", status ?? ""); cmd.Parameters.AddWithValue("@last_sync_date", (object?)lastSyncDate ?? DBNull.Value); cmd.Parameters.AddWithValue("@total_users", (object?)totalUsers ?? DBNull.Value); cmd.Parameters.AddWithValue("@machine_ip", machineIp ?? ""); cmd.ExecuteNonQuery(); } catch (Exception ex) { error = ex.Message; } } public bool TryGetMachineLastSyncDate(string machineIp, out DateTime? lastSyncDate, out string error) { lastSyncDate = null; error = ""; if (!_factory.TryBuildConnectionString(out var cs, out error)) return false; try { using var conn = new MySqlConnection(cs); conn.Open(); using var cmd = new MySqlCommand( "SELECT last_sync_date FROM attendance_machine WHERE machine_ip=@machine_ip LIMIT 1", conn); cmd.Parameters.AddWithValue("@machine_ip", machineIp ?? ""); var obj = cmd.ExecuteScalar(); if (obj == null || obj == DBNull.Value) return true; lastSyncDate = Convert.ToDateTime(obj); return true; } catch (Exception ex) { error = ex.Message; return false; } } public bool UpdateMachineLastSyncDate(string machineIp, DateTime lastSyncDate, out string error) { error = ""; if (!_factory.TryBuildConnectionString(out var cs, out error)) return false; try { using var conn = new MySqlConnection(cs); conn.Open(); using var cmd = new MySqlCommand( "UPDATE attendance_machine SET last_sync_date=@last_sync_date WHERE machine_ip=@machine_ip", conn); cmd.Parameters.AddWithValue("@last_sync_date", lastSyncDate); cmd.Parameters.AddWithValue("@machine_ip", machineIp ?? ""); cmd.ExecuteNonQuery(); return true; } catch (Exception ex) { error = ex.Message; return false; } } } internal sealed class MySqlAttendanceLogRepository : IAttendanceLogRepository { private readonly IHrmsDbConnectionFactory _factory; public MySqlAttendanceLogRepository(IHrmsDbConnectionFactory factory) { _factory = factory; } public bool UpsertAttendance(string acNo, DateTime checkTime, int processed, string machineId, int inOutTypeId, string machineIp, DateTime dateOnly, out string error) { error = ""; if (!_factory.TryBuildConnectionString(out var cs, out error)) return false; try { using var conn = new MySqlConnection(cs); conn.Open(); using var cmd = new MySqlCommand( "REPLACE INTO attendance_log (ac_no,checktime,processed,machine_id,in_out_type_id,machine_ip,date) " + "VALUES (@ac_no,@checktime,@processed,@machine_id,@in_out_type_id,@machine_ip,@date)", conn); cmd.Parameters.AddWithValue("@ac_no", acNo ?? ""); cmd.Parameters.AddWithValue("@checktime", checkTime); cmd.Parameters.AddWithValue("@processed", processed); cmd.Parameters.AddWithValue("@machine_id", machineId ?? ""); cmd.Parameters.AddWithValue("@in_out_type_id", inOutTypeId); cmd.Parameters.AddWithValue("@machine_ip", machineIp ?? ""); cmd.Parameters.AddWithValue("@date", dateOnly.Date); cmd.ExecuteNonQuery(); return true; } catch (Exception ex) { error = ex.Message; return false; } } } internal sealed class MySqlAttendanceMachineUserRepository : IAttendanceMachineUserRepository { private readonly IHrmsDbConnectionFactory _factory; public MySqlAttendanceMachineUserRepository(IHrmsDbConnectionFactory factory) { _factory = factory; } public List GetActiveUsersByMachine(string machineId, out string error) => QueryUsers(machineId, false, out error); public List GetPendingDeletionUsersByMachine(string machineId, out string error) => QueryUsers(machineId, true, out error); private List QueryUsers(string machineId, bool deletionRequested, out string error) { error = ""; var rows = new List(); if (!_factory.TryBuildConnectionString(out var cs, out error)) return rows; try { using var conn = new MySqlConnection(cs); conn.Open(); using var cmd = new MySqlCommand( "SELECT machine_id,serial_number,employee_name,is_deleted,is_deletion_requested FROM attendance_machine_user " + "WHERE machine_id=@machine_id AND is_deleted=0 AND is_deletion_requested=@is_deletion_requested", conn); cmd.Parameters.AddWithValue("@machine_id", machineId ?? ""); cmd.Parameters.AddWithValue("@is_deletion_requested", deletionRequested ? 1 : 0); using var rd = cmd.ExecuteReader(); while (rd.Read()) { rows.Add(new AttendanceMachineUserRow { MachineId = rd["machine_id"]?.ToString() ?? "", SerialNumber = rd["serial_number"]?.ToString() ?? "", EmployeeName = rd["employee_name"]?.ToString() ?? "", IsDeleted = Convert.ToInt32(rd["is_deleted"]) != 0, IsDeletionRequested = Convert.ToInt32(rd["is_deletion_requested"]) != 0 }); } } catch (Exception ex) { error = ex.Message; } return rows; } public bool UpsertMachineUser(string machineId, string serialNumber, string employeeName, out string error) { error = ""; if (!_factory.TryBuildConnectionString(out var cs, out error)) return false; try { using var conn = new MySqlConnection(cs); conn.Open(); using var cmd = new MySqlCommand( "REPLACE INTO attendance_machine_user (machine_id,serial_number,employee_name,is_deleted,is_deletion_requested,updated_at) " + "VALUES (@machine_id,@serial_number,@employee_name,0,0,NOW())", conn); cmd.Parameters.AddWithValue("@machine_id", machineId ?? ""); cmd.Parameters.AddWithValue("@serial_number", serialNumber ?? ""); cmd.Parameters.AddWithValue("@employee_name", employeeName ?? ""); cmd.ExecuteNonQuery(); return true; } catch (Exception ex) { error = ex.Message; return false; } } public bool MarkDeleted(string machineId, string serialNumber, string updatedBy, out string error) { error = ""; if (!_factory.TryBuildConnectionString(out var cs, out error)) return false; try { using var conn = new MySqlConnection(cs); conn.Open(); using var cmd = new MySqlCommand( "UPDATE attendance_machine_user SET is_deleted=1,is_deletion_requested=0,updated_by=@updated_by,updated_at=NOW() " + "WHERE machine_id=@machine_id AND serial_number=@serial_number", conn); cmd.Parameters.AddWithValue("@updated_by", updatedBy ?? "hikvision-service"); cmd.Parameters.AddWithValue("@machine_id", machineId ?? ""); cmd.Parameters.AddWithValue("@serial_number", serialNumber ?? ""); cmd.ExecuteNonQuery(); return true; } catch (Exception ex) { error = ex.Message; return false; } } } internal sealed class MySqlAttendanceMachineFaceTemplateRepository : IAttendanceMachineFaceTemplateRepository { private readonly IHrmsDbConnectionFactory _factory; public MySqlAttendanceMachineFaceTemplateRepository(IHrmsDbConnectionFactory factory) { _factory = factory; } public bool UpsertFaceTemplate(string serialNo, byte[] template, DateTime createdDate, bool isActive, out string error) { error = ""; if (!_factory.TryBuildConnectionString(out var cs, out error)) return false; try { using var conn = new MySqlConnection(cs); conn.Open(); using var cmd = new MySqlCommand( "REPLACE INTO attendance_machine_face_templates (serial_no,template,created_date,is_active) " + "VALUES (@serial_no,@template,@created_date,@is_active)", conn); cmd.Parameters.AddWithValue("@serial_no", serialNo ?? ""); cmd.Parameters.Add("@template", MySqlDbType.Blob).Value = (object?)template ?? Array.Empty(); cmd.Parameters.AddWithValue("@created_date", createdDate); cmd.Parameters.AddWithValue("@is_active", isActive ? 1 : 0); cmd.ExecuteNonQuery(); return true; } catch (Exception ex) { error = ex.Message; return false; } } public bool TryGetActiveFaceTemplateBySerial(string serialNo, out AttendanceMachineFaceTemplateRow? row, out string error) { row = null; error = ""; if (!_factory.TryBuildConnectionString(out var cs, out error)) return false; try { using var conn = new MySqlConnection(cs); conn.Open(); using var cmd = new MySqlCommand( "SELECT serial_no,template,created_date,is_active FROM attendance_machine_face_templates " + "WHERE serial_no=@serial_no AND is_active=1 ORDER BY created_date DESC LIMIT 1", conn); cmd.Parameters.AddWithValue("@serial_no", serialNo ?? ""); using var rd = cmd.ExecuteReader(); if (!rd.Read()) return false; row = new AttendanceMachineFaceTemplateRow { SerialNo = rd["serial_no"]?.ToString() ?? "", Template = rd["template"] == DBNull.Value ? Array.Empty() : (byte[])rd["template"], CreatedDate = rd["created_date"] == DBNull.Value ? DateTime.MinValue : Convert.ToDateTime(rd["created_date"]), IsActive = rd["is_active"] != DBNull.Value && Convert.ToInt32(rd["is_active"]) != 0 }; return true; } catch (Exception ex) { error = ex.Message; return false; } } }