优化钉钉与YS系统的部门与员工同步逻辑
- 重构 `DingTalkService`,添加部门和员工相关API支持。 - 更新 `DingTalkResponse` 和 `DingTalkEmployeeRsp`,支持可空类型。 - 添加 `CacheYSDepartments` 和 `CacheDingTalkDepartments` 方法,用于缓存部门数据。 - 新增 `SyncYSERPDeptToDingTalk` 方法,实现YS部门同步到钉钉。 - 优化 `GetAllYSEmployees` 和 `GetAllDingTalkEmployees`,支持缓存。 - 添加 `UpdateEmpInfo` 和 `SyncYSEmpToDingTalk` 方法,支持员工信息更新与同步。 - 新增 `HR_DingTalkDept` 和 `HR_YSDept` 类,存储部门信息。 - 添加 `YSERPEmployeeInfo` 和 `YSERPPagedResponse` 类,支持YS员工数据结构。 - 引入 `HttpUtil` 工具类,封装HTTP请求逻辑。 - 添加相关控制器API,支持部门和员工数据的缓存与同步。
This commit is contained in:
parent
f04f48ddd4
commit
9a9a41e9fa
|
|
@ -34,7 +34,7 @@ namespace VOL.DingTalk.Models.Biz
|
|||
|
||||
public class EmployeeQuery {
|
||||
|
||||
public List<string> data_list { get; set; }
|
||||
public List<string> userid_list { get; set; }
|
||||
public long? next_cursor { get; set; }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VOL.DingTalk.Models.Biz
|
||||
{
|
||||
/// <summary>
|
||||
/// 20250929 属性文档参考 https://open.dingtalk.com/document/orgapp/update-dedicated-accounts-information
|
||||
/// </summary>
|
||||
public class DingTalkEmployeeUpdate
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户ID
|
||||
/// </summary>
|
||||
public string userid { get; set; }
|
||||
/// <summary>
|
||||
/// 扩展属性,长度最大2000个字符。json对象 格式 "{\"爱好\":\"旅游\",\"年龄\":\"24\"}"
|
||||
/// </summary>
|
||||
public string extension { get; set; }
|
||||
/// <summary>
|
||||
/// 钉钉企业账号的登录名。
|
||||
/// </summary>
|
||||
public string? loginId { get; set; }
|
||||
/// <summary>
|
||||
/// 直属主管的userId。
|
||||
/// </summary>
|
||||
public string? manager_userid { get; set; }
|
||||
/// <summary>
|
||||
/// 通讯录语言,取值。
|
||||
/// </summary>
|
||||
public string? language { get; set; }
|
||||
/// <summary>
|
||||
/// 分机号,长度最大50个字符。
|
||||
/// </summary>
|
||||
public string? telephone { get; set; }
|
||||
/// <summary>
|
||||
/// 是否号码隐藏:
|
||||
/// </summary>
|
||||
public string? hide_mobile { get; set; }
|
||||
/// <summary>
|
||||
/// 入职时间,UNIX时间戳,单位毫秒。
|
||||
/// </summary>
|
||||
public string? hired_date { get; set; }
|
||||
/// <summary>
|
||||
/// 职位,长度最大200个字符。
|
||||
/// </summary>
|
||||
public string? title { get; set; }
|
||||
/// <summary>
|
||||
/// 企业账号员工的企业邮箱类型。
|
||||
/// </summary>
|
||||
public string? org_email_type { get; set; }
|
||||
/// <summary>
|
||||
/// 企业账号手机号。
|
||||
/// </summary>
|
||||
public string? avatarMediaId { get; set; }
|
||||
/// <summary>
|
||||
/// 员工在对应的部门中的职位。
|
||||
/// </summary>
|
||||
public string dept_title_list { get; set; }
|
||||
/// <summary>
|
||||
/// 办公地点,长度最大100个字符。
|
||||
/// </summary>
|
||||
public string? work_place { get; set; }
|
||||
/// <summary>
|
||||
/// 员工在对应的部门中的排序。
|
||||
/// </summary>
|
||||
public string? dept_order_list { get; set; }
|
||||
/// <summary>
|
||||
/// 是否开启高管模式,默认值false。
|
||||
/// </summary>
|
||||
public string? senior_mode { get; set; }
|
||||
/// <summary>
|
||||
/// 备注,长度最大2000个字符。
|
||||
/// </summary>
|
||||
public string? remark { get; set; }
|
||||
/// <summary>
|
||||
/// 企业邮箱
|
||||
/// </summary>
|
||||
public string? org_email { get; set; }
|
||||
/// <summary>
|
||||
/// 用户名称,长度最大80个字符。
|
||||
/// </summary>
|
||||
public string? name { get; set; }
|
||||
/// <summary>
|
||||
/// 企业账号的昵称。
|
||||
/// </summary>
|
||||
public string? nickname { get; set; }
|
||||
/// <summary>
|
||||
/// 企业账号手机号。
|
||||
/// </summary>
|
||||
public string? exclusive_mobile { get; set; }
|
||||
/// <summary>
|
||||
/// 强制更新的字段,支持清空指定的字段,多个字段之间使用逗号分隔。目前支持字段: manager_userid、org_email。
|
||||
/// </summary>
|
||||
public string? force_update_fields { get; set; }
|
||||
/// <summary>
|
||||
/// 所属部门ID列表。
|
||||
/// </summary>
|
||||
public string? dept_id_list { get; set; }
|
||||
/// <summary>
|
||||
/// 工号
|
||||
/// </summary>
|
||||
public string? job_number { get; set; }
|
||||
/// <summary>
|
||||
/// 邮箱
|
||||
/// </summary>
|
||||
public string? email { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -8,10 +8,10 @@ namespace VOL.DingTalk.Models.Biz
|
|||
{
|
||||
internal class DingTalkResponse<T>
|
||||
{
|
||||
public int errcode { get; set; }
|
||||
public string errmsg { get; set; }
|
||||
public T result { get; set; }
|
||||
public string request_id { get; set; }
|
||||
public int? errcode { get; set; }
|
||||
public string? errmsg { get; set; }
|
||||
public T? result { get; set; }
|
||||
public string? request_id { get; set; }
|
||||
public bool? success { get; set; }
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,19 @@ namespace VOL.DingTalk.Models
|
|||
*/
|
||||
public class DingTalkConfig
|
||||
{
|
||||
#if DEBUG
|
||||
public string CorpId = "dingec656045526ce9c0ee0f45d8e4f7c288";
|
||||
public string AppKey = "dingtl9hb00ktdkguzrh";
|
||||
public string AppSecret = "NkWagc8VEhdZyjEPkK0oYbi_ZzqVMHLelmFWdGswBjh2zGgSt0UWLj1W58xNHwfv";
|
||||
public long AgentId = 3973173455;
|
||||
|
||||
#else
|
||||
// 钉钉测试企业
|
||||
public string CorpId = "ding994fa2867db3cb44ee0f45d8e4f7c288";
|
||||
public string AppKey = "dingokdmazowevvpxolh";
|
||||
public string AppSecret = "sYCVrttZZC-K_sIrPw1m20j41Pcbbxt2fC5joFQGOh7xjUxD8DT2I_KKZ4S2zje8";
|
||||
public long AgentId = 4018728780;
|
||||
#endif
|
||||
public List<string> fields = [
|
||||
|
||||
"sys00-name", // 姓名
|
||||
"sys00-email", // 邮箱
|
||||
"sys00-mobile", // 手机号
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Text;
|
|||
using VOL.Core.Services;
|
||||
using VOL.DingTalk.Models;
|
||||
using VOL.DingTalk.Models.Biz;
|
||||
using static VOL.DingTalk.Util;
|
||||
|
||||
namespace VOL.DingTalk.Services.Biz
|
||||
{
|
||||
|
|
@ -14,7 +15,11 @@ namespace VOL.DingTalk.Services.Biz
|
|||
private const string DepartmentSubUrl = "https://oapi.dingtalk.com/topapi/v2/department/listsub";
|
||||
private const string RosterUrl = "https://api.dingtalk.com/v1.0/hrm/rosters/lists/query";
|
||||
private const string UpdateEmployeeUrl = "https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/v2/update";
|
||||
|
||||
private const string UserListIdUrl = "https://oapi.dingtalk.com/topapi/user/listid";
|
||||
private const string CreateDept = "https://oapi.dingtalk.com/topapi/v2/department/create";
|
||||
|
||||
private const string UpdateEmpInfoUrl = "https://oapi.dingtalk.com/topapi/v2/user/update";
|
||||
// 需要获取的花名册字段代码
|
||||
private readonly List<string> _fieldCodes =
|
||||
[
|
||||
|
|
@ -120,43 +125,46 @@ namespace VOL.DingTalk.Services.Biz
|
|||
{
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
Logger.Info("正在获取钉钉访问凭证...");
|
||||
//Logger.Info("正在获取钉钉访问凭证...");
|
||||
var accessToken = await GetValidTokenAsync();
|
||||
if (accessToken == null)
|
||||
{
|
||||
return new List<DingTalkEmployee>();
|
||||
}
|
||||
|
||||
Logger.Info("正在获取部门结构...");
|
||||
//Logger.Info("正在获取部门结构...");
|
||||
var departments = await GetSubDepartmentsAsync();
|
||||
Logger.Info($"共获取到 {departments.Count} 个部门");
|
||||
//Logger.Info($"共获取到 {departments.Count} 个部门");
|
||||
|
||||
HashSet<string> allUserIds = new HashSet<string>();
|
||||
Logger.Info("正在获取部门用户列表...");
|
||||
//Logger.Info("正在获取部门用户列表...");
|
||||
foreach (var dept in departments)
|
||||
{
|
||||
var userIds = await GetDeptUserIdsAsync(Convert.ToInt64(dept.dept_id));
|
||||
allUserIds.UnionWith(userIds);
|
||||
Logger.Info($"部门 {dept.name} 有 {userIds.Count} 个用户");
|
||||
if(userIds != null)
|
||||
{
|
||||
allUserIds.UnionWith(userIds);
|
||||
}
|
||||
//Logger.Info($"部门 {dept.name} 有 {userIds.Count} 个用户");
|
||||
}
|
||||
|
||||
List<string> userIdList = allUserIds.ToList();
|
||||
Logger.Info($"共获取到 {userIdList.Count} 个用户");
|
||||
//Logger.Info($"共获取到 {userIdList.Count} 个用户");
|
||||
|
||||
// 分批处理用户(每批100人)
|
||||
var allEmployees = new List<DingTalkEmployee>();
|
||||
var allEmployees = new List<DingTalkEmployee>();
|
||||
int batchSize = 100;
|
||||
for (int i = 0; i < userIdList.Count; i += batchSize)
|
||||
{
|
||||
var batch = userIdList.GetRange(i, Math.Min(batchSize, userIdList.Count - i));
|
||||
Logger.Info($"正在处理用户批次 {i / batchSize + 1}/{(userIdList.Count - 1) / batchSize + 1}");
|
||||
//Logger.Info($"正在处理用户批次 {i / batchSize + 1}/{(userIdList.Count - 1) / batchSize + 1}");
|
||||
|
||||
var rosterData = await GetRosterInfoAsync(batch);
|
||||
var rosterData = await GetRosterInfoAsync(batch);
|
||||
allEmployees.AddRange([.. rosterData.Select(DingTalkEmployee.TranFrom)]);
|
||||
}
|
||||
|
||||
Logger.Info($"成功获取 {allEmployees.Count} 名员工信息");
|
||||
Logger.Info($"钉钉数据获取完成,耗时: {DateTime.Now.Subtract(startTime).TotalSeconds:.2f}秒");
|
||||
//Logger.Info($"成功获取 {allEmployees.Count} 名员工信息");
|
||||
//Logger.Info($"钉钉数据获取完成,耗时: {DateTime.Now.Subtract(startTime).TotalSeconds:.2f}秒");
|
||||
|
||||
return allEmployees;
|
||||
}
|
||||
|
|
@ -173,25 +181,15 @@ namespace VOL.DingTalk.Services.Biz
|
|||
var queryParams = new Dictionary<string, string> { { "access_token", accessToken } };
|
||||
|
||||
try
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
var data = await HttpUtil.SendPostRequest<DingTalkResponse<EmployeeQuery>>(UserListIdUrl, queryParams, JsonConvert.SerializeObject(payload));
|
||||
if (data.errcode == 0)
|
||||
{
|
||||
var jsonPayload = JsonConvert.SerializeObject(payload);
|
||||
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
|
||||
var uriBuilder = new UriBuilder(UserListIdUrl);
|
||||
uriBuilder.Query = string.Join("&", queryParams.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
|
||||
var response = await client.PostAsync(uriBuilder.Uri, content);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(responseContent);
|
||||
|
||||
if (Convert.ToInt32(data["errcode"]) == 0)
|
||||
{
|
||||
var result = (Newtonsoft.Json.Linq.JObject)data["result"];
|
||||
return ((Newtonsoft.Json.Linq.JArray)result["userid_list"]).Select(x => x.ToString()).ToList();
|
||||
}
|
||||
return new List<string>();
|
||||
return data.result.userid_list;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
|
|
@ -266,48 +264,22 @@ namespace VOL.DingTalk.Services.Biz
|
|||
|
||||
try
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
var jsonPayload = JsonConvert.SerializeObject(payload);
|
||||
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
|
||||
var uriBuilder = new UriBuilder(DepartmentSubUrl);
|
||||
uriBuilder.Query = string.Join("&", queryParams.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
|
||||
var response = await client.PostAsync(uriBuilder.Uri, content);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(responseContent);
|
||||
var data = await HttpUtil.SendPostRequest<DingTalkResponse<List<DingTalkDepartment>>>(DepartmentSubUrl, queryParams, JsonConvert.SerializeObject(payload));
|
||||
|
||||
if (Convert.ToInt32(data["errcode"]) == 0)
|
||||
if (data.errcode == 0)
|
||||
{
|
||||
var result = (Newtonsoft.Json.Linq.JArray)data["result"];
|
||||
foreach (Newtonsoft.Json.Linq.JObject dept in result)
|
||||
foreach (var dept in data.result)
|
||||
{
|
||||
long deptId = (long)dept["dept_id"];
|
||||
// var department = new DingTalkDepartment
|
||||
//{
|
||||
// { "id", deptId },
|
||||
// { "name", dept["name"].ToString() },
|
||||
// { "parent_id", (long)dept["parent_id"] }
|
||||
//};
|
||||
var department = new DingTalkDepartment
|
||||
{
|
||||
dept_id = (int)dept["dept_id"],
|
||||
name = dept["name"].ToString(),
|
||||
parent_id = (int)dept["parent_id"],
|
||||
auto_add_user = (bool)dept["auto_add_user"],
|
||||
create_dept_group = (bool)dept["create_dept_group"],
|
||||
ext = dept["ext"]?.ToString()
|
||||
};
|
||||
departments.Add(department);
|
||||
departments.Add(dept);
|
||||
|
||||
//await Task.Delay(1500); // 间隔1.5秒,避免请求过快
|
||||
await Task.Delay(100); // 间隔时间,避免请求过快
|
||||
// 递归获取子部门
|
||||
departments.AddRange(await GetSubDepartmentsAsync(deptId));
|
||||
departments.AddRange(await GetSubDepartmentsAsync(dept.dept_id));
|
||||
}
|
||||
}
|
||||
return departments;
|
||||
}
|
||||
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
|
|
@ -513,26 +485,135 @@ namespace VOL.DingTalk.Services.Biz
|
|||
{ "grant_type", "client_credentials" }
|
||||
};
|
||||
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
var jsonPayload = JsonConvert.SerializeObject(payload);
|
||||
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
|
||||
//using (var client = new HttpClient())
|
||||
//{
|
||||
// client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
// var jsonPayload = JsonConvert.SerializeObject(payload);
|
||||
// var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
|
||||
|
||||
try
|
||||
{
|
||||
var response = await client.PostAsync(_tokenUrl, content);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
var data = JsonConvert.DeserializeObject<Dictionary<string, string>>(responseContent);
|
||||
return data["access_token"];
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
Logger.Error($"获取access_token失败: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
// try
|
||||
// {
|
||||
// var response = await client.PostAsync(_tokenUrl, content);
|
||||
// response.EnsureSuccessStatusCode();
|
||||
// var responseContent = await response.Content.ReadAsStringAsync();
|
||||
// var data = JsonConvert.DeserializeObject<Dictionary<string, string>>(responseContent);
|
||||
// return data["access_token"];
|
||||
// }
|
||||
// catch (HttpRequestException ex)
|
||||
// {
|
||||
// Logger.Error($"获取access_token失败: {ex.Message}");
|
||||
// return null;
|
||||
// }
|
||||
//}
|
||||
try
|
||||
{
|
||||
var data = await HttpUtil.SendPostRequest<Dictionary<string, string>>(_tokenUrl, new Dictionary<string, string>(), JsonConvert.SerializeObject(payload));
|
||||
return data["access_token"];
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
Logger.Error($"获取access_token失败: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void CreateDingTalkDept(DingTalkDepartment dept)
|
||||
{
|
||||
var accessToken = GetValidTokenAsync().Result;
|
||||
if (accessToken == null)
|
||||
{
|
||||
//return new List<DingTalkEmployee>();
|
||||
throw new Exception("Token 失效");
|
||||
}
|
||||
|
||||
var departments = new List<DingTalkDepartment>();
|
||||
var payload = new Dictionary<string, object> {
|
||||
{"name", dept.name } ,
|
||||
{"parent_id", dept.parent_id } ,
|
||||
};
|
||||
var queryParams = new Dictionary<string, string> { { "access_token", accessToken } };
|
||||
|
||||
try
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
var jsonPayload = JsonConvert.SerializeObject(payload);
|
||||
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
|
||||
var uriBuilder = new UriBuilder(CreateDept);
|
||||
uriBuilder.Query = string.Join("&", queryParams.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
|
||||
var response = client.PostAsync(uriBuilder.Uri, content).Result;
|
||||
response.EnsureSuccessStatusCode();
|
||||
var responseContent = response.Content.ReadAsStringAsync().Result;
|
||||
var data = JsonConvert.DeserializeObject<DingTalkResponse<Dictionary<string,object>>>(responseContent);
|
||||
|
||||
if(data.errcode != 0)
|
||||
{
|
||||
Logger.Error($"{data.errcode},{data.errmsg}");
|
||||
throw new Exception($"{data.errcode},{data.errmsg}");
|
||||
}
|
||||
|
||||
}
|
||||
}catch(Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> UpdateEmpInfo(DingTalkEmployeeUpdate updateInfo)
|
||||
{
|
||||
if(updateInfo == null || string.IsNullOrEmpty( updateInfo.userid))
|
||||
{
|
||||
throw new Exception("参数错误");
|
||||
}
|
||||
|
||||
var accessToken = await GetValidTokenAsync();
|
||||
if (accessToken == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var queryParams = new Dictionary<string, string> { { "access_token", accessToken } };
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
var data = await HttpUtil.SendPostRequest<DingTalkResponse<object>>(UpdateEmpInfoUrl, queryParams, JsonConvert.SerializeObject(updateInfo, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }));
|
||||
if(data.errcode != 0)
|
||||
{
|
||||
Logger.Error($"{data.errcode},{data.errmsg}");
|
||||
throw new Exception($"{data.errcode},{data.errmsg}");
|
||||
}
|
||||
|
||||
//using (var client = new HttpClient())
|
||||
//{
|
||||
// var jsonPayload = JsonConvert.SerializeObject(updateInfo, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
|
||||
// var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
|
||||
// var uriBuilder = new UriBuilder(UpdateEmpInfoUrl);
|
||||
// uriBuilder.Query = string.Join("&", queryParams.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
|
||||
// var response = await client.PostAsync(uriBuilder.Uri, content);
|
||||
// response.EnsureSuccessStatusCode();
|
||||
// var responseContent = await response.Content.ReadAsStringAsync();
|
||||
// var data = JsonConvert.DeserializeObject<DingTalkResponse<object>>(responseContent);
|
||||
|
||||
// if(data.errcode != 0)
|
||||
// {
|
||||
// Logger.Error($"{data.errcode},{data.errmsg}");
|
||||
// throw new Exception($"{data.errcode},{data.errmsg}");
|
||||
// }
|
||||
//}
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
Logger.Error($"获取部门列表失败: {ex.Message}");
|
||||
throw new Exception($"获取部门列表失败: {ex.Message}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using VOL.DingTalk.Models.Biz;
|
||||
|
||||
namespace VOL.DingTalk
|
||||
{
|
||||
public class Util
|
||||
{
|
||||
|
||||
public class HttpUtil
|
||||
{
|
||||
public static async Task<T> SendPostRequest<T>(string url,Dictionary<string,string> queryData,string bodyData)
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
//var jsonPayload = JsonConvert.SerializeObject(payload);
|
||||
var content = new StringContent(bodyData, Encoding.UTF8, "application/json");
|
||||
var uriBuilder = new UriBuilder(url);
|
||||
if(queryData != null && queryData.Count > 0)
|
||||
uriBuilder.Query = string.Join("&", queryData.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
|
||||
var response = await client.PostAsync(uriBuilder.Uri, content);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
var data = JsonConvert.DeserializeObject<T>(responseContent);
|
||||
return data!;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -29,9 +29,9 @@ namespace VOL.Entity.DomainModels
|
|||
public int ShipId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///YSERP部门ID
|
||||
///YSERP部门
|
||||
/// </summary>
|
||||
[Display(Name ="YSERP部门ID")]
|
||||
[Display(Name ="YSERP部门")]
|
||||
[MaxLength(100)]
|
||||
[Column(TypeName="nvarchar(100)")]
|
||||
[Editable(true)]
|
||||
|
|
@ -43,13 +43,12 @@ namespace VOL.Entity.DomainModels
|
|||
[Display(Name ="YS部门名称")]
|
||||
[MaxLength(100)]
|
||||
[Column(TypeName="nvarchar(100)")]
|
||||
[Editable(true)]
|
||||
public string YSDeptName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///钉钉部门ID
|
||||
///钉钉部门
|
||||
/// </summary>
|
||||
[Display(Name ="钉钉部门ID")]
|
||||
[Display(Name ="钉钉部门")]
|
||||
[MaxLength(100)]
|
||||
[Column(TypeName="nvarchar(100)")]
|
||||
[Editable(true)]
|
||||
|
|
@ -61,9 +60,16 @@ namespace VOL.Entity.DomainModels
|
|||
[Display(Name ="钉钉部门名称")]
|
||||
[MaxLength(100)]
|
||||
[Column(TypeName="nvarchar(100)")]
|
||||
[Editable(true)]
|
||||
public string DingTalkDeptName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///根节点
|
||||
/// </summary>
|
||||
[Display(Name ="根节点")]
|
||||
[Column(TypeName="bool")]
|
||||
[Editable(true)]
|
||||
public bool? IsRoot { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VOL.Entity.DomainModels.DeptShip.SystemDept
|
||||
{
|
||||
public class HR_DingTalkDept
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true)]
|
||||
[Key]
|
||||
public int dept_id { get; set; }
|
||||
public bool auto_add_user { get; set; }
|
||||
public bool create_dept_group { get; set; }
|
||||
|
||||
public string name { get; set; }
|
||||
public int parent_id { get; set; }
|
||||
|
||||
public string ext { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VOL.Entity.DomainModels.DeptShip.SystemDept
|
||||
{
|
||||
public class HR_YSDept
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true)]
|
||||
[Key]
|
||||
public string id { get; set; }
|
||||
public int orgtype { get; set; }
|
||||
public string code { get; set; }
|
||||
public string sysid { get; set; }
|
||||
public int displayorder { get; set; }
|
||||
public string parentid { get; set; }
|
||||
public int dr { get; set; }
|
||||
public string parentCode { get; set; }
|
||||
public string parentorgid { get; set; }
|
||||
public int enable { get; set; }
|
||||
public int is_biz_unit { get; set; }
|
||||
public string tenantid { get; set; }
|
||||
public string name { get; set; }
|
||||
public string pubts { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,6 @@ using VOL.Entity.SystemModels;
|
|||
namespace VOL.Entity.DomainModels
|
||||
{
|
||||
[Entity(TableCnName = "员工信息同步",TableName = "HR_EmployeeSync")]
|
||||
[SugarTable("HR_EmployeeSync")]
|
||||
public partial class HR_EmployeeSync:BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -91,6 +90,15 @@ namespace VOL.Entity.DomainModels
|
|||
[Editable(true)]
|
||||
public string EmpLastSyncInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///员工钉钉部门ID
|
||||
/// </summary>
|
||||
[Display(Name ="员工钉钉部门ID")]
|
||||
[MaxLength(50)]
|
||||
[Column(TypeName="nvarchar(50)")]
|
||||
[Editable(true)]
|
||||
public string EmpDingTalkDeptID { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,15 @@ using VOL.Entity.DomainModels;
|
|||
using VOL.Core.Utilities;
|
||||
using System.Linq.Expressions;
|
||||
using VOL.YSErp.Models.Biz;
|
||||
using VOL.DingTalk.Models.Biz;
|
||||
namespace VOL.HR.IServices
|
||||
{
|
||||
public partial interface IHR_DeptShipService
|
||||
{
|
||||
Task<List<YSERPDepartment>> GetYSERPDepartments();
|
||||
|
||||
|
||||
|
||||
Task<List<DingTalkDepartment>> GetDingTalkDepartments();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,18 @@ using VOL.Core.BaseProvider;
|
|||
using VOL.Entity.DomainModels;
|
||||
using VOL.Core.Utilities;
|
||||
using System.Linq.Expressions;
|
||||
using VOL.DingTalk.Models.Biz;
|
||||
namespace VOL.HR.IServices
|
||||
{
|
||||
public partial interface IHR_DeptSyncService
|
||||
{
|
||||
Task CacheYSDepartments();
|
||||
|
||||
|
||||
Task CacheDingTalkDepartments();
|
||||
|
||||
Task SyncYSERPDeptToDingTalk();
|
||||
|
||||
Task CreateDingTalkDept(DingTalkDepartment dept);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,5 +18,11 @@ namespace VOL.HR.IServices
|
|||
|
||||
|
||||
Task GenEmpSystemShip();
|
||||
|
||||
|
||||
Task<bool> UpdateEmpInfo(DingTalkEmployeeUpdate updateInfo);
|
||||
|
||||
|
||||
Task<bool> SyncYSEmpToDingTalk(string ysEmpId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,5 +63,23 @@ namespace VOL.HR.Services
|
|||
return depts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<List<DingTalkDepartment>> GetDingTalkDepartments()
|
||||
{
|
||||
|
||||
if (_cacheService.Exists("DINGTALK_DEPT_CACHE"))
|
||||
{
|
||||
return Task.FromResult(_cacheService.Get<List<DingTalkDepartment>>("DINGTALK_DEPT_CACHE"));
|
||||
}
|
||||
else
|
||||
{
|
||||
var depts = _dingTalkService.GetSubDepartmentsAsync();
|
||||
if (depts != null)
|
||||
{
|
||||
_cacheService.Add("DINGTALK_DEPT_CACHE", JsonConvert.SerializeObject(depts.Result), 600);
|
||||
}
|
||||
return depts;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,17 +6,17 @@
|
|||
*用户信息、权限、角色等使用UserContext.Current操作
|
||||
*HR_DeptSyncService对增、删、改查、导入、导出、审核业务代码扩展参照ServiceFunFilter
|
||||
*/
|
||||
using VOL.Core.BaseProvider;
|
||||
using VOL.Core.Extensions.AutofacManager;
|
||||
using VOL.Entity.DomainModels;
|
||||
using System.Linq;
|
||||
using VOL.Core.Utilities;
|
||||
using System.Linq.Expressions;
|
||||
using VOL.Core.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
using VOL.Core.DBManager;
|
||||
using VOL.Core.Extensions;
|
||||
using VOL.DingTalk.Models.Biz;
|
||||
using VOL.DingTalk.Services.Biz;
|
||||
using VOL.Entity.DomainModels.DeptShip.SystemDept;
|
||||
using VOL.HR.IRepositories;
|
||||
using VOL.YSErp.Models.Biz;
|
||||
using VOL.YSErp.Services.Biz;
|
||||
|
||||
namespace VOL.HR.Services
|
||||
{
|
||||
|
|
@ -24,18 +24,222 @@ namespace VOL.HR.Services
|
|||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IHR_DeptSyncRepository _repository;//访问数据库
|
||||
private readonly IHR_DeptShipRepository _shipRepository;//访问数据库
|
||||
private readonly DingTalkService _dingTalkService;
|
||||
private readonly YSERPService _ysService;
|
||||
[ActivatorUtilitiesConstructor]
|
||||
private Core.CacheManager.ICacheService _cacheService;
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public HR_DeptSyncService(
|
||||
IHR_DeptSyncRepository dbRepository,
|
||||
IHttpContextAccessor httpContextAccessor
|
||||
IHR_DeptShipRepository shipDbRepository,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
Core.CacheManager.ICacheService cacheService
|
||||
)
|
||||
: base(dbRepository)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_repository = dbRepository;
|
||||
_shipRepository = shipDbRepository;
|
||||
//多租户会用到这init代码,其他情况可以不用
|
||||
//base.Init(dbRepository);
|
||||
|
||||
_cacheService = cacheService;
|
||||
|
||||
_dingTalkService = new DingTalkService(new DingTalk.Models.SystemToken(), new DingTalk.Models.DingTalkConfig());
|
||||
_ysService = new YSERPService(new YSErp.Models.SystemToken(), new YSErp.Models.YSConfig());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task CacheYSDepartments()
|
||||
{
|
||||
var ysDepts = await _ysService.GetAllDepartmentsAsync();
|
||||
if(ysDepts != null)
|
||||
{
|
||||
_cacheService.Remove("YS_DEPT_CACHE");
|
||||
_cacheService.Add("YS_DEPT_CACHE", JsonConvert.SerializeObject(ysDepts), 60 * 60);
|
||||
|
||||
|
||||
var db = DBServerProvider.SqlSugarClient;
|
||||
|
||||
// 自动建表
|
||||
db.CodeFirst.InitTables<HR_YSDept>();
|
||||
try
|
||||
{
|
||||
db.BeginTran();
|
||||
// 通过缓存直接反序列化转换对象
|
||||
var deptsData = _cacheService.Get<List<HR_YSDept>>("YS_DEPT_CACHE");
|
||||
|
||||
// 清空再插入
|
||||
db.DbMaintenance.TruncateTable<HR_YSDept>();
|
||||
db.Fastest<HR_YSDept>().BulkCopy(deptsData);
|
||||
db.CommitTran();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
db.RollbackTran();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task CacheDingTalkDepartments()
|
||||
{
|
||||
var depts = await _dingTalkService.GetSubDepartmentsAsync();
|
||||
if (depts != null)
|
||||
{
|
||||
_cacheService.Remove("DINGTALK_DEPT_CACHE");
|
||||
_cacheService.Add("DINGTALK_DEPT_CACHE", JsonConvert.SerializeObject(depts), 60 * 60);
|
||||
|
||||
var db = DBServerProvider.SqlSugarClient;
|
||||
|
||||
try
|
||||
{
|
||||
// 自动建表
|
||||
db.CodeFirst.InitTables<HR_DingTalkDept>();
|
||||
|
||||
db.BeginTran();
|
||||
// 通过缓存直接反序列化转换对象
|
||||
var deptsData = _cacheService.Get<List<HR_DingTalkDept>>("DINGTALK_DEPT_CACHE");
|
||||
|
||||
// 清空再插入
|
||||
db.DbMaintenance.TruncateTable<HR_DingTalkDept>();
|
||||
db.Fastest<HR_DingTalkDept>().BulkCopy(deptsData);
|
||||
db.CommitTran();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
db.RollbackTran();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
public async Task CreateDingTalkDept(DingTalkDepartment dept)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public async Task SyncYSERPDeptToDingTalk()
|
||||
{
|
||||
|
||||
var ysDepts = await _ysService.GetAllDepartmentsAsync();
|
||||
//var depts = await _dingTalkService.GetSubDepartmentsAsync();
|
||||
//var deptShip = _shipRepository.Find(i => i.IsRoot ?? false);
|
||||
if(ysDepts.Count != 0)
|
||||
{
|
||||
var rootDept = _shipRepository.Find(i => i.IsRoot ?? false).FirstOrDefault();
|
||||
BuildYSDeptTree(ysDepts).Where(i => i.id == rootDept.YSDeptId).ToList().ForEach(CreateOrUpdateDingTalkDept);
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateOrUpdateDingTalkDept(YSERPDepartment ysDept)
|
||||
{
|
||||
var deptShip = _shipRepository.Find(i => i.YSDeptId == ysDept.id).FirstOrDefault();
|
||||
if (deptShip != null && (!deptShip.IsRoot ?? true))
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(ysDept.parentid))
|
||||
{
|
||||
var rootDept = _shipRepository.Find(i => i.IsRoot ?? false).FirstOrDefault();
|
||||
var pId = int.Parse(rootDept?.DingTalkDeptId ?? "1");
|
||||
try
|
||||
{
|
||||
_dingTalkService.CreateDingTalkDept(new DingTalkDepartment
|
||||
{
|
||||
parent_id = pId,
|
||||
name = ysDept.name
|
||||
});
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 将扁平的 YSERP 部门列表转换为树形结构
|
||||
/// 规则:
|
||||
/// - parentid 为 null/empty/"0" 或者父节点在列表中不存在时,视为根节点
|
||||
/// - 避免将节点添加为自己的子节点
|
||||
/// - 会初始化每个节点的 subDepts 列表并按 displayorder 排序(然后按 name 作为次级排序)
|
||||
/// </summary>
|
||||
/// <param name="flatList">扁平部门列表</param>
|
||||
/// <returns>树形结构的根节点列表</returns>
|
||||
private List<YSERPDepartment> BuildYSDeptTree(List<YSERPDepartment> flatList)
|
||||
{
|
||||
if (flatList == null || flatList.Count == 0)
|
||||
return new List<YSERPDepartment>();
|
||||
|
||||
// 初始化 subDepts,防止 null 引发 NRE
|
||||
foreach (var d in flatList)
|
||||
{
|
||||
if (d.subDepts == null)
|
||||
d.subDepts = new List<YSERPDepartment>();
|
||||
else
|
||||
d.subDepts.Clear();
|
||||
}
|
||||
|
||||
// 建立 id -> node 的字典(只包含有 id 的节点)
|
||||
var dict = flatList
|
||||
.Where(d => !string.IsNullOrEmpty(d.id))
|
||||
.GroupBy(d => d.id) // 防止重复 id 导致异常,取第一个
|
||||
.ToDictionary(g => g.Key, g => g.First());
|
||||
|
||||
var roots = new List<YSERPDepartment>();
|
||||
|
||||
foreach (var node in flatList)
|
||||
{
|
||||
// 视为根的条件:parentid 为空、等于 "0"、或父节点不存在、或者 parentid 与自身 id 相同(防止自引用)
|
||||
if (string.IsNullOrEmpty(node.parentid) || node.parentid == "0" ||
|
||||
!dict.ContainsKey(node.parentid) || node.parentid == node.id)
|
||||
{
|
||||
roots.Add(node);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 将当前节点添加到父节点的子集合
|
||||
if (dict.TryGetValue(node.parentid, out var parent))
|
||||
{
|
||||
if (parent.subDepts == null)
|
||||
parent.subDepts = new List<YSERPDepartment>();
|
||||
parent.subDepts.Add(node);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 找不到父节点视为根
|
||||
roots.Add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 递归排序子节点(按 displayorder,再按 name)
|
||||
void SortRecursively(List<YSERPDepartment> list)
|
||||
{
|
||||
if (list == null || list.Count == 0) return;
|
||||
list.Sort((a, b) =>
|
||||
{
|
||||
var c = a.displayorder.CompareTo(b.displayorder);
|
||||
if (c == 0) return string.Compare(a.name, b.name, StringComparison.Ordinal);
|
||||
return c;
|
||||
});
|
||||
foreach (var item in list)
|
||||
{
|
||||
SortRecursively(item.subDepts);
|
||||
}
|
||||
}
|
||||
|
||||
SortRecursively(roots);
|
||||
|
||||
return roots;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ using VOL.Entity.DomainModels;
|
|||
using VOL.HR.IRepositories;
|
||||
using VOL.YSErp.Models.Biz;
|
||||
using VOL.YSErp.Services.Biz;
|
||||
using static VOL.YSErp.Models.Biz.YSERPEmployeeInfo;
|
||||
|
||||
namespace VOL.HR.Services
|
||||
{
|
||||
|
|
@ -36,6 +37,7 @@ namespace VOL.HR.Services
|
|||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IHR_EmployeeSyncRepository _repository;//访问数据库
|
||||
private readonly IHR_DeptShipRepository _deptShipRepo;
|
||||
private readonly DingTalkService _dingTalkService;
|
||||
private readonly YSERPService _ysService;
|
||||
|
||||
|
|
@ -44,6 +46,7 @@ namespace VOL.HR.Services
|
|||
[ActivatorUtilitiesConstructor]
|
||||
public HR_EmployeeSyncService(
|
||||
IHR_EmployeeSyncRepository dbRepository,
|
||||
IHR_DeptShipRepository deptShipRepo,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ICacheService cacheService
|
||||
)
|
||||
|
|
@ -51,6 +54,7 @@ namespace VOL.HR.Services
|
|||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_repository = dbRepository;
|
||||
_deptShipRepo = deptShipRepo;
|
||||
//多租户会用到这init代码,其他情况可以不用
|
||||
//base.Init(dbRepository);
|
||||
|
||||
|
|
@ -59,34 +63,34 @@ namespace VOL.HR.Services
|
|||
_dingTalkService = new DingTalkService(new DingTalk.Models.SystemToken(),new DingTalk.Models.DingTalkConfig());
|
||||
_ysService = new YSERPService(new YSErp.Models.SystemToken(), new YSErp.Models.YSConfig());
|
||||
}
|
||||
public Task<List<YSERPEmployee>> GetAllYSEmployees()
|
||||
public async Task<List<YSERPEmployee>> GetAllYSEmployees()
|
||||
{
|
||||
if (_cacheService.Exists("YS_EMP_CACHE"))
|
||||
{
|
||||
return Task.FromResult(_cacheService.Get<List<YSERPEmployee>>("YS_EMP_CACHE"));
|
||||
return _cacheService.Get<List<YSERPEmployee>>("YS_EMP_CACHE");
|
||||
}
|
||||
else
|
||||
{
|
||||
var emps = _ysService.GetAllEmployeesAsync();
|
||||
if(emps != null)
|
||||
var emps = await _ysService.GetAllEmployeesAsync();
|
||||
if(emps != null && emps.Count > 0)
|
||||
{
|
||||
_cacheService.Add("YS_EMP_CACHE", JsonConvert.SerializeObject(emps.Result), 600);
|
||||
_cacheService.Add("YS_EMP_CACHE", JsonConvert.SerializeObject(emps), 600);
|
||||
}
|
||||
return emps;
|
||||
}
|
||||
}
|
||||
public Task<List<DingTalkEmployee>> GetAllDingTalkEmployees()
|
||||
public async Task<List<DingTalkEmployee>> GetAllDingTalkEmployees()
|
||||
{
|
||||
if (_cacheService.Exists("DingTalk_EMP_CACHE"))
|
||||
{
|
||||
return Task.FromResult(_cacheService.Get<List<DingTalkEmployee>>("DingTalk_EMP_CACHE"));
|
||||
return _cacheService.Get<List<DingTalkEmployee>>("DingTalk_EMP_CACHE");
|
||||
}
|
||||
else
|
||||
{
|
||||
var emps = _dingTalkService.GetAllEmployeesAsync();
|
||||
if(emps != null)
|
||||
var emps = await _dingTalkService.GetAllEmployeesAsync();
|
||||
if(emps != null && emps.Count > 0)
|
||||
{
|
||||
_cacheService.Add("DingTalk_EMP_CACHE", JsonConvert.SerializeObject(emps.Result), 600);
|
||||
_cacheService.Add("DingTalk_EMP_CACHE", JsonConvert.SerializeObject(emps), 600);
|
||||
}
|
||||
return emps;
|
||||
}
|
||||
|
|
@ -97,7 +101,7 @@ namespace VOL.HR.Services
|
|||
List<DingTalkEmployee> dingTalkEmployees = [];
|
||||
List<YSERPEmployee> ysERPEmployees = [];
|
||||
|
||||
var ysTask = GetAllYSEmployees().ContinueWith(x =>
|
||||
var ysTask = GetAllYSEmployees().ContinueWith(x =>
|
||||
{
|
||||
ysERPEmployees = x.Result;
|
||||
});
|
||||
|
|
@ -114,7 +118,25 @@ namespace VOL.HR.Services
|
|||
var dingTalkInfo = dingTalkEmployees?.FirstOrDefault(e => e.Mobile == emp.mobile);
|
||||
if (dingTalkInfo != null)
|
||||
{
|
||||
//_repository.AddRange()
|
||||
if(!newList.Any(i => i.EmpYSID == emp.id))
|
||||
{
|
||||
//_repository.AddRange()
|
||||
newList.Add(new HR_EmployeeSync
|
||||
{
|
||||
EmpYSID = emp.id,
|
||||
EmpYSMobile = emp.mobile,
|
||||
EmpYSDeptID = emp.dept_id,
|
||||
EmpJobNumber = emp.code,
|
||||
|
||||
EmpDingDingID = dingTalkInfo.UserId,
|
||||
EmpDingDingMobile = dingTalkInfo.Mobile,
|
||||
EmpDingTalkDeptID = dingTalkInfo.Dept,
|
||||
EmpLastSyncInfo = "Init generate for system.",
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newList.Add(new HR_EmployeeSync
|
||||
{
|
||||
EmpYSID = emp.id,
|
||||
|
|
@ -122,8 +144,9 @@ namespace VOL.HR.Services
|
|||
EmpYSDeptID = emp.dept_id,
|
||||
EmpJobNumber = emp.code,
|
||||
|
||||
EmpDingDingID = dingTalkInfo.UserId,
|
||||
EmpDingDingMobile = dingTalkInfo.Mobile,
|
||||
EmpDingDingID = "",
|
||||
EmpDingDingMobile = "",
|
||||
EmpDingTalkDeptID = "",
|
||||
EmpLastSyncInfo = "Init generate for system.",
|
||||
});
|
||||
}
|
||||
|
|
@ -134,7 +157,8 @@ namespace VOL.HR.Services
|
|||
{
|
||||
db.BeginTran();
|
||||
// 清空重新生成
|
||||
db.Deleteable<HR_EmployeeSync>();
|
||||
db.DbMaintenance.TruncateTable<HR_EmployeeSync>();
|
||||
//db.Deleteable<HR_EmployeeSync>();
|
||||
db.Fastest<HR_EmployeeSync>().BulkCopy(newList);
|
||||
db.CommitTran();
|
||||
}
|
||||
|
|
@ -145,6 +169,50 @@ namespace VOL.HR.Services
|
|||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateEmpInfo(DingTalkEmployeeUpdate updateInfo)
|
||||
{
|
||||
return await _dingTalkService.UpdateEmpInfo(updateInfo);
|
||||
//return Task.FromResult(true);
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> SyncYSEmpToDingTalk(string ysEmpId)
|
||||
{
|
||||
var ysEmpInfo = await _ysService.GetEmpInfoById(ysEmpId);
|
||||
if(ysEmpInfo == null)
|
||||
{
|
||||
Core.Services.Logger.Error($"未在YS中找到用户信息:{ysEmpId}");
|
||||
return false;
|
||||
}
|
||||
// 获取对应的部门关联关系
|
||||
var dingDeptId = _deptShipRepo.Find(i => i.YSDeptId == ysEmpInfo.deptId).FirstOrDefault().DingTalkDeptId;
|
||||
// 获取直系主管在钉钉的ID 只取主职
|
||||
var director = ysEmpInfo.staffJob.FirstOrDefault(it => !it.endFlag).director;
|
||||
|
||||
var reportManagerId = "";
|
||||
if (!string.IsNullOrEmpty(director))
|
||||
{
|
||||
reportManagerId = _repository.Find(i => i.EmpYSID == director)?.FirstOrDefault()?.EmpDingDingID;
|
||||
}
|
||||
// 获取要同步的用户对应的钉钉的ID
|
||||
var empDingTalkId = _repository.Find(i => i.EmpYSID == ysEmpId).FirstOrDefault().EmpDingDingID;
|
||||
|
||||
|
||||
//TODO: 增加配置表,通过反射自动获取匹配信息
|
||||
var dingTalkInfo = new DingTalkEmployeeUpdate
|
||||
{
|
||||
userid = empDingTalkId,
|
||||
dept_id_list = dingDeptId,
|
||||
manager_userid = string.IsNullOrEmpty(reportManagerId) ? null : reportManagerId,
|
||||
email = ysEmpInfo.email,
|
||||
job_number = ysEmpInfo.code,
|
||||
exclusive_mobile = ysEmpInfo.mobile
|
||||
};
|
||||
var updated = await _dingTalkService.UpdateEmpInfo(dingTalkInfo);
|
||||
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace VOL.HR.Controllers
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// table加载数据后刷新当前table数据的字典项(适用字典数据量比较大的情况)
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
|
|
|
|||
|
|
@ -29,5 +29,43 @@ namespace VOL.HR.Controllers
|
|||
_service = service;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("cacheYSERPDept")]
|
||||
public async Task<IActionResult> GetYSERPDepartments()
|
||||
{
|
||||
await Service.CacheYSDepartments();
|
||||
return Json(new { });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("cacheDingTalkDept")]
|
||||
public async Task<IActionResult> CacheDingTalkDept()
|
||||
{
|
||||
await Service.CacheDingTalkDepartments();
|
||||
return Json(new { });
|
||||
}
|
||||
|
||||
// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("syncYSERPDeptToDingTalk")]
|
||||
public async Task<IActionResult> SyncYSERPDeptToDingTalk()
|
||||
{
|
||||
await Service.SyncYSERPDeptToDingTalk();
|
||||
return Json(new { });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using VOL.Core.Filters;
|
||||
using VOL.DingTalk.Models.Biz;
|
||||
using VOL.Entity.DomainModels;
|
||||
using VOL.HR.IServices;
|
||||
|
||||
|
|
@ -17,9 +18,8 @@ namespace VOL.HR.Controllers
|
|||
{
|
||||
public partial class HR_EmployeeSyncController
|
||||
{
|
||||
private readonly IHR_EmployeeSyncService _service;//访问业务代码
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
private readonly IHR_EmployeeSyncService _service;//访问业务代码
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public HR_EmployeeSyncController(
|
||||
IHR_EmployeeSyncService service,
|
||||
|
|
@ -33,15 +33,15 @@ namespace VOL.HR.Controllers
|
|||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("/api/HR_YSEmployees/getPageData")]
|
||||
public async Task<IActionResult> GetYSAllEmployees()
|
||||
[HttpPost, Route("GenEmpSystemShip")]
|
||||
public async Task<IActionResult> GenEmpSystemShip()
|
||||
{
|
||||
var emps = await Service.GetAllYSEmployees();
|
||||
return Json(emps);
|
||||
await Service.GenEmpSystemShip();
|
||||
return Json(new { });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -56,16 +56,40 @@ namespace VOL.HR.Controllers
|
|||
return Json(emps);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("/api/HR_YSEmployees/getPageData")]
|
||||
public async Task<IActionResult> GetYSAllEmployees()
|
||||
{
|
||||
var emps = await Service.GetAllYSEmployees();
|
||||
return Json(emps);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("GenEmpSystemShip")]
|
||||
public async Task<IActionResult> GenEmpSystemShip()
|
||||
[HttpPost, Route("UpdateEmpInfo")]
|
||||
public async Task<IActionResult> UpdateEmpInfo(DingTalkEmployeeUpdate updateInfo)
|
||||
{
|
||||
await Service.GenEmpSystemShip();
|
||||
await Service.UpdateEmpInfo(updateInfo);
|
||||
return Json(new { });
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("SyncYSEmpToDingTalk")]
|
||||
public async Task<IActionResult> SyncYSEmpToDingTalk([FromBody] string ysEmpId)
|
||||
{
|
||||
var result = await Service.SyncYSEmpToDingTalk(ysEmpId);
|
||||
return Json(new { success=result });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,11 @@
|
|||
Got timeout reading communication packets at MySqlConnector.Core.ResultSet.ReadResultSetHeaderAsync(IOBehavior ioBehavior) in /_/src/MySqlConnector/Core/ResultSet.cs:line 43
|
||||
at MySqlConnector.MySqlDataReader.ActivateResultSet(CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 130
|
||||
at MySqlConnector.MySqlDataReader.CreateAsync(CommandListPosition commandListPosition, ICommandPayloadCreator payloadCreator, IDictionary`2 cachedProcedures, IMySqlCommand command, CommandBehavior behavior, Activity activity, IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 468
|
||||
at MySqlConnector.Core.CommandExecutor.ExecuteReaderAsync(IReadOnlyList`1 commands, ICommandPayloadCreator payloadCreator, CommandBehavior behavior, Activity activity, IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/CommandExecutor.cs:line 56
|
||||
at MySqlConnector.MySqlCommand.ExecuteNonQueryAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlCommand.cs:line 296
|
||||
at MySqlConnector.MySqlBulkLoader.LoadAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlBulkLoader.cs:line 213
|
||||
at SqlSugar.MySqlFastBuilder.ExecuteBulkCopyAsync(DataTable dt)
|
||||
at SqlSugar.FastestProvider`1._BulkCopy(List`1 datas)
|
||||
at SqlSugar.FastestProvider`1.BulkCopyAsync(List`1 datas)
|
||||
at SqlSugar.FastestProvider`1.BulkCopy(List`1 datas)
|
||||
at VOL.Core.Services.Logger.Start() in D:\Code\Laservall-Data-Center\vol.api.sqlsugar\VOL.Core\Services\Logger.cs:line 183System.Private.CoreLib
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VOL.YSErp.Models.Biz
|
||||
|
|
@ -41,5 +42,7 @@ namespace VOL.YSErp.Models.Biz
|
|||
public string id { get; set; }
|
||||
public string pubts { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<YSERPDepartment> subDepts { get; set; } = [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,417 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VOL.YSErp.Models.Biz
|
||||
{
|
||||
public class YSERPEmployeeInfo
|
||||
{
|
||||
public string email { get; set; }
|
||||
public string certTypeName { get; set; }
|
||||
public string staffDefines__Y40_name { get; set; }
|
||||
public string majorcorpname { get; set; }
|
||||
public string staffDefines__J1 { get; set; }
|
||||
public string nationalityName { get; set; }
|
||||
public string staffDefines__id { get; set; }
|
||||
public string staffDefines__A86 { get; set; }
|
||||
public string staffDefines__A1 { get; set; }
|
||||
public string staffDefines__A85 { get; set; }
|
||||
public bool staffDefines__A4 { get; set; }
|
||||
public string staffDefines__A84 { get; set; }
|
||||
public string staffDefines__Y38_name { get; set; }
|
||||
public int enable { get; set; }
|
||||
public int shopAssisTag { get; set; }
|
||||
public string staffDefines__Y1_name { get; set; }
|
||||
public string staffDefines__Y10_name { get; set; }
|
||||
public string delayedRetDate { get; set; }
|
||||
public string staffDefines__A06 { get; set; }
|
||||
public bool staffDefines__A05 { get; set; }
|
||||
public string characterrprName { get; set; }
|
||||
public string id { get; set; }
|
||||
public string staffDefines__Y36_name { get; set; }
|
||||
public string staffDefines__T70 { get; set; }
|
||||
public string staffDefines__Y39_name { get; set; }
|
||||
public Staffpart[] staffPart { get; set; }
|
||||
public string deptId { get; set; }
|
||||
public string staffDefines__Y41_name { get; set; }
|
||||
public string linkAddr { get; set; }
|
||||
public string permanreside { get; set; }
|
||||
public string nationality { get; set; }
|
||||
public Staffprobation[] staffProbation { get; set; }
|
||||
public string staffDefines__Y11_name { get; set; }
|
||||
public Staffresume[] staffResume { get; set; }
|
||||
public string staffDefines__Y2_name { get; set; }
|
||||
public string shortName { get; set; }
|
||||
public string staffDefines__J1_name { get; set; }
|
||||
public string staffDefines__Y51_name { get; set; }
|
||||
public string staffDefines__Y14 { get; set; }
|
||||
public string staffDefines__Y13 { get; set; }
|
||||
public string staffDefines__Y12 { get; set; }
|
||||
public string staffDefines__Y11 { get; set; }
|
||||
public string countryCode { get; set; }
|
||||
public string staffDefines__Y35_name { get; set; }
|
||||
public int workAgeAdjMon { get; set; }
|
||||
public string staffDefines__Y10 { get; set; }
|
||||
public string unitId { get; set; }
|
||||
public string pubts { get; set; }
|
||||
public string staffDefines__Y13_name { get; set; }
|
||||
public string staffDefines__Y6_name { get; set; }
|
||||
public string staffDefines__Y9_name { get; set; }
|
||||
public int sex { get; set; }
|
||||
public string maritalName { get; set; }
|
||||
public string staffDefines__Y31 { get; set; }
|
||||
public string photo { get; set; }
|
||||
public string staffDefines__Y30 { get; set; }
|
||||
public string staffDefines__T70_name { get; set; }
|
||||
public string userId { get; set; }
|
||||
public string staffDefines__Y3_name { get; set; }
|
||||
public string staffDefines__Y45_name { get; set; }
|
||||
public Staffjob[] staffJob { get; set; }
|
||||
public string staffDefines__Y42_name { get; set; }
|
||||
public string selfEmail { get; set; }
|
||||
public string politicalCode { get; set; }
|
||||
public Staffedu[] staffEdu { get; set; }
|
||||
public string originName { get; set; }
|
||||
public string country { get; set; }
|
||||
public string certType { get; set; }
|
||||
public string staffDefines__Y42 { get; set; }
|
||||
public string political { get; set; }
|
||||
public string staffDefines__Y41 { get; set; }
|
||||
public string staffDefines__Y40 { get; set; }
|
||||
public string joinWorkDate { get; set; }
|
||||
public string staffDefines__Y8_name { get; set; }
|
||||
public string staffDefines__Y39 { get; set; }
|
||||
public string staffDefines__Y38 { get; set; }
|
||||
public string idCardReverse { get; set; }
|
||||
public string nationalityCode { get; set; }
|
||||
public string staffDefines__Y36 { get; set; }
|
||||
public string staffDefines__Y35 { get; set; }
|
||||
public string staffDefines__Y34 { get; set; }
|
||||
public string attachlist { get; set; }
|
||||
public Dictionary<string,object> staffDefines { get; set; }
|
||||
public string staffDefines__Y32 { get; set; }
|
||||
public int workAgeAdjYea { get; set; }
|
||||
public string staffDefines__Y32_name { get; set; }
|
||||
public string originCode { get; set; }
|
||||
public string psnclId { get; set; }
|
||||
public string staffDefines__Y7_name { get; set; }
|
||||
public string staffDefines__Y30_name { get; set; }
|
||||
public int workAgeAdjDay { get; set; }
|
||||
public string majorcorpCode { get; set; }
|
||||
public string staffDefines__Y53 { get; set; }
|
||||
public string staffDefines__Y52 { get; set; }
|
||||
public string staffDefines__Y51 { get; set; }
|
||||
public string staffDefines__Y44_name { get; set; }
|
||||
public string staffDefines__Y50 { get; set; }
|
||||
public string certTypeCode { get; set; }
|
||||
public bool staffDefines__FK003 { get; set; }
|
||||
public Staffsocialrel[] staffSocialRel { get; set; }
|
||||
public string certNo { get; set; }
|
||||
public string permanresideName { get; set; }
|
||||
public string staffDefines__Y45 { get; set; }
|
||||
public string staffDefines__A1_name { get; set; }
|
||||
public string staffDefines__Y44 { get; set; }
|
||||
public string staffDefines__Y5_name { get; set; }
|
||||
public string staffDefines__Y14_name { get; set; }
|
||||
public string staffDefines__Y43 { get; set; }
|
||||
public string name { get; set; }
|
||||
public string staffDefines__F1 { get; set; }
|
||||
public string countryName { get; set; }
|
||||
public Staffctrt[] staffCtrt { get; set; }
|
||||
public Stafforgrel[] staffOrgRel { get; set; }
|
||||
public string code { get; set; }
|
||||
public string staffDefines__Y43_name { get; set; }
|
||||
public string staffDefines__YY01 { get; set; }
|
||||
public string origin { get; set; }
|
||||
public float workAge { get; set; }
|
||||
public string majorcorpid { get; set; }
|
||||
public float staffDefines__T42 { get; set; }
|
||||
public float staffDefines__T41 { get; set; }
|
||||
public string staffDefines__Y12_name { get; set; }
|
||||
public string joinedDate { get; set; }
|
||||
public string characterrpr { get; set; }
|
||||
public string permanresideCode { get; set; }
|
||||
public string staffDefines__Y4_name { get; set; }
|
||||
public string politicalName { get; set; }
|
||||
public string staffDefines__Y9 { get; set; }
|
||||
public Staffbankacct[] staffBankAcct { get; set; }
|
||||
public int bizManTag { get; set; }
|
||||
public string mobile { get; set; }
|
||||
public string staffDefines__Y2 { get; set; }
|
||||
public string staffDefines__Y1 { get; set; }
|
||||
public string staffDefines__Y4 { get; set; }
|
||||
public string staffDefines__Y3 { get; set; }
|
||||
public string staffDefines__Y6 { get; set; }
|
||||
public string birthDate { get; set; }
|
||||
public string staffDefines__Y5 { get; set; }
|
||||
public string idCardFront { get; set; }
|
||||
public string staffDefines__A78_name { get; set; }
|
||||
public string staffDefines__Y8 { get; set; }
|
||||
public string staffDefines__Y7 { get; set; }
|
||||
public string marital { get; set; }
|
||||
public string maritalCode { get; set; }
|
||||
public string staffDefines__Y31_name { get; set; }
|
||||
public string staffDefines__Y34_name { get; set; }
|
||||
public Staffprotocol[] staffProtocol { get; set; }
|
||||
public string staffDefines__Y50_name { get; set; }
|
||||
public string staffDefines__A78 { get; set; }
|
||||
public int age { get; set; }
|
||||
public string characterrprCode { get; set; }
|
||||
public string masterOrgKeyField { get; set; }
|
||||
public string _mddFormulaExecuteFlag { get; set; }
|
||||
public bool editPermission { get; set; }
|
||||
public bool mobile_email_validate { get; set; }
|
||||
public string subOrgId { get; set; }
|
||||
|
||||
public class Staffpart
|
||||
{
|
||||
public string deptName { get; set; }
|
||||
public string partTypeCode { get; set; }
|
||||
public bool isMainJob { get; set; }
|
||||
public bool endFlag { get; set; }
|
||||
public string orgVidCode { get; set; }
|
||||
public int recordNum { get; set; }
|
||||
public string orgId { get; set; }
|
||||
public string orgRelId { get; set; }
|
||||
public string trnsTypeCode { get; set; }
|
||||
public int assgId { get; set; }
|
||||
public string id { get; set; }
|
||||
public string staffFte { get; set; }
|
||||
public string trnsType { get; set; }
|
||||
public bool isweaken { get; set; }
|
||||
public string partType { get; set; }
|
||||
public bool lastFlag { get; set; }
|
||||
public int trnsEvent { get; set; }
|
||||
public string trnsTypeName { get; set; }
|
||||
public string orgName { get; set; }
|
||||
public string deptVid { get; set; }
|
||||
public string partTypeName { get; set; }
|
||||
public string deptId { get; set; }
|
||||
public string staffjobCode { get; set; }
|
||||
public string belongJob { get; set; }
|
||||
public string orgVid { get; set; }
|
||||
public string beginDate { get; set; }
|
||||
public int effectiveNumber { get; set; }
|
||||
public string deptVidCode { get; set; }
|
||||
public int showOrder { get; set; }
|
||||
public string staffjobId { get; set; }
|
||||
public string staffId { get; set; }
|
||||
public string orgPath { get; set; }
|
||||
public string deptPath { get; set; }
|
||||
}
|
||||
|
||||
public class Staffprobation
|
||||
{
|
||||
public string beginDate { get; set; }
|
||||
public string belongJobCode { get; set; }
|
||||
public string endDate { get; set; }
|
||||
public bool endFlag { get; set; }
|
||||
public string id { get; set; }
|
||||
public int promonth { get; set; }
|
||||
public string belongJob { get; set; }
|
||||
public string staffId { get; set; }
|
||||
public int termUnit { get; set; }
|
||||
public bool lastFlag { get; set; }
|
||||
}
|
||||
|
||||
public class Staffresume
|
||||
{
|
||||
public string workCorp { get; set; }
|
||||
public string beginDate { get; set; }
|
||||
public string newPost { get; set; }
|
||||
public string workDept { get; set; }
|
||||
public string staffJobId { get; set; }
|
||||
public string id { get; set; }
|
||||
public int resumeType { get; set; }
|
||||
public string pubts { get; set; }
|
||||
public string staffId { get; set; }
|
||||
public string workPost { get; set; }
|
||||
public string endDate { get; set; }
|
||||
public string workDuty { get; set; }
|
||||
}
|
||||
|
||||
public class Staffjob
|
||||
{
|
||||
public string deptName { get; set; }
|
||||
public string newPostId { get; set; }
|
||||
public bool isMainJob { get; set; }
|
||||
public string directorCode { get; set; }
|
||||
public string addrIdCode { get; set; }
|
||||
public bool endFlag { get; set; }
|
||||
public string orgVidCode { get; set; }
|
||||
public string staffJobDefines__id { get; set; }
|
||||
public int recordNum { get; set; }
|
||||
public string addrName { get; set; }
|
||||
public string orgId { get; set; }
|
||||
public string orgRelId { get; set; }
|
||||
public string trnsTypeCode { get; set; }
|
||||
public string newPostName { get; set; }
|
||||
public string newPostVidCode { get; set; }
|
||||
public string staffJobDefines__A01 { get; set; }
|
||||
public string psnclName { get; set; }
|
||||
public string directorName { get; set; }
|
||||
public int enable { get; set; }
|
||||
public string addrId { get; set; }
|
||||
public string id { get; set; }
|
||||
public string psnclId { get; set; }
|
||||
public string pubts { get; set; }
|
||||
public string trnsType { get; set; }
|
||||
public Staffjobdefines staffJobDefines { get; set; }
|
||||
public string empformName { get; set; }
|
||||
public bool isweaken { get; set; }
|
||||
public bool lastFlag { get; set; }
|
||||
public string psnclIdCode { get; set; }
|
||||
public int trnsEvent { get; set; }
|
||||
public string trnsTypeName { get; set; }
|
||||
public string empform { get; set; }
|
||||
public string orgName { get; set; }
|
||||
public string deptVid { get; set; }
|
||||
public string director { get; set; }
|
||||
public string deptId { get; set; }
|
||||
public string staffjobCode { get; set; }
|
||||
public string orgVid { get; set; }
|
||||
public string newPostVid { get; set; }
|
||||
public string beginDate { get; set; }
|
||||
public int effectiveNumber { get; set; }
|
||||
public string deptVidCode { get; set; }
|
||||
public int showOrder { get; set; }
|
||||
public string empformCode { get; set; }
|
||||
public string staffjobId { get; set; }
|
||||
public string staffId { get; set; }
|
||||
public string orgPath { get; set; }
|
||||
public string deptPath { get; set; }
|
||||
}
|
||||
|
||||
public class Staffjobdefines
|
||||
{
|
||||
public string A01 { get; set; }
|
||||
public string ytenant { get; set; }
|
||||
public string id { get; set; }
|
||||
public int dr { get; set; }
|
||||
}
|
||||
|
||||
public class Staffedu
|
||||
{
|
||||
public string beginDate { get; set; }
|
||||
public string major { get; set; }
|
||||
public string endDate { get; set; }
|
||||
public string school { get; set; }
|
||||
public string id { get; set; }
|
||||
public string degreeAnnex { get; set; }
|
||||
public string staffId { get; set; }
|
||||
public bool isPrefs { get; set; }
|
||||
public string educationAnnex { get; set; }
|
||||
}
|
||||
|
||||
public class Staffsocialrel
|
||||
{
|
||||
public string workCorp { get; set; }
|
||||
public bool isUrgent { get; set; }
|
||||
public string relName { get; set; }
|
||||
public string relationName { get; set; }
|
||||
public string relationCode { get; set; }
|
||||
public string id { get; set; }
|
||||
public string workDuty { get; set; }
|
||||
public string linkTel { get; set; }
|
||||
public string staffId { get; set; }
|
||||
public string relation { get; set; }
|
||||
}
|
||||
|
||||
public class Staffctrt
|
||||
{
|
||||
public int continueTime { get; set; }
|
||||
public string majorCorpCode { get; set; }
|
||||
public int termMonth { get; set; }
|
||||
public string belongJobCode { get; set; }
|
||||
public string endDate { get; set; }
|
||||
public string majorCorpId { get; set; }
|
||||
public int termType { get; set; }
|
||||
public string workAddr { get; set; }
|
||||
public bool isReceive { get; set; }
|
||||
public int contType { get; set; }
|
||||
public string signDate { get; set; }
|
||||
public string belongJob { get; set; }
|
||||
public string beginDate { get; set; }
|
||||
public string majorCorpName { get; set; }
|
||||
public string contractNum { get; set; }
|
||||
public string workAddrName { get; set; }
|
||||
public string id { get; set; }
|
||||
public int performStatus { get; set; }
|
||||
public string workAddrCode { get; set; }
|
||||
public string staffId { get; set; }
|
||||
public string termUnit { get; set; }
|
||||
public bool lastFlag { get; set; }
|
||||
}
|
||||
|
||||
public class Stafforgrel
|
||||
{
|
||||
public bool isReturn { get; set; }
|
||||
public string staffCode { get; set; }
|
||||
public string code { get; set; }
|
||||
public string orgName { get; set; }
|
||||
public string employerCode { get; set; }
|
||||
public string entrySrc { get; set; }
|
||||
public string employerName { get; set; }
|
||||
public bool endFlag { get; set; }
|
||||
public string orgIdCode { get; set; }
|
||||
public int recordNum { get; set; }
|
||||
public string orgId { get; set; }
|
||||
public string beginDate { get; set; }
|
||||
public float secretaryAge { get; set; }
|
||||
public string entrySrcName { get; set; }
|
||||
public string entrySrcCode { get; set; }
|
||||
public string groupDate { get; set; }
|
||||
public string employer { get; set; }
|
||||
public string id { get; set; }
|
||||
public float groupWorkAge { get; set; }
|
||||
public string pubts { get; set; }
|
||||
public string staffId { get; set; }
|
||||
public string seniorityDate { get; set; }
|
||||
public bool lastFlag { get; set; }
|
||||
}
|
||||
|
||||
public class Staffbankacct
|
||||
{
|
||||
public string bankCode { get; set; }
|
||||
public string openBank { get; set; }
|
||||
public string accountName { get; set; }
|
||||
public int isDefaultCard { get; set; }
|
||||
public string bankAnnex { get; set; }
|
||||
public string openBankCode { get; set; }
|
||||
public string bankName { get; set; }
|
||||
public string openBankName { get; set; }
|
||||
public string bank { get; set; }
|
||||
public string currencyName { get; set; }
|
||||
public string acctType { get; set; }
|
||||
public string currency { get; set; }
|
||||
public string id { get; set; }
|
||||
public string currencyCode { get; set; }
|
||||
public string staffId { get; set; }
|
||||
public string account { get; set; }
|
||||
}
|
||||
|
||||
public class Staffprotocol
|
||||
{
|
||||
public string majorCorpCode { get; set; }
|
||||
public string belongJobCode { get; set; }
|
||||
public string protocolNum { get; set; }
|
||||
public string majorCorpId { get; set; }
|
||||
public bool isReceive { get; set; }
|
||||
public int contType { get; set; }
|
||||
public string protocolType { get; set; }
|
||||
public string signDate { get; set; }
|
||||
public string belongJob { get; set; }
|
||||
public string beginDate { get; set; }
|
||||
public string majorCorpName { get; set; }
|
||||
public string id { get; set; }
|
||||
public string protocolTypeName { get; set; }
|
||||
public int performStatus { get; set; }
|
||||
public string termUnit { get; set; }
|
||||
public string staffId { get; set; }
|
||||
public string protocolTypeCode { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,21 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace VOL.YSErp.Models.Biz
|
||||
{
|
||||
|
||||
|
||||
public class YSERPResponse<T>
|
||||
{
|
||||
|
||||
public string code { get; set; }
|
||||
public string message { get; set; }
|
||||
public T data { get; set; }
|
||||
|
||||
public string? displayCode { get; set; }
|
||||
|
||||
public int? level { get; set; }
|
||||
}
|
||||
|
||||
public class YSERPPagedResponse<T>
|
||||
{
|
||||
|
||||
public string code { get; set; }
|
||||
|
|
@ -27,6 +41,8 @@ namespace VOL.YSErp.Models.Biz
|
|||
public bool haveNextPage { get; set; }
|
||||
public List<T> recordList { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class YSERPListResponse<T>
|
||||
{
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ namespace VOL.YSErp.Services.Biz
|
|||
private readonly string _employeeListUrl;
|
||||
private readonly string _updateEmployeeUrl;
|
||||
private readonly string _deptListUrl;
|
||||
|
||||
private readonly string _empInfoUrl;
|
||||
public YSERPService(SystemToken token, YSConfig config)
|
||||
{
|
||||
_token = token;
|
||||
|
|
@ -40,6 +40,7 @@ namespace VOL.YSErp.Services.Biz
|
|||
_employeeListUrl = $"{_apiUrl}/iuap-api-gateway/yonbip/hrcloud/staff/listdetailmdd";
|
||||
_updateEmployeeUrl = $"{_apiUrl}/iuap-api-gateway/yonbip/hrcloud/batchInsertOrUpdateDetailNew";
|
||||
_deptListUrl = $"{_apiUrl}/iuap-api-gateway/yonbip/digitalModel/basedoc/dept/list";
|
||||
_empInfoUrl = $"{_apiUrl}/iuap-api-gateway/yonbip/hrcloud/HRCloud/getStaffDetail";
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -199,7 +200,7 @@ namespace VOL.YSErp.Services.Biz
|
|||
|
||||
response.EnsureSuccessStatusCode();
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var data = JsonSerializer.Deserialize<YSERPResponse<YSERPEmployee>>(content);
|
||||
var data = JsonSerializer.Deserialize<YSERPPagedResponse<YSERPEmployee>>(content);
|
||||
if (!new[] { "200", "00000" }.Contains(data.code))
|
||||
{
|
||||
var errorMsg = data.message;
|
||||
|
|
@ -213,9 +214,10 @@ namespace VOL.YSErp.Services.Biz
|
|||
{
|
||||
Logger.Info("没有更多员工记录");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
allEmployees.AddRange(data.data.recordList);
|
||||
var toAdd = recordList.Where(r => !allEmployees.Any(e => e.id == r.id)).ToList();
|
||||
allEmployees.AddRange(toAdd);
|
||||
|
||||
var haveNext = data.data.haveNextPage;
|
||||
var pageCount = data.data.pageCount;
|
||||
|
|
@ -224,7 +226,7 @@ namespace VOL.YSErp.Services.Biz
|
|||
{
|
||||
break;
|
||||
}
|
||||
Logger.Info($"第 {pageIndex}/{pageCount} 页,获取 {recordList.Count} 条员工记录");
|
||||
//Logger.Info($"第 {pageIndex}/{pageCount} 页,获取 {recordList.Count} 条员工记录");
|
||||
pageIndex++;
|
||||
await Task.Delay(500); // 避免请求过快
|
||||
}
|
||||
|
|
@ -274,9 +276,6 @@ namespace VOL.YSErp.Services.Biz
|
|||
|
||||
if (data.TryGetProperty("data", out var resultData))
|
||||
{
|
||||
//_logger.LogInformation($"处理总数: {GetPropertyValueOrDefault(resultData, "count", 0)}");
|
||||
//_logger.LogInformation($"成功数: {GetPropertyValueOrDefault(resultData, "successCount", 0)}");
|
||||
//_logger.LogInformation($"失败数: {GetPropertyValueOrDefault(resultData, "failCount", 0)}");
|
||||
|
||||
if (resultData.TryGetProperty("messages", out var messages))
|
||||
{
|
||||
|
|
@ -303,8 +302,6 @@ namespace VOL.YSErp.Services.Biz
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task<List<YSERPDepartment>> GetAllDepartmentsAsync()
|
||||
{
|
||||
var token = await GetValidTokenAsync();
|
||||
|
|
@ -376,25 +373,57 @@ namespace VOL.YSErp.Services.Biz
|
|||
//_logger.LogInformation($"共获取 {allEmployees.Count} 条YS员工记录");
|
||||
return allDepts;
|
||||
}
|
||||
|
||||
private string ConvertGender(string gender)
|
||||
|
||||
public async Task<YSERPEmployeeInfo> GetEmpInfoById(string empId)
|
||||
{
|
||||
return gender switch
|
||||
var token = await GetValidTokenAsync();
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
"男" => "1",
|
||||
"女" => "2",
|
||||
_ => gender
|
||||
};
|
||||
}
|
||||
return new YSERPEmployeeInfo();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
id = empId
|
||||
};
|
||||
|
||||
private static string GetPropertyValueOrEmpty(JsonElement element, string propertyName)
|
||||
{
|
||||
return element.TryGetProperty(propertyName, out var property) ? property.GetString() ?? "" : "";
|
||||
}
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, $"{_empInfoUrl}?access_token={token}")
|
||||
{
|
||||
Content = new StringContent(
|
||||
JsonSerializer.Serialize(payload),
|
||||
Encoding.UTF8,
|
||||
"application/json")
|
||||
};
|
||||
|
||||
private static int GetPropertyValueOrDefault(JsonElement element, string propertyName, int defaultValue)
|
||||
{
|
||||
return element.TryGetProperty(propertyName, out var property) ? property.GetInt32() : defaultValue;
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
{
|
||||
Logger.Error("认证失败: 无效的API令牌或权限不足");
|
||||
_accessToken = null;
|
||||
return new YSERPEmployeeInfo();
|
||||
}
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var data = JsonSerializer.Deserialize<YSERPResponse<YSERPEmployeeInfo>>(content);
|
||||
if (!new[] { "200", "00000" }.Contains(data.code))
|
||||
{
|
||||
var errorMsg = data.message;
|
||||
Logger.Error($"API业务错误: {errorMsg}");
|
||||
throw new Exception(errorMsg);
|
||||
}
|
||||
|
||||
return data.data;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"获取部门数据失败,{ex}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
Loading…
Reference in New Issue