feat: add DB_TO_DEVICE planning and employee selection
Resolve EmpIds / DepartmentIds / registered employees; keep SourceIp optional.main
parent
5424d0abb8
commit
7741523d25
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -97,7 +97,7 @@ namespace HanvonF710XAttendanceService
|
|||
var enabled = TryParseBool((string)jobEl.Attribute("Enabled"), out var jEnabled) && jEnabled;
|
||||
var sourceIp = (string)jobEl.Attribute("SourceIp") ?? (string)jobEl.Attribute("SourceIP") ?? "";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(id) || string.IsNullOrWhiteSpace(sourceIp))
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -124,7 +124,20 @@ namespace HanvonF710XAttendanceService
|
|||
.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;
|
||||
|
|
@ -188,13 +201,14 @@ namespace HanvonF710XAttendanceService
|
|||
|
||||
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 ?? "";
|
||||
Enabled = enabled;
|
||||
SourceIp = sourceIp ?? "";
|
||||
TargetIps = targetIps ?? new List<string>();
|
||||
EmpIds = empIds ?? new List<string>();
|
||||
DepartmentIds = departmentIds ?? new List<string>();
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
|
@ -202,6 +216,7 @@ namespace HanvonF710XAttendanceService
|
|||
public string SourceIp { get; }
|
||||
public List<string> TargetIps { get; }
|
||||
public List<string> EmpIds { get; }
|
||||
public List<string> DepartmentIds { get; }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue