Коллеги, здравствуйте!

Дано: подготовленная Excel-таблица выставленных счетов, но наименования контрагентов отличаются по написанию с теми, что внесены в базу. Однако есть поле "код" контрагента, уникальное и однозначно идентифицирующее контрагента независимо от того как он написан.

Однако существующий механизм импорта в 7.5 не позволяет учесть поля, которые не существуют в разделе "Счет" напрямую (в 3.Х кстати был посильнее механизм Excel импорта :-)).

Я правильно понимаю, что если добавить в раздел "Счет" поле "Контрагент.Код" то можно будет по нему при импорте синхронизировать импортируемые счета со справочником контрагентов?

Если да, подскажите, как такое поле добавить в конфигураторе, сходу не обнаружил.

Нравится

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

Добрый день!

Дело в том что для справочника "Контрагенты" первичная колонка для отображения является имя. При загрузке, справочные поля заполняются по первичной колонке для отображения. Поэтому с кодом не получится.

А можно как-то настроить, чтобы при добавлении нового имени контрагента, добавлялся и код? чтобы потом оперативно поиском дублей все почистить?

Если при импорте счетов - то контрагенты создаются по имени и в этом случае код добавить нельзя.
Если происходит просто импорт контрагентов - то в шаблонном Excel файле нужно указать код.

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

Владимир, можете раскрыть ваше предложение поподробнее?

Показать все комментарии
7.4
Технические вопросы
7.x

Добрый день, подскажите где искать алгоритм происходящий после нажатия на зеленую трубку в детали средства связи
http://goo.gl/It5cS1
необходимо добавить дополнительные параметры при создания звонка

Нравится

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

Нажатие на кнопку обрабатывается в CTIBaseCommunicationViewModel функцией onLinkClick: function()

Не нашел там такой функции, есть

/**
 * Совершает звонок по текущему номеру телефона.
 * @protected
 */
call: function() {
	var number = this.get("Number");
	var contact = this.get("Contact");
	var customerId = contact ? contact.value : this.get("Account").value;
	var entitySchemaName = contact ? "Contact" : "Account";
	this.sandbox.publish("CallCustomer", {
		number: number,
		customerId: customerId,
		entitySchemaName: entitySchemaName
	});
},

кто то может подсказать где принимается этот CallCustomer?

CallCustomer - в BaseCommunicationDetail

нашел только это в BaseCommunicationDetail

messages: {
	"CallCustomer": {
		mode: Terrasoft.MessageMode.PTP,
		direction: Terrasoft.MessageDirectionType.PUBLISH
	},
	"DoNotUseCommunication": {
		mode: Terrasoft.MessageMode.PTP,
		direction: Terrasoft.MessageDirectionType.PUBLISH
	}
},

и никакого намека на то что именно происходит при нажатии, а конкретно, как создается сам звонок, чтобы заполнить ему дополнительные поля при создании

Добрый день.
Если я правильно понял, речь идет о 7.4.0 (cti-панель находится слева).
Вы все верно нашли - отправляется сообщение "CallCustomer", c параметрами: номер, идентификатор абонента и название объекта (Контакт или Контрагент).

Обрабатывается сообщение в схеме CtiPanelPage, метод onCallCustomer:

/**
 * Обработчик события звонка клиенту.
 * @param numberInfo {Object} Информация о параметрах звонка.
 * @param numberInfo.number {String} Номер телефона клиента.
 * @param numberInfo.isConsultCall {Boolean} Консультационный звонок.
 * @param numberInfo.isCanvassCall {Boolean} Звонок по исходящей кампании.
 * @param numberInfo.customerId {String} Id записи клиента.
 * @param numberInfo.callCampaignId {String} Id исходящей кампании.
 * @param numberInfo.callCampaignTargetId {String} Id целевой аудитории исходящей кампании.
 * @param numberInfo.callActivityId {String} Id активности.
 * @private
 */
function onCallCustomer(numberInfo) {
	if (!Ext.isEmpty(ctiModel.get("CurrentCallNumber")) && !Ext.isEmpty(ctiModel.get("ConsultCallNumber"))) {
		return;
	}
	if (!Ext.isEmpty(ctiModel.get("CurrentCallNumber"))) {
		if (numberInfo.number === ctiModel.get("CurrentCallNumber")) {
			return;
		} else {
			numberInfo.isConsultCall = true;
		}
	}
	var customerId = numberInfo.customerId;
	if (!numberInfo.isConsultCall && Ext.isEmpty(ctiModel.get("CurrentCallNumber"))) {
		var callLaterControlContainer = Ext.getCmp("callLaterControlContainer");
		if (callLaterControlContainer.rendered && !callLaterControlContainer.el) {
			callLaterControlContainer.rendered = false;
		}
		ctiModel.set("isCanvassCall", numberInfo.isCanvassCall);
		ctiModel.set("callCampaignId", numberInfo.callCampaignId);
		ctiModel.set("callCampaignTargetId", numberInfo.callCampaignTargetId);
		ctiModel.set("callActivityId", numberInfo.callActivityId);
		caller = numberInfo.number;
		callerInfo = null;
		if (Terrasoft.isGUID(customerId)) {
			ctiModel.set("customerId", customerId);
		}
		ctiModel.dial(numberInfo.number);
	} else if (numberInfo.isConsultCall && Ext.isEmpty(ctiModel.get("ConsultCallNumber"))) {
		ctiModel.transfering();
		if (!ctiModel.set("isModalWindowOpened")) {
			ctiModel.showModalWindow();
		}
		if (Terrasoft.isGUID(customerId)) {
			ctiModel.set("consultCustomerId", customerId);
		}
		ctiModel.callConsultCustomer(numberInfo.number);
	}
}

Можете добавить дополнительные свойства в numberInfo и обработать их в расширенной схеме CtiPanelPage.

Добрый день, почти то что нужно, метод вызывается из детали CommunicationDetail, он отправляет сообщение CallCustomer, эту деталь добавили на страницу активности и вопрос собственно заключается в том как связать Актиность со звонком, в объекте Call есть поле ActivityId вот как туда передать Id активности из которой был сделан звонок, на каком этапе задаются параметры numberInfo не могу найти перекопал уже все файлы что мог заподозрить в связи со звонками

Олег, к сожалению на этапе совершения звонка, еще не существует записи в таблице Call. Она появится уже после того, как телефония отправит сообщение о начале звонка в cti-панель.
Вы можете сделать по аналогии с тем, как происходит сохранение ссылки на кампанию обзвона, а именно сохранить ссылку на ActivityId в модели в onCallCustomer, а затем записать собственно в объект звонка в методе updateCall схемы CtiPanelPage

updateCall: function(columnName, customerId) {
	// ...
	var activityId = ctiModel.get("activityId");
	if (activityId) {
		update.setParameterValue("Activity", activityId,
			Terrasoft.DataValueType.GUID);
		ctiModel.set("activityId", null);
	}
	update.execute();
},

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

спасибо, это понял, но вот последний вопрос как передать параметр activityid в numberinfo и собственно на модель? в подписке на сообщение CallCustomer в CtiPanelPage есть

ctiModel.set("callActivityId", numberInfo.callActivityId);

откуда он берет айди активности?

Собственно мы его передаем, когда отправляем сообщение CallCustomer откуда-либо:
1. http://www.community.terrasoft.ru/forum/topic/12519#comment-53353

this.sandbox.publish("CallCustomer", {
		number: number,
		customerId: customerId,
		entitySchemaName: entitySchemaName,
// Передаем идентификатор активности
		callActivityId: <id активности>
});

2. Далее http://www.community.terrasoft.ru/forum/topic/12519#comment-53359

function onCallCustomer(numberInfo) {
	// ...
	// Это уже есть
	ctiModel.set("callActivityId", numberInfo.callActivityId);
	// ...
}

3. И наконец наш http://www.community.terrasoft.ru/forum/topic/12519#comment-53372

updateCall: function(columnName, customerId) {
        // ...
        var activityId = ctiModel.get("callActivityId");
        if (activityId) {
                update.setParameterValue("Activity", activityId,
                        Terrasoft.DataValueType.GUID);
                ctiModel.set("callActivityId", null);
        }
        update.execute();
},

Спасибо большое, а есть ли возможность перед публикации сообщений вытащить как то айди активности, объект this никаких связей с тем откуда от вызван в том моменте не содержит

Ну получение id активности уже зависит от того, как Вы реализовали деталь в карточке Активности.
Для контактов или контрагентов достаточно из модели взять атрибут "Contact" или "Account" соответственно. Могу предположить, что у Вас это какой-нибудь "Activity".

Артем, я не много не так выразился, наверное, в CTIBaseCommunicationViewModel.js мы собираем переменные для сообщения, вот как туда передать айди активности, к примеру там есть обращение к this но это обращение к объекту на детали

var number = this.get("Number");
var contact = this.get("Contact");

Олег, мы немного отошли от темы звонка и перешли к теме создания детали в карточке активности.
CTIBaseCommunicationViewModel - это view-модель средства связи, которая создается в NUI.BaseCommunicationDetail. Там же инициализируются атрибуты модели, такие как CommunicationTypes, DetailColumnName, PhoneCommunicationTypes. Вот где-то там Вам нужно также передать активность в качестве дополнительного атрибута, который затем читать в CTIBaseCommunicationViewModel.

Показать все комментарии
entity
Технические вопросы
7.x

Добрый день!

Подскажите, где можно получить информацию классе Entity, и о его методах в частности? На удивление, в SDK этой информации не нашел :(

Нравится

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

Добрый день, Николай!
На данный момент класс Entity находится в процессе review.
Если у Вас есть точечные вопросы по какому-то методу – будем рады объяснить его предназначение с примерами использования.

С уважением, техподдержка «Террасофт»

Здравствуйте, Николай!

В папке с дистрибутивом есть папка Terrasoft.WebApp, в ней в папке bin лежат библиотеки (dll-файлы), которые вы можете добавить в проект в Visual Studio и затем посмотреть классы в данных библиотеках со всеми методами, свойствами, типами и так далее (скриншот во вложении). Либо можете посмотреть структуру любой другой программой для просмотра dll библиотек.

Александр, спасибо большое - то, что нужно!

Показать все комментарии
бизнес-процесс
вызов
параметры
Хранимая процедура
Технические вопросы
7.x

Добрый день!
В разделе создано действие, при нажатии на которое запускается бизнес-процесс.
Смысл бизнес-процесса состоит в том, что в нем необходимо вызвать хранимую процедуру.
Т.е. бизнес-процесс состоит из трех элементов:
начальное простое событие
задание-сценарий
завершающее событие.

Вопросы:
1) как правильно вызвать хранимую процедуру
StoredProcedure storedProcedure = new StoredProcedure(UserConnection, "tsp_test") as StoredProcedure;
storedProcedure.PackageName = UserConnection.DBEngine.SystemPackageName;
storedProcedure.Execute();
return true;

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

2) как правильно передавать параметры входящие в процедуру(вызывать процедуру с параметрами)
и как правильно получить исходящие параметры хранимой процедуры

3) как правильно передавать в скрипте параметры БП во входящие параметры хранимой процедуры

4) где можно в конфигурации стандартной посмотреть вызов хранимой процедуры ( у вас есть несколько хранимых процедур в конфигурации, где можно посмотреть вызов и использование хотя бы одной из них)

Нравится

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

Добрый день, Дарья!

1) Правильный вызов процедуры (пример):

DataValueTypeManager dataValueTypeManager = UserConnection.DataValueTypeManager; 
var dateTimeValue = new DateTime(2009, 01, 02, 22, 12, 0);
                    Stream stream = new MemoryStream(Encoding.Unicode.GetBytes("Тест большого бинарного объекта"));
                    var textDataValueType = new TextDataValueType(dataValueTypeManager);
                    var guidDataValueType = new GuidDataValueType(dataValueTypeManager);
                    var integerDataValueType = new IntegerDataValueType(dataValueTypeManager);
                    var floatDataValueType = new Float2DataValueType(dataValueTypeManager);
                    var booleanDataValueType = new BooleanDataValueType(dataValueTypeManager);
                    var dateTimeDataValueType = new DateTimeDataValueType(dataValueTypeManager);
                    var idValue = new Guid("{BCDB8392-55BC-472A-A49D-22A975E0BEF6}");
 
                    StoredProcedure storedProcedure =
                           new StoredProcedure(Page.UserConnection, "tsp_TestStoredProcedure")
                           .WithParameter("IdParameter", idValue)
                           .WithVarParameter("VarIdParameter", idValue, guidDataValueType)
                           .WithParameter("TextParameter", "Украина")
                           .WithVarParameter("VarTextParameter", "Украина", textDataValueType)
                           .WithParameter("IntegerParameter", 10)
                           .WithVarParameter("VarIntegerParameter", 10, integerDataValueType)
                           .WithParameter("FloatParameter", 3.14)
                           .WithVarParameter("VarFloatParameter", 3.14, floatDataValueType)
                           .WithParameter("BooleanParameter", true)
                           .WithVarParameter("VarBooleanParameter", false, booleanDataValueType)
                           .WithParameter("DateTimeParameter", dateTimeValue)
                           .WithVarParameter("VarDateTimeParameter", dateTimeValue, dateTimeDataValueType)
                           .WithParameter("BinaryParameter", stream)
                           .WithVarParameter("VarBinaryParameter", stream)
                           .WithOutputParameter("ResultParameter", textDataValueType) as StoredProcedure;
storedProcedure.PackageName = Page.UserConnection.DBEngine.SystemPackageName;
storedProcedure.Execute();

2. Исходящий параметр .WithOutputParameter
3. Передача параметров в хранимую процедуру описана в первом пункте.
4. Вызов хранимой процедуры, Вы можете посмотреть в BaseAdministrativeGridPage

Спасибо.

по поводу пункта № 1- как правильно вызвать процедуру.

) Вызываю по аналогии в сценарии в БП
(процедура пока без параметров)

StoredProcedure storedProcedure = new StoredProcedure(UserConnection, "tsp_test") as StoredProcedure;
storedProcedure.PackageName = UserConnection.DBEngine.SystemPackageName;
storedProcedure.Execute();
return true;

Выходит ошибка при публикации (см. вложение).
Что не так?

2) по поводу пункта № 3 - не поняла.
Как передать просто параметры входящие - понятно.
Но как в скрипте правильно обратить к параметру бизнес-процесса, чтобы его уже значение передать в параметр хранимой процедуры ?

3) где именно искать вызов хранимой процедуры в BaseAdministrativeGridPage?

1. Код помещенный в БП в ScriptTask:

StoredProcedure storedProcedure = new StoredProcedure(UserConnection, "tsp_test") as StoredProcedure;
storedProcedure.PackageName = UserConnection.DBEngine.SystemPackageName;
storedProcedure.Execute();
return true;
не вызывает ошибки компиляции. Возможно у Вас есть еще какой-то код, который приводит к ошибке?

2. По имени параметра
var temp = ProcessSchemaParameter1

3. Поиском в исходных кодах
Открыть схему. Кнопка «Дополнительно» пункт меню «Открыть исходный код»

Спасибо, я посмотрю

Применение конструкции
.WithOutputParameter("ResultParameter", textDataValueType)
позволяет вызвать процедуру с исходящими параметрами.

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

Добрый день, Дарья!
Вы можете обратиться к значению, используя следующий пример кода:

var resultParameter = (string) storedProcedure.Parameters.FindByName("ResultParameter ").Value;

Добрый день еще раз!
Пытаюсь опять вызвать процедуру согласно инструкции (в БП в задании-сценарии)

DataValueTypeManager dataValueTypeManager = UserConnection.DataValueTypeManager;
var textDataValueType = new TextDataValueType(dataValueTypeManager);

StoredProcedure storedProcedure = new StoredProcedure(UserConnection, "tsp_test")
.WithOutputParameter("res_msg",textDataValueType) as StoredProcedure;

storedProcedure.PackageName = UserConnection.DBEngine.SystemPackageName;
storedProcedure.Execute();
return true;

Текст процедуры выглядит так:

CREATE procedure [dbo].[tsp_test]
as declare @res_msg nvarchar(250);
select @res_msg='1'
select @res_msg as res_msg

Процесс компилируется, но при запуске процесса элемент сценарий завершается с ошибкой

"System.Data.SqlClient.SqlException (0x80131904): Процедуре tsp_test не переданы параметры и аргументы"

О каких параметрах и аргументах может идти речь, если у процедуры этой нет вовсе входящих параметров, которые можно было бы передать?

Причем если вызвать процедуру без исходящего параметра
StoredProcedure storedProcedure = new StoredProcedure(UserConnection, "tsp_test")
as StoredProcedure;

то такой ошибки нет, сценарий выполняется.

Что-то не так с получением исходящего параметра процедуры?

Поняла в чем дело:
чтобы использовать конструкцию .WithOutputParameter("ResultParameter", textDataValueType) as StoredProcedure в скрипте,
необходимо в тексте самой процедуры прописывать параметр как OUTPUT:

CREATE procedure [dbo].[tsp_test]
(@res_msg nvarchar(250) OUTPUT)
as
select @res_msg='1'
select @res_msg as res_msg
return @res_msg

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

OUT | OUTPUT
Показывает, что параметр процедуры является выходным. Используйте параметры OUTPUT для возврата значений в вызвавший процедуру код.
В.Использование выходных параметров (OUTPUT)
В следующем примере создается процедура uspGetList. Эта процедура возвращает список товаров, цена на которые не превышает указанный предел. Данный пример поясняет использование нескольких инструкций SELECT и нескольких параметров OUTPUT. Параметры OUTPUT предоставляют внешней процедуре, пакету или нескольким инструкциям Transact-SQL доступ к значениям, заданным во время выполнения процедуры.

IF OBJECT_ID ( 'Production.uspGetList', 'P' ) IS NOT NULL 
    DROP PROCEDURE Production.uspGetList;
GO
CREATE PROCEDURE Production.uspGetList @Product varchar(40) 
    , @MaxPrice money 
    , @ComparePrice money OUTPUT
    , @ListPrice money OUT
AS
    SET NOCOUNT ON;
    SELECT p.[Name] AS Product, p.ListPrice AS 'List Price'
    FROM Production.Product AS p
    JOIN Production.ProductSubcategory AS s 
      ON p.ProductSubcategoryID = s.ProductSubcategoryID
    WHERE s.[Name] LIKE @Product AND p.ListPrice < @MaxPrice;
-- Populate the output variable @ListPprice.
SET @ListPrice = (SELECT MAX(p.ListPrice)
        FROM Production.Product AS p
        JOIN  Production.ProductSubcategory AS s 
          ON p.ProductSubcategoryID = s.ProductSubcategoryID
        WHERE s.[Name] LIKE @Product AND p.ListPrice < @MaxPrice);
-- Populate the output variable @compareprice.
SET @ComparePrice = @MaxPrice;
GO

Источник: https://msdn.microsoft.com/ru-ru/library/ms187926.aspx#Parameters

Спасибо, но вопрос не в этом заключался. Процедура, которую вы привели, просто возвращает два параметра выходных, а не выборку данных
А меня интересовало получение результатов выполнения процедуры в скрипте в bpmonline.
Например, процедура возвращает результат выполнения запроса select * from tbl_city
(это просто пример, выборка может быть сложнее), интересовало получение результата выполнения процедуры в bpmonline.
Но, наверное, мой вопрос уже не связан с темой бизнес-процессов - озвучу его в другой теме.
Спасибо

Добрый день, Дарья!

Для получения результата запроса из процедуры необходимо в процессе добавить в Usings:
Пространство имен: System.Data.IDataReader
Псевдоним: IDataReader

Вызов процедуры:

StoredProcedure storedProcedure = new StoredProcedure(UserConnection, "Test") as StoredProcedure;
storedProcedure.PackageName = UserConnection.DBEngine.SystemPackageName;
using (var dbExecutor = UserConnection.EnsureDBConnection()) {
            using (IDataReader dataReader = storedProcedure.ExecuteReader(dbExecutor)) {
                        while (dataReader.Read()) {
                                   var valueColumn1 = dataReader.GetValue(0);
                                   var operation = dataReader.GetColumnValue<int>("Operation");
                        }
            }
}
Показать все комментарии

Здравствуйте! Подскажите, пожалуйста, можно ли добавить на страницу Контрагенты в модуль "Действия" свой БП, при этом выбрать несколько записей - несколько контрагентов, и передать ID выбранных записей в бизнес-процесс. Если это можно, то каким образом это можно реализовать.

Нравится

1 комментарий

Добрый день, Юлия!

Процесс в действие можно добавить. Варианты реализации описаны по ссылкам:
http://www.community.terrasoft.ru/forum/topic/10554
http://www.community.terrasoft.ru/forum/topic/12414

Id выбранных записей хранятся в GridData в атрибуте SelectedRow. Это значение необходимо передавать в бизнес процесс.

Показать все комментарии
Enabled
действие
Технические вопросы
7.x

Создал для контрагента действие:

              getActions: function() {
                  var actionMenuItems = this.callParent(arguments);
                  actionMenuItems.addItem(this.getActionsMenuItem({
                      "Caption": { "bindTo": "Resources.Strings.SxActionOpportunity" },
                      "Enabled":  { bindTo: "isClient" },
                      methodName: "runOpportunityManagement"
                  }));
                  return actionMenuItems;
              },

Действие должно быть активно только для контрагента с типом Клиент или Потенциальный клиент. Написал метод:

              isClient: function() {
                  var type = this.get("Type");
                  if(type) {
                      type = type.value;
                  }
                  if(type === Home32Constants.Account.Type.Client || type === Home32Constants.Account.Type.PotencialClient){
                      return true;
                  }else{
                      return false;
                  }
              },

Функция возвращает "true" а действие всё-равно не активно. А вот если закрыть левую панель(там где список записей раздела) и обновить страничку - всё отрабатывает как нужно.

Та же ошибка наблюдается и с Caption. Если открыта панель, то Caption пустой, а если закрыта - всё ок.
Я так понимаю если открыта эта панель система считает, что пользователь находится в разделе...

Вопрос с Caption решается добавлением Strings в Section раздела, а вот как быть с Enabled не понятно.

Нравится

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

Добрый день, Вячеслав!

Код должен отрабатывать, на первый взгляд ошибок нет.
Скорее всего ошибка в чем-то другом.
Я Вам написал личное сообщения для продолжения анализа возникшей ошибки.

В версии 7,4 удалось решить проблему таким образом:

              initCardActionHandler: function() {
                  this.callParent(arguments);
                  var propertyNames = [
                      "isClient"
                  ];
                  this.Terrasoft.each(propertyNames, function(propertyName) {
                      this.on("change:" + propertyName, function(model, value) {
                          this.sandbox.publish("CardChanged", {
                              key: propertyName,
                              value: value
                          }, [this.sandbox.id]);
                      }, this);
                  }, this);
              },

В следующем проекте уже на версии 7.5 стоит похожая задача, но данное решение уже не помогает.

В версии 7.5 такое решение тоже будет работать если isClient будет свойством, а не функцией. Для того, чтоб все работало, нужно в Карточке сделать isClient и уже к нему байндить "Enabled" действия.

Да. Действительно если биндиться на свойство всё отрабатывает. Спасибо!

Показать все комментарии
Технические вопросы
7.x

Добрый вечер, коллеги. Не подскажите как сделать чтобы при скачивании отчета Word изменить имя файла.
Пробовал на InvoiceSection переопределить метод из PrintReportUtilities

downloadReport: function(caption, key) {
        var report = document.createElement("a");
        report.href = "../rest/ReportService/GetReportFile/" + key;
        report.download = "123.docx";
        document.body.appendChild(report);
        report.click();
      document.body.removeChild(report);
}

Но почему-то имя файла всё равно дефолтное, которое указано в справочнике Печатные формы.

Нравится

1 комментарий

Для получения названия файла необходимо попробовать переопределить метод фабричного класса ReportHelper следующим образом (обратите внимание на report.Caption):

                   public virtual Stream GetReportFile(string key) {
                             var userConnection = (UserConnection)HttpContext.Current.Session["UserConnection"];
                             var reportObj = userConnection.SessionData[key];
                             userConnection.SessionData.Remove(key);
                             ReportData report = reportObj as ReportData;
                             if (report.Format == "pdf") {
                                      WebOperationContext.Current.OutgoingResponse.ContentType = "application/pdf";
                             } else {
                                      WebOperationContext.Current.OutgoingResponse.ContentType = "application/octet-stream";
                             }
                             var reportStream = new MemoryStream(report.Data);
                             WebOperationContext.Current.OutgoingResponse.ContentLength = reportStream.Length;
                             WebOperationContext.Current.OutgoingResponse.Headers.Add("Content-Disposition", "attachment; filename=\"" +
Показать все комментарии
Технические вопросы
7.x

Подскажите пожалуйста, запутался совсем.
Есть сущности UsrVacancies, UsrCandidates и UsrVacancyCandidates. На основе UsrVacancyCandidates сделана деталь. Заходишь на вакансию, и можешь ассайнить кандидатов, одного или нескольких. Если кандидат УЖЕ добавлен для вакансии, то должно быть невозможно добавить этого же кандидата дважды или более.
Когда нажимаешь "Add" в детали, там есть лукапное поле Candidate (имя - UsrCandidates). Что я хочу сделать - чтобы в этом лукапе НЕ отображались те кандидаты, что УЖЕ добавлены для ЭТОЙ вакансии, т.е. отфильтровать его.
Для этого я должен получить список тех кандидатов, что уже добавлены для этой вакансии, и наложить фильтр на лукап - показывать всех кандидатов, КРОМЕ уже добавленных.

Вот как должно быть. Обратите внимание на переменную "candidates", где я захардкодил айдишники.

attributes: {
        "UsrCandidates": {
                dataValueType: Terrasoft.DataValueType.LOOKUP,
                lookupListConfig: {
                        filter: function() {
                                var candidates = ["5d4d6b06-4de2-4d23-8e6e-f2ab624af5d0", "3c6f72a7-3218-4f23-b96a-e344bac099f4", "a9b68651-d313-4c83-9007-31a659547bd5"];
                                var filterGroup = new Terrasoft.createFilterGroup();
                                var filter1 = Terrasoft.createColumnInFilterWithParameters(
                                        "Id",
                                        candidates);
                                filter1.comparisonType = Terrasoft.ComparisonType.NOT_EQUAL;
                                filterGroup.add("filter1", filter1);
                                return filterGroup;
                        }
                }
        }
},

Однако, мне нужно получить эти айдишники, зная Id текущуй вакансии. Вот ESQ запрос, который возвращает эти айдишники.

getCurrentlyAssignedCandidates: function(vacancyId, callback) {
        var esq = this.Ext.create("Terrasoft.EntitySchemaQuery", {
                rootSchemaName: "UsrVacancyCandidates"
        });
        esq.addColumn("UsrCandidates", "CandidateId");
        esq.filters.add(
                "filter1",
                this.Terrasoft.createColumnFilterWithParameter(
                        Terrasoft.ComparisonType.EQUAL,
                        "UsrVacancies",
                        vacancyId
                )
        );
        esq.getEntityCollection(function(result) {
                var candidates = [];
                if (result.success) {
                        result.collection.each(function(item) {
                                candidates.push(item.get("CandidateId"));
                        });
                        callback.call(this, candidates);
                }
        });
}

Однако, если вызывать callback в "filter", то он выполняется асинхронно, и сама функция выполняется асинхронно. Получается, что функция "filter" возвращает фильтр (который включает в себя ВСЕХ кандидатов) раньше, чем выполняется callback. Т.е. ничего не фильтруется.

Подскажите, пожалуйста, как это реализовать.

Заранее благодарю!

Нравится

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

На самом деле вариантов несколько:
1. Через filterMethod, добавляете на деталь ссылку на метод, а в самом методе добавляете в фильтр уже добавленные записи
Как пример UIv2/AccountPageV2 только здесь без фильтрации по существующим

                                                               details: /**SCHEMA_DETAILS*/{
                                                                              EmailDetailV2: {
                                                                                              schemaName: "EmailDetailV2",
                                                                                              filter: {
                                                                                                              masterColumn: "Id",
                                                                                                              detailColumn: "Account"
                                                                                              },
                                                                                              filterMethod: "emailDetailFilter"
                                                                              }
…
 
                                                                              /**
                                                                              * Функция создания фильтров детали email
                                                                              * @protected
                                                                              * @returns {createFilterGroup}
                                                                              */
                                                                              emailDetailFilter: function() {
                                                                                              var recordId = this.get("Id");
                                                                                              var filterGroup = new this.Terrasoft.createFilterGroup();
                                                                                              filterGroup.add("AccountNotNull", this.Terrasoft.createColumnIsNotNullFilter("Account"));
                                                                                              filterGroup.add("AccountConnection", this.Terrasoft.createColumnFilterWithParameter(
                                                                                                                             this.Terrasoft.ComparisonType.EQUAL, "Account", recordId));
                                                                                              filterGroup.add("ActivityType", this.Terrasoft.createColumnFilterWithParameter(
                                                                                                                             this.Terrasoft.ComparisonType.EQUAL, "Type", ConfigurationConstants.Activity.Type.Email));
                                                                                              return filterGroup;
                                                                              },

Но здесь возникнут сложности в асинхронностью, но Вы можете формировать первоначальную коллекцию например в методе init, а затем актуализировать эту коллекцию при изменении детали через подписку на сообщение updateDetail
2. Через правила
Как пример UIv2/ContactAddressPageV2

                               rules: {
                                               "AddressType": {
                                                              "FiltrationAddressTypeByOwner": {
                                                                              ruleType: BusinessRuleModule.enums.RuleType.FILTRATION,
                                                                              autocomplete: true,
                                                                              baseAttributePatch: "ForContact",
                                                                              comparisonType: Terrasoft.ComparisonType.EQUAL,
                                                                              type: BusinessRuleModule.enums.ValueType.CONSTANT,
                                                                              value: true
                                                              }
                                               }
                               },
или NUI/ActivityPage

3. Через один из методов Lookup, как пример loadVocabulary или prepareLookupList

                                                              /**
                                                              * Подготовка параметров для открытия окна выбора из пользователей.
                                                              * @return {Object} Config настроек окна выбора из справочника.
                                                              */
                                                              prepareLookupConfig: function() {
                                                                              var filters = this.getLookupFilter();
                                                                              var config = {
                                                                                              entitySchemaName: "SysAdminUnit",
                                                                                              multiSelect: true,
                                                                                              columns: ["Contact", "Name"],
                                                                                              hideActions: true,
                                                                                              filters: filters
                                                                              };
                                                                              return config;
                                                              }
 
 
                                                              getLookupFilter: function(isDelegate) {
                                                                              var filters = this.Terrasoft.createFilterGroup();
                                                                              var parentFilter =  this.Terrasoft.createColumnFilterWithParameter(
                                                                                              this.Terrasoft.ComparisonType.EQUAL,
                                                                                              isDelegate ? "GrantorSysAdminUnit.Id" : "GranteeSysAdminUnit.Id",
                                                                                              this.get("MasterRecordId"));
                                                                              var sameIdFilter =  this.Terrasoft.createColumnFilterWithParameter(
                                                                                              this.Terrasoft.ComparisonType.NOT_EQUAL,
                                                                                              "Id",
                                                                                              this.get("MasterRecordId"));
...
                                                                              filters.addItem(sameIdFilter);
                                                                              return filters;
                                                              },

Подобный вопрос задавали http://www.community.terrasoft.ru/forum/topic/11658

Мне нужно сформировать в javascript подобный запрос для фильтрации

declare @vacancyId uniqueidentifier = '3984d4b1-de5d-405b-b7c6-320b03b0a8ec';
select distinct
	vc.UsrCandidatesId
from UsrVacancyCandidates vc
where
	vc.UsrCandidatesId not in(
		select vc.UsrCandidatesId from UsrVacancyCandidates vc 
		where vc.UsrVacanciesId = @vacancyId
	);

Пока не могу понять, как..

1. SELECT DISTINCT vc.UsrCandidatesId

реализуется так:

esq.isDistinct = true;
esq.addColumn("UsrCandidatesId");

2. vc.UsrCandidatesId NOT IN(

                SELECT vc.UsrCandidatesId FROM UsrVacancyCandidates vc 
                WHERE vc.UsrVacanciesId = @vacancyId
        );
 
Реализуется через NOT_EQUAL
 
Пример NUI/InFolderDetail
 
var filterGroup = Terrasoft.createFilterGroup();
if (inFolders.length > 0) {
var notInFilter = Terrasoft.createColumnInFilterWithParameters('Id', inFolders);
notInFilter.comparisonType = Terrasoft.ComparisonType.NOT_EQUAL;
filterGroup.addItem(notInFilter);
}

Можете переделать NOT IN на Not Exists тогда, как пример фильтр в модуле

LeadContactsInFolderDetailV2
 
                                                               /**
                                                               * Метод действия "Добавить группу контактов".
                                                               * @private
                                                               */
                                                               addContactFolder: function() {
                                                                              var masterColumnValue = this.get("MasterRecordId");
                                                                              var config = {
                                                                                              entitySchemaName: "ContactFolder",
                                                                                              multiSelect: true,
                                                                                              columns: ["FolderType", "Id"]
                                                                              };
                                                                              var existsFilterGroup = this.Terrasoft.createFilterGroup();
                                                                               existsFilterGroup.addItem(this.Terrasoft.createColumnFilterWithParameter(
                                                                                              this.Terrasoft.ComparisonType.EQUAL, "Contact", masterColumnValue));
                                                                              var existsFilter = this.Terrasoft.createNotExistsFilter("[ContactInFolder:Folder:Id].Contact",
                                                                                              existsFilterGroup);
                                                                              var filterGroup = this.Terrasoft.createFilterGroup();
                                                                              filterGroup.addItem(existsFilter);
                                                                              var folderFilter = this.Terrasoft.createColumnFilterWithParameter(
                                                                                              this.Terrasoft.ComparisonType.EQUAL, "FolderType", ConfigurationConstants.Folder.Type.General);
                                                                              filterGroup.addItem(folderFilter);
                                                                              config.filters = filterGroup;
                                                                              this.openLookup(config, this.addContactCallback, this);
                                                               },

Теория по Exists
http://www.terrasoft.ua/bpmonlinesdk/Terrasoft.Core~Terrasoft.Core.Enti…

Спасибо, разобрался. Сделал так:

attributes:
{
	"UsrCandidates":
	{
		dataValueType: Terrasoft.DataValueType.LOOKUP,
		lookupListConfig:
		{
			filter: function()
			{
				var vacancy = this.get("UsrVacancies");
				if (!this.Ext.isEmpty(vacancy))
				{
					var filterGroup1 = this.Terrasoft.createFilterGroup();
					var filter1 = this.Terrasoft.createColumnFilterWithParameter(
						this.Terrasoft.ComparisonType.EQUAL,
						"[UsrVacancyCandidates:UsrCandidates].UsrVacancies", vacancy
						.value);
					filterGroup1.addItem(filter1);
					var filterGroup2 = this.Terrasoft.createFilterGroup();
					var filter2 = this.Terrasoft.createNotExistsFilter(
						"[UsrCandidates:Id].Id", filterGroup1);
					filterGroup2.addItem(filter2);
					return filterGroup2;
				}
			}
		}
	},
},
Показать все комментарии
Технические вопросы
7.x

Добрый день!

Подскажите, почему при создании Счета из Продажи не срабатывает событие InvoiceSaving из объекта счета?
Версия 7.3

Нравится

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

Александра, добрый день!

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

Спасибо.

Да, верно.
Так ж я использовала не то событие, надо было пользоваться InvoiceSaved.
У меня в процессе есть еще событие InvoiceInserted.
Подскажите какое из событий срабатывает раньше?
InvoiceInserted или InvoiceSaved?
Можете кратко написать порядок срабатывания событий объектов?

Александра, просмотреть данную информацию можно, если перейти на свойства схемы.

Пример прикрепляю к данному письму.

Спасибо.

Показать все комментарии
Технические вопросы
7.x

Доброго дня!

BPM 7.5. У нас в данный момент два кастомных пакета. Когда заходишь в конфигурацию, то автоматически выделяется (фокус) не тот пакет, что нужно, люди могут по ошибке создать сущности там, да и раздражает это. Как сделать, чтобы нужный автоматически выделялся нужный пакет?

Спасибо!

Нравится

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

Здравствуйте!

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

Спасибо за ответ!

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

Вопрос - могу ли я удалить такой неиспользуемый пакет без последствий?

Вероятнее всего, имя неиспользуемого Вами пакета "Custom+номер сборки" - в таком случае, в версии 7.5 - это предварительно созданный пользовательский пакет.

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

Также, если Вы на постоянной основе работаете со своим (уже созданным) пакетом, укажите его в системной настройке "Текущий пакет".

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

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

Владимир, системная настройка с пакетом по умолчанию не влияет на фокус на пакете в списке.

Как я и писал ранее - при переходе на вкладку "Пакеты" фокус устанавливается на верхний в списке пользовательский пакет.

"Владимир Соколов" написал:

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


Тоже в настройках шарился, устанавливал этот параметр, безрезультатно :lol:

А запланировано ли когда-нибудь такое удобство, как указание пакета по умолчанию, при открытии конфигурации? :-) каждый раз при входе в конфигурацию - лишний клик мышкой.. За день набегает на пару минут... И это только на одного разработчика... А в масштабах планеты? Месяцы трудозатрат в трубу. ;-)

Здравствуйте, Алексей!

Это же сколько нужно сделать кликов мышкой, чтобы пару минут времени за день набежало? И сколько времени при этом уходит на загрузку конфигурации?:smile:

Коллеги, также интересует вопрос,как, выбрать правильный пакет, если конфигураций несколько,кроме научного метода тыка) ? В таком случае пакетов с одинаковым названием получаем столько, сколько конфигураций.

"Демьяник Алексей" написал:

Здравствуйте, Алексей!

Это же сколько нужно сделать кликов мышкой, чтобы пару минут времени за день набежало? И сколько времени при этом уходит на загрузку конфигурации?


Давайте сойдемся на 3х секундах на то, чтобы открыть конфигурацию, увидеть, что пакет не тот, что надо, довести указатель мышки до нужного пакета (если он виден на экране сразу, и не нужно использовать скролл), нажать, и дождаться обновления экрана на новый список модулей :)
Итого 120/3 = 40 раз открыть конфигуратор в течение дня... вполне реально :cool:

Здравствуйте.
Что касается пакетов с одинаковыми именами, то в конфигурации Вы видите, пакеты только своей конфигурации, Если Вы смотрите в базе, то пакеты отличаются значением поля WorkSpaceID. Если Вы смотрите в файловой системе (SVN), то у каждого пакета указан номер репозитория (0, 1, 2...). По вопросу фокуса на нужном пакете при открытии конфигурации - можно, конечно, зарегистрировать пожелание на реализацию в одной из следующих версий, но эта задача, скорее всего, будет иметь невысокий приоритет.

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