From bed8c9172b260faa40b2bbf2fa3a6a77294131f8 Mon Sep 17 00:00:00 2001 From: Deryushev_av Date: Thu, 19 Feb 2026 14:23:20 +0400 Subject: [PATCH 1/2] =?UTF-8?q?=D0=A8=D0=B0=D0=B1=D0=BB=D0=BE=D0=BD=20?= =?UTF-8?q?=D1=81=20=D0=BF=D1=80=D0=B8=D0=BC=D0=B5=D1=80=D0=B0=D0=BC=D0=B8?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D0=BE=D0=BD=D0=BD=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=B0=20=D0=BA=20SAP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 + ...ortRuleDepartmentManagerServerFunctions.cs | 161 ++++++++ .../ImportRuleDepartmentServerFunctions.cs | 222 +++++++++++ .../ImportRuleEmployeeServerFunctions.cs | 355 ++++++++++++++++++ .../ImportRuleJobTitleServerFunctions.cs | 89 +++++ SAPConnector/README.md | 81 ++++ 6 files changed, 911 insertions(+) create mode 100644 SAPConnector/ImportRuleDepartmentManagerServerFunctions.cs create mode 100644 SAPConnector/ImportRuleDepartmentServerFunctions.cs create mode 100644 SAPConnector/ImportRuleEmployeeServerFunctions.cs create mode 100644 SAPConnector/ImportRuleJobTitleServerFunctions.cs create mode 100644 SAPConnector/README.md diff --git a/README.md b/README.md index a649aab..0108228 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,6 @@ 7. **[Шаблон кода для работы с S3-хранилищем](S3Connector/)** - Шаблон с методами получения файлов из хранилища, работающего по протоколу S3. + +7. **[Шаблон обработки ответов от коннектора к SAP](SAPConnector/)** + - Шаблон с методами синхронизации орг. структуры с SAP. diff --git a/SAPConnector/ImportRuleDepartmentManagerServerFunctions.cs b/SAPConnector/ImportRuleDepartmentManagerServerFunctions.cs new file mode 100644 index 0000000..76bde04 --- /dev/null +++ b/SAPConnector/ImportRuleDepartmentManagerServerFunctions.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Sungero.Core; +using Sungero.CoreEntities; +using DirRX.SapIntegration.ImportRuleDepartmentManager; + +namespace DirRX.SapIntegration.Server +{ + partial class ImportRuleDepartmentManagerFunctions + { + /// + /// Выполнить сохранение полученных данных в RX. + /// + /// Матрица с ответом от SAP. + /// Структурированный лог. + public override void SaveData(List> response, List logs) + { + var managers = new List(); + + // Получить руководящие должности, где OType = S, Vrsign = B и Vrelat = 012. + var filteredResponse = response.Where(x => + x.Any(y => y.Key == Constants.ImportRuleBase.IsStructuralExist && y.Value == "1") && + x.Any(y => y.Key == Constants.ImportRuleBase.SAPStructuralFields.Otype && y.Value == "S") && + x.Any(y => y.Key == Constants.ImportRuleBase.SAPStructuralFields.Vrsign && y.Value == "B") && + x.Any(y => y.Key == Constants.ImportRuleBase.SAPStructuralFields.Vrelat && y.Value == "012") + ); + foreach (var responseItem in filteredResponse) + { + var manager = ParseResponseItem(responseItem, response, logs); + if (manager != null) + managers.Add(manager); + } + + if (managers.Any()) + { + ImportDepartmentManagers(managers, logs); + } + } + + /// + /// Обработка строки матрицы с ответом от SAP. + /// + /// Строка матрицы с ответом от SAP. + /// Полная матрица с ответом от SAP. + /// Структурированный лог. + /// Свойства сущности в структурированном виде. + public virtual DirRX.SapIntegration.Structures.ImportRuleDepartmentManager.DepartmentManager ParseResponseItem(System.Collections.Generic.Dictionary responseItem, + List> response, + List logs) + { + var departmentSapId = string.Empty; + var managerSapId = string.Empty; + var jobTitleSapId = responseItem[Constants.ImportRuleBase.SAPObjectFields.SapID]; + // Получить вышестоящий элемент - подразделение. + var parentId = responseItem[Constants.ImportRuleBase.SAPStructuralFields.Pup]; + if (parentId != "0") + { + var parentNode = response.FirstOrDefault(x => x.Any(y => y.Key == Constants.ImportRuleBase.SAPStructuralFields.Seqnr && y.Value == parentId)); + if (parentNode != null) + { + departmentSapId = parentNode[Constants.ImportRuleBase.SAPObjectFields.SapID]; + } + } + else + { + var errorMessage = string.Format("Нет вышестоящих элементов (подразделений) для должности {0}.", jobTitleSapId); + Logger.Error(errorMessage); + logs.Add(DirRX.SapIntegration.Functions.Module.CreateLogItem(_obj.Name, + DirRX.SapIntegration.Constants.Module.Logging.MessageLevel.ResponseLevel, + DirRX.SapIntegration.Constants.Module.Logging.MessageType.Error, + errorMessage)); + } + + // Получить сотрудника по штатной должности. + var jobTitle = Solution.JobTitles.GetAll().Where(x => x.IDSAP != null && x.IDSAP == jobTitleSapId).FirstOrDefault(); + var managers = Solution.Employees.GetAll().Where(x => Solution.JobTitles.Equals(x.JobTitle, jobTitle) && x.Status == Sungero.CoreEntities.DatabookEntry.Status.Active); + if (managers.Count() > 1) + { + var errorMessage = string.Format("Найдено больше одного сотрудника для должности {0}.", jobTitleSapId); + Logger.Error(errorMessage); + logs.Add(DirRX.SapIntegration.Functions.Module.CreateLogItem(_obj.Name, + DirRX.SapIntegration.Constants.Module.Logging.MessageLevel.ResponseLevel, + DirRX.SapIntegration.Constants.Module.Logging.MessageType.Error, + errorMessage)); + } + else + { + var manager = managers.FirstOrDefault(); + if (manager != null) + managerSapId = manager.IDSAP; + else + { + var errorMessage = string.Format("Не найдено ни одного сотрудника для должности {0}.", jobTitleSapId); + Logger.Error(errorMessage); + logs.Add(DirRX.SapIntegration.Functions.Module.CreateLogItem(_obj.Name, + DirRX.SapIntegration.Constants.Module.Logging.MessageLevel.ResponseLevel, + DirRX.SapIntegration.Constants.Module.Logging.MessageType.Error, + errorMessage)); + + } + } + + if (!string.IsNullOrWhiteSpace(departmentSapId) && !string.IsNullOrWhiteSpace(managerSapId)) + { + var managerData = new DirRX.SapIntegration.Structures.ImportRuleDepartmentManager.DepartmentManager(); + managerData.DepartmentSapId = departmentSapId; + managerData.ManagerSapId = managerSapId; + return managerData; + } + else + return null; + } + + /// + /// Процедура импорта руководителей подразделений. + /// + /// Структурированный набор данных по импортируемым руководителям подразделений. + /// Список структурированных логов. + [Remote] + public virtual void ImportDepartmentManagers(List + items, List logs) + { + var i = 0; + var total = items.Count; + foreach (var item in items) + { + i++; + var department = Solution.Departments.GetAll().Where(d => d.IDSAP == item.DepartmentSapId).FirstOrDefault(); + if (department != null) + { + var manager = Solution.Employees.Null; + if (!string.IsNullOrWhiteSpace(item.ManagerSapId)) + manager = Solution.Employees.GetAll().Where(x => x.IDSAP == item.ManagerSapId).FirstOrDefault(); + if (!Solution.Departments.Equals(department.Manager, manager)) + department.Manager = manager; + } + + try + { + if (department.State.IsChanged) + { + Logger.DebugFormat("Обновление руководителей подразделений: {0}/{1}", i, total); + department.Save(); + Logger.Debug(string.Format("Заполнение руководителя Подразделения {0}.", department.Name)); + } + } + catch (Exception ex) + { + var errorMessage = string.Format("Ошибка при заполнении руководителя Подразделения {0}. Подробности: {1}.", department.Name, ex.Message); + Logger.Error(errorMessage, ex); + logs.Add(DirRX.SapIntegration.Functions.Module.CreateLogItem(_obj.Name, + DirRX.SapIntegration.Constants.Module.Logging.MessageLevel.ResponseLevel, + DirRX.SapIntegration.Constants.Module.Logging.MessageType.Error, + errorMessage)); + } + } + } + + } +} \ No newline at end of file diff --git a/SAPConnector/ImportRuleDepartmentServerFunctions.cs b/SAPConnector/ImportRuleDepartmentServerFunctions.cs new file mode 100644 index 0000000..988e72d --- /dev/null +++ b/SAPConnector/ImportRuleDepartmentServerFunctions.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Sungero.Core; +using Sungero.CoreEntities; +using DirRX.SapIntegration.ImportRuleDepartment; + +namespace DirRX.SapIntegration.Server +{ + partial class ImportRuleDepartmentFunctions + { + #region Импорт Подразделений из SAP. + + /// + /// Выполнить сохранение полученных данных в RX. + /// + /// Матрица с ответом от SAP. + /// Структурированный лог. + public override void SaveData(List> response, List logs) + { + var departments = new List(); + + var filteredResponse = response.Where(x => x.Any(y => y.Key == Constants.ImportRuleBase.IsStructuralExist && y.Value == "1")); + foreach (var responseItem in filteredResponse) + { + departments.Add(ParseResponseItem(responseItem, response)); + } + + if (departments.Any()) + { + var businessUnitSapId = departments.FirstOrDefault().BusinessUnitSapId; + ImportDepartments(businessUnitSapId, departments, logs); + UpdateDepartamentsHeadOffice(departments, logs); + CloseDepartments(businessUnitSapId, departments, logs); + } + } + + /// + /// Обработка строки матрицы с ответом от SAP. + /// + /// Строка матрицы с ответом от SAP. + /// Свойства сущности в структурированном виде. + public virtual DirRX.SapIntegration.Structures.ImportRuleDepartment.Department ParseResponseItem(System.Collections.Generic.Dictionary responseItem, + List> response) + { + // Получить ИД ведущего подразделения через связь Pup - Seqnr - Objid. + var headOfficeSapId = string.Empty; + var itemUpId = responseItem[Constants.ImportRuleBase.SAPStructuralFields.Pup]; + if (itemUpId != "0") + { + var itemUp = response.FirstOrDefault(x => x.Any(y => y.Key == Constants.ImportRuleBase.SAPStructuralFields.Seqnr && y.Value == itemUpId)); + if (itemUp != null) + headOfficeSapId = itemUp[Constants.ImportRuleBase.SAPObjectFields.SapID]; + } + + var department = new DirRX.SapIntegration.Structures.ImportRuleDepartment.Department(); + department.SapId = responseItem[Constants.ImportRuleBase.SAPObjectFields.SapID]; + department.Name = responseItem[Constants.ImportRuleBase.SAPObjectFields.Name]; + department.BusinessUnitSapId = responseItem[Constants.ImportRuleBase.RequestParamOrgUnintSAPID]; + department.HeadOfficeSapId = headOfficeSapId; + // Если данные пришли из SAP то статс записи всегда Действующий. Статус Закрытая будет проставлен для тех записей, которые не пришли из SAP. + department.Status = Sungero.Company.Department.Status.Active; + return department; + } + + /// + /// Процедура обновления головного подразделения для подразделений. + /// + /// Структурированный набор данных по импортируемым подразделениям. + [Remote] + public void UpdateDepartamentsHeadOffice(List items, List logs) + { + var i = 0; + var total = items.Count; + foreach (var item in items) + { + i++; + var department = Solution.Departments.GetAll().Where(x => x.IDSAP == item.SapId).FirstOrDefault(); + if (department != null) + { + var headOffice = Solution.Departments.Null; + if (!string.IsNullOrWhiteSpace(item.HeadOfficeSapId)) + headOffice = Solution.Departments.GetAll().Where(d => d.IDSAP == item.HeadOfficeSapId).FirstOrDefault(); + if (!Solution.Departments.Equals(department.HeadOffice, headOffice)) + department.HeadOffice = headOffice; + + try + { + if (department.State.IsChanged) + { + Logger.DebugFormat("Обновление головного подразделения: {0}/{1}", i, total); + department.Save(); + Logger.Debug(string.Format("Обновление головного подразделения карточки Подразделения {0}", department.Name)); + } + } + catch (Exception ex) + { + var errorMessage = string.Format("Ошибка при обновлении головного подразделения карточки Подразделения {0}. Подробности: {1}.", department.Name, ex.Message); + Logger.Error(errorMessage, ex); + logs.Add(DirRX.SapIntegration.Functions.Module.CreateLogItem(_obj.Name, + DirRX.SapIntegration.Constants.Module.Logging.MessageLevel.ResponseLevel, + DirRX.SapIntegration.Constants.Module.Logging.MessageType.Error, + errorMessage)); + } + } + } + } + + /// + /// Процедура импорта Подразделений. + /// + /// Код SAP Нашей организации. + /// Структурированный набор данных по импортируемым Подразделений. + /// Структурированный лог. + [Remote] + public void ImportDepartments(string businessUnitSapId, List items, List logs) + { + var businessUnit = Solution.BusinessUnits.GetAll().Where(x => x.IDSAP == businessUnitSapId).FirstOrDefault(); + + var i = 0; + var total = items.Count; + foreach (var item in items) + { + i++; + var department = Solution.Departments.GetAll().Where(x => x.IDSAP == item.SapId).FirstOrDefault(); + if (department == null) + { + department = Solution.Departments.Create(); + department.IDSAP = item.SapId; + } + + if (department.Name != item.Name) + department.Name = item.Name; + + if (businessUnit != null && !Solution.BusinessUnits.Equals(department.BusinessUnit, businessUnit)) + department.BusinessUnit = businessUnit; + + if (department.Status != item.Status) + department.Status = item.Status; + + try + { + if (department.State.IsChanged || department.State.IsInserted) + { + Logger.DebugFormat("Импорт подразделений: {0}/{1}", i, total); + department.Save(); + Logger.Debug(string.Format("Обновление/создание карточки Подразделения {0}.", department.Name)); + } + } + catch (Exception ex) + { + var errorMessage = string.Format("Ошибка при обновлении карточки Подразделения {0}. Подробности: {1}.", department.Name, ex.Message); + Logger.Error(errorMessage, ex); + logs.Add(DirRX.SapIntegration.Functions.Module.CreateLogItem(_obj.Name, + DirRX.SapIntegration.Constants.Module.Logging.MessageLevel.ResponseLevel, + DirRX.SapIntegration.Constants.Module.Logging.MessageType.Error, + errorMessage)); + } + } + } + + /// + /// Закрыть подразделения, которые не пришли при синхронизации и у которых заполнено свойство IDSAP. + /// + /// Код SAP Нашей организации. + /// Структурированный набор данных по импортируемым Подразделениям. + /// Структурированный лог. + public void CloseDepartments(string businessUnitSapId, List items, List logs) + { + var businessUnit = Solution.BusinessUnits.GetAll().Where(x => x.IDSAP == businessUnitSapId).FirstOrDefault(); + + // Составить список обработанных подразделений, так как в linq нет трансляции для поиска по списку структур. + var updatedDepartmentSapIDs = new List(); + foreach (var department in items) + updatedDepartmentSapIDs.Add(department.SapId); + + // Составить список подразделений не обработанных при синхронизации, так как linq не осилит сравнение через contains несколько тысяч значений без замыкания в ToList. + var allDepartmentSapIDs = Solution.Departments.GetAll() + .Where(d => d.IDSAP != null + && Solution.BusinessUnits.Equals(d.BusinessUnit, businessUnit) + && d.Status == Sungero.CoreEntities.DatabookEntry.Status.Active) + .Select(d => d.IDSAP) + .ToList(); + var notSynchedDepartments = allDepartmentSapIDs.Where(d => !updatedDepartmentSapIDs.Contains(d)).ToList(); + var i = 0; + var total = notSynchedDepartments.Count; + foreach (var departmentSapID in notSynchedDepartments) + { + i++; + var department = Solution.Departments.GetAll() + .Where(d => d.IDSAP == departmentSapID + && Solution.BusinessUnits.Equals(d.BusinessUnit, businessUnit) + && d.Status == Sungero.CoreEntities.DatabookEntry.Status.Active) + .FirstOrDefault(); + + if (department.Status != Sungero.CoreEntities.DatabookEntry.Status.Closed) + department.Status = Sungero.CoreEntities.DatabookEntry.Status.Closed; + + try + { + if (department.State.IsChanged) + { + Logger.DebugFormat("Закрытие подразделений: {0}/{1}", i, total); + department.Save(); + Logger.DebugFormat("Закрытие Подразделения {0}.", department.Name); + } + } + catch (Exception ex) + { + var errorMessage = string.Format("Ошибка при обновлении карточки Подразделения {0}. Подробности: {1}.", department.Name, ex.Message); + Logger.Error(errorMessage, ex); + logs.Add(DirRX.SapIntegration.Functions.Module.CreateLogItem(_obj.Name, + DirRX.SapIntegration.Constants.Module.Logging.MessageLevel.ResponseLevel, + DirRX.SapIntegration.Constants.Module.Logging.MessageType.Error, + errorMessage)); + } + } + } + #endregion + + } +} \ No newline at end of file diff --git a/SAPConnector/ImportRuleEmployeeServerFunctions.cs b/SAPConnector/ImportRuleEmployeeServerFunctions.cs new file mode 100644 index 0000000..3e877d6 --- /dev/null +++ b/SAPConnector/ImportRuleEmployeeServerFunctions.cs @@ -0,0 +1,355 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Sungero.Core; +using Sungero.CoreEntities; +using DirRX.SapIntegration.ImportRuleEmployee; + +namespace DirRX.SapIntegration.Server +{ + partial class ImportRuleEmployeeFunctions + { + + #region Импорт Сотрудников из SAP. + + /// + /// Выполнить сохранение полученных данных в RX. + /// + /// Матрица с ответом от SAP. + /// Структурированный лог. + public override void SaveData(List> response, List logs) + { + var employees = new List(); + + foreach (var responseItem in response) + employees.Add(ParseResponseItem(responseItem)); + + if (employees.Any()) + { + var businessUnitSapId = employees.FirstOrDefault().BusinessUnitSapId; + ImportEmployees(employees, logs); + UpdateEmployeesManager(employees, logs); + CloseEmployees(businessUnitSapId, employees, logs); + } + } + + /// + /// Обработка строки матрицы с ответом от SAP. + /// + /// Строка матрицы с ответом от SAP. + /// Свойства сущности в структурированном виде. + public virtual DirRX.SapIntegration.Structures.ImportRuleEmployee.Employee ParseResponseItem(System.Collections.Generic.Dictionary responseItem) + { + var employee = new DirRX.SapIntegration.Structures.ImportRuleEmployee.Employee(); + employee.SapId = responseItem[Constants.ImportRuleEmployee.SAPFields.SapID]; + employee.LastName = responseItem[Constants.ImportRuleEmployee.SAPFields.LastName]; + employee.FirstName = responseItem[Constants.ImportRuleEmployee.SAPFields.FirstName]; + employee.MiddleName = responseItem[Constants.ImportRuleEmployee.SAPFields.MiddleName]; + employee.LoginName = responseItem[Constants.ImportRuleEmployee.SAPFields.LoginName]; + employee.DepartmentSapId = responseItem[Constants.ImportRuleEmployee.SAPFields.DepartmentSapId]; + employee.JobTitleSapId = responseItem[Constants.ImportRuleEmployee.SAPFields.JobTitleSapId]; + employee.Email = responseItem[Constants.ImportRuleEmployee.SAPFields.Email]; + employee.Phone = responseItem[Constants.ImportRuleEmployee.SAPFields.Phone]; + employee.NameSAP = responseItem[Constants.ImportRuleEmployee.SAPFields.NameSAP]; + employee.ManagerSapId = responseItem[Constants.ImportRuleEmployee.SAPFields.ManagerSapId]; + employee.BusinessUnitSapId = responseItem[Constants.ImportRuleBase.RequestParamOrgUnintSAPID]; + // Если данные пришли из SAP то статс записи всегда Действующий. Статус Закрытая будет проставлен для тех записей, которые не пришли из SAP. + employee.Status = Sungero.Company.Employee.Status.Active; + return employee; + } + + /// + /// Процедура импорта Персон. + /// + /// Сессия. + /// Данные по сотруднику в виде структуры. + /// Фиксация логов. + /// Персона + private Sungero.Parties.IPerson ImportPerson(Sungero.Domain.Session session, + DirRX.SapIntegration.Structures.ImportRuleEmployee.Employee employee, + List logs) + { + var person = session.GetAll().Where(x => x.IDSAP == employee.SapId) + .Select(x => x.Person).FirstOrDefault(); + + if (person == null) + person = session.Create(); + + if (person.LastName != employee.LastName) + person.LastName = employee.LastName; + + if (person.FirstName != employee.FirstName) + person.FirstName = employee.FirstName; + + if (person.MiddleName != employee.MiddleName) + person.MiddleName = employee.MiddleName; + + if (person.Status != employee.Status) + person.Status = employee.Status; + + try + { + if (person.State.IsChanged) + { + // Либо Save, либо session.SubmitChanges. Сейчас SubmitChanges чтобы не было дублей персон. + //person.Save(); + + Logger.Debug(string.Format("Обновление/создание карточки персоны {0} {1} {2}.", person.LastName, person.FirstName, person.MiddleName)); + } + } + catch (Exception ex) + { + var errorMessage = string.Format("Ошибка при обновлении карточки персоны {0} {1} {2}. Подробности: {3}.", person.LastName, person.FirstName, person.MiddleName, ex.Message); + Logger.Error(errorMessage, ex); + logs.Add(DirRX.SapIntegration.Functions.Module.CreateLogItem(_obj.Name, + DirRX.SapIntegration.Constants.Module.Logging.MessageLevel.ResponseLevel, + DirRX.SapIntegration.Constants.Module.Logging.MessageType.Error, + errorMessage)); + } + return person; + } + + /// + /// Процедура импорта Учетных записей. + /// + /// Сессия. + /// Данные по сотруднику в виде структуры. + /// Фиксация логов. + /// Логин. + private Sungero.CoreEntities.ILogin ImportLogin(Sungero.Domain.Session session, + DirRX.SapIntegration.Structures.ImportRuleEmployee.Employee employee, + List logs) + { + if (string.IsNullOrWhiteSpace(employee.LoginName)) + return Sungero.CoreEntities.Logins.Null; + + var login = session.GetAll().Where(l => l.LoginName == employee.LoginName).FirstOrDefault(); + if (login == null) + { + login = session.Create(); + login.LoginName = employee.LoginName; + } + + if (login.TypeAuthentication != Sungero.CoreEntities.Login.TypeAuthentication.Windows) + login.TypeAuthentication = Sungero.CoreEntities.Login.TypeAuthentication.Windows; + + try + { + if (login.State.IsChanged) + { + // Либо Save, либо session.SubmitChanges. Сейчас SubmitChanges чтобы не было дублей персон. + //login.Save(); + Logger.Debug(string.Format("Обновление/создание карточки учетной записи {0}.", login.LoginName)); + } + } + catch (Exception ex) + { + var errorMessage = string.Format("Ошибка при обновлении карточки учетной записи {0}. Подробности: {1}.", login.LoginName, ex.Message); + Logger.Error(errorMessage, ex); + logs.Add(DirRX.SapIntegration.Functions.Module.CreateLogItem(_obj.Name, + DirRX.SapIntegration.Constants.Module.Logging.MessageLevel.ResponseLevel, + DirRX.SapIntegration.Constants.Module.Logging.MessageType.Error, + errorMessage)); + } + return login; + } + + /// + /// Процедура импорта Сотрудников. + /// + /// Структурированный набор данных по импортируемым Сотрудникам. + /// Структурированный лог. + /// + [Remote] + public void ImportEmployees(List items, List logs) + { + var i = 0; + var total = items.Count; + foreach (var item in items) + { + i++; + // Транзакция для обхода наведенной ошибки "Транзакция откатилась" при сохранении всех последующих сущностей. + Transactions.Execute( + () => + { + // Сессия для атомарности операции - создать логин, персону и сотрудника, либо не создать ничего. Без сессии создаются дубли персон, так как поиск персоны идет от сотрудника который не создается. + using (var session = new Sungero.Domain.Session()) + { + var employee = session.GetAll().Where(x => x.IDSAP == item.SapId).FirstOrDefault(); + if (employee == null) + { + employee = session.Create(); + employee.IDSAP = item.SapId; + } + + var person = ImportPerson(session, item, logs); + if (person != null && (!Sungero.Parties.People.Equals(employee.Person, person) || !Equals(person.Name, employee.Name))) + employee.Person = person; + + var login = ImportLogin(session, item, logs); + if (!Sungero.CoreEntities.Logins.Equals(employee.Login, login)) + employee.Login = login; + + var department = Solution.Departments.GetAll().Where(d => d.IDSAP == item.DepartmentSapId).FirstOrDefault(); + if (department != null && !Solution.Departments.Equals(employee.Department, department)) + employee.Department = department; + + var jobTitle = Solution.JobTitles.GetAll().Where(j => j.IDSAP == item.JobTitleSapId).FirstOrDefault(); + if (jobTitle != null && !Solution.JobTitles.Equals(employee.JobTitle, jobTitle)) + employee.JobTitle = jobTitle; + + if (employee.NameSAP != item.NameSAP) + employee.NameSAP = item.NameSAP; + + if (employee.Phone != item.Phone) + employee.Phone = item.Phone; + + if (employee.Status != item.Status) + employee.Status = item.Status; + + if (employee.Email != item.Email) + employee.Email = item.Email; + + if (string.IsNullOrWhiteSpace(employee.Email)) + { + // Проверки (!= true и != false) нужны, чтобы исключить установку признака изменения сущности (IsChanged). + if (employee.NeedNotifyExpiredAssignments != false) + employee.NeedNotifyExpiredAssignments = false; + + if (employee.NeedNotifyNewAssignments != false) + employee.NeedNotifyNewAssignments = false; + } + + try + { + if (employee.State.IsChanged) + { + Logger.DebugFormat("Импорт сотрудников: {0}/{1}", i, total); + // Либо Save, либо session.SubmitChanges. Сейчас один SubmitChanges (вместо 3 Save 3-х сущностей) чтобы не было дублей персон. + //employee.Save(); + session.SubmitChanges(); + Logger.Debug(string.Format("Обновление/создание карточки Сотрудника {0}.", employee.Name)); + } + } + catch (Exception ex) + { + var errorMessage = string.Format("Ошибка при обновлении карточки Сотрудника {0}. Подробности: {1}.", employee.Name, ex.Message); + Logger.Error(errorMessage, ex); + logs.Add(DirRX.SapIntegration.Functions.Module.CreateLogItem(_obj.Name, + DirRX.SapIntegration.Constants.Module.Logging.MessageLevel.ResponseLevel, + DirRX.SapIntegration.Constants.Module.Logging.MessageType.Error, + errorMessage)); + } + } + }); + } + } + + /// + /// Процедура заполнения руководителя у сотрудника. + /// + /// Структурированный набор данных по импортируемым Сотрудникам. + /// Структурированный лог. + [Remote] + public void UpdateEmployeesManager(List items, List logs) + { + var i = 0; + var total = items.Count; + foreach (var item in items) + { + i++; + var employee = Solution.Employees.GetAll().Where(x => x.IDSAP == item.SapId).FirstOrDefault(); + if (employee != null) + { + var manager = Solution.Employees.Null; + if (!string.IsNullOrWhiteSpace(item.ManagerSapId)) + manager = Solution.Employees.GetAll().Where(e => e.IDSAP == item.ManagerSapId).FirstOrDefault(); + if (!Solution.Employees.Equals(employee.Manager, manager)) + employee.Manager = manager; + + try + { + if (employee.State.IsChanged) + { + Logger.DebugFormat("Обновление руководителей сотрудников: {0}/{1}", i, total); + employee.Save(); + Logger.Debug(string.Format("Обновление руководителя Сотрудника {0}.", employee.Name)); + } + } + catch (Exception ex) + { + var errorMessage = string.Format("Ошибка при обновлении руководителя Сотрудника {0}. Подробности: {1}.", employee.Name, ex.Message); + Logger.Error(errorMessage, ex); + logs.Add(DirRX.SapIntegration.Functions.Module.CreateLogItem(_obj.Name, + DirRX.SapIntegration.Constants.Module.Logging.MessageLevel.ResponseLevel, + DirRX.SapIntegration.Constants.Module.Logging.MessageType.Error, + errorMessage)); + } + } + } + } + + /// + /// Закрыть сотрудников, которые не пришли при синхронизации и у которых заполнено свойство IDSAP. + /// + /// Код SAP Нашей организации. + /// Структурированный набор данных по импортируемым Сотрудникам. + /// Структурированный лог. + public void CloseEmployees(string businessUnitSapId, List items, List logs) + { + var businessUnit = Solution.BusinessUnits.GetAll().Where(x => x.IDSAP == businessUnitSapId).FirstOrDefault(); + + // Составить список обработанных сотрудников, так как в linq нет трансляции для поиска по списку структур. + var updatedEmployeeSapIDs = new List(); + foreach (var employee in items) + updatedEmployeeSapIDs.Add(employee.SapId); + + // Составить список сотрудников не обработанных при синхронизации, так как linq не осилит сравнение через contains несколько тысяч значений без замыкания в ToList. + var allEmployeeSapIDs = Solution.Employees.GetAll() + .Where(e => e.IDSAP != null + && Solution.BusinessUnits.Equals(e.Department.BusinessUnit, businessUnit) + && e.Status == Sungero.CoreEntities.DatabookEntry.Status.Active) + .Select(e => e.IDSAP) + .ToList(); + var notSynchedEmployees = allEmployeeSapIDs.Where(e => !updatedEmployeeSapIDs.Contains(e)).ToList(); + + var i = 0; + var total = notSynchedEmployees.Count; + foreach (var employeeSapID in notSynchedEmployees) + { + i++; + var employee = Solution.Employees.GetAll() + .Where(e => e.IDSAP == employeeSapID + && Solution.BusinessUnits.Equals(e.Department.BusinessUnit, businessUnit) + && e.Status == Sungero.CoreEntities.DatabookEntry.Status.Active) + .FirstOrDefault(); + if (employee != null && !string.IsNullOrWhiteSpace(employee.IDSAP)) + { + if (employee.Status != Sungero.CoreEntities.DatabookEntry.Status.Closed) + employee.Status = Sungero.CoreEntities.DatabookEntry.Status.Closed; + + try + { + if (employee.State.IsChanged) + { + Logger.DebugFormat("Закрытие сотрудников: {0}/{1}", i, total); + employee.Save(); + Logger.DebugFormat("Закрытие Сотрудника {0}.", employee.Name); + } + } + catch (Exception ex) + { + var errorMessage = string.Format("Ошибка при обновлении карточки Сотрудника {0}. Подробности: {1}.", employee.Name, ex.Message); + Logger.Error(errorMessage, ex); + logs.Add(DirRX.SapIntegration.Functions.Module.CreateLogItem(_obj.Name, + DirRX.SapIntegration.Constants.Module.Logging.MessageLevel.ResponseLevel, + DirRX.SapIntegration.Constants.Module.Logging.MessageType.Error, + errorMessage)); + } + } + } + } + #endregion + + } +} \ No newline at end of file diff --git a/SAPConnector/ImportRuleJobTitleServerFunctions.cs b/SAPConnector/ImportRuleJobTitleServerFunctions.cs new file mode 100644 index 0000000..c1776ba --- /dev/null +++ b/SAPConnector/ImportRuleJobTitleServerFunctions.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Sungero.Core; +using Sungero.CoreEntities; +using DirRX.SapIntegration.ImportRuleJobTitle; + +namespace DirRX.SapIntegration.Server +{ + partial class ImportRuleJobTitleFunctions + { + + #region Импорт Должностей из SAP. + + /// + /// Выполнить сохранение полученных данных в RX. + /// + /// Матрица с ответом от SAP. + /// Структурированный лог. + public override void SaveData(List> response, List logs) + { + var jobTitles = new List(); + + foreach (var responseItem in response) + jobTitles.Add(ParseResponseItem(responseItem)); + + if (jobTitles.Any()) + ImportJobTitles(jobTitles, logs); + } + + /// + /// Обработка строки матрицы с ответом от SAP. + /// + /// Строка матрицы с ответом от SAP. + /// Свойства сущности в структурированном виде. + public virtual DirRX.SapIntegration.Structures.ImportRuleJobTitle.JobTitle ParseResponseItem(System.Collections.Generic.Dictionary responseItem) + { + var jobTitle = new DirRX.SapIntegration.Structures.ImportRuleJobTitle.JobTitle(); + jobTitle.SapId = responseItem[Constants.ImportRuleBase.SAPObjectFields.SapID]; + jobTitle.Name = responseItem[Constants.ImportRuleBase.SAPObjectFields.Name]; + return jobTitle; + } + + /// + /// Процедура импорта Должностей. + /// + /// Структурированный набор данных по импортируемым Должностям. + /// Список структурированных логов. + [Remote] + public void ImportJobTitles(List items, List logs) + { + var i = 0; + var total = items.Count; + foreach (var item in items) + { + i++; + var jobTitle = Solution.JobTitles.GetAll().Where(x => x.IDSAP == item.SapId).FirstOrDefault(); + if (jobTitle == null) + { + jobTitle = Solution.JobTitles.Create(); + jobTitle.IDSAP = item.SapId; + } + + if (jobTitle.Name != item.Name) + jobTitle.Name = item.Name; + + try + { + if (jobTitle.State.IsChanged) + { + Logger.DebugFormat("Импорт должностей: {0}/{1}", i, total); + jobTitle.Save(); + Logger.Debug(string.Format("Обновление/создание карточки Должности {0}.", jobTitle.Name)); + } + } + catch (Exception ex) + { + var errorMessage = string.Format("Ошибка при обновлении карточки Должности {0}. Подробности: {1}.", jobTitle.Name, ex.Message); + Logger.Error(errorMessage, ex); + logs.Add(DirRX.SapIntegration.Functions.Module.CreateLogItem(_obj.Name, + DirRX.SapIntegration.Constants.Module.Logging.MessageLevel.ResponseLevel, + DirRX.SapIntegration.Constants.Module.Logging.MessageType.Error, + errorMessage)); + } + } + } + #endregion + } +} \ No newline at end of file diff --git a/SAPConnector/README.md b/SAPConnector/README.md new file mode 100644 index 0000000..4b6024c --- /dev/null +++ b/SAPConnector/README.md @@ -0,0 +1,81 @@ +# Шаблоны обработки +Шаблон с примерами обработки ответов на запросы орг. структуры в SAP. Примеры основаны на коннекторе к SAP. + +### Описание кейса, когда можно применить +1. Обработка ответа от SAP по орг. структуре, полученного с помощью коннектора к SAP. +2. Синхронизация орг. структуры, когда подготовлены данные в виде словаря и на их основе создаются/обновляются записи в Directum RX. + +### Использование +1. Скопируйте реализованные серверные функции из файлов ImportRuleDepartmentManagerServerFunctions.cs, ImportRuleDepartmentServerFunctions.cs, ImportRuleEmployeeServerFunctions.cs, ImportRuleJobTitleServerFunctions.cs в серверные функции соответствующих записей коннектора к SAP. +2. Скорректируйте функции, т.к. перечень реквизитов из примера может отличаться от, тех, что есть у Вас. В первую очередь функции ParseResponseItem, ImportPerson, ImportLogin, ImportEmployees, ImportDepartments. + +### Примеры запросов в SAP + +##Должности/Подразделения +``` +POST http://appm.local:8005/sap/bc/srt/rfc/sap/zws_hr_orgunitext_data_get/200/zws_hr_orgunitext_data_get/zws_hr_orgunitext_data_get +``` + +``` + + + + + 2020-07-09 + 50000708 + 01 + O + PLSTE + MDT1 + + + +``` + +##Руководители подразделений + +``` +POST http://appm.local:8005/sap/bc/srt/rfc/sap/zws_hr_orgunitext_data_get/200/zws_hr_orgunitext_data_get/zws_hr_orgunitext_data_get +``` + +``` + + + + + + + + + + + 2020-06-16 + + O + 50000708 + + 01 + + ORGC + + + +``` + +##Сотрудники + +``` +POST https://appm.local:1443/sap/bc/srt/rfc/sap/zws_hr_sync_edoc_manage/200/zws_hr_sync_edoc_manage/edoc +``` + +``` + + + + + 2020-06-16 + 50000708 + + + +``` \ No newline at end of file From 909897e754ccf8d1b0e2d5c3686e28eda00d1a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B5=D1=80=D1=8E=D1=88=D0=B5=D0=B2=20=D0=90=D0=BB?= =?UTF-8?q?=D0=B5=D0=BA=D1=81=D0=B5=D0=B9?= Date: Thu, 19 Feb 2026 14:24:39 +0400 Subject: [PATCH 2/2] Update README.md --- SAPConnector/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SAPConnector/README.md b/SAPConnector/README.md index 4b6024c..4222f59 100644 --- a/SAPConnector/README.md +++ b/SAPConnector/README.md @@ -11,7 +11,7 @@ ### Примеры запросов в SAP -##Должности/Подразделения +#### Должности/Подразделения ``` POST http://appm.local:8005/sap/bc/srt/rfc/sap/zws_hr_orgunitext_data_get/200/zws_hr_orgunitext_data_get/zws_hr_orgunitext_data_get ``` @@ -32,7 +32,7 @@ POST http://appm.local:8005/sap/bc/srt/rfc/sap/zws_hr_orgunitext_data_get/200/zw ``` -##Руководители подразделений +#### Руководители подразделений ``` POST http://appm.local:8005/sap/bc/srt/rfc/sap/zws_hr_orgunitext_data_get/200/zws_hr_orgunitext_data_get/zws_hr_orgunitext_data_get @@ -62,7 +62,7 @@ POST http://appm.local:8005/sap/bc/srt/rfc/sap/zws_hr_orgunitext_data_get/200/zw ``` -##Сотрудники +#### Сотрудники ``` POST https://appm.local:1443/sap/bc/srt/rfc/sap/zws_hr_sync_edoc_manage/200/zws_hr_sync_edoc_manage/edoc @@ -78,4 +78,4 @@ POST https://appm.local:1443/sap/bc/srt/rfc/sap/zws_hr_sync_edoc_manage/200/zws_ -``` \ No newline at end of file +```