ContactServerFunctions.cs
14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Sungero.Core;
using Sungero.CoreEntities;
using Sungero.Parties.Contact;
namespace Sungero.Parties.Server
{
partial class ContactFunctions
{
/// <summary>
/// Создать асинхронное событие обновления имени контакта из персоны.
/// </summary>
/// <param name="personId">ИД персоны.</param>
[Public]
public static void CreateUpdateContactNameAsyncHandler(int personId)
{
var asyncUpdateContactName = Sungero.Parties.AsyncHandlers.UpdateContactName.Create();
asyncUpdateContactName.PersonId = personId;
asyncUpdateContactName.ExecuteAsync();
}
/// <summary>
/// Получить дубли контактов.
/// </summary>
/// <returns>Контакты, дублирующие текущего по ФИО.</returns>
[Remote(IsPure = true)]
public IQueryable<IContact> GetDuplicates()
{
return Contacts.GetAll()
.Where(contact =>
!Equals(_obj, contact) &&
Equals(contact.Name, _obj.Name) &&
Equals(contact.Company, _obj.Company) &&
contact.Status != Sungero.Parties.Contact.Status.Closed);
}
/// <summary>
/// Получить контактное лицо по имени.
/// </summary>
/// <param name="name">Имя контакта.</param>
/// <param name="counterparty">Контрагент, владелец контакта.</param>
/// <returns>Найденный контакт, если он только один, иначе - null.</returns>
[Public]
public static Parties.IContact GetContactByName(string name, ICounterparty counterparty)
{
var contacts = GetContactsByName(name, name, counterparty)
.Where(x => x.Status != Sungero.CoreEntities.DatabookEntry.Status.Closed);
return contacts.Count() == 1 ? contacts.FirstOrDefault() : null;
}
/// <summary>
/// Получить контактные лица по имени.
/// </summary>
/// <param name="name">Имя в формате "Фамилия И.О." или "Фамилия Имя Отчество".</param>
/// <param name="personShortName">Имя в формате "Фамилия И.О.".</param>
/// <param name="counterparty">Контрагент, владелец контакта.</param>
/// <returns>Коллекция контактных лиц.</returns>
[Public]
public static IQueryable<IContact> GetContactsByName(string name, string personShortName, ICounterparty counterparty)
{
var nonBreakingSpace = new string('\u00A0', 1);
var space = new string('\u0020', 1);
name = name.ToLower().Replace(nonBreakingSpace, space).Replace(". ", ".");
var contacts = Contacts.GetAll()
.Where(x => (x.Name.ToLower().Replace(nonBreakingSpace, space).Replace(". ", ".") == name) ||
(x.Person != null && string.Equals(x.Person.ShortName,
personShortName,
StringComparison.InvariantCultureIgnoreCase)));
if (counterparty != null)
return contacts.Where(c => c.Company.Equals(counterparty));
return contacts;
}
/// <summary>
/// Получить контактное лицо контрагента по ФИО с использованием нечеткого поиска.
/// </summary>
/// <param name="fullName">ФИО/фамилия с инициалами(-ом)/фамилия.</param>
/// <param name="counterpartyId">ИД компании для фильтрации.</param>
/// <returns>Найденный контакт.</returns>
[Public]
public static Parties.IContact GetContactsByNameFuzzy(string fullName, int counterpartyId)
{
var counterpartyIds = new List<int>();
if (counterpartyId > 0)
counterpartyIds.Add(counterpartyId);
return GetContactsByNameFuzzy(fullName, counterpartyIds);
}
/// <summary>
/// Получить контактное лицо по ФИО по списку контрагентов с использованием нечеткого поиска.
/// </summary>
/// <param name="fullName">ФИО/фамилия с инициалами(-ом)/фамилия.</param>
/// <param name="counterpartyIds">Список ИД компаний для фильтрации.</param>
/// <returns>Найденный контакт.</returns>
[Public]
public static Parties.IContact GetContactsByNameFuzzy(string fullName, List<int> counterpartyIds)
{
var contact = Parties.Contacts.Null;
var name = Sungero.Commons.PublicFunctions.Module.TrimSpecialSymbols(fullName);
if (string.IsNullOrWhiteSpace(name))
return contact;
// Сформировать условие для нечеткого поиска по ФИО с фильтром по активным сущностям.
var filter = Commons.PublicFunctions.Module.GetTermQuery("Status", CoreEntities.DatabookEntry.Status.Active.Value);
if (counterpartyIds.Count == 1)
filter = string.Format("{0},{1}", filter, Commons.PublicFunctions.Module.GetTermQuery("CompanyId", counterpartyIds.Single().ToString()));
if (counterpartyIds.Count > 1)
filter = string.Format("{0},{1}", filter,
Commons.PublicFunctions.Module.GetTermsQuery("CompanyId", counterpartyIds.Select(x => x.ToString()).ToList()));
var matchInitials = Regex.Match(fullName, Constants.Module.InitialsRegex, RegexOptions.IgnoreCase);
var splittedName = fullName.Split(' ');
// Поиск по полным ФИО или фамилия+имя.
var contactIds = new List<int>();
if (!matchInitials.Success && splittedName.Length > 1)
{
// Четкий поиск.
var querySearch = Commons.PublicFunctions.Module.GetBoolQuery(Commons.PublicFunctions.Module.GetMatchQuery("FullName", fullName, true),
string.Empty,
string.Format("[{0}]", filter));
contactIds = Commons.PublicFunctions.Module.ExecuteElasticsearchQuery(Parties.Contacts.Info.Name, querySearch);
// Нечеткий поиск.
if (contactIds.Count == 0)
{
querySearch = Commons.PublicFunctions.Module.GetBoolQuery(Commons.PublicFunctions.Module.GetMatchFuzzyQuery("FullName", fullName, true),
string.Empty,
string.Format("[{0}]", filter));
contactIds = Commons.PublicFunctions.Module.ExecuteElasticsearchQuery(Parties.Contacts.Info.Name, querySearch, Constants.Contact.ElasticsearchMinScore);
}
// Если были найдены записи - уточнять среди них на следующих шагах.
if (contactIds.Count > 0)
{
if (contactIds.Count == 1)
{
contact = Parties.Contacts.GetAll(x => Equals(x.Id, contactIds.First())).FirstOrDefault();
if (contact == null)
Logger.ErrorFormat("SearchContactByNameFuzzy. Cant get contact by id {0}.", contactIds.First());
}
else
{
if (!string.IsNullOrEmpty(filter))
filter = string.Format("{0},{1}", filter, Commons.PublicFunctions.Module.GetTermsQuery("Id", contactIds.Select(x => x.ToString()).ToList()));
else
filter = Commons.PublicFunctions.Module.GetTermsQuery("Id", contactIds.Select(x => x.ToString()).ToList());
}
}
}
if (contact == null)
{
// Поиск по фамилии и инициалам.
if (!matchInitials.Success)
{
// Если не удалось найти по полному ФИО в поисковом запросе, возможно в индексе запись в виде фамилии с инициалами.
// Попытка из поискового запроса выделить фамилию, а от ИО взять лишь инициалы.
// Проверяется 2 возможных варианта в поисковом запросе: "ФИО" и "ИОФ".
if (splittedName.Length > 1 && splittedName.Length < 4)
{
for (var i = 0; i < 2; ++i)
{
var names = splittedName.ToList();
var searchValue = i == 0 ? names.First() : names.Last();
names.Remove(searchValue);
var initialFirstName = names[0].Substring(0, 1);
var initialMiddleName = names.Count > 1 ? names[1].Substring(0, 1) : string.Empty;
var querySearch = Commons.PublicFunctions.Module.GetBoolQuery(Commons.PublicFunctions.Module.GetMatchQuery("FullName", searchValue, true),
string.Empty,
string.Format("[{0}]", filter));
contactIds = Commons.PublicFunctions.Module.ExecuteElasticsearchQuery(Parties.Contacts.Info.Name, querySearch);
contact = GetSingleContactFilteredByInitials(contactIds, initialFirstName, initialMiddleName);
}
}
}
else
{
// В поисковом запросе фамилия + инициалы, но найти не удалось. В индексе может находиться полное ФИО.
// Попытка поискать по фамилии, убрав инициалы из поискового запроса.
var searchValue = Regex.Replace(fullName, Constants.Module.InitialsRegex, string.Empty);
var initialFirstName = matchInitials.Groups[1].Value;
var initialMiddleName = matchInitials.Groups[2].Value;
var querySearch = Commons.PublicFunctions.Module.GetBoolQuery(Commons.PublicFunctions.Module.GetMatchQuery("FullName", searchValue, true),
string.Empty,
string.Format("[{0}]", filter));
contactIds = Commons.PublicFunctions.Module.ExecuteElasticsearchQuery(Parties.Contacts.Info.Name, querySearch);
contact = GetSingleContactFilteredByInitials(contactIds, initialFirstName, initialMiddleName);
}
}
return contact;
}
/// <summary>
/// Получить контакт с указанными инициалами.
/// </summary>
/// <param name="contactIds">Список ИД контактов.</param>
/// <param name="initialFirstName">Инициал имени.</param>
/// <param name="initialMiddleName">Инициал отчества.</param>
/// <returns>Первый контакт, если все найденные контакты принадлежат одной организации, или null, если контакты принадлежат разным организациям.</returns>
[Public]
public static IContact GetSingleContactFilteredByInitials(List<int> contactIds, string initialFirstName, string initialMiddleName)
{
var contact = Parties.Contacts.Null;
var contactsFiltered = new List<Parties.IContact>();
foreach (var foundContact in Parties.Contacts.GetAll(x => contactIds.Contains(x.Id)))
{
if (EqualsInitials(foundContact.Name, initialFirstName, initialMiddleName))
contactsFiltered.Add(foundContact);
}
// Если все найденные контакты принадлежат одной организации - вернуть первый контакт.
if (contactsFiltered.Select(c => c.Company).Distinct().Count() == 1)
contact = contactsFiltered.First();
return contact;
}
/// <summary>
/// Проверить соответствие имени(ФИО) инициалам.
/// </summary>
/// <param name="fullName">Имя (для полного проверяются 2 варианта: ФИО и ИОФ).</param>
/// <param name="initialFirstName">Инициал имени.</param>
/// <param name="initialMiddleName">Инициал отчества.</param>
/// <returns>Результат проверки на соответствие.</returns>
[Public]
public static bool EqualsInitials(string fullName, string initialFirstName, string initialMiddleName)
{
var result = false;
if (string.IsNullOrWhiteSpace(fullName))
return result;
if (!string.IsNullOrWhiteSpace(initialFirstName))
{
var matchInitialsIndex = Regex.Match(fullName.Trim(), Constants.Module.InitialsRegex, RegexOptions.IgnoreCase);
if (matchInitialsIndex.Success)
{
if (matchInitialsIndex.Groups.Count >= 2)
// ФИО записано с инициалами.
result = initialFirstName == matchInitialsIndex.Groups[1].Value &&
(string.IsNullOrWhiteSpace(initialMiddleName) || string.IsNullOrWhiteSpace(matchInitialsIndex.Groups[2].Value) || initialMiddleName == matchInitialsIndex.Groups[2].Value);
}
else
{
// Проверить по двум последовательностям в имени: ФИО / ИОФ.
var nameSplitted = fullName.Trim().Split(' ');
if (nameSplitted.Any(x => string.IsNullOrWhiteSpace(x)))
return result;
if (!string.IsNullOrWhiteSpace(initialMiddleName) && nameSplitted.Count() >= 3)
{
// Оба инициала присутствуют.
result = (nameSplitted[0].Substring(0, 1) == initialFirstName && nameSplitted[1].Substring(0, 1) == initialMiddleName) ||
(nameSplitted[1].Substring(0, 1) == initialFirstName && nameSplitted[2].Substring(0, 1) == initialMiddleName);
}
else
{
// Присутствует только инициал имени.
result = nameSplitted[0].Substring(0, 1) == initialFirstName || nameSplitted[1].Substring(0, 1) == initialFirstName;
}
}
}
return result;
}
}
}