feat: map device serial to hrms.employee for name and photo
Match serial_number_int → id + concatenated_name (+ department).main
parent
2b0f3df888
commit
5a1f68077d
|
|
@ -0,0 +1,130 @@
|
||||||
|
using MySql.Data.MySqlClient;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace HanvonF710XAttendanceService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// HRMS employee row used for Hanvon display name and portal photo URL.
|
||||||
|
/// Device enroll/serial must NOT be treated as employee.id.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class HrmsEmployeeInfo
|
||||||
|
{
|
||||||
|
public HrmsEmployeeInfo(string employeeId, string serialNumberInt, string concatenatedName, string department = "")
|
||||||
|
{
|
||||||
|
EmployeeId = employeeId ?? "";
|
||||||
|
SerialNumberInt = serialNumberInt ?? "";
|
||||||
|
ConcatenatedName = concatenatedName ?? "";
|
||||||
|
Department = department ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>employee.id — used for portal photo URL only.</summary>
|
||||||
|
public string EmployeeId { get; }
|
||||||
|
|
||||||
|
/// <summary>employee.serial_number_int — matches device / attendance_machine_user.serial_number.</summary>
|
||||||
|
public string SerialNumberInt { get; }
|
||||||
|
|
||||||
|
/// <summary>employee.concatenated_name — Hanvon user display name.</summary>
|
||||||
|
public string ConcatenatedName { get; }
|
||||||
|
|
||||||
|
/// <summary>Department display name when available; otherwise department_id.</summary>
|
||||||
|
public string Department { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class HrmsEmployeeDAO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Match device serial (attendance_machine_user.serial_number) to employee.serial_number_int.
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetByDeviceSerial(MySqlConnection connection, string deviceSerial, out HrmsEmployeeInfo employee)
|
||||||
|
{
|
||||||
|
employee = null;
|
||||||
|
if (connection == null || string.IsNullOrWhiteSpace(deviceSerial))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string serial = deviceSerial.Trim();
|
||||||
|
|
||||||
|
// Prefer department name; fall back to department_id if departments table/join is unavailable.
|
||||||
|
const string queryWithDeptName =
|
||||||
|
"SELECT e.id, e.serial_number_int, e.concatenated_name, " +
|
||||||
|
"COALESCE(NULLIF(TRIM(d.name), ''), CAST(e.department_id AS CHAR), '') AS department " +
|
||||||
|
"FROM hrms.employee e " +
|
||||||
|
"LEFT JOIN hrms.departments d ON d.id = e.department_id " +
|
||||||
|
"WHERE CAST(e.serial_number_int AS CHAR) = @DeviceSerial " +
|
||||||
|
"LIMIT 1";
|
||||||
|
|
||||||
|
const string queryBasic =
|
||||||
|
"SELECT e.id, e.serial_number_int, e.concatenated_name, " +
|
||||||
|
"CAST(e.department_id AS CHAR) AS department " +
|
||||||
|
"FROM hrms.employee e " +
|
||||||
|
"WHERE CAST(e.serial_number_int AS CHAR) = @DeviceSerial " +
|
||||||
|
"LIMIT 1";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (TryReadEmployee(connection, queryWithDeptName, serial, out employee))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// departments table/column may differ; fall back.
|
||||||
|
}
|
||||||
|
|
||||||
|
return TryReadEmployee(connection, queryBasic, serial, out employee);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadEmployee(MySqlConnection connection, string query, string serial, out HrmsEmployeeInfo employee)
|
||||||
|
{
|
||||||
|
employee = null;
|
||||||
|
using (var cmd = new MySqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
cmd.CommandTimeout = 60;
|
||||||
|
cmd.Parameters.AddWithValue("@DeviceSerial", serial);
|
||||||
|
using (var reader = cmd.ExecuteReader())
|
||||||
|
{
|
||||||
|
if (!reader.Read())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string id = reader.IsDBNull(0) ? "" : Convert.ToString(reader.GetValue(0))?.Trim();
|
||||||
|
string serialInt = reader.IsDBNull(1) ? "" : Convert.ToString(reader.GetValue(1))?.Trim();
|
||||||
|
string name = reader.IsDBNull(2) ? "" : Convert.ToString(reader.GetValue(2))?.Trim();
|
||||||
|
string department = reader.FieldCount > 3 && !reader.IsDBNull(3)
|
||||||
|
? Convert.ToString(reader.GetValue(3))?.Trim()
|
||||||
|
: "";
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
employee = new HrmsEmployeeInfo(id, serialInt, name, department);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ResolveDisplayName(
|
||||||
|
MySqlConnection connection,
|
||||||
|
string deviceSerial,
|
||||||
|
string fallbackName = null)
|
||||||
|
{
|
||||||
|
if (TryGetByDeviceSerial(connection, deviceSerial, out HrmsEmployeeInfo employee)
|
||||||
|
&& !string.IsNullOrWhiteSpace(employee.ConcatenatedName))
|
||||||
|
{
|
||||||
|
return employee.ConcatenatedName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(fallbackName))
|
||||||
|
{
|
||||||
|
return fallbackName.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.IsNullOrWhiteSpace(deviceSerial) ? "" : deviceSerial.Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace HanvonF710XAttendanceService
|
||||||
|
{
|
||||||
|
internal static class HrmsEmployeeMappingTests
|
||||||
|
{
|
||||||
|
public static int RunAll()
|
||||||
|
{
|
||||||
|
int failed = 0;
|
||||||
|
failed += Run("photo URL uses employees.id not device serial", TestPhotoUrlUsesEmployeeId);
|
||||||
|
failed += Run("display name prefers concatenated_name", TestDisplayNamePrefersConcatenatedName);
|
||||||
|
failed += Run("display name falls back to device serial", TestDisplayNameFallback);
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int Run(string name, Func<bool> test)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!test())
|
||||||
|
{
|
||||||
|
Console.WriteLine("FAIL: " + name);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("PASS: " + name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine("FAIL: " + name + " err=" + ex.Message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TestPhotoUrlUsesEmployeeId()
|
||||||
|
{
|
||||||
|
// Device serial=15057 maps to employees.id=84321 for photo URL only.
|
||||||
|
string url = HrmsEmployeePhotoClient.BuildPhotoUrl(
|
||||||
|
"https://portal.utopiaindustries.pk/uind/employee-photo/",
|
||||||
|
"84321");
|
||||||
|
return string.Equals(
|
||||||
|
url,
|
||||||
|
"https://portal.utopiaindustries.pk/uind/employee-photo/84321.jpeg",
|
||||||
|
StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TestDisplayNamePrefersConcatenatedName()
|
||||||
|
{
|
||||||
|
var info = new HrmsEmployeeInfo("84321", "15057", "John Smith");
|
||||||
|
return info.EmployeeId == "84321"
|
||||||
|
&& info.SerialNumberInt == "15057"
|
||||||
|
&& info.ConcatenatedName == "John Smith"
|
||||||
|
&& info.EmployeeId != info.SerialNumberInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TestDisplayNameFallback()
|
||||||
|
{
|
||||||
|
var info = new HrmsEmployeeInfo("84321", "15057", "");
|
||||||
|
string name = !string.IsNullOrWhiteSpace(info.ConcatenatedName)
|
||||||
|
? info.ConcatenatedName
|
||||||
|
: "15057";
|
||||||
|
return name == "15057";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue