В описании дополнения File storage for Creatio (https://marketplace.terrasoft.ru/app/file-storage-creatio) написано, что оно работает только с Субд MS SQL. Может ли данное дополнение работать с СУБД Postgre SQL?

Если нет, то есть ли подобное решение для данной БД?

Нравится

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

Нет, надо дорабатывать.

Добрый день, Оксана.

 

Евгений правильно уточнил, что дополнение не совместимо с СУБД Postgre SQL. Подобные решения также поддерживают работу с Субд MS SQL. Предлагаю уточнить у разработчиков решений планы по добавлению совместимости с Postgre SQL:

https://marketplace.terrasoft.ru/app/external-file-storage-creatio

https://marketplace.terrasoft.ru/app/file-manager-creatio

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

Коллеги, добрый день!

 

Такая ситуация:

 

к полю привязал атрибут, к которому подвязал вызов функции checkEpicId. Функция обращается к сервису, который в свою очередь обращается к стороннему сервису и возвращает строку для валидации. Получив ответ, функция анализирует респонс и устанавливает для колонки invalidMessage i isValid. На странице внизу колонки появляется установленный invalidMessage, но карточка все равно сохраняется с таким invalidMessage. Добавил ф-ю валидатор isInJiraValidator, но тоже не помогает.

 

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

 

код:

setValidationConfig: function() {
                this.callParent(arguments);
                this.addColumnValidator("UsrCoEpicId", this.isInJiraValidator);
            },
isInJiraValidator: function(value) {
                var invalidMessage = "";
                return {
                    invalidMessage: invalidMessage
                };
            },
 checkEpicId: function() {
                  var newEpicIdTitle = "";
                  var epicId = this.get("UsrCoEpicId");
                  var invalidMessage = "";
                  var isValid = false;
                ServiceHelper.callService(
                    "UsrJiraApi",
                    "getEpicId",
                    function(response) {
                          var res = response.getEpicIdResult;
                          var epicResult = response.substring(10, 11);
                          if(epicResult == "t") {
                              newEpicIdTitle = response.substring(response.indexOf("summary") + 10, response.indexOf("assetId") - 3);
                              console.log("Length after assetId: " + response.substring(response.indexOf("assetId")).length);
                              if(response.substring(response.indexOf("assetId")).length > 15) {
                                  var jiraAssetId = response.substring(response.indexOf("assetId") + 10, response.indexOf("assetId") + 46);
                                  console.log("Asset in CRM: " + this.get("UsrAssetTypeGeneral").value);
                                  console.log("Asset in Jira: " + jiraAssetId);
                                  if(this.get("UsrAssetTypeGeneral") && (this.get("UsrAssetTypeGeneral").value == jiraAssetId)) {
                                    invalidMessage = "";
                                      isValid = true;
                                   } else {
                                    invalidMessage = "Asset Type in Jira is not the same as in CRM for current Epic Id."
                                          + " Please set correct values or refer to administrator.";
                                      isValid = false;
                                  }
                            } else {
                                invalidMessage = "";
                                  isValid = true;
                                  this.showMessage("Asset Type in Jira is not set for current Epic Id. Validation is not awailable.");
                            }
                              this.validationInfo.set("UsrCoEpicId",{invalidMessage: invalidMessage, isValid: isValid});
                              this.set("UsrJiraEpicName", newEpicIdTitle);
                        } else {
                              invalidMessage = epicId + " - there is no such Epic Id in Jira. Please enter correct Epic Id."
                                  + " In case the epic Id in Jira has not been yet created - please do it now";
                              isValid = false;
                              this.validationInfo.set("UsrCoEpicId",{invalidMessage: invalidMessage, isValid: isValid});
                              this.set("UsrJiraEpicName", newEpicIdTitle);
                        }
                    },epicId,
                this);
            },

Нравится

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

в ф-и валидаторе не получилось реализовать получение ответа от внешнего сервиса из-за ассинхронности.

Сергей, возможно, есть смысл пересмотреть логику и получать нужную информацию из Jira в момент открытия карточки или изменения поля UsrAssetTypeGeneral, а затем получать список подходящих для выбора значений справочника getEpicId и фильтровать справочное поле по нему? Тогда и валидация не потребеутся.

Александр, спасибо! Ваш совет помог.

Сергей Хоменко пишет:

в ф-и валидаторе не получилось реализовать получение ответа от внешнего сервиса из-за ассинхронности.

А как сделать что бы return дождался ответа от внешнего сервиса?

Evgeniy Grigorev,

Здравствуйте, можно реализовать проверку полученного значения сообщения. Например, если получен ответ от сервера возвращать один результат, если значение отсутствует ошибку. 

Задачу также можно решить с помощью ajax.

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

Добрый день!

Есть ли у кого нибудь кодовый код, чтобы сделать раздел где будет только фрейм.

Фрейм должен занимать максимум страницы.

Нравится

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

Александр, можно даже без «кодового кода». Создать в итогах новую вкладку, там новый итог типа «веб-страница» и максимально растянуть:

А если нужно именно вместо раздела, см., как добавляется iframe вместо контрола в WelcomeScreen (только там это происходит по кнопке, а не в момент открытия): 

//VideoContainer
{
	"operation": "insert",
	"name": "VideoContainer",
	"parentName": "VideoScreen",
	"propertyName": "items",
	"values": {
		"id": "VideoContainer",
		"itemType": Terrasoft.ViewItemType.CONTAINER,
		"classes": {
			wrapClassName: ["video-wrapper"]
		},
		items: []
	}
}
 
...
/**
 * Handles a click on the "Play" button
 * @private
 */
onPlayButtonClick: function() {
	this.set("WelcomeScreenVisible", false);
	this.set("VideoScreenVisible", true);
	var html = this.getVideo();
	var videoContainer = this.Ext.get("VideoContainer");
	this.Ext.create("Terrasoft.HtmlControl", {
		id: "videoControl",
		renderTo: videoContainer,
		html: html,
		selectors: {
			wrapEl: ".video-wrapper"
		}
	});
},
...
getVideo: function() {
	var html = "<iframe width=\"996\" height=\"698\" src=\"{0}\" frameborder=\"0\" allowfullscreen></iframe>";
	var welcomeScreenVideoUrl = this.get("WelcomeScreenVideoUrl");
	return this.Ext.String.format(html, welcomeScreenVideoUrl);
},

 

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

Добрый день, Коллеги!

В Sales в разделе Заказы есть подбор продуктов. Стандартная логика позволяет в карточке контрагента в поле прайс-лист выбрать прайс-лист и тогда, при подборе товаров в заказ будет видна цена согласно этого прайс-листа. Система как-то пробрасывает его туда. Я бы хотел подменить эту логику на передачу туда прайс-листа из карточки самого заказа (я поле создал справочное). Где искать? Какой метод подменить или использовать?

Нравится

1 комментарий
Лучший ответ

Прайс-лист из таблицы контрагента используется в С#-схеме AccountPriceListPicker (там обычный Select), её вызывает тоже С#-схема веб-сервиса PriceListService:

/// <summary>
/// Get Price List using account. Took from account, if there is no Price List,
/// then took it from partnership
/// </summary>
/// <param name="accountId">Account identifier.</param>
/// <returns>PriceList identifier</returns>
[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped,
	ResponseFormat = WebMessageFormat.Json)]
public Guid GetPriceList(Guid accountId) {
	var priceListPicker = ClassFactory.Get<IPriceListPicker>(new ConstructorArgument("userConnection",
UserConnection));
	var	preSetPriceList = priceListPicker.GetPriceList(accountId);
	return preSetPriceList != default(Guid)
? preSetPriceList
: priceListPicker.GetPriceList(UserConnection.CurrentUser.AccountId);
}

А уже к нему обращаются из JS в странице заказа BaseOrderPage пакета Order:

/**
 * Sets predefined price list.
 * @protected
 * @virtual
 */
initializePredefinedPriceList: function() {
	if (this.isPredefinedPriceListsEnabled()) {
		this.$PredefinedPriceList = this.$Account && this.$Account.PriceList;
		if (this.isEmpty(this.$PredefinedPriceList)) {
			const config = this.getPriceListServiceConfig();
			this.callService(config, this.onPredefinedPriceListInitialized, this);
		}
	}
},
...
/**
 * Sets predefined price list.
 * @protected
 * @virtual
 */
initializePredefinedPriceList: function() {
	if (this.isPredefinedPriceListsEnabled()) {
		this.$PredefinedPriceList = this.$Account && this.$Account.PriceList;
		if (this.isEmpty(this.$PredefinedPriceList)) {
			const config = this.getPriceListServiceConfig();
			this.callService(config, this.onPredefinedPriceListInitialized, this);
		}
	}
},

 

 

Прайс-лист из таблицы контрагента используется в С#-схеме AccountPriceListPicker (там обычный Select), её вызывает тоже С#-схема веб-сервиса PriceListService:

/// <summary>
/// Get Price List using account. Took from account, if there is no Price List,
/// then took it from partnership
/// </summary>
/// <param name="accountId">Account identifier.</param>
/// <returns>PriceList identifier</returns>
[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped,
	ResponseFormat = WebMessageFormat.Json)]
public Guid GetPriceList(Guid accountId) {
	var priceListPicker = ClassFactory.Get<IPriceListPicker>(new ConstructorArgument("userConnection",
UserConnection));
	var	preSetPriceList = priceListPicker.GetPriceList(accountId);
	return preSetPriceList != default(Guid)
? preSetPriceList
: priceListPicker.GetPriceList(UserConnection.CurrentUser.AccountId);
}

А уже к нему обращаются из JS в странице заказа BaseOrderPage пакета Order:

/**
 * Sets predefined price list.
 * @protected
 * @virtual
 */
initializePredefinedPriceList: function() {
	if (this.isPredefinedPriceListsEnabled()) {
		this.$PredefinedPriceList = this.$Account && this.$Account.PriceList;
		if (this.isEmpty(this.$PredefinedPriceList)) {
			const config = this.getPriceListServiceConfig();
			this.callService(config, this.onPredefinedPriceListInitialized, this);
		}
	}
},
...
/**
 * Sets predefined price list.
 * @protected
 * @virtual
 */
initializePredefinedPriceList: function() {
	if (this.isPredefinedPriceListsEnabled()) {
		this.$PredefinedPriceList = this.$Account && this.$Account.PriceList;
		if (this.isEmpty(this.$PredefinedPriceList)) {
			const config = this.getPriceListServiceConfig();
			this.callService(config, this.onPredefinedPriceListInitialized, this);
		}
	}
},

 

 

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

После обновления системы перестал работать БП(по таймеру), который скриптом отправлял письмо с отчетом. В журнале пишет, что процесс выполняется

Изображение удалено.

В карточке:

Изображение удалено.

Что за настройка? Как это исправить?

Нравится

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

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

Нравится

3 комментария
Лучший ответ

Файлы внешних сборок лежат в папке пакета .\Assemblies\

Находите путь куда к вашим свн пакет и в самом пакете эту папку там будет .dll

Файлы внешних сборок лежат в папке пакета .\Assemblies\

Находите путь куда к вашим свн пакет и в самом пакете эту папку там будет .dll

Полозюков Евгений Петрович,

а если мне необходимо в папку с этой dll закинуть какой либо файл, что в таком случае делать, если я в облаке

Задача такая, для работы библиотеки нужен .dep файл, который загрузить в систему я не могу, я написал обращение в СП, но меня просят полный путь к папке, что бы положить туда, который я не знаю

в информации об ошибке предлагается 

Trying to load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\company\0\249501fa\2a0a463a\assembly\dl3\a10a36bd\file.dep



Но вот последний путь я не могу найти, что бы сообщить его СП

Только через поддержку.

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

Доброго времен суток!

 

Подскажите, 

Есть на marketplace (https://marketplace.terrasoft.ru/template/vydelenie-cvetom-zapisey-v-ra…)

приложение для выделения цветом записей в заказах и 

обращениях. Как, используя это приложение как template, добиться того же и в счетах?

Нравится

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

Привет. 



При запуске процесса из элемента "Задание сценарий" следующим образом: 



var manager    = UserConnection.ProcessSchemaManager;

var flowEngine = new FlowEngine(UserConnection);

var processSchema = manager.GetInstanceByName("test process");

Dictionary parameter = new Dictionary();

TestCompositeObjectList list = new TestCompositeObjectList();

list.Add(new A { ColName= "test1" });

list.Add(new A { ColName= "test2" });

parameter.Add("TestCollection", list);

parameter.Add("Name", "Test");

flowEngine.RunProcess(processSchema, parameter);

return true;

 

Сам класс TestCompositeObjectList был создан по этому примеру.

 

При запуске процесса ловим: 

System.InvalidCastException: Unable to cast object of type 'Terrasoft.Common.CompositeObjectList`1[Terrasoft.Common.CompositeObject]' to type 'System.Collections.Generic.List`1[System.String]'.



В документации не нашел примера заполнение и этот способ, что выше, тоже как ясно - не работает. 

TestCollection - и есть коллекция записей с полем стринг.

Name - просто ещё один параметр.



Дайте пожалуйста пример как сделать это.

 

 

Нравится

4 комментария
Лучший ответ

Разобрался. 

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



Код вызова: 

 

var manager	= UserConnection.ProcessSchemaManager;
var flowEngine = new FlowEngine(UserConnection);
var processSchema = manager.GetInstanceByName("ProcessName");
 
Dictionary<string, object> parameter = new Dictionary<string, object>();
 
CompositeObjectList<CompositeObject> objList = new CompositeObjectList<CompositeObject>();
 
objList.Add(new CompositeObject (new Dictionary<string, object>() {{"SubParamName",  "SubParamValue1"}}));
objList.Add(new CompositeObject (new Dictionary<string, object>() {{"SubParamName",  "SubParamValue2"}}));
// ....
// Первый коллекция записей - второе примитивный 
parameter.Add("CollectionName", objList);
parameter.Add("PrimaryParam", "Test");
 
flowEngine.RunProcess(processSchema, parameter);

 

Получение в скрипте: 

 

CompositeObjectList<CompositeObject> collection = Get<CompositeObjectList<CompositeObject>>("CollectionName");

 

if (ProcessSchemaManager.GetCanUseFlowEngine(userConnection, processSchema)) {

                    var flowEngine = new FlowEngine(userConnection);

                    Dictionary<string, string> parameter = new Dictionary<string, string>();

                    parameter.Add("CaseRecordId", CaseRecordId.ToString());

                    flowEngine.RunProcess(processSchema, parameter);

                } else {

                    var moduleProcess = processSchema.CreateProcess(userConnection);

                    if (processSchema.Parameters.ExistsByName("CaseRecordId")) {

                        moduleProcess.SetPropertyValue("CaseRecordId", CaseRecordId);

                    }

                    moduleProcess.Execute(userConnection);

                }

Полозюков Евгений Петрович,

У вас в примере обычный параметр, а не лист. С обычными то все понятно. Меня интересует передача именно параметра типа коллекция записей.

Возможно ли это вообщ? Потому что тут последний коммент говорит что "неа".

Разобрался. 

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



Код вызова: 

 

var manager	= UserConnection.ProcessSchemaManager;
var flowEngine = new FlowEngine(UserConnection);
var processSchema = manager.GetInstanceByName("ProcessName");
 
Dictionary&lt;string, object&gt; parameter = new Dictionary&lt;string, object&gt;();
 
CompositeObjectList&lt;CompositeObject&gt; objList = new CompositeObjectList&lt;CompositeObject&gt;();
 
objList.Add(new CompositeObject (new Dictionary&lt;string, object&gt;() {{"SubParamName",  "SubParamValue1"}}));
objList.Add(new CompositeObject (new Dictionary&lt;string, object&gt;() {{"SubParamName",  "SubParamValue2"}}));
// ....
// Первый коллекция записей - второе примитивный 
parameter.Add("CollectionName", objList);
parameter.Add("PrimaryParam", "Test");
 
flowEngine.RunProcess(processSchema, parameter);

 

Получение в скрипте: 

 

CompositeObjectList&lt;CompositeObject&gt; collection = Get&lt;CompositeObjectList&lt;CompositeObject&gt;&gt;("CollectionName");

 

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

Для реализации логики в событийном слове после вызова base.onSaved() в entity отсутствуют значения, которые устанавливаются в тех самых процессах объекта Saved.

Пока сделали дополнительный Select для получения обновленного entity и Update.

 

Эта верная логика или можно получить обновленный entity (полученный после отработки base.onSaved()) на событийном слое без вызова Select?

Нравится

12 комментариев
Лучший ответ

Полозюков Евгений Петрович пишет:

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

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

Добрый вечер.

Получить обновленный entity можно таким образом:

public override void OnSaved(object sender, EntityAfterEventArgs e)
{
   base.OnSaved(sender, e);
   Entity entity = (Entity)sender;
   var userConnection = entity.UserConnection;
  // реализуем нужную логику
 }

Ещё посмотрите вот эту статью на Академии: https://academy.terrasoft.ua/docs/developer/back-end_development/entity…

Алла Савельева,

именно так и делали по этой статье. Простой пример, который должен был показать значение Code

 

[EntityEventListener(SchemaName = "Account")] 
public class UsrAccountEntityEventListener : BaseEntityEventListener
 {
  public override void OnSaved(object sender, EntityAfterEventArgs e) {
    base.OnSaved(sender, e);
 
    var entity = (Entity) sender;
    var userConnection = entity.UserConnection;
 
    string accountCode = entity.GetTypedColumnValue&amp;lt;string&amp;gt;("Code");
 
    throw new Exception(entity.Schema.Name + " " + accountCode);

Однако значение Code, которое генерируется в процессе объекта onSaved было пустое.



Добавили Select, получили значение.

 

public override void OnSaved(object sender, EntityAfterEventArgs e) {
 base.OnSaved(sender, e);
 
 var entity = (Entity) sender;
 var userConnection = entity.UserConnection;
 Guid id = entity.PrimaryColumnValue;
 string schemaName = entity.Schema.Name;
 
 Select select = new Select(userConnection).Top(1)
  .Column("Code")
  .From(schemaName)
  .Where("Id").IsEqual(Column.Parameter(id)) as Select;
 string accountCode = select.ExecuteScalar&lt;string&gt;();

 

Владимир Соколов,

А зачем вам асинхронный вариант?

Обычный событийный подпроцесс нормально работает.

В ScriptTask получаем Entity.GetTypedColumnValue<string>("Code")

 

Полозюков Евгений Петрович пишет:

А зачем вам асинхронный вариант?

в каком месте у нас асинхронный вариант? 

Как раз в процессе получается асинхронно - иногда раньше, иногда позже процесса из родительского объекта

Владимир Соколов,

Мне показалось что это асинхронный вариант.

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

Полозюков Евгений Петрович,

потому что событийные подпроцессы как раз работают асинхронно

Скорее всего, из-за того, что в процессе значение для поля 'Code' устанавливается на onSaved, оно не попадает в параметр sender в обработчике в событийном слое.

 

Хотя это может быть и ошибкой, так как в документации написано, что механизм событийного слоя Entity срабатывает после выполнения событийных подпроцессов объекта.

Насчет того действительно ли это является ошибкой, могут ответить только в поддержке.

 

Как вариант решения, можно либо устаналивать значение для поля 'Code' не в обработчике onSaved, а в onSaving в подпроцессе объекта, либо реализовать всю логику в одном месте или в подпроцессе, или в событийном слое.

Полозюков Евгений Петрович пишет:

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

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

Алла Савельева пишет:

Как вариант решения, можно либо устаналивать значение для поля 'Code' не в обработчике onSaved, а в onSaving в подпроцессе объекта, либо реализовать всю логику в одном месте или в подпроцессе, или в событийном слое.

А есть идеи, как "выпилить" из базового функционала генерацию Code, оставив при этом остальные обработки процесса (синхронизация средств связи и прочее)?

Владимир Соколов,

Создайте свое поле и пишите к нему свою логику.

Владимир Соколов,

Очень часто сталкиваюсь с ситуациями, когда понимаешь, что проще было реализовать свое, чем использовать базовый функционал crying

Алла Савельева пишет:

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

Это всё понятно. Иногда незамещаемый базовый функционал доставляет больше проблем, чем его гипотетическое отсутствие.



Workaround мы нашли, но хотелось бы понять, как было задумано в системе

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

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

Пишет 401ю, хотя сервис логина работает

 

Прикрепленные файлы

Нравится

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

Запустите Компилировать все.

не помогло, скомпилировал все, не сработало, после сгенерировал все и скомпилировал все еще раз, все еще не помогло

 

Александр, если Вы подключаетесь к сервису аутентификации из другой программы, то дело может быть в неправильной работе с другими сервисами после этого. Например, если не передаёте все полученные куки и CSRF-токен. Подробнее о логине см. тут.

Или у Вас всё работало, а в какой-то момент перестало?

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