feat: add DB_TO_DEVICE planning and employee selection

Resolve EmpIds / DepartmentIds / registered employees; keep SourceIp optional.
main
SYED MUSTUFA AHMED NAQVI 2026-09-04 12:31:17 +05:00
parent 5424d0abb8
commit 7741523d25
3 changed files with 238 additions and 3 deletions

88
DbToDevicePlanning.cs Normal file
View File

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace HanvonF710XAttendanceService
{
/// <summary>
/// Pure planning logic for DB_TO_DEVICE jobs (testable without device/DB I/O).
/// </summary>
internal static class DbToDevicePlanning
{
public const int TemplateLoadBatchSize = 50;
public static bool ShouldIgnoreSourceDevice(TemplateTransferMode mode)
{
return mode == TemplateTransferMode.DB_TO_DEVICE;
}
public static List<string> NormalizeEmployeeIds(IEnumerable<string> employeeIds)
{
if (employeeIds == null)
{
return new List<string>();
}
return employeeIds
.Where(id => !string.IsNullOrWhiteSpace(id))
.Select(id => id.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
/// <summary>
/// Resolves which employee serial numbers should be synced from HRMS DB to target devices.
/// </summary>
public static List<string> ResolveEmployeeIds(
TemplateTransferJob job,
Func<IReadOnlyList<string>, List<string>> loadByDepartmentIds,
Func<List<string>> loadRegisteredOnTargets)
{
if (job == null)
{
return new List<string>();
}
if (job.EmpIds != null && job.EmpIds.Count > 0)
{
return NormalizeEmployeeIds(job.EmpIds);
}
if (job.DepartmentIds != null && job.DepartmentIds.Count > 0)
{
if (loadByDepartmentIds != null)
{
var fromDepartments = loadByDepartmentIds(job.DepartmentIds);
if (fromDepartments != null && fromDepartments.Count > 0)
{
return NormalizeEmployeeIds(fromDepartments);
}
}
}
return NormalizeEmployeeIds(loadRegisteredOnTargets != null ? loadRegisteredOnTargets() : null);
}
public static int ComputeTemplateLoadBatchCount(int employeeCount)
{
if (employeeCount <= 0)
{
return 0;
}
return (employeeCount + TemplateLoadBatchSize - 1) / TemplateLoadBatchSize;
}
public static int ComputeLastTemplateLoadBatchSize(int employeeCount)
{
if (employeeCount <= 0)
{
return 0;
}
int remainder = employeeCount % TemplateLoadBatchSize;
return remainder == 0 ? TemplateLoadBatchSize : remainder;
}
}
}

132
DbToDevicePlanningTests.cs Normal file
View File

@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
namespace HanvonF710XAttendanceService
{
/// <summary>
/// Lightweight unit tests for DB_TO_DEVICE planning logic (no MSTest dependency).
/// </summary>
internal static class DbToDevicePlanningTests
{
public static int RunAll()
{
int failed = 0;
failed += Run("1 DB_TO_DEVICE ignores source device", TestDbToDeviceIgnoresSource);
failed += Run("2 DEVICE_TO_DB requires source device", TestDeviceToDbRequiresSource);
failed += Run("3 DB_TO_DEVICE allows empty SourceIp planning", TestDbToDeviceAllowsEmptySourceIp);
failed += Run("4 EmpIds only selection", TestEmpIdsFilter);
failed += Run("5 DepartmentIds selection", TestDepartmentIdsFallback);
failed += Run("6 empty EmpIds uses registered employees", TestEmptyEmpIdsUsesRegistered);
failed += Run("7 normalize employee ids", TestNormalizeEmployeeIds);
failed += Run("8 DEVICE_TO_DEVICE requires source device", TestDeviceToDeviceRequiresSource);
failed += Run("9 template load batches for 673 employees", TestTemplateLoadBatchCount673);
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 TestDbToDeviceIgnoresSource()
{
return DbToDevicePlanning.ShouldIgnoreSourceDevice(TemplateTransferMode.DB_TO_DEVICE);
}
private static bool TestDeviceToDbRequiresSource()
{
return !DbToDevicePlanning.ShouldIgnoreSourceDevice(TemplateTransferMode.DEVICE_TO_DB);
}
private static bool TestDeviceToDeviceRequiresSource()
{
return !DbToDevicePlanning.ShouldIgnoreSourceDevice(TemplateTransferMode.DEVICE_TO_DEVICE);
}
private static bool TestDbToDeviceAllowsEmptySourceIp()
{
var job = new TemplateTransferJob("1", true, "", new List<string> { "192.168.90.223" },
new List<string> { "5" }, null);
var ids = DbToDevicePlanning.ResolveEmployeeIds(job, _ => new List<string>(), () => new List<string> { "5" });
return ids.Count == 1 && ids[0] == "5";
}
private static bool TestOtherModesRequireSource()
{
return TestDeviceToDbRequiresSource() && TestDeviceToDeviceRequiresSource();
}
private static bool TestEmpIdsFilter()
{
var job = new TemplateTransferJob("1", true, "10.0.1.3", new List<string> { "192.168.90.223" },
new List<string> { "5", "55", "96" }, null);
var ids = DbToDevicePlanning.ResolveEmployeeIds(
job,
_ => new List<string> { "999" },
() => new List<string> { "999" });
return ids.Count == 3
&& ids.Contains("5")
&& ids.Contains("55")
&& ids.Contains("96");
}
private static bool TestDepartmentIdsFallback()
{
var job = new TemplateTransferJob("1", true, "", new List<string> { "192.168.90.223" },
null, new List<string> { "10", "20" });
bool deptCalled = false;
var ids = DbToDevicePlanning.ResolveEmployeeIds(
job,
departmentIds =>
{
deptCalled = true;
return new List<string> { "100", "200" };
},
() => new List<string> { "300" });
return deptCalled && ids.Count == 2 && ids.Contains("100") && ids.Contains("200");
}
private static bool TestEmptyEmpIdsUsesRegistered()
{
var job = new TemplateTransferJob("1", true, "", new List<string> { "192.168.90.223" },
null, null);
var ids = DbToDevicePlanning.ResolveEmployeeIds(
job,
_ => new List<string>(),
() => new List<string> { "5", "55" });
return ids.Count == 2 && ids.Contains("5") && ids.Contains("55");
}
private static bool TestNormalizeEmployeeIds()
{
var ids = DbToDevicePlanning.NormalizeEmployeeIds(new[] { " 5 ", "5", "", null, "96" });
return ids.Count == 2 && ids[0] == "5" && ids[1] == "96";
}
private static bool TestTemplateLoadBatchCount673()
{
return DbToDevicePlanning.ComputeTemplateLoadBatchCount(673) == 14
&& DbToDevicePlanning.ComputeLastTemplateLoadBatchSize(673) == 23;
}
}
}

View File

@ -97,7 +97,7 @@ namespace HanvonF710XAttendanceService
var enabled = TryParseBool((string)jobEl.Attribute("Enabled"), out var jEnabled) && jEnabled; var enabled = TryParseBool((string)jobEl.Attribute("Enabled"), out var jEnabled) && jEnabled;
var sourceIp = (string)jobEl.Attribute("SourceIp") ?? (string)jobEl.Attribute("SourceIP") ?? ""; var sourceIp = (string)jobEl.Attribute("SourceIp") ?? (string)jobEl.Attribute("SourceIP") ?? "";
if (string.IsNullOrWhiteSpace(id) || string.IsNullOrWhiteSpace(sourceIp)) if (string.IsNullOrWhiteSpace(id))
{ {
continue; continue;
} }
@ -124,7 +124,20 @@ namespace HanvonF710XAttendanceService
.ToList(); .ToList();
} }
ttConfig.Jobs.Add(new TemplateTransferJob(id.Trim(), enabled, sourceIp.Trim(), targets, empIds)); var departmentIdsAttr = (string)jobEl.Attribute("DepartmentIds")
?? (string)jobEl.Attribute("DepartmentIDs")
?? (string)jobEl.Attribute("DeptIds");
List<string> departmentIds = null;
if (!string.IsNullOrWhiteSpace(departmentIdsAttr))
{
departmentIds = departmentIdsAttr
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(v => v.Trim())
.Where(v => v.Length > 0)
.ToList();
}
ttConfig.Jobs.Add(new TemplateTransferJob(id.Trim(), enabled, sourceIp.Trim(), targets, empIds, departmentIds));
} }
parsed.TemplateTransfer = ttConfig; parsed.TemplateTransfer = ttConfig;
@ -188,13 +201,14 @@ namespace HanvonF710XAttendanceService
internal sealed class TemplateTransferJob internal sealed class TemplateTransferJob
{ {
public TemplateTransferJob(string id, bool enabled, string sourceIp, List<string> targetIps, List<string> empIds) public TemplateTransferJob(string id, bool enabled, string sourceIp, List<string> targetIps, List<string> empIds, List<string> departmentIds = null)
{ {
Id = id ?? ""; Id = id ?? "";
Enabled = enabled; Enabled = enabled;
SourceIp = sourceIp ?? ""; SourceIp = sourceIp ?? "";
TargetIps = targetIps ?? new List<string>(); TargetIps = targetIps ?? new List<string>();
EmpIds = empIds ?? new List<string>(); EmpIds = empIds ?? new List<string>();
DepartmentIds = departmentIds ?? new List<string>();
} }
public string Id { get; } public string Id { get; }
@ -202,6 +216,7 @@ namespace HanvonF710XAttendanceService
public string SourceIp { get; } public string SourceIp { get; }
public List<string> TargetIps { get; } public List<string> TargetIps { get; }
public List<string> EmpIds { get; } public List<string> EmpIds { get; }
public List<string> DepartmentIds { get; }
} }
} }