Всем привет!
С недавних пор появилось немного свободного времени и настроение поделиться некоторыми мыслями, может кому-то из новичков пригодится.
Сегодня хочу поговорить о средствах связи контрагента.
Итак, рассмотрим коробочную версию данного функционала (хотя как правило, оно достается уже с высокой степенью кастомизации) для Terrasoft XRM 3.2 например (более низкие версии тоже подойдут).
Имеем: средства связи контрагента у нас хранятся в 2-х местах:
1) В таблице контрагента tbl_Account (поля Communication1, Communication2,...,Communication5 и Communication1TypeID, Communication2TypeID,...Communication5TypeID)
2) В Таблице средств связи контрагента tbl_AccountCommunication
Плюсов такого подхода, если честно, не вижу (кроме может быть случая, когда мы данные импортируем, например, из файла Excel и в "грязном" виде заливаем в таблицу tbl_Account, что так же считаю не правильным: их можно залить в какую-то таблицу, специально созданную для импорта, а потом после обработки уже переносить в целевую таблицу).
Минусы ниже:
1) Главный: нам нужно поддерживать целостность данных в 2-х местах: операция Insert/Update/Delete в средствах связи ведет к необходимости выполнить операцию Update в контрагенте и наоборот.
2) Очень часто изменения в таблицах логируются (например, в обеих таблицах). Изменяем 1 телефон в таблице контрагента, вопрос: Сколько записей будет вставлено в таблицы логов? Зависит от реализации механизма поддержки целостности: от 2-х до 6.
3) Бизнес-пользователям захотелось видеть на форме компаний 6 средств связи... или даже 10. Придется добавлять новые поля в таблицу, изменять механизм поддержки целостности, апдейтить таблицу контрагентов (не забыв/забыв о том, что изменения логируются)
4) Часто CRM интегрирована с другими существующими системами. Сколько пакетов интеграции "полетит" при изменении 1 телефона в таблице контрагентов? Аналогично: от 2 до 6.
Возможно, забыл что-то еще. Выяснить все детали того, что происходит можно профайлером на тестовой среде.
Как избавиться от данных проблем: провести нормализацию. Не буду вдаваться в подробности 1,2,3 нормальных форм, нормализации/денормализации, но, очевидно, что средства связи должны храниться в таблице средств связи (Вопрос о целесообразности tbl_AccountCommunication и tbl_ContactCommunication оставим в стороне).
Что тогда с выводом информации об N средствах связи на форме компании? Есть 2 опции:
1) Заменить вывод информации в соответствующие контролы из таблицы tbl_Account на tbl_AccountCommunication
2) Вывести на форме компании весь грид средств связи и убрать его из деталей.
Я рассмотрю 1 подход.
Нам потребуется внести незначительные изменения в 2 сервиса: wnd_AccountEdit и scr_AccountEdit.
1) Добавляем 5 невизуальных компонентов DatasetLink на форму Контрагента (dlCommunication1, dlCommunication2, ... dlCommunication5) и для каждого из них выбираем датасет ds_AccountCommunication.
2) На самой форме меняем в соответствующих контролах DatasetLink на те, что мы создали в первом шаге и выбираем соответствующие поля DatafieldName (тип средства связи CommunicationTypeID и номер Number).
Сохраняем изменения и переходим к обработчику.
3) Теперь нам нужно подтянуть данные для новых контролов при открытии формы.
Напишем функцию, которая в качестве параметра будет принимать порядковый номер средства связи (Position) и будет заполнять данными наши новые датасеты.
Если какого-то средства связи нет, то нужно добавить запись в датасет, обозначив только связь с контрагентом и номер позиции.
//Функция подготовки данных для отображения средств связи из таблицы tbl_AccountCommunication
function InitCommunication(position){
eval('var CommunicationDataset = dlCommunication'+ position + '.Dataset');//Определяем целевой датасет
ApplyDatasetFilter(CommunicationDataset, 'AccountID', dlData.Dataset('ID'), true);//Фильтр по контрагенту
ApplyDatasetFilter(CommunicationDataset, 'Position', position, true);//Фильтр по номеру позиции
RefreshDataset(CommunicationDataset);
if(CommunicationDataset.IsEmpty){//Если средства связи не существует, нужно его добавить и связать с контрагентом и присвоить номер позиции
CommunicationDataset.Append();
CommunicationDataset('Position') = position;
CommunicationDataset('AccountID') = dlData.Dataset('ID');
}
}
В обработчик события OnPrepare добавляем вызов функции.
function wnd_AccountEditOnPrepare(Window) {
Initialize();
wnd_BaseDBEditOnPrepare(Window);
/* PRODUCT XRM */
UpdateAccountBasicCurrencyCaptions();
InitializePostponementPayment(Self);
/* ENDPRODUCT XRM */
InitAccountInfo();
//Заполняем датасеты средств связи
for(var i = 1; i 6; i++)
InitCommunication(i);
}
На этом этапе можно сохранить изменения и посмотреть что получилось в клиенте. Данные действительно будут подтягиваться из средств связи. Но при изменении средств связи на форме контрагента изменения не будут сохранены в базу данных.
4) Займемся сохранением данных в tbl_AccountCommunication при сохранении формы контрагента. Нужно добавить обработчик события OnDatasetAfterPost к невизуальному компоненту основного датасета dlData.
Это самый простой вариант: если запись контрагента новая, то при срабатывании этого события она гарантированно сохранена в базе данных, и у нас не получится так, что средства связи окажутся привязаны к несуществующей компании (или если у нас есть внешний ключ из tbl_AccountCommunication на tbl_Account мы получим ошибку от SQL сервера).
Если нам вдруг требуется валидация средств связи перед их сохранением (например у нас нет обработки правильности ввода информации "на лету" еще при заполнении данных на форме), то можем соответствующий функционал добавить в обработчик OnDatasetBeforePost к невизуальному компоненту основного датасета, с возможностью отменить сохранение основной записи контрагента.
function dlDataOnDatasetAfterPost(Dataset) {
for(var i = 1; i 6; i++)
CommitCommunication(i);
}
function CommitCommunication(position){
eval('var CommunicationDataset = dlCommunication'+ position + '.Dataset');
if(GetFieldsValuesAreChanged(CommunicationDataset, 'Number', 'CommunicationTypeID'))
CommunicationDataset.Post();
}
Основная идея здесь - нам нужно сохранять записи только в том случае, если они действительно менялись, причем менялись или тип или номер. Если например мы добавили пустые датасеты (при подготовке данных для формы) и ничего в эти поля не ввели, то у нас не будет никакого "мусора" из пустых записей в таблице tbl_AccountCommunication.
Можно сохранять изменения и проверять работу.
Замечания.
Поскольку это "учебный" пример, для его доработки до уровня продакшена нужно будет самостоятельно посмотреть следующие моменты:
1) Стоит сохранить число средств связи на форме в глобальной переменной AccountEdit в одном месте (чтобы не приходилось искать каждый раз где нужно изменить его если число контролов на форме потом увеличится/уменьшится), и соответственно исправить код вызова функций.
2) Нужно будет найти и отключить функционал поддержки целостности при изменении записи в детали Средства связи (чтобы запись контрагента не обновлялась) и, на всякий случай, такой же функционал у контрагента.
3) Возможно у кого-то возникнет вопрос, что если раньше при открытии формы контрагента шел 1 запрос на выборку из tbl_Account а теперь 1 + 5 запросов из tbl_AccountCommunication и в чем тогда преимущество? Преимущество в том, что мы избавляемся от 4-х серьезных минусов, которые я описал в самом начале, но да, получаем увеличение числа запросов на выборку данных при открытии формы контрагента, но эти запросы очень "легкие" и вряд ли скажутся на производительности (+ мы всегда можем ее увеличить "допиливанием" индексов).
4) Потребуется доработка функционала для отображения телефонов в гриде контрагентов.
Вроде все. Надеюсь, получилось не слишком сложно о простом
Будут какие-то мысли, комментарии, вопросы, предложения - welcome!