Кто-нибудь делал интеграцию с 1С 8.3
Возможно ли это? Есть готовая инструкция? Или может в новой версии Terrasoft XRM (для Firebird) появилась такая возможность?

Нравится

3 комментария

Здравствуйте!
Официально пока нет поддержки интеграции с 1С 8.3, но Вы можете попробовать использовать утилиту интеграции , добавив свою версию в перечисление "enm_1CVersion".

А встроенной, проверенной и гарантированной авторами программы Terrasoft XRM интеграции не будет? Хотя бы в следующих версиях?

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

Показать все комментарии

Добрый день! Помогите разобратся в одном отчёте. Писали его разработчики до меня, я пока что вникаю в суть. До поры до времени работало, а в один прекрасный день стала появлятся ошибка.
Суть в том, что в одном поле есть такая строка:

[ds_CustomerAddress."AddressTypeName"]: [IIF(Length()>0,[ds_CustomerAddress."ZIP"], >, >)][IIF(Length()>0,[ds_CustomerAddress."TerritoryName"], >, >)][IIF(Length()>0,[ds_CustomerAddress."StateName"], >, >)][IIF(Length()>0,[ds_CustomerAddress."CityName"], >, >)][ds_CustomerAddress."Address"]

Я понимаю смысл того, что строка должна выдавать, но не понимаю некоторых особенностей. Объясните, что обозначает пустое место после запятой в первом iif вот здесь:
[ds_CustomerAddress."ZIP"], >

и почему в етом же iif в разделе false написано просто " >" - ето пустая строка?

И почему в данном отчёте выдаётся ошибка

Unknown variable of datafield:
Unknown variable of datafield: [ds_CustomerAddress."CityName"]
Unknown variable of datafield: [ds_CustomerAddress."TerritoryName"]
, хотя ети поля имеются в подгружаемых данных. Более того, сам отчёт формируется правильно, все значения отображаются. Откуда тогда ошибке взятся?

Нравится

2 комментария

Добрый день, Дмитрий.

Оператор "< >" в блоке false функции IIF означает пустой DataField, т.е. в отчет подставится пустое место.
По поводу пустого места после запятой, до конца не уверен, однако могу предположить, что у Вас в одном поле выводится совокупный адрес (индекс, страна, город, и пр.) соответственно такой конструкцией они выводятся через запятую с пробелом.
По поводу ошибки, к сожалению ничего конкретного сказать не могу. Возможно где-то в коде нужно заменить конструкцию [ds_CustomerAddress."TerritoryName"] на

<ds_CustomerAddress."TerritoryName">

.

Оказалось всё просто, хотя пришлось повозится. Вместо

<[ds_CustomerAddress."TerritoryName"], >

надо было написать

<ds_CustomerAddress."TerritoryName">+", "

А вместо

< >
" "

Уж не говоря о том, что в скрипте были ссылки на несуществующие выборки.

А работало до поры до времени наверняка из-за кеширования. Возможно, что у клиентов когда-то закешировался правильный вариант отчёта, на етом всё и держалось. Хотя, ето просто предположение. Спасибо за подсказку ;)

Показать все комментарии

Добрый день!
Являюсь новичком. Подскажите пож-а как показать данные на Grid'e? Я уже создал таблицу в БД, сделал sq_SelectQuery, на его основе DataSet. Кинул на форму Grid, GridView, Columns. Соединил все параметры. В конструкторе все появилось (столбцы). Но при открытии формы с TSClient.exe грид пустой, а при нажатии на PrintPreview этого грида - данные есть. Форму я не наследовал ни от чего, т.к. мне не нужны лишние элементы управления. Нужно ли в OnPrepare() этой формы дописывать код, для показа данных?

Нравится

6 комментариев

"Исаев Нурбек Сыргакович" написал:Нужно ли в OnPrepare() этой формы дописывать код, для показа данных?

как минимум открыть датасет

dlData.Dataset.Open();

ой спасибо, а то думал на такой простой вопрос никогда ответа не получу

А еще вопрос: есть ли возможность добавления новой записи прямо в Grid (т.е. встать на новую строку грида и добавить новую запись)? Или же для добавления новой записи нужно открывать овое окно и туда заполнять данные?

В базовой версии нет возможности добавления записи непосредственно в реестре. Для добавления необходимо открыть окно редактирования и заполнить обязательные поля.

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

	var Dataset = dlData.Dataset;
	Dataset.DisableEvents();
	Dataset.Append();
	Dataset.Values('ID') = Connector.GenGUID();
	Dataset.Values('Name') = ' ';
	<Заполняем обязательные поля>
	Dataset.Post();
	Dataset.EnableEvents();

"Лабьяк Олег Игоревич" написал:В базовой версии нет возможности добавления записи непосредственно в реестре.

А если сделать грид редактируемым и сказать датасету Append() ?
Визуально ж будет добавление записи в реестр, нет?

Да, Юрий, Вы правы.
Но я бы всё-таки рекомендовал после выполнения Append заполнить обязательные поля. Иначе есть вероятность свала при переходе на другую строку, когда не все обязательные поля заполнены. Также желательно установить признак "Всегда выбирать в запросе" для всех полей, которые используются в скриптах датасета, чтобы не возникало исключение "Поле с названием ... неактивно".

Показать все комментарии

Ни раз возникала задача переноса данных из одной БД в другую. Как правило, это Контакты, Контрагенты, Задачи. Задача усложнялась тем, что БД могут быть не совсем похожи по структурам. Приходилось писать insert-select конструкции с перечислением всех полей, которые необходимо перенести. Дабы упростить, а главное ускорить сей процесс был написал скрипт, которому собственно и посвящен этот пост.
Собственно, что делает сам скрипт. Идет по списку таблиц, указанных пользователем, проверяет соответствие полей в таблицах обеих БД, формирует запрос и выполняет его.
Возможно эти труды будут кому-то полезны.
Скрипт:

begin transaction
declare @syncTablesNames TABLE (TableName varchar(50));
declare @sourceDBName varchar(50),
                @tableName varchar(50),
                @columnName varchar(50),
                @sourceColumnExists bit,
                @insertSQLText nvarchar(MAX),
                @selectSQLText nvarchar(MAX),
                @columnExistsSQLText nvarchar(MAX),
                @idExistsCondition nvarchar(MAX),
                @resultSQLText nvarchar(MAX);

SET @sourceDBName = 'SourceDataBaseName';

-- set tables names
INSERT INTO @syncTablesNames
        SELECT 'tbl_Contact' UNION
        SELECT 'tbl_Account';


declare tableNameCursor cursor LOCAL FAST_FORWARD FOR SELECT * FROM @syncTablesNames;

open tableNameCursor;

fetch next FROM tableNameCursor INTO @tableName

WHILE (@@fetch_status = 0)
BEGIN
        SET @insertSQLText = 'insert into ' + @tableName + '(';
        SET @selectSQLText = 'select ';
        SET @idExistsCondition = ' where not ID in (select ID from ' +
           @tableName + ');';

        --Disable Table Constraint
        exec ('alter table ' + @tableName + ' nocheck constraint all; ');

        declare columnNameCursor cursor LOCAL FAST_FORWARD FOR SELECT COLUMN_NAME                                                  
                                                     FROM information_schema.COLUMNS
                                                     WHERE TABLE_NAME = @tableName;
        open columnNameCursor;
        fetch next FROM columnNameCursor INTO @columnName;
        while (@@fetch_status = 0)
        begin
                SET @columnExistsSQLText = 'if exists (select COLUMN_NAME ' +
                                                'from ' + @sourceDBName +  
                                                '.information_schema.columns ' +
                                                'where TABLE_NAME = ''' +
                                                       @tableName + ''' AND ' +
                                                      'COLUMN_NAME = ''' +  
                                                       @columnName + ''') ' +
                                              'set @sourceColumnExists = 1 ' +
                                           'else ' +
                                              'set @sourceColumnExists = 0 ';
       
                --check remote table column exists
                exec sp_executesql @columnExistsSQLText,
                                   N'@sourceColumnExists bit out',
                                   @sourceColumnExists = @sourceColumnExists output

                --include column into select-insert statment
                IF (@sourceColumnExists = 1)
                begin
                        SET @insertSQLText = @insertSQLText + @columnName + ', ';
                        SET @selectSQLText = @selectSQLText + @columnName + ', ';
                end;

                fetch next FROM columnNameCursor INTO @columnName;
        end;
        close columnNameCursor;
        deallocate columnNameCursor;

        SET @insertSQLText = substring(@insertSQLText, 1, len(@insertSQLText) - 2);
        SET @selectSQLText = substring(@selectSQLText, 1, len(@selectSQLText) - 2);

        SET @insertSQLText = @insertSQLText + ') ';
        SET @selectSQLText = @selectSQLText + ' from ' + @sourceDBName + '.dbo.' +    
           @tableName + @idExistsCondition;
       
        --result select-insert statment
        SET @resultSQLText = @insertSQLText + @selectSQLText;

        exec (@resultSQLText);

        fetch next FROM tableNameCursor INTO @tableName
END;

close tableNameCursor;
deallocate tableNameCursor;


--Enable Table Constraint
declare tableNameCursor cursor LOCAL FAST_FORWARD FOR SELECT * FROM @syncTablesNames;
open tableNameCursor;
fetch next FROM tableNameCursor INTO @tableName;

WHILE (@@fetch_status = 0)
BEGIN
        exec ('alter table ' + @tableName + ' check constraint all; ');
        fetch next FROM tableNameCursor INTO @tableName;
END;

close tableNameCursor;
deallocate tableNameCursor;

rollback

Нравится

Поделиться

16 комментариев

Юрий, в принципе очень добротный скрипт, но есть пара замечаний:
1. Я бы обрамлял идентификаторы в []
2.

"Доленко Юрий" написал:TableName varchar(50)

Есть системный тип - sysname, он лучше подходит для идентификаторов таблиц, колонок
3.
"Доленко Юрий" написал:--Disable Table Constraint
      exec ('alter table ' + @tableName + ' nocheck constraint all; ');

Я бы еще и триггера отключал, ну и ";" - лишнее
4.
"Доленко Юрий" написал:rollback

Это как говорится: "Счастливой отладки:)"?

В принципе это нужный скрипт (если разговор идет только в рамках MSSQL). Сам столкнулся с такой проблемой, но решил ее стандартными средствами. Нужно было перенести данные с 3.2.0 FB в 3.3.2 MSSQL.В версии 3.3.2 появилась возможность выгружать и загружать данные таблиц (wnd_DataManager). Эти сервисы переносятся и в 3.2 (была такая необходимость :)).
Во всяком случае два варианта лучше чем один :smile:

5.

"Доленко Юрий" написал:@insertSQLText nvarchar(MAX),

К сожалению для mssql 2000 не пойдет... Для него можно пойти путем как сделано в ХП - tsp_AdministratedByRecords

Александр, большое спасибо за советы, обязательно учту. (я рассчитывал черпнуть чего полезного из этого поста :) )

"Евгений Либин" написал:В версии 3.3.2 появилась возможность выгружать и загружать данные таблиц (wnd_DataManager).

Как же все-таки полезно писать блоги ). Относительно давно работаю с террасофтом, но о такое полезной утилитке не слышал. Действительно, выгружает/загружает xml, хотя судя по коду не отключаются ни триггера, ни constraint и грузится только по одной таблице. Могут ли быть нарушения целостности в процессе?

Нет, она не отключает не триггеры и констреинты. Можно выгружать сразу несколько таблиц. Я делал так, сначала выгрузил все справочники и загружал полученный скрипт пока на уйдут ошибки о несуществующих данных в связанных справочниках :lol:
После этого перешел к контактам и контрагентам. Тут как всегда философия "что было раньше контакт или контрагент?". В общем, сделал сначала загрузку контактов без ссылки на контрагента, а потом загрузил контрагентов и еще раз загрузил контакты.:lol:

Тоже была подобная задача по перебросу из FB в MS SQL. Есть такая штука как EMS SQL Managment studio для фаерберда. Там можно выгружать sql-скрипт(вида insert ... values) таблицы в синтаксисе t-sql(в нашем случае), при чем выбирать поля, которые будут фигурировать в запросе.
Потом можно отключить ограничения и просто выполнить каждый по одному разу...

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

В вашем скрипте есть пара неточностей, которые мешают нормально загружать данные.
в скрипте scr_DataManagerUtils
процедура SetDatasetValuesByDataRowNode
строку

if (DataRowNode.GetAttributeAsStr(DataField.Name, '') == 'IsNull') {

надо заменить на

if (DataRowNode.GetAttributeAsStr(DataField.Name, 'IsNull') == 'IsNull') {

процедура LoadDataFromDataRowNodeToDataset
строку

Log.Write(2, "Ошибка загрузки: " + e.Message)

надо заменить на

Log.Write(2, "Ошибка загрузки: " + e.message)

Если вы посчитаете мои замечания корректными - то включите их в базовую версию

Евгений, спасибо за замечания.

"Евгений Либин" написал:надо заменить на

if (DataRowNode.GetAttributeAsStr(DataField.Name, 'IsNull') == 'IsNull') 

{


В таком случае выйдет, что если в базе, в которую загружают, есть поле, которого не было в базе, из которой выгрузили данные, и данные обновляются (например, повторная загрузка), а значение этого поля было заполнено, то оно перетрется. Не думаю, что это всегда правильно. Думаю, правильнее будет дописать еще одно условие:

if (DataRowNode.GetAttributeAsStr(DataField.Name, '') == '') {
	continue;
}

Четко подмечено :twisted:

Т.е. в результате мы получим такой код?

		if (DataRowNode.GetAttributeAsStr(DataField.Name, '') == 'IsNull') {
			DataField.Value = null;
			continue;
		}
		if (DataRowNode.GetAttributeAsStr(DataField.Name, '') == '') {
			continue;
		}

Надо бы добавить обработку загрузки контрактов-контрагентов

Да, думаю, такой, только вынести бы в отдельную переменную

var DataFieldNameAttr = DataRowNode.GetAttributeAsStr(DataField.Name, '');

"Евгений Либин" написал:Надо бы добавить обработку загрузки контрактов-контрагентов

Какую именно обработку Вы имеете ввиду?

Если переносить в чистую базу раздел контакты, то без контрагентов контакты не зальются (Поле контрагент заполнено), а контрагента нельзя залить пока не залиты контакты (поле основной контакт). Вот и получается что нужно сделать признак вида "сохранять контакт при отсутствующем контрагенте" и в коде проверять: если грузим контакты, то проверить наличие контрагента и тд.... Хотя, наверно, у вас найдется более изящное решение этой проблемы :wink:

Теперь я чувствую себя просто обязанной придумать альтернативный вариант :) Не уверена, что изящно, но можно удалять связи контрагента с контактом и контакта с контрагентом в таблицах, а после импорта создавать заново. Тогда должен быть более сложный выбор таблиц, потом полей, на которые не должна срабатывать проверка связи. Зато это будет в один проход.

"Раловец Ольга" написал:придумать альтернативный вариант :)

Так вот же он) в первом посте. Буквально вчера этим скриптом за две минуты перекинул Контакты, контрагенты, средства связи(обоих разделов) и задачи (и группы всех разделов), причем структуры таблиц несколько отличались.
К слову, с группами без отключения ограничений будет крайне неприятно работать ибо связь сама на себя.

"Доленко Юрий" написал:Раловец Ольга пишет:
придумать альтернативный вариант :)
Так вот же он) в первом посте.

Юрий, отличный вариант :) Просто мы с Евгением уже начали обсуждение в контексте Datamanager.

К сожаление скрипт не смотрел, но идея с переносом ссылок должна быть такой:
1. Переносим все данные без внешних ссылок
2. Обновляем все записи, где проставляем ссылки, благо у нас GUID, и тем более все ссылки уже физически присутствуют в БД
Есть вопрос со ссылками, когда они обязательны на уровне БД. Тогда надо проверить, если поле ссылка обязательное, то в таблицу справочник вносим мнимую запись скажем нулевым GUID или с каким-то признаком в Name (как правило такое поле есть) - например со значением "Импорт. Удалить после привязки ссылок". И тогда во все записи с таким и полями вставляем ссылку на мнимую запись и обновляем ее после переноса всех записей.

Показать все комментарии

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

Предлагаю рассмотреть пример изменения Caption закладки "Адрес" раздела "Контрагенты" при наличии в ней записей на "Адрес*" (здесь можно придумать какие угодно варианты).
Итак, необходимо выполнить следующие действия:
1. Открываем сервис wnd_AccountsWorkspace, выделяем элемент деталей и переходим в обработчик события OnChangeActivePage.
2. В обработчике прописываем следующий код:

RefreshDetails();
 if (pcDetails.ActivePage.Name == pgAddressesDetail.Name) {   // вместо pgAddressesDetail нужно вставить название //Вашей закладки
  if (!IsDatasetEmpty(wndAddressesDetail.Window.ComponentsByName('dlData').Dataset)) { // вместо wndAddressesDetail нужно вставить название окна //закладки
   pgAddressesDetail.Caption = 'Адреса';
  } else {
   pgAddressesDetail.Caption = 'Адреса*'
  }
 }

Вышеприведенный код реализует смену Caption закладки при смене активной закладки в менеджере деталей.
3. Затем откройте закладку "Невизуальные", выделите датасет dlAccounts и перейдите в обработчик события OnDatasetAfterPositionChange.
4. Вставьте в обработчик события следующий код:
if (Dataset.Attributes('IsNew') != true) {
  RefreshDetails();
 }
 Dataset.Attributes('IsNew') = false;
 var AccountID = Dataset.Values('ID');
 var AddressDataset = Services.GetNewItemByUSI('ds_AccountAddress'); // вместо //ds_AccountsAddress нужно указать датасет соответсвующей //закладки
 ApplyDatasetFilter(AddressDataset, 'AccountID', AccountID, true);
 AddressDataset.Open();
 var AddressDatasetIsEmpty = IsDatasetEmpty(AddressDataset);
 AddressDataset.Close();
 if (!AddressDatasetIsEmpty) {
  pgAddressesDetail.Caption = 'Адреса';
 } else {
  pgAddressesDetail.Caption = 'Адреса*'
 }

Вышеприведенный код реализует смену Caption закладки при переходе по записям в основном реестре.
5. Сохраните все внесенные изменения. Перезапустите рабочее приложение Terrasoft CRM и протестируйте работоспособность системы.

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

Желаю удачи!

С уважением,
Мельникова Екатерина

Нравится

Поделиться

10 комментариев

А зачем код в OnChangeActivePage?

Код в OnChangeActivePage реализует смену Caption закладки при смене активной закладки в менеджере деталей. Конкретный пример: у пользователя активна одна запись в реестре и какая-то закладка в менеджере деталей. В этот момент другой пользователь может добавить записи на закладку, которая сейчас у первого не активна. Таким образом, при переходе на эту закладку первым пользователем - Caption меняется.

Так основной смысл был знать наличие записей до перехода на закладку. После перехода и так понятно есть они там или нет.

"Underscore a.k.a. _" написал:Так основной смысл был знать наличие записей до перехода на закладку

До перехода и будем знать, если перейдем не в Адреса, а в любую другую. В любом случае необходимы события, не вешать же для этого таймер.

"Ключник Алексей" написал:До перехода и будем знать

Я так понимаю, OnChangeActivePage вызывается непосредственно в момент перехода.

В данном случае не важно когда. Если мы переходим с детали Средства Связи на деталь Группы, то все равно увидим актуальную информацию по детали Адреса относительно наличия данных в ней.

Алексей, посмотрите, пожалуйста, внимательно на код, который предлагается разместить в OnChangeActivePage.

Я бы убрал проверку на название активной закладки. Такой вариант представляется мне более логичным.

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

Вообще предложенный функционал не говорит нам однозначно есть ли на данный момент записи в детали или нет. Он говорит, что они там были когда-то (или их там не было). Получается такое полурешение.

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

Показать все комментарии

В процессе моей работы иногда возникает ситуация, когда необходимо передать настроенную в TerrasoftCRM интеграцию, пользовательский запрос либо другие данные. Можно делать это разными способами, но хотелось иметь под рукой инструмент, с помощью которого можно быстро выгрузить данные из одной базы и загрузить в другой. Помимо этого, хотелось бы иметь возможность просто поменять содержимое перед загрузкой и не быть зависимым от конкретной СУБД.
Я реализовал небольшую утилиту в конфигурации TerrasoftCRM, которая позволяет выгрузить наполнение из выбранных таблиц и загрузить его на другой базе.
На примере ее реализации можно посмотреть как организована работа с объектом XMLStorage. На текущий момент она очень проста - выгружает все данные из выбранных таблиц и сохраняет все это в XML-файл. В будущем я планирую сделать возможность фильтрации выгружаемых данных, "умную" обработку полей, которые являются внешними ключами и прочее.

Нравится

Поделиться

3 комментария

Костя! Спасибо!
Сэкономил мне время на модуль для СВТ :)

Всегда пожалуйста, Евгений!

Спасибо, Костя! Я тоже уже заюзал пару функций из DataManager-а, что очень облегчило мою задачу.

Показать все комментарии