Terrasoft CRM: руководство разработчика

в 8:57, , рубрики: CRM-системы, tutorial, разработка, руководство для разработчика

По долгу работы в последние несколько месяцев мне пришлось столкнуться с разработкой инфраструктуры CRM для одного издательского дома. Руководство компании выбрало именно десктопную версию CRM, т.к. на тот момент web версия BMPonline была достаточно сырой. До начала разработки были проработаны и согласованы основные сущности, специфические для издательства, которые не имели аналогов из коробки (такие как «Издания», «Форматы», «Номера», для рекламы в журналах «Заказы», «Размещения» и т.д., тесно взаимодействующие между собой). Но каково было мое удивление, когда, приступив к разработке я не нашел адекватной документации для разработчиков, а точнее и вовсе ничего не нашел. Все что удалось откопать это ответы на вопросы и блоги самих разработчиков CRM, на форуме. Скудную документацию можно найти на terrasoft.ru/sdk/, однако в самом начале знакомства с системой мне это не очень то и помогло. Поэтому я потратил достаточно много времени, чтобы понять, что и как работает.

Итак, всю систему можно разделить на разделы (Контакты, Контрагенты, Банки и детали к этим разделам, например Адреса, Договоры, Карьера для Контактов, Бренды, Платежные реквизиты для Контрагентов и т.д.)

Terrasoft CRM: руководство разработчика

Вот пример раздела «Контакты» и детали «Адреса» Хочется отметить, что разделы создаются с помощью специальной утилиты.

Как создать раздел

Для этого необходимо прописать специальный ключ для ярлыка запуска Terrasoft Client в поле «Объект»:

 wnd=wnd_CreateNewWorkspace

Должно получиться что-то вроде этого:

"C:Program Files (x86)Terrasoft PressBinTSClient.exe" wnd=wnd_CreateNewWorkspace

Кстати, таким же образом с помощью другого ключа можно включать профайлер запросов к БД, который бывает незаменим при поиске ошибок:

"C:Program Files (x86)Terrasoft PressBinTSClient.exe" /profiler

Собственно самое важное для разработчика это как раз добавление, редактирование и структуризация данных, проработка связей всех сущностей между собой. Поэтому создадим раздел (или также Workspace) MyWorkspace и наладим добавление данных.

Terrasoft CRM: руководство разработчика

После чего надо добавить только что созданный раздел на рабочее место:

Файл -> Настройки -> Рабочие места

Terrasoft CRM: руководство разработчика

После этого мы можем открыть Terrasoft Administrator и найти все сервисы, необходимые для работы нашего раздела (воркспейса).
Найти его можно в дереве сервисов:

Custom -> Workspaces -> myworkspace -> General -> Main Grid

Terrasoft CRM: руководство разработчика

Теперь коротко о сервисах, из которых состоит наш воркспейс.

  • tbl_myworkspace (tbl_ = Table) Таблица с описанием полей, индексов и связей;
  • sq_myworkspace (sq_ = Select Query) Запрос, который достает данные из таблицы (или нескольких таблиц с использованием JOIN’ов);
  • ds_myworkspace (ds_ = Data Set) Набор данных, позволяет добавлять, удалять или изменять данные в таблице;
  • wnd_myworkspaceEdit (wnd_ = Window) Окно редактирования, с помощью которого пользователь добавляет данные в данный раздел;
  • wnd_myworkspaceGridArea Это грид, в котором отображаются все наши записи из данного раздела;
  • wnd_myworkspaceWorkspace Главное окно раздела, которое содержит как основной грид, так детали, связанные с ним. Также, при необходимости здесь можно добавлять новые детали (получается связь один-ко-многим, т.е. одна запись грида может иметь несколько записей в детали, как я писал раньше «Контакт» может иметь несколько адресов и т.д.);

Добавление данных

Попробуем создать раздел с информацией о сотрудниках. Добавим несколько новых полей:

  • E-mail
  • Должность
  • Зарплата

Сначала необходимо создать все эти поля в таблице tbl_myworkspace. Ни каких сложностей возникнуть не должно. После сохранения поля обновятся в БД которую вы используете (у меня Microsoft SQL). Здесь же мы можем добавить индексы по полям или связи при необходимости.

Теперь надо обновить запрос sq_myworkspace, который отвечает за выборку данных из этой таблицы.

Terrasoft CRM: руководство разработчика

Добавляем необходимые поля и сохраняем. При желании можно просмотреть SQL, запрос нажав ctrl + P.

В конце добавляем все эти поля в датасет ds_myworkspace, чтобы можно было работать с данными через специальный объект.

Terrasoft CRM: руководство разработчика

Тоже самое можно проделать гораздо проще, надо найти справочник нашего раздела (myworkspace) и изменить структуру полей:

Инструменты -> Справочники 

Теперь, когда мы добавили необходимые поля, можно отобразить их на форме добавления/редактирования данных wnd_myworkspaceEdit.

Из множества доступных элементов, выбираем TextDataControl. В свойствах необходимо указать следующие параметры:

  • DatasetLink — обычно он всего один на форме dlData (находится на вкладке «Невизуальные»);
  • DataFieldName – выбираем поле, которое мы хотим отобразить (если наших добавленных полей не видно, попробуйте закрыть и заново открыть окно wnd_myworkspaceEdit)
  • Name – можно оставить стандартное название, но лучше использовать определенный стиль для таких элементов (например edtNameOfField).

Для зарплаты выбираем IntegerDataControl и проделываем все то же самое. Теперь мы можем открыть Terrasoft Client и добавить запись, используя окно редактирования данных раздела. Запись без проблем добавляется, но в гриде не отображаются наши пользовательские поля. Почему? Да потому что мы не добавили их в этот самый грид. Поэтому открываем wnd_myworkspaceGridArea, находим элемент grdData -> gdvData, щелкаем правой кнопкой мыши, выбираем пункт «Определить колонки» и добавляем наши поля. После чего они станут видны в гриде в клиенте (возможно вам понадобится добавить их, нажав плюсик в правом верхнем углу грида, там вы можете добавлять или наоборот прятать необходимые колонки).

Выпадающие списки

Довольно часто приходится иметь дело с выпадающими списками. Гораздо удобнее внести, например, список должностей один раз в справочник, а потом просто выбирать название должности из этого списка. Это гораздо удобнее и правильнее с точки зрения проектирования БД.

Предположим, мы уже создали справочник со списком должностей MyWorkspaceAppointments. Создаем поле AppointmentID в таблице tbl_myworkspace, где будет храниться ID должности. Добавляем его в sq_myworkspace, и также нам надо будет делать выборку поля «Название должности», которое будет отображаться в выпадающем списке. Поэтому делаем JOIN с таблицей должностей по полю AppointmentID. И добавляем в блок SELECT это поле. Извлекаем его как AppointmentName.

Terrasoft CRM: руководство разработчика

Теперь добавляем поле «Поле справочника» в датасет. Выбираем колонку AppointmentID, в источнике данных справочника выбираем датасет справочника должностей, а в поле «Колонка для отображения» выбираем поле AppointmentName, выбранное нами ранее. Внизу ставим галочку «Отображать как выпадающий список в карточках». На окно добавляем элемент LookupDataControl и выбираем в DataFieldName. Все, можно проверять.

Terrasoft CRM: руководство разработчика

Собственно, это и есть самые основы на которых построена система Terrasoft (по моему личному мнению).

Работа с набором данных Dataset

Как я уже говорил, работа с БД происходит через набор данных dataset. Получить текущий dataset для окна, в котором мы работаем, можно через DataLink:

var Dataset = dlData.Dataset;

Получить любой другой набор данных можно также по его названию:

var TestDataset = Services.GetNewItemByUSI('ds_Test');

Добавление записи в коде

TestDataset.Append();
TestDataset.Values('Name') = 'Василий Иванов';
TestDataset.Values('Salary') = 30000;
TestDataset.Post();

ID – уникальный идентификатор добавлять не надо, это произойдет автоматически.

Редактирование записи
Для редактирования конкретной записи применяем фильтр (далее фильтры будут более подробно рассмотрены) к набору данных:

ApplyDatasetFilter(TestDataset, 'ID', '{e2ad6e6f-481a-40d0-abb6-50e4b19a43d6}', true);
TestDataset.Open(); // открываем набор
if(!TestDataset.IsEmptyPage) {
    TestDataset.Edit();
    TestDataset.Values('Salary') = 35000; // изменяем нужные поля
    TestDataset.Post(); // сохраняем	
}
TestDataset.Close(); // закрываем по завершении работы

Если необходимо отредактировать более одной записи:

TestDataset.Open();
while (!TestDataset.IsEOF) {  
    TestDataset.Edit();              
    TestDataset.Values('Salary') = 40000;  
    TestDataset.Post();  
    TestDataset.GotoNext(); // переходим к следующей записи
}             
TestDataset.Close();  

Удаление

// не забывайте применять соответствующие фильтры, чтобы не удалить все записи
TestDataset.Open();
while(!TestDataset.IsEOF) {
    TestDataset.Delete();
    TestDataset.Open();
}
TestDataset.Close();

Посчитать количество записей в наборе можно с помощью свойства RecordsCount.

var Records = TestDataset.RecordsCount;

Select Query и Dataset: пользовательские фильтры

Получаемые с помощью датасетов данные, можно фильтровать с помощью пользовательских фильтров. Они задаются в сервисах sq_. В нашем случае это sq_myworkspace. По умолчанию будет создан фильтр по ID, который сразу можно использовать в коде.

Применяется обычный фильтр с помощью функции ApplyDatasetFilter:

ApplyDatasetFilter(Dataset, 'ID',  id, true);

  • Dataset – датасет, к которому надо применить фильтр
  • 'ID' – название фильтра в блоке WHERE
  • id – идентификатор по которому будет производится поиск
  • true – данный параметр говорит о том, что фильтр надо включить

Создадим простой фильтр по полю «Зарплата»:

1) создаем параметр, который будет передаваться в фильтр (SalarySum);
2) в блок WHERE добавляем «Фильтр сравнения», указываем название такое же, как и у параметра (SalarySum). Выбираем поле (tbl_myworkspace.Salary) для сравнения с параметром, который мы будем передавать из функции ApplyDatasetFilter.

Terrasoft CRM: руководство разработчика

Теперь можем применить его в коде и найти всех сотрудников с зарплатой 30000.

var ds = Services.GetNewItemByUSI('ds_myworkspace');	
ApplyDatasetFilter(ds, 'SalarySum',  30000,  true);
ds.Open();
Log.Write(2, ds.ValAsStr('Name')); 
ds.Close();

Можно также применять несколько фильтров. Например, найти всех директоров с зарплатой больше 30000 и т.д.

События dataset

Также хочется отметить наиболее используемые события dataset:

OnDatasetAfterOpen — возникает после открытия набора данных;
OnDatasetAfterPositionChange — возникает после перехода на другую запись (бывает полезно при отслеживании смены выделенной записи в гриде);
OnDatasetAfterPost — возникает после добавления/изменения записи (бывает полезно, когда надо изменить связанные данные в другой таблице);
OnDatasetBeforePost — возникает перед добавлением/изменением записи (бывает полезно для пользовательской проверки данных, перед сохранением). Также в событие передается параметр DoPost, если по какой-то причине нужно отменить добавление (например, данные не прошли пользовательскую проверку) просто делаем:

DoPost.Value = false;

OnDatasetDataChange — возникает при изменении данных датасета. Например нужна какая-то логика при изменении поля должность из нашего выпадающего списка.

function dlDataOnDatasetDataChange(DataField) {
	var FieldName = DataField.Name;
	switch(FieldName) {
        case 'EditionID':   
            // организуем необходимую логики при каждом изменении поля
        break;		
    }
}

OnDatasetBeforeDelete — возникает перед удалением записи (например можно повесить пользовательскую проверку данных, и при необходимости отменить удалить используя параметр DoDelete).

DoDelete.Value = false;

OnDatasetAfterDelete — возникает после удаления записи (например можно проверить, обновить или удалить связанные данные с данной записью).

События окна, обращение к элементам

Иногда нужно прописать определенную логику при открытии окна, сделать это можно в событии OnPrepare.
По умолчанию у окна, в событии, будет прописана функция wnd_BaseDBEditOnPrepare, которая должна вызываться для правильной инициализации каждого окна. Но нам необходимо добавить свою логику, поэтому переименуем функцию в wnd_MyWorkspaceDBEditOnPrepare и дважды нажмем на нее. Откроется (или создастся новый) скрипт окна.

function wnd_MyWorkspaceDBEditOnPrepare(Window) {	
    scr_BaseDBEdit.wnd_BaseDBEditOnPrepare(Window); 
}

Не забываем вызвать данную функцию для корректной инициализации и добавляем свою логику. Предположим, что нам надо высчитывать подоходный налог с зарплаты сотрудника. Добавляем в окно новый элемент NumericEdit.

Определяем события для поля Salary OnKeyDown и OnKeyUp, эти события буду срабатывать при вводе данных или при стирании. Напишем функцию, которая будет обновлять при каждом изменении поля Salary поле Tax (налог). Должно получиться что-то вроде этого:

function edtSalaryOnKeyDown(Control, Key, Shift) {
	calculateTax();
}

function edtSalaryOnKeyUp(Control, Key, Shift) {
	calculateTax();
}

function calculateTax() {
	var Dataset = dlData.Dataset; // получаем набор данных для текущего окна
	edtTax.Value = dlData.Dataset.ValAsInt('Salary') * 0.13;
}

Все отлично работает. Однако при открытии окна поле «Налог» не будет заполнено. Это можно исправить с помощью события окна OnPrepare.

function wnd_MyWorkspaceDBEditOnPrepare(Window) {	
    scr_BaseDBEdit.wnd_BaseDBEditOnPrepare(Window); 
    calculateTax(); // просчитываем и заполняем поле налог
}

В процессе работы появились некоторые наработки, которые мне не раз пригодились.

Посмотреть

//изменение цвета и текста в ячейке у DataGrid
function grdDataOnGetRowDrawInfo(DataGrid, Color, TextColor, ImageName, Font) {
    if (/* условие */) {
        Color.Value= clRed; // цвет ячейки
        TextColor.Value = clYellow; // цвет текста
    }
}
//фильтрация значений выпадающего списка по определенном полю
function edtContactOnPrepareSelectWindow(LookupDataControl, SelectWindow) {
	ApplyDatasetFilter(LookupDataControl.DataField.LookupDataset, 'IsActive', true, true);
}

// сбрасывает выпадающий список, и при следующем обращении опять вызовет событие OnPrepareSelectWindow< (например если данные в списке зависят от другого поля, которое изменилось, и необходимо обновить фильтр) 
edtContact.UnprepareDropDownList();
// открытие любого окна 
var EditWindowUSI = 'wnd_OpportunityEdit';
var Attributes = GetNewDictionary();
Attributes.Add('RecordID', GUID_NULL);  // если не указываем RecordID то открывается окно для добавления данных, если указываем, то запись с ID = RecordID открывается для редактирование
var DefaultValues = GetNewDictionary();  // значения по умолчанию
DefaultValues.Add('CustomerID', AccountID); 
ShowEditWindowEx(EditWindowUSI, Attributes, DefaultValues);
// что-то вроде глобальной области видимости, можно таким образом передавать данные из одного окна в другое
Connector.Attributes('DatasetToSave') = object; 
// обращение к родительскому окну (также можно обратиться к элементам на этом окне)
var ParentWindow = Self.Attributes('NotifyObject').ParentContainer.ParentWindow.ComponentsByName('edtPriceListsEditionID');
// обращение к элементу родительского окна (текущее окно находится в контейнере)
var ParentWindow = Self.ParentContainer.ParentWindow.ComponentsByName('edtName’);
// обращение к одному гриду из другого через workspace
var grd = Self.ParentContainer.ParentWindow.ComponentsByName('wndGridData').Window.ComponentsByName('grdData');
// проверка, является ли данная запись новой, или она открыта на редактирование
Window.Attributes('IsNewRecordAppend')
// получить выделенные записи грида
var ArrayIDs = GetArrayByCollection(grdData.SelectedIDs);
// вывод каких либо данных в консоль
Log.Write(2, 'Text');
// например при отладке, удобно использовать исключение
try {
    // ваш код
} catch(e) {
    Log.Write(2, e.message);
}

Собственно, я описал все ключевые моменты по работе с системой. Думаю, что для начала разработки под данную CRM (актуально для версии 3.4, на счет других не знаю) этого будет вполне достаточно.

Автор: vkitki

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js