跳轉到

應用情境 - 組織人員同步

「維護組織人員」功能用於快速更新組織架構及人員資訊。在整合 UOF X 與外部系統的過程中,可能出現兩邊系統的組織資料不同步的情況,因此,UOF X 提供了此功能,讓公司能夠有效解決資料不同步的問題,確保與外部系統資料的一致性。該功能支援建立組織架構及同步組織人員資訊,以下會展示一個應用情境:「組織人員同步」,並提供具體的實作步驟與方法作為參考。

應用情境

公司 A 最近導入 UOF X 系統,但組織架構與人員資訊以 HR 系統為主,公司 A 希望能保持在 HR 系統維護組織人員資訊,再將資料同步到 UOF X 上,確保兩個系統的資料一致。

面對隨時異動且大量的組織與人員資料,如果依賴手動輸入,不僅耗費時間與人力,還容易出現資料遺漏與輸入錯誤的風險。

透過 UOF X 提供的 SDK,我們可以撰寫出「組織人員同步」功能,公司能快速進行批次資料的同步,避免手動操作的繁瑣,確保資料的正確性與一致性。

UOF X 與外部系統組織人員同步優點

  • 資料同步與一致性:確保 UOF X 與外部系統中的組織架構及人員資訊保持一致,避免因資料不同步導致的管理混亂。
  • 提升管理效率:支援自動同步與批次更新,減少重複輸入的工作,降低人為錯誤風險。
  • 即時更新組織變動:當組織調整或人事異動時,可透過 UOF X 快速反映變動,確保內部系統即時更新。
  • 減少人力與時間成本:自動化的同步流程降低人力需求,提升資料維護效率,節省大量時間。
  • 降低資料不一致風險:整合過程中即時檢查與更新,避免資料不一致對業務流程造成的影響。

同步規則

在進行同步前,必須先處理欄位對應、資料格式轉換以及定義出同步規則,以確保 HR 系統與 UOF X 之間的資料正確無誤。

程式邏輯說明

  • 雙方系統人員及部門資料以人員帳號 Account 及部門代號 Code 作為比對的依據

部門同步流程圖

flowchart LR
    A(一、處理外部系統資料) --> B(欄位名稱、資料轉換) & C(建立部門階層<br>-非必要-) --> D(二、與 UOF X 比對資料)
    D --> E(更新 UOF X 部門)
    D --> F(新增 UOF X 部門)
    D --> G(停用 UOF X 部門)
  • 部門同步規則:
    • UOF X「部門層級」資料依據 HR 系統的「部門代號」及「父部門代號」資料建立部門階層結構
    • 部門階層同時存在於 HR 系統與 UOF X,更新 UOF X 該部門階層
    • 部門階層存在於 HR 系統,而不存在於 UOF X,將該部門階層新增到 UOF X
    • 部門階層不存在於 HR 系統,在 UOF X 仍存在,停用 UOF X 該部門階層
    • 部門同時存在於 HR 系統與 UOF X,更新 UOF X 該部門
    • 部門存在於 HR 系統,而不存在於 UOF X,將該部門新增到 UOF X
    • 部門不存在於 HR 系統,在 UOF X 仍存在,停用 UOF X 該部門

人員同步流程圖

flowchart LR
    A(一、處理外部系統資料) --> B(欄位名稱、資料轉換) & C(同步職稱職務) --> D(二、與 UOF X 比對資料)
    D --> E(更新 UOF X 人員)
    D --> F(新增 UOF X 人員)
    D --> G(停用 UOF X 人員)
    E & F & G --> H(三、同步主管)
  • 人員同步規則:
    • 同步職稱職務:
      1. 若「職務」不存在 UOF X,新增該職務到 UOF X
      2. 若「職稱」不存在 UOF X,新增該職稱到 UOF X
    • 人員同時存在於 HR 系統與 UOF X,更新 UOF X 該人員
    • 人員存在於 HR 系統,而不存在於 UOF X,將該人員新增到 UOF X
  • 現在日期大於帳號過期時間,將該人員帳號設為停用

新增職稱設定

因為該範例無法從職稱的字面上判斷出「簽何層級」,因此將簽核層級預設為 50,後續再由管理員到 UOF X 進行調整

Usertype

因為人員帳號以 Account 作為比對的依據,因此統一將 UserType 設為 Account

Supervisortype

因為 SupervisorType 沒有對應的欄位,因此將其預設為 DeptManager

欄位對應

人員欄位對應:

UOF X 欄位 HR 系統欄位 說明
UserType (無對應欄位) 人員的類別 (帳號 Account, 員編 EmployeeNo )
UserCode (無對應欄位) 帳號或員編
Account Account 人員帳號
Name Name 中文姓名
EnglishName EnglishName 英文姓名
EmployeeNumber EmployeeID 員工編號
ExpiredTime ExpiredTime 帳號過期時間
Gender Gender 性別 ( UOF X: 0, 1, 2 / HR : male, female, other )
IdCardNumber IDNumber 身份證字號
BirthDate BirthDate 生日 ( UOF X: 西元 / HR : 民國)
PhoneNumber PhoneNumber 行動電話
BusinessCard CardTitle 名片職稱
HireDate HireDate 到職日
ResignationDate ResignationDate 離職日
Email PrimaryEmail 主要信箱
EmailEx SecondaryEmail 其他信箱
Locked AccountLocked 是否鎖定人員帳號
Active AccountActive 是否停用人員帳號
SupervisorType (無對應欄位) 主管類別 (依部門主管 DeptManager, 依自訂簽核主管 Customize)
SupervisorAccount (無對應欄位) 當主管類別為「依自訂簽核主管」時,需設定主管帳號
JobTitleCode JobTitleID 職稱代號
JobFuncs JobFunctions 職務代號 (可多筆)
DeptCode DeptCode 部門代號
IsMainDept PrimaryDepartment 是否為主要部門
(無對應欄位) IsSupervisor 是否為主管

部門的欄位對應:

UOF X 欄位 HR 系統欄位 說明
Code DeptCode 部門代號
Name DeptName 部門名稱
Seq Order 部門的排序 (從 0 開始)
Active IsActive 是否啟用部門
IncludeSubDept HasSubDepartments 是否包含子部門
ParentCode ParentDeptCode 父部門代號
DeptLevelCode (無對應欄位) 部門層級代號
Description DeptDescription 描述

實作

在繼續往下說明之前,請確認您下列事項皆已經準備完成:

  1. 透過 Visual Studio 建立一個新的主控台應用程式 (Console App)
  2. 已將專案透過 NuGet 安裝 SDK,並設定好 SDK 參數
  3. 已取得 UOF X 站台網址
  4. 已取得 金鑰

範例表單下載: https://github.com/UOfficeForceX/SDK-Quickstart

完整程式範例

以下為此情境中組織人員同步的完整程式範例,後續會針對同步部門同步人員兩大部分做詳細說明。

Program.cs
using Ede.Uofx.PubApi.Sdk.NetStd;
using Ede.Uofx.PubApi.Sdk.NetStd.Models.Base;
using Ede.Uofx.PubApi.Sdk.NetStd.Service;

//設定金鑰
UofxService.Key = "xxx";

//設定 UOF X 站台網址
UofxService.UofxServerUrl = "https://myuofx.com.tw";

// 儲存成功訊息
List<string> successMsg = new List<string> { "====================================================同步成功====================================================" };
// 儲存錯誤訊息
List<string> errorMsg = new List<string> { "====================================================同步失敗====================================================" };

// (1)!
// HR 系統部門資料 
List<HrDeptModel> originHrDepts = new List<HrDeptModel>{...};
// HR 系統人員資料
List<HrEmplModel> originHrEmpls = new List<HrEmplModel>{...};

#region 主程式
try
{
    // 同步人員部門資料
    await SyncDepts(originHrDepts);
    await SyncEmpls(originHrEmpls);
    // 列印錯誤成功訊息
    ConsoleList(errorMsg);
    ConsoleList(successMsg);
}
catch (Exception ex)
{
    //將 exception 轉換成較容易判斷的 model
    var model = UofxService.Error.ConvertToModel(ex);
    //將 model 轉成 json 格式印出
    Console.WriteLine(UofxService.Json.Convert(model));
}
#endregion

// 列印錯誤成功訊息
void ConsoleList(List<string> list)
{
    foreach (var item in list)
    {
        Console.WriteLine(item);
    }
};

#region 同步部門
// 同步部門資料
async Task SyncDepts(List<HrDeptModel> originHrDepts)
{
    // 部門資料階層化
    var hierarchyDeptList = BuildHierarchy(originHrDepts);
    // 建立部門層級與轉換後的部門 List 
    var (deptLevList, deptList) = BuildDeptLevs(hierarchyDeptList, 1);
    // 比對與同步部門階層資料
    await MapDeptLevels(deptLevList);
    // 比對與同步部門資料
    await MapDepts(deptList);
};
// 部門資料階層化
List<HrDeptModel> BuildHierarchy(List<HrDeptModel> originHrDepts)
{
    // 取得有子部門的父部門資料
    var getHRParentDeptList = originHrDepts.Where(x => x.HasSubDepartments).ToList();
    // 將子部門資料加入父部門
    foreach (var dept in getHRParentDeptList)
    {
        var getSubDept = originHrDepts.Where(x => x.ParentDeptCode == dept.DeptCode).ToList();
        dept.SubDepts = getSubDept;
    };
    // 取得沒有父部門的部門資料
    var hierarchyDeptList = originHrDepts.Where(x => string.IsNullOrEmpty(x.ParentDeptCode)).ToList();
    return hierarchyDeptList;
};
// 建立部門層級與轉換後的部門 List 
(List<DeptLevelViewModel> deptLevList, List<DeptModel> deptList) BuildDeptLevs(List<HrDeptModel> hierarchy, int level)
{
    // 部門層級 List 
    var deptLevList = new List<DeptLevelViewModel>();
    // 部門 List 
    var deptList = new List<DeptModel>();

    foreach (var dept in hierarchy)
    {
        // 確認不新增重複部門層級
        if (!deptLevList.Any(x => x.Seq == level))
        {
            // 新增當前層級的資料
            deptLevList.Add(new DeptLevelViewModel
            {
                Code = $"lev{level}", // 部門層級代號
                Name = $"Level{level}", // 部門層級名稱
                Seq = level, // 部門層級的階層與排序 ( 從 1 開始 )
                CorpCode = _corpCode, // 公司代號
                Active = true // 是否啟用部門層級
            });
        }
        // 轉換部門資料並加入部門層級
        deptList.Add(new DeptModel
        {
            Code = dept.DeptCode, // 部門代號
            Name = dept.DeptName, // 部門名稱
            Active = true, // 是否啟用部門
            IncludeSubDept = dept.HasSubDepartments, // 是否包含子部門
            ParentCode = dept.ParentDeptCode, // 父部門代號 ( 可設定為空值,代表無父部門 )
            Description = dept.DeptDescription, // 描述
            DeptLevelCode = $"lev{level}", // 部門層級代號
            Seq = dept.Order - 1 // 部門的排序 ( 從 0 開始 )
        });
        // 遞迴處理子部門,層級加 1,並將結果加入 deptLevList 與 deptList
        if (dept.SubDepts != null && dept.SubDepts.Count > 0)
        {
            var (deptLevListTemp, deptListTemp) = BuildDeptLevs(dept.SubDepts, level + 1);
            deptLevList.AddRange(deptLevListTemp);
            deptList.AddRange(deptListTemp);
        }
    };
    return (deptLevList, deptList);
};
// 比對與同步部門階層資料
async Task MapDeptLevels(List<DeptLevelViewModel> hrDeptLevs)
{
    // 更新 List 
    var deptLevelToUpdate = new List<DeptLevelViewModel>();
    // 新增 List 
    var deptLevelToAdd = new List<DeptLevelViewModel>();
    // 停用 List 
    var deptLevelToDeactivate = new List<DeptLevelViewModel>();

    // 取得部門層級資料
    var uofDeptLevs = await UofxService.BASE.DeptLevel.Get(_corpCode);

    // 建立以 Code 為鍵的字典以加快查找速度
    var hrDeptDict = hrDeptLevs.ToDictionary(x => x.Code, x => x);
    var uofDeptDict = uofDeptLevs.ToDictionary(x => x.Code, x => x);

    // 處裡要更新和新增的項目
    foreach (var hrDept in hrDeptLevs)
    {
        // 若此項目已存在,加入更新 List 
        if (uofDeptDict.ContainsKey(hrDept.Code))
        {
            deptLevelToUpdate.Add(hrDept);
        }
        // 此項目不存在,加入新增 List 
        else
        {
            deptLevelToAdd.Add(hrDept);
        }
    };
    // 處理要停用的項目
    foreach (var uofDept in uofDeptLevs)
    {
        // 若此項目不存在於 HR 系統,加入停用 List 
        if (!hrDeptDict.ContainsKey(uofDept.Code))
        {
            deptLevelToDeactivate.Add(uofDept);
        };
    };

    // 更新部門層級
    await UpdateDeptLevels(deptLevelToUpdate);
    // 新增部門層級
    await CreateDeptLevels(deptLevelToAdd);
    // 停用部門層級
    await DeactivateDeptLevels(deptLevelToDeactivate);  
};
// 更新部門層級
async Task UpdateDeptLevels(List<DeptLevelViewModel> deptLevels)
{
    foreach (var deptLevel in deptLevels)
    {
        try
        {
            // 更新部門層級狀態
            var updateStatus = await UofxService.BASE.DeptLevel.UpdateStatus(new DeptLevelUpdateStatusModel()
            {
                CorpCode = _corpCode, // 公司代號
                Code = deptLevel.Code, // 部門層級代號
                Active = true // 是否啟用部門層級
            });
            // 更新部門層級資料
            var update = await UofxService.BASE.DeptLevel.Update(new DeptLevelUpdateModel()
            {
                CorpCode = _corpCode, // 公司代號
                OriginalCode = deptLevel.Code, // 原始部門層級代號 ( 設為部門層級代號 )
                Code = deptLevel.Code, // 部門層級代號
                Name = deptLevel.Name // 部門層級名稱
            });
            // 若部門層級狀態、資料更新成功,加入成功訊息
            if (updateStatus == true && update == true) successMsg.Add($"已更新 {deptLevel.Code} 部門層級");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法更新 {deptLevel.Code} 部門層級,error: {ex.Message}");
        }
    };
};
// 新增部門層級
async Task CreateDeptLevels(List<DeptLevelViewModel> deptLevels)
{
    foreach (var deptLevel in deptLevels)
    {
        try
        {
            // 新增部門層級
            var resualt = await UofxService.BASE.DeptLevel.Create(deptLevel);
            // 若新增成功,加入成功訊息
            if (resualt == true) successMsg.Add($"已新增 {deptLevel.Code} 部門層級");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法新增 {deptLevel.Code} 部門層級,error: {ex.Message}");
        }
    };
};
// 停用部門層級
async Task DeactivateDeptLevels(List<DeptLevelViewModel> deptLevels)
{
    foreach (var deptLevel in deptLevels)
    {
        try
        {
            // 停用部門層級
            var resualt = await UofxService.BASE.DeptLevel.UpdateStatus(new DeptLevelUpdateStatusModel()
            {
                CorpCode = _corpCode, // 公司代號
                Code = deptLevel.Code, // 部門層級代號
                Active = false // 停用部門層級
            });
            // 若停用成功,加入成功訊息
            if (resualt == true) successMsg.Add($"已停用 {deptLevel.Code} 部門層級");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法停用 {deptLevel.Code} 部門層級,error: {ex.Message}");
        }
    };
};
// 比對與同步部門資料
async Task MapDepts(List<DeptModel> hrDepts)
{
    // 更新 List 
    var deptToUpdate = new List<DeptModel>();
    // 新增 List 
    var deptToAdd = new List<DeptModel>();
    // 停用 List 
    var deptToDeactivate = new List<DeptModel>();

    // 取得部門資料
    var uofDepts = await UofxService.BASE.Department.Get(new DepartmentGetModel() { CorpCode = _corpCode, IncludeSubDept = true });

    // 建立以 Code 為鍵的字典以加快查找速度
    var hrDeptDict = hrDepts.ToDictionary(x => x.Code, x => x);
    var uofDeptDict = uofDepts.ToDictionary(x => x.Code, x => x);

    // 處理要更新和新增的項目
    foreach (var hrDept in hrDepts)
    {
        // 若此項目已存在,加入更新 List 
        if (uofDeptDict.ContainsKey(hrDept.Code))
        {
            deptToUpdate.Add(new DeptModel
            {
                ParentCode = hrDept.ParentCode, // 父部門代號 ( 可設定為空值,代表無父部門 )
                Code = hrDept.Code, // 部門代號
                Name = hrDept.Name, // 部門名稱
                DeptLevelCode = hrDept.DeptLevelCode, // 部門層級代號
                Description = hrDept.Description, // 描述
                Active = hrDept.Active, // 是否啟用部門
                Seq = hrDept.Seq // 部門的排序 ( 從 0 開始 )
            });
        }
        // 此項目不存在,加入新增 List 
        else
        {
            deptToAdd.Add(new DeptModel
            {
                ParentCode = hrDept.ParentCode, // 父部門代號 ( 可設定為空值,代表無父部門 )
                Code = hrDept.Code, // 部門代號
                Name = hrDept.Name, // 部門名稱
                DeptLevelCode = hrDept.DeptLevelCode, // 部門層級代號
                Description = hrDept.Description, // 描述
                Active = hrDept.Active, // 是否啟用部門
                Seq = hrDept.Seq // 部門的排序 ( 從 0 開始 )
            });
        }
    };
    // 處理要停用的項目
    foreach (var uofDept in uofDepts)
    {
        // 若此項目不存在於 HR 系統,加入停用 List 
        if (!hrDeptDict.ContainsKey(uofDept.Code))
        {
            deptToDeactivate.Add(new DeptModel
            {
                Code = uofDept.Code, // 部門代號
                Active = uofDept.Active, // 是否啟用部門
                DeptLevelCode = uofDept.DeptLevelCode // 部門層級代號
            });
        }
    };

    // 更新部門
    await UpdateDepartments(deptToUpdate);
    // 新增部門
    await CreateDepartments(deptToAdd);
    // 停用部門
    await DeactivateDepartments(deptToDeactivate);
};
// 更新部門
async Task UpdateDepartments(List<DeptModel> departments)
{
    foreach (var dept in departments)
    {
        try
        {
            // 更新部門狀態
            var updateStatus = await UofxService.BASE.Department.UpdateState(new DepartmentUpdateStatusModel()
            {
                CorpCode = _corpCode, // 公司代號
                Code = dept.Code, // 部門代號
                Active = dept.Active // 是否啟用部門
            });
            // 更新部門資料
            var update = await UofxService.BASE.Department.Update(new DepartmentUpdateModel()
            {
                CorpCode = _corpCode, // 公司代號
                OriginalCode = dept.Code, // 原始部門代號 ( 設為部門代號 )
                Code = dept.Code, // 部門代號
                Name = dept.Name, // 部門名稱
                DeptLevelCode = dept.DeptLevelCode, // 部門層級代號
                Description = dept.Description // 描述
            });
            // 若部門狀態、資料更新成功,加入成功訊息
            if (update == true) successMsg.Add($"已更新 {dept.Code} 部門");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法更新 {dept.Code} 部門,error: {ex.Message}");
        }
    };
};
// 新增部門
async Task CreateDepartments(List<DeptModel> departments)
{
    try
    {
        foreach (var dept in departments)
        {
            // 新增部門
            var result = await UofxService.BASE.Department.Create(new DepartmentCreateModel()
            {
                ParentCode = dept.ParentCode, // 父部門代號 ( 可設定為空值,代表無父部門 )
                CorpCode = _corpCode, // 公司代號
                Code = dept.Code, // 部門代號
                DeptLevelCode = dept.DeptLevelCode, // 部門層級代號
                Name = dept.Name, // 部門名稱
                Description = dept.Description // 描述
            });
            // 若新增成功,加入成功訊息
            if (result == true) successMsg.Add($"已新增 {dept.Code} 部門");
        };
    }
    catch (Exception ex)
    {
        // 捕捉錯誤並記錄到 errorMsg 中  
        errorMsg.Add($"無法新增 {departments} 部門,error: {ex.Message}");
    }
};
// 停用部門
async Task DeactivateDepartments(List<DeptModel> departments)
{
    foreach (var dept in departments)
    {
        try
        {
            // 停用部門
            var result = await UofxService.BASE.Department.UpdateState(new DepartmentUpdateStatusModel()
            {
                CorpCode = _corpCode, // 公司代號
                Code = dept.Code, // 部門代號
                Active = false // 是否啟用部門
            });
            // 若停用成功,加入成功訊息
            if (result == true) successMsg.Add($"已停用 {dept.Code} 部門");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法停用 {dept.Code} 部門,error: {ex.Message}");
        }
    };
};
#endregion

#region 同步人員
// 同步人員資料
async Task SyncEmpls(List<HrEmplModel> originHrEmpls)
{
    // 同步職稱職務
    await SyncJobTitlesAndFunctions(originHrEmpls);
    // 轉換人員 List 
    var hrEmpls = ConvertToEmplModels(originHrEmpls);
    // 比對與同步人員資料
    await MapEmpls(hrEmpls);
};
// 同步職稱職務
async Task SyncJobTitlesAndFunctions(List<HrEmplModel> originHrEmpls)
{
    // 取得 HR 系統職稱職務
    List<string> JobTitleCodes = originHrEmpls.Select(e => e.JobTitleID).ToList();
    List<string> JobFuncs = originHrEmpls.SelectMany(e => e.JobFunctions ?? new List<string>()).ToList();

    // 取得 UOF X 系統職稱職務
    var uofJobTitles = await UofxService.BASE.JobTitle.Get(_corpCode);
    var uofJobFuncs = await UofxService.BASE.JobFunc.Get(_corpCode);

    // 找出要新增的職稱職務
    var JobTitleToAdd = JobTitleCodes.Except(uofJobTitles.Select(j => j.Code)).ToList();
    var JobFuncToAdd = JobFuncs.Except(uofJobFuncs.Select(j => j.Code)).ToList();

    foreach (var jobTitle in JobTitleToAdd)
    {
        try
        {
            // 新增職稱
            var result = await UofxService.BASE.JobTitle.Create(new JobTitleViewModel()
            {
                CorpCode = _corpCode, // 職稱代號
                Rank = 50, // 職稱階層 ( 將簽核層級預設為 50 再由管理員到 UOF X 調整 )
                Seq = 1, // 職稱排序 ( 從 1 開始 )
                Code = jobTitle, // 職稱代號
                Title = jobTitle, // 職稱名稱
                Active = true // 啟用職稱
            });
            // 若新增成功,加入成功訊息
            if (result == true) successMsg.Add($"已新增 {jobTitle} 職稱");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法新增 {jobTitle} 職稱,error: {ex.Message}");
        }
    };
    foreach (var jobFunc in JobFuncToAdd)
    {
        try
        {
            // 新增職務
            var result = await UofxService.BASE.JobFunc.Create(new JobFuncViewModel()
            {
                CorpCode = _corpCode, // 職稱代號
                CategoryName = jobFunc, // 職務類別名稱
                Code = jobFunc, // 職務代號
                JobFunc = jobFunc, // 職務名稱
                Active = true // 啟用職務
            });
            // 若新增成功,加入成功訊息
            if (result == true) successMsg.Add($"已新增 {jobFunc} 職務");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法新增 {jobFunc} 職務,error: {ex.Message}");
        }
    };
};
// 轉換人員 List 
List<EmplModel> ConvertToEmplModels(List<HrEmplModel> originHrEmpls)
{
    // 以 Account 作為 key 分組
    var groupedEmpls = originHrEmpls.GroupBy(e => e.Account);
    // 每一組的分組資料 g 轉換成一個新的 EmplModel
    return groupedEmpls.Select(g => new EmplModel
    {
        CorpCode = _corpCode, // 公司代號
        Account = g.Key, // 人員帳號
        Name = g.First().Name, // 中文姓名
        Gender = g.First().Gender == "male" ? "0" : (g.First().Gender == "female" ? "1" : "2"), // 性別 ( 男性 male => 0, 女性 female => 1, 其他 other => 2 )
        EnglishName = g.First().EnglishName, // 英文姓名
        EmployeeNumber = g.First().EmployeeID, // 員工編號
        ExpiredTime = g.First().ExpiredTime, // 帳號過期時間
        ResignationDate = g.First().ResignationDate, // 離職日
        IdCardNumber = g.First().IDNumber, // 身份證字號
        BirthDate = new DateTime(g.First().BirthDate.Year + 1911, g.First().BirthDate.Month, g.First().BirthDate.Day), // 生日 (民國年轉為西元年)
        PhoneNumber = g.First().PhoneNumber, // 行動電話
        BusinessCard = g.First().CardTitle, // 名片職稱
        HireDate = g.First().HireDate, // 到職日
        Email = g.First().PrimaryEmail, // 主要信箱
        EmailEx = g.First().SecondaryEmail, // 其他信箱
        Active = g.First().AccountActive, // 是否停用人員帳號
        Locked = g.First().AccountLocked, // 是否鎖定人員帳號
        // 將每個分組中的資料轉換成一個新的 EmpCreateOfDeptItemRequestModel
        Depts = g.Select(e => new EmpCreateOfDeptItemRequestModel
        {
            Code = e.DeptCode, // 部門代號
            IsMainDept = e.PrimaryDepartment, // 是否為主要部門
            JobTitleCode = e.JobTitleID, // 職稱代號
            JobFuncs = e.JobFunctions // 職務代號 ( 可多筆 )
        }).ToList(),
        // 將每個分組中的主管資料轉換成一個新的 DeptSetManagerModel
        Supervisor = g.Where(g => g.IsSupervisor).Select(e => new DeptSetManagerModel
        {
            CorpCode = _corpCode, // 公司代號
            Code = e.DeptCode, // 部門代號
            Type = UserType.Account, // 人員的類別 ( 設為 Account )
            Value = e.Account // 帳號
        }).ToList()
    }).ToList();
};
// 比對與同步人員資料
async Task MapEmpls(List<EmplModel> hrEmpls)
{
    // 更新 List 
    var emplToUpdate = new List<EmplModel>();
    // 新增 List 
    var emplToAdd = new List<EmplModel>();
    // 停用 List 
    var emplToDeactivate = new List<EmplModel>();

    // 將已存在人員放入需更新 List,不存在人員放入需新增 List 
    foreach (var hrEmpl in hrEmpls)
    {
        // 取得人員資料
        var empl = await UofxService.BASE.OrgEmpl.Get(new EmplQueryRequestModel()
        {
            CorpCode = _corpCode, // 公司代號
            UserType = UserType.Account, // 人員的類別 ( 設為 Account )
            UserCode = hrEmpl.Account // 帳號
        });
        // 若人員存在,加入更新 List 
        if (empl.Account != null)
        {
            emplToUpdate.Add(hrEmpl);
        }
        // 不存在,加入新增 List 
        else
        {
            emplToAdd.Add(hrEmpl);
            // 如果現在日期大於帳號過期時間,加入停用 List 
            if (hrEmpl.ExpiredTime != null && DateTime.Now > hrEmpl.ExpiredTime)
            {
                emplToDeactivate.Add(hrEmpl);
            };
        }
    };
    // 更新人員
    await UpdateEmpls(emplToUpdate);
    // 新增人員
    await CreateEmpls(emplToAdd);
    // 停用人員
    await DeactivateEmpls(emplToDeactivate);
};
// 更新人員
async Task UpdateEmpls(List<EmplModel> empls)
{
    foreach (var empl in empls)
    {
        try
        {
            // 更新人員狀態
            var updateStatus = await UofxService.BASE.OrgEmpl.UpdateAcctStatus(new EmplUpdateAcctStatusRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                UserType = UserType.Account, // 人員的類別 ( 設為 Account )
                UserCode = empl.Account, // 帳號
                Active = empl.Active // 是否停用人員帳號
            });
            // 更新人員鎖定狀態
            var updateLocked = await UofxService.BASE.OrgEmpl.UpdateAcctLocked(new EmplUpdateAcctLockedRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                UserType = UserType.Account, // 人員的類別 ( 設為 Account )
                UserCode = empl.Account, // 帳號
                Locked = empl.Locked // 是否鎖定人員帳號
            });
            // 更新人員資料
            var update = await UofxService.BASE.OrgEmpl.Update(new EmplUpdateRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                UserType = UserType.Account, // 人員的類別 ( 設為 Account )
                UserCode = empl.Account, // 帳號
                Account = empl.Account, // 人員帳號
                Name = empl.Name, // 中文姓名
                EmployeeNumber = empl.EmployeeNumber, // 員工編號
                Gender = empl.Gender, // 性別
                EnglishName = empl.EnglishName, // 英文姓名
                IdCardNumber = empl.IdCardNumber, // 身份證字號
                BirthDate = empl.BirthDate, // 生日
                PhoneNumber = empl.PhoneNumber, // 行動電話
                BusinessCard = empl.BusinessCard, // 名片職稱
                HireDate = empl.HireDate, // 到職日
                Email = empl.Email, // 主要信箱
                EmailEx = empl.EmailEx //   其他信箱
            });
            // 更新人員過期時間
            var updateExpiredTime = await UofxService.BASE.OrgEmpl.UpdateAcctExpiredTime(new EmplUpdateAcctExpiredTimeRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                UserType = UserType.Account, // 人員的類別 ( 設為 Account )
                UserCode = empl.Account, // 帳號
                ExpiredTime = empl.ExpiredTime // 帳號過期時間
            });
            // 更新人員離職日期
            var updateResignationDate = await UofxService.BASE.OrgEmpl.UpdateEmplResignationDate(new EmplUpdateResignationDateRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                UserType = UserType.Account, // 人員的類別 ( 設為 Account )
                UserCode = empl.Account, // 帳號
                ResignationDate = empl.ResignationDate // 離職日
            });
            // 更新人員部門
            var updateDepts = await UofxService.BASE.OrgEmpl.UpdateEmplDept(new EmplUpdateDeptRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                UserType = UserType.Account, // 人員的類別 ( 設為 Account )
                UserCode = empl.Account, // 帳號
                Depts = empl.Depts.Select(d => new DeptRequestModel
                {
                    Code = d.Code, // 部門代號
                    IsMainDept = d.IsMainDept, // 是否為主要部門
                    JobTitleCode = d.JobTitleCode, // 職稱代號
                    JobFuncs = d.JobFuncs // 職務代號 ( 可多筆 )
                }).ToList()
            });
            // 若有部門主管,同步部門主管
            if (empl.Supervisor != null && empl.Supervisor.Count > 0)
            {
                await SyncSuperiors(empl.Supervisor);
            };
            // 若所有更新皆成功,記錄成功訊息
            if (updateStatus == true && updateLocked == true && update == true && updateExpiredTime == true && updateResignationDate == true && updateDepts == true)
            {
                successMsg.Add($"已更新 {empl.Account} 人員");
            };
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法更新 {empl.Account} 人員,error: {ex.Message}");
        }
    };
};
// 新增人員
async Task CreateEmpls(List<EmplModel> empls)
{
    foreach (var empl in empls)
    {
        try
        {
            // 新增人員
            var result = await UofxService.BASE.OrgEmpl.CreateEmpl(new EmpCreateRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                Account = empl.Account, // 人員帳號
                Name = empl.Name, // 中文姓名
                EnglishName = empl.EnglishName, // 英文姓名
                EmployeeNumber = empl.EmployeeNumber, // 員工編號
                ExpiredTime = empl.ExpiredTime, // 帳號過期時間
                Gender = empl.Gender, // 性別
                IdCardNumber = empl.IdCardNumber, // 身份證字號
                BirthDate = empl.BirthDate, //  生日
                PhoneNumber = empl.PhoneNumber, // 行動電話
                BusinessCard = empl.BusinessCard, // 名片職稱
                HireDate = empl.HireDate, // 到職日
                Email = empl.Email, // 主要信箱
                EmailEx = empl.EmailEx, // 其他信箱
                Depts = empl.Depts
            });
            // 若新增成功,加入成功訊息
            if (result == true)
            {
                successMsg.Add($"已新增 {empl.Account} 人員");
                // 若有部門主管,同步部門主管
                if (empl.Supervisor != null && empl.Supervisor.Count > 0)
                {
                    await SyncSuperiors(empl.Supervisor);
                };
            };
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法新增 {empl.Account} 人員,error: {ex.Message}");
        }
    };
};
// 停用人員
async Task DeactivateEmpls(List<EmplModel> empls)
{
    foreach (var empl in empls)
    {
        try
        {
            // 停用人員
            var result = await UofxService.BASE.OrgEmpl.UpdateAcctStatus(new EmplUpdateAcctStatusRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                UserType = UserType.Account, // 人員的類別 ( 設為 Account )
                UserCode = empl.Account, // 帳號
                Active = false // 停用人員帳號
            });
            // 若停用成功,加入成功訊息
            if (result == true) successMsg.Add($"已停用 {empl.Account} 人員");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法停用 {empl.Account} 人員,error: {ex.Message}");
        }
    };
};
// 同步部門主管
async Task SyncSuperiors(List<DeptSetManagerModel> Superiors)
{
    foreach (var Superior in Superiors)
    {
        try
        {
            // 設定部門主管
            var result = await UofxService.BASE.Department.Manager.Set(Superior);
            // 若設定成功,加入成功訊息
            if (result == true) successMsg.Add($"已設定 {Superior.Value} 為 {Superior.Code} 部門主管");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法設定 {Superior.Value} 為 {Superior.Code} 部門主管,error: {ex.Message}");
        }
    };
};
#endregion

# region 類別
/// <summary>
/// UOF X 部門類別
/// </summary>
public class DeptModel
{
    public string Code { get; set; }
    public string Name { get; set; }
    public bool Active { get; set; }
    public bool IncludeSubDept { get; set; }
    public string ParentCode { get; set; }
    public string Description { get; set; }
    public string DeptLevelCode { get; set; }
    public int Seq { get; set; }
    public List<DeptModel> SubDepts { get; set; } = new List<DeptModel>(); // 用於儲存子部門
}
/// <summary>
/// UOF X 人員類別
/// </summary>
public class EmplModel
{
    public string CorpCode { get; set; }
    public string Account { get; set; }
    public string Name { get; set; }
    public string EnglishName { get; set; }
    public string EmployeeNumber { get; set; }
    public DateTime? ExpiredTime { get; set; }
    public DateTime? ResignationDate { get; set; }
    public string Gender { get; set; }
    public string IdCardNumber { get; set; }
    public DateTime? BirthDate { get; set; }
    public string PhoneNumber { get; set; }
    public string BusinessCard { get; set; }
    public DateTime? HireDate { get; set; }
    public string Email { get; set; }
    public string EmailEx { get; set; }
    public bool Locked { get; set; }
    public bool Active { get; set; }
    public bool IsSupervisor { get; set; }
    public List<EmpCreateOfDeptItemRequestModel> Depts { get; set; }
    public List<DeptSetManagerModel> Supervisor { get; set; }
}
/// <summary>
/// HR 部門類別
/// </summary>
public class HrDeptModel
{
    public string DeptName { get; set; }
    public string DeptCode { get; set; }
    public int Order { get; set; }
    public bool IsActive { get; set; }
    public bool HasSubDepartments { get; set; }
    public string ParentDeptCode { get; set; }
    public string DeptDescription { get; set; }
    public string DeptLevelCode { get; set; }
    public List<HrDeptModel> SubDepts { get; set; } = new List<HrDeptModel>();
}
/// <summary>
/// HR 人員類別
/// </summary>
public class HrEmplModel
{
    public string Account { get; set; }
    public string Name { get; set; }
    public string EnglishName { get; set; }
    public string EmployeeID { get; set; }
    public DateTime? ExpiredTime { get; set; }
    public string Gender { get; set; }
    public string IDNumber { get; set; }
    public DateTime BirthDate { get; set; }
    public string PhoneNumber { get; set; }
    public string CardTitle { get; set; }
    public DateTime HireDate { get; set; }
    public DateTime? ResignationDate { get; set; }
    public string PrimaryEmail { get; set; }
    public string SecondaryEmail { get; set; }
    public bool AccountLocked { get; set; }
    public bool AccountActive { get; set; }
    public string JobTitleID { get; set; }
    public List<string> JobFunctions { get; set; }
    public string DeptCode { get; set; }
    public bool PrimaryDepartment { get; set; }
    public bool IsSupervisor { get; set; }
}
#endregion
  1. 🙋‍♂️ 因為資料量較龐大,為了不影響觀看範例,請自行將 HR 系統資料 複製貼上到下方的 originHrDeptsoriginHrEmpls

HR 系統資料

HR 系統資料
originHrDepts
// HR 系統部門資料
List<HrDeptModel> originHrDepts = new List<HrDeptModel>
{
    new HrDeptModel
    {
        DeptName = "Research and Development",
        DeptCode = "RD",
        Order = 2,
        IsActive = true,
        HasSubDepartments = false,
        ParentDeptCode = "HR",
        DeptDescription = "Contamination NEC"
    },
    new HrDeptModel
    {
        DeptName = "Business Development",
        DeptCode = "BD",
        Order = 1,
        IsActive = true,
        HasSubDepartments = false,
        ParentDeptCode = "HR",
        DeptDescription = "Adenovirus infect NOS"
    },
    new HrDeptModel
    {
        DeptName = "Training",
        DeptCode = "T",
        Order = 3,
        IsActive = true,
        HasSubDepartments = false,
        ParentDeptCode = "HR",
        DeptDescription = "TB peritonitis-histo dx"
    },
    new HrDeptModel
    {
        DeptName = "Human Resources",
        DeptCode = "HR",
        Order = 1,
        IsActive = true,
        HasSubDepartments = true,
        ParentDeptCode = "S",
        DeptDescription = "Spon abor w pel inf-unsp"
    },
    new HrDeptModel
    {
        DeptName = "Services",
        DeptCode = "S",
        Order = 1,
        IsActive = true,
        HasSubDepartments = true,
        ParentDeptCode = "",
        DeptDescription = "Oth coll stn obj-per NEC"
    }
};
originHrEmpls
// HR 系統人員資料
List<HrEmplModel> originHrEmpls = new List<HrEmplModel>
{
    new HrEmplModel{
        Account = "costanza",
        Name = "松源",
        EnglishName = "costanza Posvner",
        EmployeeID = "A3897",
        ExpiredTime = null,
        Gender = "female",
        IDNumber = "A123456789",
        BirthDate = new DateTime(85, 7, 25),
        PhoneNumber = "0946323278",
        CardTitle = "Human Resources Assistant II",
        HireDate = new DateTime(2013, 12, 21),
        ResignationDate = new DateTime(2027, 10, 18),
        PrimaryEmail = "cposvner0@jalbum.net",
        SecondaryEmail = "cposvner0@phoca.cz",
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "UX",
        JobFunctions = null,
        DeptCode = "T",
        PrimaryDepartment = true,
        IsSupervisor = true
    },
    new HrEmplModel{
        Account = "natty",
        Name = "丰逸",
        EnglishName = "natty Yankin",
        EmployeeID = "A9618",
        ExpiredTime = null,
        Gender = "female",
        IDNumber = "A123456789",
        BirthDate = new DateTime(85, 1, 14),
        PhoneNumber = "0934756998",
        CardTitle = "Pharmacist",
        HireDate = new DateTime(2020, 3, 2),
        ResignationDate = null,
        PrimaryEmail = "nyankin1@wiley.com",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "PM",
        JobFunctions = new List<string> { "資訊安全管理" },
        DeptCode = "HR",
        PrimaryDepartment = true,
        IsSupervisor = false
    },
    new HrEmplModel{
        Account = "julina",
        Name = "思宇",
        EnglishName = "julina Andryushchenko",
        EmployeeID = "A8329",
        ExpiredTime = new DateTime(2029, 2, 10),
        Gender = "other",
        IDNumber = "A123456789",
        BirthDate = new DateTime(87, 4, 10),
        PhoneNumber = "0960836336",
        CardTitle = "Budget/Accounting Analyst III",
        HireDate = new DateTime(2024, 2, 7),
        ResignationDate = null,
        PrimaryEmail = "jandryushchenko2@amazon.co.uk",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "TA",
        JobFunctions = null,
        DeptCode = "RD",
        PrimaryDepartment = true,
        IsSupervisor = false
    },
    new HrEmplModel{
        Account = "catharina",
        Name = "琪煜",
        EnglishName = "catharina Edmonson",
        EmployeeID = "A6879",
        ExpiredTime = null,
        Gender = "female",
        IDNumber = "A123456789",
        BirthDate = new DateTime(90, 7, 18),
        PhoneNumber = "0995946581",
        CardTitle = "Actuary",
        HireDate = new DateTime(2012, 4, 15),
        ResignationDate = null,
        PrimaryEmail = "cedmonson3@free.fr",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "TA",
        JobFunctions = null,
        DeptCode = "BD",
        PrimaryDepartment = true,
        IsSupervisor = false
    },
    new HrEmplModel{
        Account = "nita",
        Name = "梓焓",
        EnglishName = "nita Lowre",
        EmployeeID = "A4523",
        ExpiredTime = new DateTime(2025, 9, 2),
        Gender = "male",
        IDNumber = "A123456789",
        BirthDate = new DateTime(63, 7, 18),
        PhoneNumber = "0995537127",
        CardTitle = "Compensation Analyst",
        HireDate = new DateTime(2011, 5, 17),
        ResignationDate = null,
        PrimaryEmail = "nlowre4@privacy.gov.au",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "SA",
        JobFunctions = new List<string> { "使用者介面設計" },
        DeptCode = "T",
        PrimaryDepartment = true,
        IsSupervisor = false
    },
    new HrEmplModel{
        Account = "cary",
        Name = "可馨",
        EnglishName = "cary Ralton",
        EmployeeID = "A4550",
        ExpiredTime = null,
        Gender = "male",
        IDNumber = "A123456789",
        BirthDate = new DateTime(62, 1, 26),
        PhoneNumber = "0969873340",
        CardTitle = "Dental Hygienist",
        HireDate = new DateTime(2008, 11, 24),
        ResignationDate = null,
        PrimaryEmail = "cralton5@admin.ch",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "QA",
        JobFunctions = null,
        DeptCode = "BD",
        PrimaryDepartment = true,
        IsSupervisor = false
    },
    new HrEmplModel{
        Account = "gelya",
        Name = "培安",
        EnglishName = "gelya Louder",
        EmployeeID = "A8117",
        ExpiredTime = null,
        Gender = "male",
        IDNumber = "A123456789",
        BirthDate = new DateTime(74, 3, 20),
        PhoneNumber = "0971386307",
        CardTitle = "Compensation Analyst",
        HireDate = new DateTime(2013, 7, 16),
        ResignationDate = null,
        PrimaryEmail = "glouder6@homestead.com",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "PM",
        JobFunctions = null,
        DeptCode = "HR",
        PrimaryDepartment = true,
        IsSupervisor = false
    },
    new HrEmplModel
    {
        Account = "nita",
        Name = "梓焓",
        EnglishName = "nita Lowre",
        EmployeeID = "A4523",
        ExpiredTime = new DateTime(2025, 9, 2),
        Gender = "male",
        IDNumber = "A123456789",
        BirthDate = new DateTime(63, 7, 18),
        PhoneNumber = "0995537127",
        CardTitle = "Compensation Analyst",
        HireDate = new DateTime(2011, 5, 17),
        ResignationDate = null,
        PrimaryEmail = "nlowre4@privacy.gov.au",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "TA",
        JobFunctions = null,
        DeptCode = "HR",
        PrimaryDepartment = false,
        IsSupervisor = false
    },
    new HrEmplModel
    {
        Account = "costanza",
        Name = "松源",
        EnglishName = "costanza Posvner",
        EmployeeID = "A3897",
        ExpiredTime = null,
        Gender = "female",
        IDNumber = "A123456789",
        BirthDate = new DateTime(85, 7, 25),
        PhoneNumber = "0946323278",
        CardTitle = "Human Resources Assistant II",
        HireDate = new DateTime(2013, 12, 21),
        ResignationDate = new DateTime(2027, 10, 18),
        PrimaryEmail = "cposvner0@jalbum.net",
        SecondaryEmail = "cposvner0@phoca.cz",
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "UX",
        JobFunctions = new List<string> { "資訊安全管理" },
        DeptCode = "RD",
        PrimaryDepartment = false,
        IsSupervisor = false
    },
    new HrEmplModel
    {
        Account = "carmelina",
        Name = "漫妮",
        EnglishName = "carmelina Beadle",
        EmployeeID = "A3583",
        ExpiredTime = null,
        Gender = "male",
        IDNumber = "A123456789",
        BirthDate = new DateTime(85, 10, 8),
        PhoneNumber = "0911960804",
        CardTitle = "Accounting Assistant I",
        HireDate = new DateTime(2011, 3, 26),
        ResignationDate = new DateTime(2027, 9, 25),
        PrimaryEmail = "cbeadle9@go.com",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "TA",
        JobFunctions = new List<string> { "軟體開發" },
        DeptCode = "HR",
        PrimaryDepartment = true,
        IsSupervisor = false
    },
    new HrEmplModel
    {
        Account = "raviv",
        Name = "澤瀚",
        EnglishName = "raviv Atcherley",
        EmployeeID = "A8007",
        ExpiredTime = null,
        Gender = "male",
        IDNumber = "A123456789",
        BirthDate = new DateTime(98, 4, 20),
        PhoneNumber = "0976843491",
        CardTitle = "Tax Accountant",
        HireDate = new DateTime(2018, 4, 21),
        ResignationDate = null,
        PrimaryEmail = "ratcherleya@yellowpages.com",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "TA",
        JobFunctions = null,
        DeptCode = "HR",
        PrimaryDepartment = true,
        IsSupervisor = false
    },
    new HrEmplModel
    {
        Account = "chev",
        Name = "雅靜",
        EnglishName = "chev Rowell",
        EmployeeID = "A287",
        ExpiredTime = null,
        Gender = "female",
        IDNumber = "A123456789",
        BirthDate = new DateTime(88, 5, 2),
        PhoneNumber = "0913313487",
        CardTitle = "Automation Specialist I",
        HireDate = new DateTime(2011, 5, 4),
        ResignationDate = null,
        PrimaryEmail = "crowellb@paginegialle.it",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "BA",
        JobFunctions = null,
        DeptCode = "S",
        PrimaryDepartment = true,
        IsSupervisor = true
    },
    new HrEmplModel
    {
        Account = "ezmeralda",
        Name = "若瑾",
        EnglishName = "ezmeralda Durkin",
        EmployeeID = "A6996",
        ExpiredTime = new DateTime(2029, 5, 18),
        Gender = "other",
        IDNumber = "A123456789",
        BirthDate = new DateTime(56, 6, 8),
        PhoneNumber = "0918733181",
        CardTitle = "Office Assistant I",
        HireDate = new DateTime(2024, 10, 28),
        ResignationDate = new DateTime(2027, 1, 9),
        PrimaryEmail = "edurkinc@tripadvisor.com",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "UX",
        JobFunctions = null,
        DeptCode = "RD",
        PrimaryDepartment = true,
        IsSupervisor = true
    },
    new HrEmplModel
    {
        Account = "rina",
        Name = "俊澤",
        EnglishName = "rina Cootes",
        EmployeeID = "A9043",
        ExpiredTime = new DateTime(2029, 10, 8),
        Gender = "female",
        IDNumber = "A123456789",
        BirthDate = new DateTime(62, 6, 6),
        PhoneNumber = "0971141652",
        CardTitle = "Senior Quality Engineer",
        HireDate = new DateTime(2017, 3, 9),
        ResignationDate = null,
        PrimaryEmail = "rcootesd@unblog.fr",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "UX",
        JobFunctions = new List<string> { "軟體開發" },
        DeptCode = "HR",
        PrimaryDepartment = true,
        IsSupervisor = false
    },
    new HrEmplModel
    {
        Account = "dory",
        Name = "雨婷",
        EnglishName = "dory Ashard",
        EmployeeID = "A6241",
        ExpiredTime = null,
        Gender = "male",
        IDNumber = "A123456789",
        BirthDate = new DateTime(61, 4, 4),
        PhoneNumber = "0944897131",
        CardTitle = "Business Systems Development Analyst",
        HireDate = new DateTime(2013, 2, 25),
        ResignationDate = null,
        PrimaryEmail = "dasharde@latimes.com",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "BE",
        JobFunctions = null,
        DeptCode = "HR",
        PrimaryDepartment = true,
        IsSupervisor = true
    },
    new HrEmplModel
    {
        Account = "conway",
        Name = "浩霖",
        EnglishName = "conway McAlpine",
        EmployeeID = "A2169",
        ExpiredTime = null,
        Gender = "female",
        IDNumber = "A123456789",
        BirthDate = new DateTime(100, 6, 25),
        PhoneNumber = "0975728820",
        CardTitle = "Physical Therapy Assistant",
        HireDate = new DateTime(2016, 4, 8),
        ResignationDate = new DateTime(2029, 12, 20),
        PrimaryEmail = "cmcalpinef@ted.com",
        SecondaryEmail = "cmcalpinef@scientificamerican.com",
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "PM",
        JobFunctions = null,
        DeptCode = "BD",
        PrimaryDepartment = true,
        IsSupervisor = false
    },
    new HrEmplModel
    {
        Account = "rebekkah",
        Name = "博豪",
        EnglishName = "rebekkah Trotter",
        EmployeeID = "A1287",
        ExpiredTime = null,
        Gender = "male",
        IDNumber = "A123456789",
        BirthDate = new DateTime(85, 4, 1),
        PhoneNumber = "0981131325",
        CardTitle = "Assistant Professor",
        HireDate = new DateTime(2020, 5, 8),
        ResignationDate = new DateTime(2026, 6, 4),
        PrimaryEmail = "rtrotterg@abc.net.au",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "IS",
        JobFunctions = null,
        DeptCode = "T",
        PrimaryDepartment = true,
        IsSupervisor = false
    },
    new HrEmplModel
    {
        Account = "kath",
        Name = "博裕",
        EnglishName = "kath Duplain",
        EmployeeID = "A1665",
        ExpiredTime = null,
        Gender = "male",
        IDNumber = "A123456789",
        BirthDate = new DateTime(81, 11, 11),
        PhoneNumber = "0934443938",
        CardTitle = "Payment Adjustment Coordinator",
        HireDate = new DateTime(2011, 12, 21),
        ResignationDate = new DateTime(2026, 4, 5),
        PrimaryEmail = "kduplainh@cnet.com",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "FE",
        JobFunctions = null,
        DeptCode = "BD",
        PrimaryDepartment = true,
        IsSupervisor = true
    },
    new HrEmplModel
    {
        Account = "betteanne",
        Name = "芮涵",
        EnglishName = "betteanne Facchini",
        EmployeeID = "A3542",
        ExpiredTime = null,
        Gender = "female",
        IDNumber = "A123456789",
        BirthDate = new DateTime(62, 6, 8),
        PhoneNumber = "0960426450",
        CardTitle = "Account Coordinator",
        HireDate = new DateTime(2009, 9, 25),
        ResignationDate = new DateTime(2029, 11, 13),
        PrimaryEmail = "bfacchinii@cnbc.com",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "SA",
        JobFunctions = null,
        DeptCode = "RD",
        PrimaryDepartment = true,
        IsSupervisor = false
    },
    new HrEmplModel
    {
        Account = "betteanne",
        Name = "芮涵",
        EnglishName = "betteanne Facchini",
        EmployeeID = "A3542",
        ExpiredTime = null,
        Gender = "female",
        IDNumber = "A123456789",
        BirthDate = new DateTime(62, 6, 8),
        PhoneNumber = "0960426450",
        CardTitle = "Account Coordinator",
        HireDate = new DateTime(2009, 9, 25),
        ResignationDate = new DateTime(2029, 11, 13),
        PrimaryEmail = "bfacchinii@cnbc.com",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "TA",
        JobFunctions = new List<string> { "技術支援" },
        DeptCode = "T",
        PrimaryDepartment = false,
        IsSupervisor = false
    },
    new HrEmplModel
    {
        Account = "antons",
        Name = "辰華",
        EnglishName = "antons Walcher",
        EmployeeID = "A8962",
        ExpiredTime = new DateTime(2029, 5, 14),
        Gender = "female",
        IDNumber = "A123456789",
        BirthDate = new DateTime(79, 7, 26),
        PhoneNumber = "0931240619",
        CardTitle = "Environmental Tech",
        HireDate = new DateTime(2013, 5, 8),
        ResignationDate = null,
        PrimaryEmail = "awalcherj@google.fr",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "BE",
        JobFunctions = new List<string> { "後端開發", "技術支援" },
        DeptCode = "T",
        PrimaryDepartment = true,
        IsSupervisor = false
    },
    new HrEmplModel
    {
        Account = "antons",
        Name = "辰華",
        EnglishName = "antons Walcher",
        EmployeeID = "A8962",
        ExpiredTime = new DateTime(2029, 5, 14),
        Gender = "female",
        IDNumber = "A123456789",
        BirthDate = new DateTime(79, 7, 26),
        PhoneNumber = "0931240619",
        CardTitle = "Environmental Tech",
        HireDate = new DateTime(2013, 5, 8),
        ResignationDate = null,
        PrimaryEmail = "awalcherj@google.fr",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "PM",
        JobFunctions = new List<string> { "專案管理" },
        DeptCode = "S",
        PrimaryDepartment = false,
        IsSupervisor = false
    }
};

一、同步部門

接下來,我們將逐步撰寫範例,首先會針對 HR 系統資料做處理,再將 HR 系統與 UOF X 部門資料進行比對與同步:

A. 部門資料階層化

因為該範例的 HR 系統資料並沒有 部門階層,所以在第一步,我們先將部門資料階層化:

  1. 篩選出 originHrDeptsHasSubDepartmentstrue 的父部門,並儲存到 getHRParentDeptList
  2. 針對每個父部門,在 originHrDepts 中篩選出 ParentDeptCode 等於父部門代號的子部門,並將結果存入父部門的 SubDepts 中。
  3. 篩選出 ParentDeptCode 為空的部門,表示這些是最高階層的部門,並回傳結果。

如此一來,程式會生成部門的階層結構,每個部門的 SubDepts 欄位會包含其子部門,最上層的部門結構則會作為結果回傳。

部門資料階層化
List<HrDeptModel> BuildHierarchy(List<HrDeptModel> originHrDepts)
{
    // 取得有子部門的父部門資料
    var getHRParentDeptList = originHrDepts.Where(x => x.HasSubDepartments).ToList();
    // 將子部門資料加入父部門
    foreach (var dept in getHRParentDeptList)
    {
        var getSubDept = originHrDepts.Where(x => x.ParentDeptCode == dept.DeptCode).ToList();
        dept.SubDepts = getSubDept;
    };
    // 取得沒有父部門的部門資料
    var hierarchyDeptList = originHrDepts.Where(x => string.IsNullOrEmpty(x.ParentDeptCode)).ToList();
    return hierarchyDeptList;
};

資料的變化
getHRParentDeptList
new List<HrDeptModel>
{
    new HrDeptModel
    {
        DeptName = "Human Resources",
        DeptCode = "HR",
        Order = 1,
        IsActive = true,
        HasSubDepartments = true,
        ParentDeptCode = "S",
        DeptDescription = "Spon abor w pel inf-unsp"
    },
    new HrDeptModel
    {
        DeptName = "Services",
        DeptCode = "S",
        Order = 1,
        IsActive = true,
        HasSubDepartments = true,
        ParentDeptCode = "",
        DeptDescription = "Oth coll stn obj-per NEC"
    }
}
originHrDepts
new List<HrDeptModel>
{
    new HrDeptModel
    {
        DeptName = "Research and Development",
        DeptCode = "RD",
        Order = 2,
        IsActive = true,
        HasSubDepartments = false,
        ParentDeptCode = "HR",
        DeptDescription = "Contamination NEC"
    },
    new HrDeptModel
    {
        DeptName = "Business Development",
        DeptCode = "BD",
        Order = 1,
        IsActive = true,
        HasSubDepartments = false,
        ParentDeptCode = "HR",
        DeptDescription = "Adenovirus infect NOS"
    },
    new HrDeptModel
    {
        DeptName = "Training",
        DeptCode = "T",
        Order = 3,
        IsActive = true,
        HasSubDepartments = false,
        ParentDeptCode = "HR",
        DeptDescription = "TB peritonitis-histo dx"
    },
    new HrDeptModel
    {
        DeptName = "Human Resources",
        DeptCode = "HR",
        Order = 1,
        IsActive = true,
        HasSubDepartments = true,
        ParentDeptCode = "S",
        DeptDescription = "Spon abor w pel inf-unsp"
        SubDepts = new List<HrDeptModel>
        {
            new HrDeptModel
            {
                DeptName = "Research and Development",
                DeptCode = "RD",
                Order = 2,
                IsActive = true,
                HasSubDepartments = false,
                ParentDeptCode = "HR",
                DeptDescription = "Contamination NEC"
            },
            new HrDeptModel
            {
                DeptName = "Business Development",
                DeptCode = "BD",
                Order = 1,
                IsActive = true,
                HasSubDepartments = false,
                ParentDeptCode = "HR",
                DeptDescription = "Adenovirus infect NOS"
            },
            new HrDeptModel
            {
                DeptName = "Training",
                DeptCode = "T",
                Order = 3,
                IsActive = true,
                HasSubDepartments = false,
                ParentDeptCode = "HR",
                DeptDescription = "TB peritonitis-histo dx"
            }  
        }
    },
    new HrDeptModel
    {
        DeptName = "Services",
        DeptCode = "S",
        Order = 1,
        IsActive = true,
        HasSubDepartments = true,
        ParentDeptCode = "",
        DeptDescription = "Oth coll stn obj-per NEC"
        SubDepts = new List<HrDeptModel>
        {
            new HrDeptModel
            {
                DeptName = "Human Resources",
                DeptCode = "HR",
                Order = 1,
                IsActive = true,
                HasSubDepartments = true,
                ParentDeptCode = "S",
                DeptDescription = "Spon abor w pel inf-unsp"
                SubDepts = new List<HrDeptModel>
                {
                    new HrDeptModel
                    {
                        DeptName = "Research and Development",
                        DeptCode = "RD",
                        Order = 2,
                        IsActive = true,
                        HasSubDepartments = false,
                        ParentDeptCode = "HR",
                        DeptDescription = "Contamination NEC"
                    },
                    new HrDeptModel
                    {
                        DeptName = "Business Development",
                        DeptCode = "BD",
                        Order = 1,
                        IsActive = true,
                        HasSubDepartments = false,
                        ParentDeptCode = "HR",
                        DeptDescription = "Adenovirus infect NOS"
                    },
                    new HrDeptModel
                    {
                        DeptName = "Training",
                        DeptCode = "T",
                        Order = 3,
                        IsActive = true,
                        HasSubDepartments = false,
                        ParentDeptCode = "HR",
                        DeptDescription = "TB peritonitis-histo dx"
                    }  
                }
            }  
        }
    }
}
hierarchyDeptList
new HrDeptModel
{
    DeptName = "Services",
    DeptCode = "S",
    Order = 1,
    IsActive = true,
    HasSubDepartments = true,
    ParentDeptCode = "",
    DeptDescription = "Oth coll stn obj-per NEC"
    SubDepts = new List<HrDeptModel>
    {
        new HrDeptModel
        {
            DeptName = "Human Resources",
            DeptCode = "HR",
            Order = 1,
            IsActive = true,
            HasSubDepartments = true,
            ParentDeptCode = "S",
            DeptDescription = "Spon abor w pel inf-unsp"
            SubDepts = new List<HrDeptModel>
            {
                new HrDeptModel
                {
                    DeptName = "Research and Development",
                    DeptCode = "RD",
                    Order = 2,
                    IsActive = true,
                    HasSubDepartments = false,
                    ParentDeptCode = "HR",
                    DeptDescription = "Contamination NEC"
                },
                new HrDeptModel
                {
                    DeptName = "Business Development",
                    DeptCode = "BD",
                    Order = 1,
                    IsActive = true,
                    HasSubDepartments = false,
                    ParentDeptCode = "HR",
                    DeptDescription = "Adenovirus infect NOS"
                },
                new HrDeptModel
                {
                    DeptName = "Training",
                    DeptCode = "T",
                    Order = 3,
                    IsActive = true,
                    HasSubDepartments = false,
                    ParentDeptCode = "HR",
                    DeptDescription = "TB peritonitis-histo dx"
                }  
            }
        }  
    }
}

B. 建立部門層級與轉換後的部門 List

有了階層化的部門資料,我們就可以建立部門階層資料及包含 DeptLevelCode,的完整部門資料:

  1. 逐一處理階層化的部門資料,若該層級還未加入,則新增一筆新的層級資料至 deptLevList
  2. 將部門資料轉換為 DeptModel 型別,並加入 DeptLevelCode
  3. 若該部門存在子部門,則將子部門資料以及層級 +1 傳遞給遞迴函式。

最終將回傳所有部門層級資料的 List,以及每個部門具備階層資訊的完整部門 List。

建立部門層級與轉換後的部門 List
(List<DeptLevelViewModel> deptLevList, List<DeptModel> deptList) BuildDeptLevs(List<HrDeptModel> hierarchy, int level)
{
    // 部門層級 List 
    var deptLevList = new List<DeptLevelViewModel>();
    // 部門 List 
    var deptList = new List<DeptModel>();

    foreach (var dept in hierarchy)
    {
        // 確認不新增重複部門層級
        if (!deptLevList.Any(x => x.Seq == level))
        {
            // 新增當前層級的資料
            deptLevList.Add(new DeptLevelViewModel
            {
                Code = $"lev{level}", // 部門層級代號
                Name = $"Level{level}", // 部門層級名稱
                Seq = level, // 部門層級的階層與排序 ( 從 1 開始 )
                CorpCode = _corpCode, // 公司代號
                Active = true // 是否啟用部門層級
            });
        }
        // 轉換部門資料並加入部門層級
        deptList.Add(new DeptModel
        {
            Code = dept.DeptCode, // 部門代號
            Name = dept.DeptName, // 部門名稱
            Active = true, // 是否啟用部門
            IncludeSubDept = dept.HasSubDepartments, // 是否包含子部門
            ParentCode = dept.ParentDeptCode, // 父部門代號 ( 可設定為空值,代表無父部門 )
            Description = dept.DeptDescription, // 描述
            DeptLevelCode = $"lev{level}", // 部門層級代號
            Seq = dept.Order - 1 // 部門的排序 ( 從 0 開始 )
        });
        // 遞迴處理子部門,層級加 1,並將結果加入 deptLevList 與 deptList
        if (dept.SubDepts != null && dept.SubDepts.Count > 0)
        {
            var (deptLevListTemp, deptListTemp) = BuildDeptLevs(dept.SubDepts, level + 1);
            deptLevList.AddRange(deptLevListTemp);
            deptList.AddRange(deptListTemp);
        }
    };
    return (deptLevList, deptList);
};
回傳的資料
deptLevList
new List<DeptLevelViewModel>()
{
    new DeptLevelViewModel
    {
        "Seq": 1,
        "Code": "lev1",
        "Name": "Level1",
        "Active": true,
        "CorpCode": "woni"
    },
    new DeptLevelViewModel
    {
        "Seq": 2,
        "Code": "lev2",
        "Name": "Level2",
        "Active": true,
        "CorpCode": "woni"
    },
    new DeptLevelViewModel
    {
        "Seq": 3,
        "Code": "lev3",
        "Name": "Level3",
        "Active": true,
        "CorpCode": "woni"
    }
}
deptList
new List<DeptLevelViewModel>()
{
    new DeptLevelViewModel
    {
        "Code": "S",
        "Name": "Services",
        "Active": true,
        "IncludeSubDept": true,
        "ParentCode": "",
        "Description": "Oth coll stn obj-per NEC",
        "DeptLevelCode": "lev1",
        "Seq": 0,
        "SubDepts": []
    },
    {
        "Code": "HR",
        "Name": "Human Resources",
        "Active": true,
        "IncludeSubDept": true,
        "ParentCode": "S",
        "Description": "Spon abor w pel inf-unsp",
        "DeptLevelCode": "lev2",
        "Seq": 0,
        "SubDepts": []
    },
    {
        "Code": "RD",
        "Name": "Research and Development",
        "Active": true,
        "IncludeSubDept": false,
        "ParentCode": "HR",
        "Description": "Contamination NEC",
        "DeptLevelCode": "lev3",
        "Seq": 1,
        "SubDepts": []
    },
    {
        "Code": "BD",
        "Name": "Business Development",
        "Active": true,
        "IncludeSubDept": false,
        "ParentCode": "HR",
        "Description": "Adenovirus infect NOS",
        "DeptLevelCode": "lev3",
        "Seq": 0,
        "SubDepts": []
    },
    {
        "Code": "T",
        "Name": "Training",
        "Active": true,
        "IncludeSubDept": false,
        "ParentCode": "HR",
        "Description": "TB peritonitis-histo dx",
        "DeptLevelCode": "lev3",
        "Seq": 2,
        "SubDepts": []
    }
}

C. 比對與同步部門階層資料

若要 同步 部門層級資料,需要將 HR 系統與 UOF X 的部門階層資料做比對:

  1. 建立三個 List 來存放待處理的資料。
  2. 取得現有的 UOF X 部門層級資料。
  3. 建立以 Code 為鍵的字典 hrDeptDictuofDeptDict,以加快查找速度。
  4. 逐一處理 hrDeptLevs 中的每個部門層級資料:
    • hrDept 已存在於 uofDeptDict 中,則加入 deptLevelToUpdate List 進行更新。
    • hrDept 不存在於 uofDeptDict 中,則加入 deptLevelToAdd List 進行新增。
  5. 逐一處理 uofDeptLevs 中的每個部門層級資料:
    • uofDept 不存在於 hrDeptDict 中,則加入 deptLevelToDeactivate List 進行停用。
  6. 使用非同步方法處理 List 中需要更新、新增與停用的部門層級資料。

如此一來,部門層級資料會被分為 需更新需新增需停用 三種 List。

比對與同步部門階層資料
async Task MapDeptLevels(List<DeptLevelViewModel> hrDeptLevs)
{
    // 更新 List 
    var deptLevelToUpdate = new List<DeptLevelViewModel>();
    // 新增 List 
    var deptLevelToAdd = new List<DeptLevelViewModel>();
    // 停用 List 
    var deptLevelToDeactivate = new List<DeptLevelViewModel>();

    // 取得部門層級資料
    var uofDeptLevs = await UofxService.BASE.DeptLevel.Get(_corpCode);

    // 建立以 Code 為鍵的字典以加快查找速度
    var hrDeptDict = hrDeptLevs.ToDictionary(x => x.Code, x => x);
    var uofDeptDict = uofDeptLevs.ToDictionary(x => x.Code, x => x);

    // 處裡要更新和新增的項目
    foreach (var hrDept in hrDeptLevs)
    {
        // 若此項目已存在,加入更新 List 
        if (uofDeptDict.ContainsKey(hrDept.Code))
        {
            deptLevelToUpdate.Add(hrDept);
        }
        // 此項目不存在,加入新增 List 
        else
        {
            deptLevelToAdd.Add(hrDept);
        }
    };
    // 處理要停用的項目
    foreach (var uofDept in uofDeptLevs)
    {
        // 若此項目不存在於 HR 系統,加入停用 List 
        if (!hrDeptDict.ContainsKey(uofDept.Code))
        {
            deptLevelToDeactivate.Add(uofDept);
        };
    };

    // 更新部門層級
    await UpdateDeptLevels(deptLevelToUpdate);
    // 新增部門層級
    await CreateDeptLevels(deptLevelToAdd);
    // 停用部門層級
    await DeactivateDeptLevels(deptLevelToDeactivate);
};
更新、新增與停用部門階層的方法
UpdateDeptLevels
async Task UpdateDeptLevels(List<DeptLevelViewModel> deptLevels)
{
    foreach (var deptLevel in deptLevels)
    {
        try
        {
            // 更新部門層級狀態
            var updateStatus = await UofxService.BASE.DeptLevel.UpdateStatus(new DeptLevelUpdateStatusModel()
            {
                CorpCode = _corpCode, // 公司代號
                Code = deptLevel.Code, // 部門層級代號
                Active = true // 是否啟用部門層級
            });
            // 更新部門層級資料
            var update = await UofxService.BASE.DeptLevel.Update(new DeptLevelUpdateModel()
            {
                CorpCode = _corpCode, // 公司代號
                OriginalCode = deptLevel.Code, // 原始部門層級代號 ( 設為部門層級代號 )
                Code = deptLevel.Code, // 部門層級代號
                Name = deptLevel.Name // 部門層級名稱
            });
            // 若部門層級狀態、資料更新成功,加入成功訊息
            if (updateStatus == true && update == true) successMsg.Add($"已更新 {deptLevel.Code} 部門層級");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法更新 {deptLevel.Code} 部門層級,error: {ex.Message}");
        }
    };
};
CreateDeptLevels
async Task CreateDeptLevels(List<DeptLevelViewModel> deptLevels)
{
    foreach (var deptLevel in deptLevels)
    {
        try
        {
            // 新增部門層級
            var resualt = await UofxService.BASE.DeptLevel.Create(deptLevel);
            // 若新增成功,加入成功訊息
            if (resualt == true) successMsg.Add($"已新增 {deptLevel.Code} 部門層級");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法新增 {deptLevel.Code} 部門層級,error: {ex.Message}");
        }
    };
};
DeactivateDeptLevels
async Task DeactivateDeptLevels(List<DeptLevelViewModel> deptLevels)
{
    foreach (var deptLevel in deptLevels)
    {
        try
        {
            // 停用部門層級
            var resualt = await UofxService.BASE.DeptLevel.UpdateStatus(new DeptLevelUpdateStatusModel()
            {
                CorpCode = _corpCode, // 公司代號
                Code = deptLevel.Code, // 部門層級代號
                Active = false // 停用部門層級
            });
            // 若停用成功,加入成功訊息
            if (resualt == true) successMsg.Add($"已停用 {deptLevel.Code} 部門層級");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法停用 {deptLevel.Code} 部門層級,error: {ex.Message}");
        }
    };
};

D. 比對與同步部門資料

若要 同步 部門資料,需要將 HR 系統與 UOF X 的部門資料做比對:

  1. 建立三個 List 來存放待處理的資料。
  2. 取得現有的 UOF X 部門資料。
  3. 建立以 Code 為鍵的字典 hrDeptDictuofDeptDict,以加快查找速度。
  4. 逐一處理 hrDepts 中的每個部門資料:
    • hrDept 已存在於 uofDeptDict 中,則加入 deptToUpdate List 進行更新。
    • hrDept 不存在於 uofDeptDict 中,則加入 deptToAdd List 進行新增。
  5. 逐一處理 uofDepts 中的每個部門資料:
    • uofDept 不存在於 hrDeptDict 中,則加入 deptToDeactivate List 進行停用。
  6. 使用非同步方法處理 List 中需要更新、新增與停用的部門資料。

如此一來,部門資料會被分為 需更新需新增需停用 三種 List。

比對與同步部門資料
async Task MapDepts(List<DeptModel> hrDepts)
{
    // 更新 List 
    var deptToUpdate = new List<DeptModel>();
    // 新增 List 
    var deptToAdd = new List<DeptModel>();
    // 停用 List 
    var deptToDeactivate = new List<DeptModel>();

    // 取得部門資料
    var uofDepts = await UofxService.BASE.Department.Get(new DepartmentGetModel() { CorpCode = _corpCode, IncludeSubDept = true });

    // 建立以 Code 為鍵的字典以加快查找速度
    var hrDeptDict = hrDepts.ToDictionary(x => x.Code, x => x);
    var uofDeptDict = uofDepts.ToDictionary(x => x.Code, x => x);

    // 處理要更新和新增的項目
    foreach (var hrDept in hrDepts)
    {
        // 若此項目已存在,加入更新 List 
        if (uofDeptDict.ContainsKey(hrDept.Code))
        {
            deptToUpdate.Add(new DeptModel
            {
                ParentCode = hrDept.ParentCode, // 父部門代號 ( 可設定為空值,代表無父部門 )
                Code = hrDept.Code, // 部門代號
                Name = hrDept.Name, // 部門名稱
                DeptLevelCode = hrDept.DeptLevelCode, // 部門層級代號
                Description = hrDept.Description, // 描述
                Active = hrDept.Active, // 是否啟用部門
                Seq = hrDept.Seq // 部門的排序 ( 從 0 開始 )
            });
        }
        // 此項目不存在,加入新增 List 
        else
        {
            deptToAdd.Add(new DeptModel
            {
                ParentCode = hrDept.ParentCode, // 父部門代號 ( 可設定為空值,代表無父部門 )
                Code = hrDept.Code, // 部門代號
                Name = hrDept.Name, // 部門名稱
                DeptLevelCode = hrDept.DeptLevelCode, // 部門層級代號
                Description = hrDept.Description, // 描述
                Active = hrDept.Active, // 是否啟用部門
                Seq = hrDept.Seq // 部門的排序 ( 從 0 開始 )
            });
        }
    };
    // 處理要停用的項目
    foreach (var uofDept in uofDepts)
    {
        // 若此項目不存在於 HR 系統,加入停用 List 
        if (!hrDeptDict.ContainsKey(uofDept.Code))
        {
            deptToDeactivate.Add(new DeptModel
            {
                Code = uofDept.Code, // 部門代號
                Active = uofDept.Active, // 是否啟用部門
                DeptLevelCode = uofDept.DeptLevelCode // 部門層級代號
            });
        }
    };

    // 更新部門
    await UpdateDepartments(deptToUpdate);
    // 新增部門
    await CreateDepartments(deptToAdd);
    // 停用部門
    await DeactivateDepartments(deptToDeactivate);
};
更新、新增與停用部門的方法
UpdateDepartments
async Task UpdateDepartments(List<DeptModel> departments)
{
    foreach (var dept in departments)
    {
        try
        {
            // 更新部門狀態
            var updateStatus = await UofxService.BASE.Department.UpdateState(new DepartmentUpdateStatusModel()
            {
                CorpCode = _corpCode, // 公司代號
                Code = dept.Code, // 部門代號
                Active = dept.Active // 是否啟用部門
            });
            // 更新部門資料
            var update = await UofxService.BASE.Department.Update(new DepartmentUpdateModel()
            {
                CorpCode = _corpCode, // 公司代號
                OriginalCode = dept.Code, // 原始部門代號 ( 設為部門代號 )
                Code = dept.Code, // 部門代號
                Name = dept.Name, // 部門名稱
                DeptLevelCode = dept.DeptLevelCode, // 部門層級代號
                Description = dept.Description // 描述
            });
            // 若部門狀態、資料更新成功,加入成功訊息
            if (update == true) successMsg.Add($"已更新 {dept.Code} 部門");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法更新 {dept.Code} 部門,error: {ex.Message}");
        }
    };
};
CreateDepartments
async Task CreateDepartments(List<DeptModel> departments)
{
    try
    {
        foreach (var dept in departments)
        {
            // 新增部門
            var result = await UofxService.BASE.Department.Create(new DepartmentCreateModel()
            {
                ParentCode = dept.ParentCode, // 父部門代號 ( 可設定為空值,代表無父部門 )
                CorpCode = _corpCode, // 公司代號
                Code = dept.Code, // 部門代號
                DeptLevelCode = dept.DeptLevelCode, // 部門層級代號
                Name = dept.Name, // 部門名稱
                Description = dept.Description // 描述
            });
            // 若新增成功,加入成功訊息
            if (result == true) successMsg.Add($"已新增 {dept.Code} 部門");
        };
    }
    catch (Exception ex)
    {
        // 捕捉錯誤並記錄到 errorMsg 中  
        errorMsg.Add($"無法新增 {departments} 部門,error: {ex.Message}");
    }
};
DeactivateDepartments
async Task DeactivateDepartments(List<DeptModel> departments)
{
    foreach (var dept in departments)
    {
        try
        {
            // 停用部門
            var result = await UofxService.BASE.Department.UpdateState(new DepartmentUpdateStatusModel()
            {
                CorpCode = _corpCode, // 公司代號
                Code = dept.Code, // 部門代號
                Active = false // 是否啟用部門
            });
            // 若停用成功,加入成功訊息
            if (result == true) successMsg.Add($"已停用 {dept.Code} 部門");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法停用 {dept.Code} 部門,error: {ex.Message}");
        }
    };
};

E. 彙整部門同步函式

因為執行多個函式,因此彙整至 SyncDepts 函式中:

同步部門資料
async Task SyncDepts(List<HrDeptModel> originHrDepts)
{
    // 部門資料階層化
    var hierarchyDeptList = BuildHierarchy(originHrDepts);
    // 建立部門層級與轉換後的部門 List 
    var (deptLevList, deptList) = BuildDeptLevs(hierarchyDeptList, 1);
    // 比對與同步部門階層資料
    await MapDeptLevels(deptLevList);
    // 比對與同步部門資料
    await MapDepts(deptList);
};

二、同步人員

在同步完部門後,即可確保人員中的部門資料已同步完畢,首先一樣針對 HR 系統資料做處理,再將 HR 系統與 UOF X 人員資料進行比對與同步:

A. 同步職稱職務

在同步前,需要先確認每筆人員資料中的職稱職務是否存在:

  1. 取得 HR 系統中的所有職稱(JobTitleID)、職務(JobFunctions)。
  2. 取得現有的 UOF X 職稱(uofJobTitles)、職務(uofJobFuncs)。
  3. 使用 Except 找出需要新增的職稱職務:
    • JobTitleCodes 中不存在於 uofJobTitles 的項目,將其存入 JobTitleToAdd
    • JobFuncs 中不存在於 uofJobFuncs 的項目,將其存入 JobFuncToAdd
  4. JobTitleToAdd 的職稱項目進行新增。
  5. JobFuncToAdd 的職務項目進行新增。
同步職稱職務
async Task SyncJobTitlesAndFunctions(List<HrEmplModel> originHrEmpls)
{
    // 取得 HR 系統職稱職務
    List<string> JobTitleCodes = originHrEmpls.Select(e => e.JobTitleID).ToList();
    List<string> JobFuncs = originHrEmpls.SelectMany(e => e.JobFunctions ?? new List<string>()).ToList();

    // 取得 UOF X 系統職稱職務
    var uofJobTitles = await UofxService.BASE.JobTitle.Get(_corpCode);
    var uofJobFuncs = await UofxService.BASE.JobFunc.Get(_corpCode);

    // 找出要新增的職稱職務
    var JobTitleToAdd = JobTitleCodes.Except(uofJobTitles.Select(j => j.Code)).ToList();
    var JobFuncToAdd = JobFuncs.Except(uofJobFuncs.Select(j => j.Code)).ToList();

    foreach (var jobTitle in JobTitleToAdd)
    {
        try
        {
            // 新增職稱
            var result = await UofxService.BASE.JobTitle.Create(new JobTitleViewModel()
            {
                CorpCode = _corpCode, // 職稱代號
                Rank = 50, // 職稱階層 (1)
                Seq = 1, // 職稱排序 ( 從 1 開始 )
                Code = jobTitle, // 職稱代號
                Title = jobTitle, // 職稱名稱
                Active = true // 啟用職稱
            });
            // 若新增成功,加入成功訊息
            if (result == true) successMsg.Add($"已新增 {jobTitle} 職稱");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法新增 {jobTitle} 職稱,error: {ex.Message}");
        }
    };
    foreach (var jobFunc in JobFuncToAdd)
    {
        try
        {
            // 新增職務
            var result = await UofxService.BASE.JobFunc.Create(new JobFuncViewModel()
            {
                CorpCode = _corpCode, // 職稱代號
                CategoryName = jobFunc, // 職務類別名稱
                Code = jobFunc, // 職務代號
                JobFunc = jobFunc, // 職務名稱
                Active = true // 啟用職務
            });
            // 若新增成功,加入成功訊息
            if (result == true) successMsg.Add($"已新增 {jobFunc} 職務");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法新增 {jobFunc} 職務,error: {ex.Message}");
        }
    };
};
  1. 🙋‍♂️ 因為該範例無法從職稱的字面上判斷出「簽何層級」,因此將簽核層級預設為 50,後續再由管理員到 UOF X 進行調整。

B. 轉換人員 List

在進行人員資料的比對前,需要先進行以下處理:

  1. Account 為 Key 做分組,並將結果存入 groupedEmpls
  2. 將人員資料轉換為 EmplModel
  3. 將需要整理的資料做轉換:
    • Gendermale, female, other 轉換為 0, 1, 2
    • BirthDate 將 民國年轉換為西元年。
轉換人員 List
List<EmplModel> ConvertToEmplModels(List<HrEmplModel> originHrEmpls)
{
    // 以 Account 作為 key 分組
    var groupedEmpls = originHrEmpls.GroupBy(e => e.Account);
    // 每一組的分組資料 g 轉換成一個新的 EmplModel
    return groupedEmpls.Select(g => new EmplModel
    {
        CorpCode = _corpCode, // 公司代號
        Account = g.Key, // 人員帳號
        Name = g.First().Name, // 中文姓名
        Gender = g.First().Gender == "male" ? "0" : (g.First().Gender == "female" ? "1" : "2"), // 性別 ( 男性 male => 0, 女性 female => 1, 其他 other => 2 )
        EnglishName = g.First().EnglishName, // 英文姓名
        EmployeeNumber = g.First().EmployeeID, // 員工編號
        ExpiredTime = g.First().ExpiredTime, // 帳號過期時間
        ResignationDate = g.First().ResignationDate, // 離職日
        IdCardNumber = g.First().IDNumber, // 身份證字號
        BirthDate = new DateTime(g.First().BirthDate.Year + 1911, g.First().BirthDate.Month, g.First().BirthDate.Day), // 生日 (民國年轉為西元年)
        PhoneNumber = g.First().PhoneNumber, // 行動電話
        BusinessCard = g.First().CardTitle, // 名片職稱
        HireDate = g.First().HireDate, // 到職日
        Email = g.First().PrimaryEmail, // 主要信箱
        EmailEx = g.First().SecondaryEmail, // 其他信箱
        Active = g.First().AccountActive, // 是否停用人員帳號
        Locked = g.First().AccountLocked, // 是否鎖定人員帳號
        // 將每個分組中的資料轉換成一個新的 EmpCreateOfDeptItemRequestModel
        Depts = g.Select(e => new EmpCreateOfDeptItemRequestModel
        {
            Code = e.DeptCode, // 部門代號
            IsMainDept = e.PrimaryDepartment, // 是否為主要部門
            JobTitleCode = e.JobTitleID, // 職稱代號
            JobFuncs = e.JobFunctions // 職務代號 ( 可多筆 )
        }).ToList(),
        // 將每個分組中的主管資料轉換成一個新的 DeptSetManagerModel
        Supervisor = g.Where(g => g.IsSupervisor).Select(e => new DeptSetManagerModel
        {
            CorpCode = _corpCode, // 公司代號
            Code = e.DeptCode, // 部門代號
            Type = UserType.Account, // 人員的類別 ( 設為 Account )
            Value = e.Account // 帳號
        }).ToList()
    }).ToList();
};
資料的變化
originHrEmpls
new List<HrEmplModel>
{
    new HrEmplModel
    {
        Account = "costanza",
        Name = "松源",
        EnglishName = "costanza Posvner",
        EmployeeID = "A3897",
        ExpiredTime = null,
        Gender = "female",
        IDNumber = "A123456789",
        BirthDate = new DateTime(85, 7, 25),
        PhoneNumber = "0946323278",
        CardTitle = "Human Resources Assistant II",
        HireDate = new DateTime(2013, 12, 21),
        ResignationDate = new DateTime(2027, 10, 18),
        PrimaryEmail = "cposvner0@jalbum.net",
        SecondaryEmail = "cposvner0@phoca.cz",
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "UX",
        JobFunctions = null,
        DeptCode = "T",
        PrimaryDepartment = true,
        IsSupervisor = true
    },
    new HrEmplModel
    {
        Account = "natty",
        Name = "丰逸",
        EnglishName = "natty Yankin",
        EmployeeID = "A9618",
        ExpiredTime = null,
        Gender = "female",
        IDNumber = "A123456789",
        BirthDate = new DateTime(85, 1, 14),
        PhoneNumber = "0934756998",
        CardTitle = "Pharmacist",
        HireDate = new DateTime(2020, 3, 2),
        ResignationDate = null,
        PrimaryEmail = "nyankin1@wiley.com",
        SecondaryEmail = null,
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "PM",
        JobFunctions = new List<string> { "資訊安全管理" },
        DeptCode = "HR",
        PrimaryDepartment = true,
        IsSupervisor = false
    },
    new HrEmplModel
    {
        Account = "costanza",
        Name = "松源",
        EnglishName = "costanza Posvner",
        EmployeeID = "A3897",
        ExpiredTime = null,
        Gender = "female",
        IDNumber = "A123456789",
        BirthDate = new DateTime(85, 7, 25),
        PhoneNumber = "0946323278",
        CardTitle = "Human Resources Assistant II",
        HireDate = new DateTime(2013, 12, 21),
        ResignationDate = new DateTime(2027, 10, 18),
        PrimaryEmail = "cposvner0@jalbum.net",
        SecondaryEmail = "cposvner0@phoca.cz",
        AccountLocked = false,
        AccountActive = true,
        JobTitleID = "UX",
        JobFunctions = new List<string> { "資訊安全管理" },
        DeptCode = "RD",
        PrimaryDepartment = false,
        IsSupervisor = false        
    }...
}
groupedEmpls
[
    [
        {
            "Account": "costanza",
            "Name": "松源",
            "EnglishName": "costanza Posvner",
            "EmployeeID": "A3897",
            "ExpiredTime": null,
            "Gender": "female",
            "IDNumber": "A123456789",
            "BirthDate": "0085-07-25T00:00:00",
            "PhoneNumber": "0946323278",
            "CardTitle": "Human Resources Assistant II",
            "HireDate": "2013-12-21T00:00:00",
            "ResignationDate": "2027-10-18T00:00:00",
            "PrimaryEmail": "cposvner0@jalbum.net",
            "SecondaryEmail": "cposvner0@phoca.cz",
            "AccountLocked": false,
            "AccountActive": true,
            "JobTitleID": "UX",
            "JobFunctions": null,
            "DeptCode": "T",
            "PrimaryDepartment": true,
            "IsSupervisor": true
        },
        {
            "Account": "costanza",
            "Name": "松源",
            "EnglishName": "costanza Posvner",
            "EmployeeID": "A3897",
            "ExpiredTime": null,
            "Gender": "female",
            "IDNumber": "A123456789",
            "BirthDate": "0085-07-25T00:00:00",
            "PhoneNumber": "0946323278",
            "CardTitle": "Human Resources Assistant II",
            "HireDate": "2013-12-21T00:00:00",
            "ResignationDate": "2027-10-18T00:00:00",
            "PrimaryEmail": "cposvner0@jalbum.net",
            "SecondaryEmail": "cposvner0@phoca.cz",
            "AccountLocked": false,
            "AccountActive": true,
            "JobTitleID": "UX",
            "JobFunctions": ["資訊安全管理"],
            "DeptCode": "RD",
            "PrimaryDepartment": false,
            "IsSupervisor": false
        }
    ],
    [
        {
            "Account": "natty",
            "Name": "丰逸",
            "EnglishName": "natty Yankin",
            "EmployeeID": "A9618",
            "ExpiredTime": null,
            "Gender": "female",
            "IDNumber": "A123456789",
            "BirthDate": "0085-01-14T00:00:00",
            "PhoneNumber": "0934756998",
            "CardTitle": "Pharmacist",
            "HireDate": "2020-03-02T00:00:00",
            "ResignationDate": null,
            "PrimaryEmail": "nyankin1@wiley.com",
            "SecondaryEmail": null,
            "AccountLocked": false,
            "AccountActive": true,
            "JobTitleID": "PM",
            "JobFunctions": ["資訊安全管理"],
            "DeptCode": "HR",
            "PrimaryDepartment": true,
            "IsSupervisor": false
        }
    ]...
]
return
new List<EmplModel>
{
    new EmplModel
    {
        "CorpCode": "woni",
        "Account": "costanza",
        "Name": "松源",
        "EnglishName": "costanza Posvner",
        "EmployeeNumber": "A3897",
        "ExpiredTime": null,
        "ResignationDate": "2027-10-18T00:00:00",
        "Gender": "1",
        "IdCardNumber": "A123456789",
        "BirthDate": "1996-07-25T00:00:00",
        "PhoneNumber": "0946323278",
        "BusinessCard": "Human Resources Assistant II",
        "HireDate": "2013-12-21T00:00:00",
        "Email": "cposvner0@jalbum.net",
        "EmailEx": "cposvner0@phoca.cz",
        "Locked": false,
        "Active": true,
        "Depts": [
            {
                "Code": "T",
                "IsMainDept": true,
                "JobTitleCode": "UX",
                "JobFuncs": null
            },
            {
                "Code": "RD",
                "IsMainDept": false,
                "JobTitleCode": "UX",
                "JobFuncs": ["資訊安全管理"]
            }
        ],
        "Supervisor": [
            {
                "Code": "T",
                "Type": 0,
                "Value": "costanza",
                "CorpCode": "woni"
            }
        ]
    },
    new EmplModel
    {
        "CorpCode": "woni",
        "Account": "natty",
        "Name": "丰逸",
        "EnglishName": "natty Yankin",
        "EmployeeNumber": "A9618",
        "ExpiredTime": null,
        "ResignationDate": null,
        "Gender": "1",
        "IdCardNumber": "A123456789",
        "BirthDate": "1996-01-14T00:00:00",
        "PhoneNumber": "0934756998",
        "BusinessCard": "Pharmacist",
        "HireDate": "2020-03-02T00:00:00",
        "Email": "nyankin1@wiley.com",
        "EmailEx": null,
        "Locked": false,
        "Active": true,
        "Depts": [
            {
                "Code": "HR",
                "IsMainDept": true,
                "JobTitleCode": "PM",
                "JobFuncs": ["資訊安全管理"]
            }
        ],
        "Supervisor": []
    }...
}

C. 比對與同步人員資料

若要 同步 人員資料,需要將 HR 系統與 UOF X 的人員資料做比對:

  1. 建立兩個 List 來存放待處理的資料。
  2. 逐一處理 hrEmpls 中的每個部門資料:
    • 呼叫 UofxService.BASE.OrgEmpl.Get 確認人員是否存在。
    • hrEmpl 已存在於 UOF X 中,則加入 emplToUpdate List 進行更新。
      • 現在日期 > 帳號過期時間(hrEmpl.ExpiredTime),則加入 emplToDeactivate List 進行停用。
    • hrEmpl 不存在於 UOF X 中,則加入 emplToAdd List 進行新增。
  3. 使用非同步方法處理 List 中需要更新與新增的人員資料。

如此一來,人員資料會被分為 需更新需新增需停用 三種 List。

比對與同步人員資料
async Task MapEmpls(List<EmplModel> hrEmpls)
{
    // 更新 List 
    var emplToUpdate = new List<EmplModel>();
    // 新增 List 
    var emplToAdd = new List<EmplModel>();
    // 停用 List 
    var emplToDeactivate = new List<EmplModel>();

    // 將已存在人員放入需更新 List,不存在人員放入需新增 List 
    foreach (var hrEmpl in hrEmpls)
    {
        // 取得人員資料
        var empl = await UofxService.BASE.OrgEmpl.Get(new EmplQueryRequestModel()
        {
            CorpCode = _corpCode, // 公司代號
            UserType = UserType.Account, // 人員的類別 ( 設為 Account )
            UserCode = hrEmpl.Account // 帳號
        });
        // 若人員存在,加入更新 List 
        if (empl.Account != null)
        {
            emplToUpdate.Add(hrEmpl);
        }
        // 不存在,加入新增 List 
        else
        {
            emplToAdd.Add(hrEmpl);
            // 如果現在日期大於帳號過期時間,加入停用 List 
            if (hrEmpl.ExpiredTime != null && DateTime.Now > hrEmpl.ExpiredTime)
            {
                emplToDeactivate.Add(hrEmpl);
            };
        }
    };
    // 更新人員
    await UpdateEmpls(emplToUpdate);
    // 新增人員
    await CreateEmpls(emplToAdd);
    // 停用人員
    await DeactivateEmpls(emplToDeactivate);
};
更新、新增、停用人員與同步部門主管的方法
UpdateEmpls
async Task UpdateEmpls(List<EmplModel> empls)
{
    foreach (var empl in empls)
    {
        try
        {
            // 更新人員狀態
            var updateStatus = await UofxService.BASE.OrgEmpl.UpdateAcctStatus(new EmplUpdateAcctStatusRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                UserType = UserType.Account, // 人員的類別 ( 設為 Account )
                UserCode = empl.Account, // 帳號
                Active = empl.Active // 是否停用人員帳號
            });
            // 更新人員鎖定狀態
            var updateLocked = await UofxService.BASE.OrgEmpl.UpdateAcctLocked(new EmplUpdateAcctLockedRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                UserType = UserType.Account, // 人員的類別 ( 設為 Account )
                UserCode = empl.Account, // 帳號
                Locked = empl.Locked // 是否鎖定人員帳號
            });
            // 更新人員資料
            var update = await UofxService.BASE.OrgEmpl.Update(new EmplUpdateRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                UserType = UserType.Account, // 人員的類別 ( 設為 Account )
                UserCode = empl.Account, // 帳號
                Account = empl.Account, // 人員帳號
                Name = empl.Name, // 中文姓名
                EmployeeNumber = empl.EmployeeNumber, // 員工編號
                Gender = empl.Gender, // 性別
                EnglishName = empl.EnglishName, // 英文姓名
                IdCardNumber = empl.IdCardNumber, // 身份證字號
                BirthDate = empl.BirthDate, // 生日
                PhoneNumber = empl.PhoneNumber, // 行動電話
                BusinessCard = empl.BusinessCard, // 名片職稱
                HireDate = empl.HireDate, // 到職日
                Email = empl.Email, // 主要信箱
                EmailEx = empl.EmailEx //   其他信箱
            });
            // 更新人員過期時間
            var updateExpiredTime = await UofxService.BASE.OrgEmpl.UpdateAcctExpiredTime(new EmplUpdateAcctExpiredTimeRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                UserType = UserType.Account, // 人員的類別 ( 設為 Account )
                UserCode = empl.Account, // 帳號
                ExpiredTime = empl.ExpiredTime // 帳號過期時間
            });
            // 更新人員離職日期
            var updateResignationDate = await UofxService.BASE.OrgEmpl.UpdateEmplResignationDate(new EmplUpdateResignationDateRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                UserType = UserType.Account, // 人員的類別 ( 設為 Account )
                UserCode = empl.Account, // 帳號
                ResignationDate = empl.ResignationDate // 離職日
            });
            // 更新人員部門
            var updateDepts = await UofxService.BASE.OrgEmpl.UpdateEmplDept(new EmplUpdateDeptRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                UserType = UserType.Account, // 人員的類別 ( 設為 Account )
                UserCode = empl.Account, // 帳號
                Depts = empl.Depts.Select(d => new DeptRequestModel
                {
                    Code = d.Code, // 部門代號
                    IsMainDept = d.IsMainDept, // 是否為主要部門
                    JobTitleCode = d.JobTitleCode, // 職稱代號
                    JobFuncs = d.JobFuncs // 職務代號 ( 可多筆 )
                }).ToList()
            });
            // 若所有更新皆成功,記錄成功訊息
            if (updateStatus == true && updateLocked == true && update == true && updateExpiredTime == true && updateResignationDate == true && updateDepts == true)
            {
                successMsg.Add($"已更新 {empl.Account} 人員");
            };
            // 若有部門主管,同步部門主管
            if (empl.Supervisor != null && empl.Supervisor.Count > 0)
            {
                await SyncSuperiors(empl.Supervisor);
            };
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法更新 {empl.Account} 人員,error: {ex.Message}");
        }
    };
};
CreateEmpls
async Task CreateEmpls(List<EmplModel> empls)
{
    foreach (var empl in empls)
    {
        try
        {
            // 新增人員
            var result = await UofxService.BASE.OrgEmpl.CreateEmpl(new EmpCreateRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                Account = empl.Account, // 人員帳號
                Name = empl.Name, // 中文姓名
                EnglishName = empl.EnglishName, // 英文姓名
                EmployeeNumber = empl.EmployeeNumber, // 員工編號
                ExpiredTime = empl.ExpiredTime, // 帳號過期時間
                Gender = empl.Gender, // 性別
                IdCardNumber = empl.IdCardNumber, // 身份證字號
                BirthDate = empl.BirthDate, //  生日
                PhoneNumber = empl.PhoneNumber, // 行動電話
                BusinessCard = empl.BusinessCard, // 名片職稱
                HireDate = empl.HireDate, // 到職日
                Email = empl.Email, // 主要信箱
                EmailEx = empl.EmailEx, // 其他信箱
                Depts = empl.Depts // 所屬部門職務
            });
            // 若新增成功,加入成功訊息
            if (result == true)
            {
                successMsg.Add($"已新增 {empl.Account} 人員");
                // 若有部門主管,同步部門主管
                if (empl.Supervisor != null && empl.Supervisor.Count > 0)
                {
                    await SyncSuperiors(empl.Supervisor);
                };
            };
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法新增 {empl.Account} 人員,error: {ex.Message}");
        }
    };
};
DeactivateEmpls
async Task DeactivateEmpls(List<EmplModel> empls)
{
    foreach (var empl in empls)
    {
        try
        {
            // 停用人員
            var result = await UofxService.BASE.OrgEmpl.UpdateAcctStatus(new EmplUpdateAcctStatusRequestModel
            {
                CorpCode = _corpCode, // 公司代號
                UserType = UserType.Account, // 人員的類別 ( 設為 Account )
                UserCode = empl.Account, // 帳號
                Active = false // 停用人員帳號
            });
            // 若停用成功,加入成功訊息
            if (result == true) successMsg.Add($"已停用 {empl.Account} 人員");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法停用 {empl.Account} 人員,error: {ex.Message}");
        }
    };
};
SyncSuperiors
async Task SyncSuperiors(List<DeptSetManagerModel> Superiors)
{
    foreach (var Superior in Superiors)
    {
        try
        {
            // 設定部門主管
            var result = await UofxService.BASE.Department.Manager.Set(Superior);
            // 若設定成功,加入成功訊息
            if (result == true) successMsg.Add($"已設定 {Superior.Value} 為 {Superior.Code} 部門主管");
        }
        catch (Exception ex)
        {
            // 捕捉錯誤並記錄到 errorMsg 中  
            errorMsg.Add($"無法設定 {Superior.Value} 為 {Superior.Code} 部門主管,error: {ex.Message}");
        }
    };
};

D. 彙整人員同步函式

因為執行多個函式,因此彙整至 SyncEmpls 函式中:

同步人員資料
async Task SyncEmpls(List<HrEmplModel> originHrEmpls)
{
    // 同步職稱職務
    await SyncJobTitlesAndFunctions(originHrEmpls);
    // 轉換人員 List 
    var hrEmpls = ConvertToEmplModels(originHrEmpls);
    // 比對與同步人員資料
    await MapEmpls(hrEmpls);
}

三、呼叫同步函式

最後,呼叫彙整完的 SyncDeptsSyncEmpls,並將成功與錯誤訊息列印出來。

try
{
    // 同步人員部門資料
    await SyncDepts(originHrDepts);
    await SyncEmpls(originHrEmpls);
    // 列印錯誤成功訊息
    ConsoleList(errorMsg);
    ConsoleList(successMsg);
}
catch (Exception ex)
{
    //將 exception 轉換成較容易判斷的 model
    var model = UofxService.Error.ConvertToModel(ex);
    //將 model 轉成 json 格式印出
    Console.WriteLine(UofxService.Json.Convert(model));
}