Процесс показан на видео. Добавил card page к лукапу сохранил. Потом понял, что добавил не к тому лукапу. Пытаюсь удалить - крэш при смене фокуса. Ребята, это несерьёзно с такими багами релизить продукт.
https://www.youtube.com/watch?v=aCS7DoFTyBU
Как удалить? Workaround сам могу придумать - создать страницу для текущего лукапа и подменить. .

An error occurred in bpm'online. We apologize for the inconvenience.
Back to Previous Page
Please notify the bpm'online support about this error.
We guarantee your confidentiality and anonymity.
Show Error Details

Date: 29/05/2015 01:11:44
Date (UTC): 28/05/2015 22:11:44

Exception Message: Object reference not set to an instance of an object.
Exception Type: System.NullReferenceException
Exception Source: Terrasoft.Configuration

Exception Stack Trace:
at Terrasoft.WebApp.LookupEditPageEventsProcess`1.SysEditPageSchemaEditChangeScriptTaskExecute(ProcessExecutingContext context)
at Terrasoft.Core.Process.ProcessFlowElement.Execute(ProcessExecutingContext context)
at Terrasoft.WebApp.LookupEditPageEventsProcess`1.ProcessQueue(ProcessExecutingContext context)
at Terrasoft.WebApp.LookupEditPageEventsProcess`1.OnExecuted(Object sender, ProcessActivityAfterEventArgs e)
at Terrasoft.Core.Process.ProcessFlowElement.Execute(ProcessExecutingContext context)
at Terrasoft.WebApp.LookupEditPageEventsProcess`1.ProcessQueue(ProcessExecutingContext context)
at Terrasoft.WebApp.BaseEditPageEventsProcess`1.ThrowEvent(ProcessExecutingContext context, String message)
at Terrasoft.WebApp.LookupEditPageEventsProcess`1.ThrowEvent(ProcessExecutingContext context, String message)
at Terrasoft.UI.WebControls.PageSchemaUserControl.ThrowEvent(String message)
at Terrasoft.UI.WebControls.Controls.ScriptManager.RaisePostBackEvent(String eventArgument)
at Terrasoft.UI.WebControls.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument)
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

Нравится

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

Добрый день!

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

Решение будет предоставлено в понедельник.

Спасибо! Кстати, куда можно рапортовать найденные баги?

"trickbz" написал:

Спасибо! Кстати, куда можно рапортовать найденные баги?


Найденные некорректные срабатывания системы с описанием кейсов воспроизведения, Вы можете отправлять на support@terrasoft.ru.

Данное неудобство будет исправлено в ближайших версиях.

Промежуточное решение:

1. Заходим в управление конфигурацией (на сайте клиента) -> Configuration -> закладка Packages
2. Выбираем пакет Base и в нем становимся на схеме LookupEditPage
3. Правой кнопкой мыши по схеме -> MetaData -> Open
4. В открывшимся окне становимся на закладку Modifications Package, копируем весь текст из него в текстовый файл (это наш бекап) и заменяем весь текст на текст из вложения lookupeditpage_metadata.txt
5. Нажимаем кнопку Save. После успешного сохранения закрываем окно и возвращаемся к списку схем
6. Правой кнопкой мыши по схеме -> Source Code -> Generate for selected items:

7. После генерации кода – закладка Actions -> Compile modified items:

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

Почему маска сама закрывается без вызова MaskHelper.HideBodyMask?
По кнопке вызывается метод import1C, в котором запускается БП.

import1C : function()
                                {
                                          var processArgs = {
                          sysProcessName: 'ImportProduct',
                          parameters: {}
                          };
                          this.runProcessWithTimeout(processArgs.sysProcessName, processArgs.parameters, 90000000, this.callBackImport, this);
                                },
                                runProcessWithTimeout: function(processName, processParameters, timeout, callback, scope) {
                                        var parametersQueryString = "";
                                        if (!Ext.isEmpty(processParameters)) {
                                                parametersQueryString += "?";
                                                var isNotFirstParameter = false;
                                                Terrasoft.each(processParameters, function(value, name) {
                                                        if (isNotFirstParameter) {
                                                                parametersQueryString += "&";
                                                        } else {
                                                                isNotFirstParameter = true;
                                                        }
                                                        parametersQueryString += name + "=" + value;
                                                }, scope);
                                        }
                                        MaskHelper.ShowBodyMask();
                                        ProcessModuleUtilities.callService({
                                                timeout: timeout,
                                                serviceName: ProcessModuleUtilities.PROCESS_ENGINE_SERVICE_NAME,
                                                methodName: processName + "/RunProcess" + parametersQueryString
                                        }, callback, scope);
                                },
                                callBackImport: function() {
                                        MaskHelper.HideBodyMask();
                                }

Получается что ещё не вызвался метод callBackImport, а маска сама закрылась, а когда завершил работу БП то и метод callBackImport вызвался. timeout тоже не помогает.
Нужно сделать пока работает БП маска показывается, как заканчивает работу скрывается. Пробовал через ProcessModuleUtilities.runProcess и там вызывать колбэк тоже сама закрывается.

Версия 7.5.0.1054

Нравится

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

Здравствуйте, Сергей!

Предлагаем Вам следующее решение:

import1C : function()
{
var processId = 'yourProcessId';
this.runProcess(processId);
//Или
var processName = 'yourProcessName';
var someFunction = function () {};
this.executeProcess(processName , someFunction);
}

/**
* Запускает бизнес-процесс из списка глобальной кнопки запуска процессов
* @param {Object} tag UId схемы бизнес-процесса
*/
runProcess: function(tag) {
ProcessModuleUtilities.executeProcess({
sysProcessId: tag
});
}

/**
* @private
*/
executeProcess: function(sysProcessName, callback) {
ProcessModuleUtilities.responseCallback = callback;
ProcessModuleUtilities.executeProcess({
sysProcessName: sysProcessName
});
},

import1C : function()
{
	var processName = 'ImportProduct';
	var someFunction = function () {};
	this.executeProcess(processName , someFunction);
},
executeProcess: function(sysProcessName, callback) {
	ProcessModuleUtilities.responseCallback = callback;
	ProcessModuleUtilities.executeProcess({
	sysProcessName: sysProcessName
	});
}

Всё равно маска сама скрывается буквально через 5 секунд после запуска БП, даже без вызова HideBodyMask в колбэке.

Добрый день!

Для решения данного вопроса предлагаем Вам обходное решение. Для запуска БП использовать не кнопку а запуск из кнопки на боковой панели (рис. 1).

Для этого необходимо в дизайнере бизнес-процессов в нужном БП перейти в свойства и в поле "Тег" написать "Business Process" (Рис.2).

Спасибо!

Добрый день!
К сожалению тоже самое, БП ещё работает, а маска уже скрывается.
Если кроме как вышеописанных действий ничего не нужно, то не помогло.

Спасибо, разобрался мою маску скрывал другой БП подсчет уведомлений, сделал вызов по maskId для своего случая.

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

Как запустить бизнес процесс только по выделенной записи в реестре?

Нравится

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

http://www.community.terrasoft.ru/forum/topic/10554

Как пример можно посмотреть запуск процесса LeadManagement из реестра Лидов запускается по ИД (файл LeadSectionV2 пакета CoreLead)

/**
* Выполняет запуск процесса квалификации с точки его последней активности.
*/
executeQualificationProcess: function() {
var activeRow = this.getActiveRow();
var qualificationProcessId = activeRow.get("QualificationProcessId");
if (qualificationProcessId) {
ProcessModuleUtilities.continueExecuting(qualificationProcessId, this);
} else {
ProcessModuleUtilities.executeProcess({
"sysProcessName": "LeadManagement",
"parameters": {
"LeadId": this.getPrimaryColumnValue(),
"ManualLaunch": true
}
});
}},

Попытался создать, но почему-то не запускается, где я ошибаюсь?

{
    "operation": "insert",
    "name": "runActiveSaleButton",
    "parentName": "DataGrid",
    "propertyName": "activeRowActions",
    "values": {
        "className": "Terrasoft.Button",
        "style": Terrasoft.controls.ButtonEnums.style.GREEN,
        "classes": {"textClass": "actions-button-margin-right"},
        "click": {"bindTo": "runActiveSale"},
        "caption": "Активная продажа"
    }
}
runActiveSale: function() {
    //var AccountId = this.get('Id');
    var AccountId = this.getPrimaryColumnValue();
    var processArgs = {
        sysProcessName: 'ActiveSale',
        parameters: {
            AccountId: AccountId
        }
    };
    this.runProcess(processArgs.sysProcessName, processArgs.parameters, this);
}

Я так понял, что с версии 7.3 идет не click,а tag (у нас 7.5)
Даже добавил ProcessModuleUtilities
Но все равно не получается:

{
    "operation": "insert",
    "name": "runActiveSaleButton",
    "parentName": "DataGrid",
    "propertyName": "activeRowActions",
    "values": {
        "className": "Terrasoft.Button",
        "style": Terrasoft.controls.ButtonEnums.style.GREEN,
        "caption": "Активная продажа",
        "tag": "runActiveSale"
    }
}
runActiveSale: function() {
    //var AccountId = this.get('Id');
    var AccountId = this.getPrimaryColumnValue();
    var processArgs = {
        sysProcessName: 'ActiveSale',
        parameters: {
            AccountId: AccountId
        }
    };
    ProcessModuleUtilities.runProcess(processArgs.sysProcessName, processArgs.parameters, this);
}

Александр, пример реализации кнопки запуска процесса по активной записи в разделе Контакты (змещающая схема ContactSectionV2)

define("ContactSectionV2", ["ProcessModuleUtilities"], function(ProcessModuleUtilities) {
		return {
			entitySchemaName: "Contact",
			methods: {
				runProcess: function() {
					var activeRow = this.get("ActiveRow");
					var config = {
						sysProcessName: "MyProcess",
						parameters: {
							RecordId: activeRow
						}
					};
					ProcessModuleUtilities.executeProcess(config);
				},
				onActiveRowAction: function(buttonTag) {
					if (buttonTag === "runProcess") {
						this.runProcess();
					} else {
						this.callParent(arguments);
					}
				}
			},
			diff: /**SCHEMA_DIFF*/[{
				"operation": "insert",
				"name": "DataGridActiveRowQualificationProcessAction",
				"parentName": "DataGrid",
				"propertyName": "activeRowActions",
				"values": {
					"className": "Terrasoft.Button",
					"style": Terrasoft.controls.ButtonEnums.style.GREEN,
					"caption": "Run process",
					"tag": "runProcess"
				}
			}]/**SCHEMA_DIFF*/
		};
	}
);
Показать все комментарии

Здраствуйте! Подскажите пожалуйста как можно заместить базовый исходного код, например SearchDuplicatesService. Если указать в нем мой пакет Custom и публиковать, выдает ошибку "Для заданного локального пути не указан путь к хранилищу". На базе которая не подключена к SVN работало и опубликовало в Custom пакете.

Нравится

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

Добрый день, Владимир!

Прошу уточнить версию приложения, и четкий кейс действий. Так как, судя по тексту (если мы правильно поняли), Вы пытаетесь перенести схему из одного пакета, который под SVN в другой (Custom) который не под SVN. Это делать нельзя.

Версия 7.5.0.1122 entherprise. Тогда как можно грамотно переопределить метод getDuplicates() класса SearchDuplicatesService? Наследовать этот класс и переопределить метод getDuplicates() я не могу так как он не виртуальный.

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

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

Добрый день!
Почему может не запускаться автоматически процесс управления инцидентами.
Настроены ящик службы поддержки(указан в специальной настройке) и общий ящик.
У пользователя веб-портала в карточке контакт прописан e-mail.

Пользователь веб-портала заводит обращение, оператор назначает ответственного, переводит обращение в состояние в работе и т.п...
Но никакие e-mail сообщения пользователю не приходят,
и вообще сам процесс почему-то не запускается ( в журнале процессов нет о нем информации).

Версия 7.5.0.1138 transitions

Нравится

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

Дарья, добрый день.
Чтобы попытаться проанализировать эту ситуацию, пожалуйста, приложите скриншоты:
- настройки почты
- настройки процесса отправки почты
- настройки того процесса из которого вызывается отправка почты

что значит: "настройки процесса отправки почты" и "настройки того процесса,из которого вызывается отправка почты"?
Это стандартный процесс "Управление инцидентами" в стандартной версии bpm service desk itil transitions.
Это коробочная версия

настройки почты - заведены два почтовые записи, прикреплены к учетной записи Supervisor.
Один из этих ящиков указан как ящик службы поддержки в системной настройке,
у другого указано, что он общий.

Также в карточке контакта(пользователя веб-портала), который завел обращение, в средствах связи указан его ящик.

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

Базовый процесс управления инцидентами стартует по следующим событиям:
1. Создано новое обращение, в котором указаны:
- Категория = Инцидент
- Состояние = Новое
- заполнены поля "Сервис" и "Ответственный" любыми значениями;
2. Изменено любое поле записи обращения, если по результатам сохранения в записи указаны:
- Категория = Инцидент
- Состояние = Новое
- заполнены поля "Сервис" и "Ответственный" любыми значениями.
В описанном Вами кейсе выполняется работа с существующей записью, но по результатам сохранения запись не соответствует выше перечисленным параметрам.

Рекомендую протестировать запуск и работу процесса повторно с учетом описанной логики.

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

А есть где-нибудь описание вообще этого процесса. Какие шаги, что происходит в том или ином случае.
В руководстве пользователя нет(

Например, процесс корпоративных продаж в руководстве к sales очень хорошо описан, и это удобно.
А вот про процесс управление инцидентов в руководстве к service desk написано пару строчек.

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

Дарья, ниже приведены комментарии по Вашим вопросам.

1. «А есть где-нибудь описание вообще этого процесса. Какие шаги, что происходит в том или ином случае.»

Процесс описан верхнеуровнево на ресурсе Terrasoft Academy (http://academy.terrasoft.ru/documents/?product=transitions&ver=7.5.0) в разделе Функциональность bpm’online ITIL service/Раздел [Обращения]/Бизнес-процесс управления инцидентами.
Если у Вас будут возникать дополнительные вопросы, то мы готовы ответить.

2. «Подскажите, пожалуйста, еще почему может не заполняться по умолчанию ответственный в обращении. У сервиса указан сервисный инженер и владелец. В какой момент должен заполниться ответственный по обращению на основании выбранного сервиса?»

Сервисные инженеры в сервисе указываются с целью порекомендовать специалисту поддержки, выполняющему классификацию обращения, в чью компетенцию входит обслуживание текущего сервиса. Иными словами, указав одного или несколько сервисных инженеров по определенному сервису, система не будет автоматически определять ответственно по обращению с таким сервисом, а ограничит список рекомендуемых специалистов для выбора.
Детально данная функциональность описана в параграфе «Подбор сервисных инженеров» раздела Функциональность bpm’online ITIL service/Раздел [Обращения]/Страница обращения на ресурсе Terrasoft Academy (http://academy.terrasoft.ru/documents/?product=transitions&ver=7.5.0)

Ответственный по обращению при этом устанавливается вручную.

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

Задам вопрос по инциденту и процессу тогда:
Если я правильно поняла, система не подставляет ответственного по обращению автоматически (даже если в сервисе указан только один сервисный инженер).
Тогда возникает следующий вопрос:
Вы писали, что базовый процесс по инциденту стартует в этих случаях.
1. Создано новое обращение, в котором указаны:
- Категория = Инцидент
- Состояние = Новое
- заполнены поля "Сервис" и "Ответственный" любыми значениями;
2. Изменено любое поле записи обращения, если по результатам сохранения в записи указаны:
- Категория = Инцидент
- Состояние = Новое
- заполнены поля "Сервис" и "Ответственный" любыми значениями.

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

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

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

Только если будете вносить изменения процесс нужно "Сохранить как новую версию"

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

Добрый день!
Версия Сервис-Деск Transitions 7.5.0.1138
Есть базовый календарь - время работы с понедельника по пятницу с 9.00 до 18.00
(обед с 13.00 до 14.00)
Создан сервисный договор по умолчанию, привязанный к базовому календарю.
В него добавлены сервисы.
Сроки разрешения и реакции всех сервисов указаны в рабочих часах,днях

При создании обращения время реакции и время разрешения рассчитываются БЕЗ учета базового календаря, без учета рабочих часов,дней.

Пример(см. вложение):
Сервис: время реакции - 4 рабочих часа, время разрешения - 2 рабочих дня

При создании обращения 27 мая в 16.07
план. реакция ставится 27 мая 21:00 и план. разрешение 29.05.2015 21:00
Хотя это не рабочее время по календарю.

С чем это может быть связано?

Нравится

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

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

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

Данная информация поможет нам предоставить Вам консультацию по этому вопросу.

Спасибо.

В базовом календаре указан пояс "GMT(время в формате)" (см. вложение)
Я нахожусь в часовом поясе, совпадающем с московским.

Добрый день, Дарья.
Вам необходимо установить часовой пояс, который соответствует вашему расположению, а именно GMT+3. После установки вашего пояса сроки должны считаться корректно, обратите внимание что проверять надо на новом обращении.
Скриншот с правильным поясом прилагаю.

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

Добрый день!

Подскажите, можно ли получить в сценарии javascript город контрагента без использования функции callback?

Сейчас знаю только реализацию через callback, например:

qetCity : function (stringValue, callback) {
        var esq = this.Ext.create("Terrasoft.EntitySchemaQuery", {rootSchemaName : "Account"});
        esq.addColumn("Id");
        esq.filters.addItem(Terrasoft.createColumnFilterWithParameter(Terrasoft.ComparisonType.EQUAL, "Id", account.value));
        esq.getEntityCollection(function (result) {
                if (result.success) {
                        var items = result.collection.getItems();
                        var City = [];
                        this.Terrasoft.each(items, function (item) {
                                City.push(item.get("Id"));
                        }, this);
                        callback.call(this, City);
                }
        }, this);
},
showCity : function () {
        this.qetCity("ASP; C++; C#", function (City) {
                var City = this.getIds("ASP; C++; C#", function (City);
        });
}

Нравится

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

Если речь про карточку или реестр (на основе базовых), то есть способы). А в общем случае, как я понимаю, нельзя - т.к. запрос-ответ, асинхронность и все такое.
Но если можно, тоже было бы интересно узнать, как.

"Андросов Дмитрий" написал:

Если речь про карточку или реестр (на основе базовых), то есть способы). А в общем случае, как я понимаю, нельзя - т.к. запрос-ответ, асинхронность и все такое.

Но если можно, тоже было бы интересно узнать, как.

А если о карточке, то какие есть способы?

Если это поле из текущего объекта (в карточке Контакта - Контакт)

this.get("КодПоля");

Если это справочное поле (в карточке Контакта - Контрагент)

//в атрибутах добавить/изменить атрибут этого справочника
"Account": {
	lookupListConfig:{
		//вроде бы влияет на данные, подтягиваемые из справочника при выборе
		columns: ["Country", "Owner"]
	},
	//вроде бы влияет на данные, подтягиваемые из справочника при открытии ранее сохраненной записи
	columns: ["Country", "Owner"]
}
 
// в методах получить можно будет так
var Account = this.get("Account");
if (Account && Account.value) {
	var Country = Account.Country;
	var Owner = Account.Owner;
}

Если контрагент никак не связан с текущей карточкой, то на init, записать нужные города в переменные (все равно колбэками), но зато абстрагированно от остального кода) и использовать далее уже эти переменные.
Но, может, лучше разобраться с колбэками?

"Андросов Дмитрий" написал:

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

Но, может, лучше разобраться с колбэками?

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

Возможно вам подойдет вариант через Entity

var AccountId = "..."; //GUID контрагента

var select = Ext.create("Terrasoft.EntitySchemaQuery", {
rootSchemaName: "Account"
});
select.addColumn('City.Name', 'CityName');
select.filters.add(select.createColumnFilterWithParameter(
Terrasoft.ComparisonType.EQUAL, "Id", AccountId, Terrasoft.DataValueType.TEXT));
select.getEntityCollection(function(result) {
if (result.success) {
var collection = result.collection;
collection.each(function(item) {
console.log(item.get("CityName")); !!! Ваши города
}, this);
}
}, this);

"Вильшанский Дмитрий" написал:вариант через Entity

хаха точно

"Вильшанский Дмитрий" написал:

Возможно вам подойдет вариант через Entity

var AccountId = "..."; //GUID контрагента

var select = Ext.create("Terrasoft.EntitySchemaQuery", {

rootSchemaName: "Account"

});

select.addColumn('City.Name', 'CityName');

select.filters.add(select.createColumnFilterWithParameter(

Terrasoft.ComparisonType.EQUAL, "Id", AccountId, Terrasoft.DataValueType.TEXT));

select.getEntityCollection(function(result) {

if (result.success) {

var collection = result.collection;

collection.each(function(item) {

console.log(item.get("CityName")); !!! Ваши города

}, this);

}

}, this);

Дмитрий, но в этом случае select.getEntityCollection(function(result) разве не является callback?

Александра, у нас запросы к базе данных происходит в асинхронном режиме (через callback можно получить результат). Вариант синхронного запроса у нас не используется из-за того, что это замедляет работу приложения.

"Вильшанский Дмитрий" написал:

Александра, у нас запросы к базе данных происходит в асинхронном режиме (через callback можно получить результат). Вариант синхронного запроса у нас не используется из-за того, что это замедляет работу приложения.

Понятно. Спасибо за информацию.

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

Как сделать агрегатирующий EntityShemaQuery запрос join по двум таблицам на клиенте (JavaScript)?

Нравится

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

Пример который выводит в консоль количество типов для контрагентов, имя которых содержит “111”

var select = Ext.create("Terrasoft.EntitySchemaQuery", {
                rootSchemaName: "Account"   
});
select.addColumn("Name");
select.addAggregationSchemaColumn("Type.Id", Terrasoft.AggregationType.COUNT, "Count");
select.filters.add(select.createColumnFilterWithParameter(
                Terrasoft.ComparisonType.CONTAIN, "Name", "111", Terrasoft.DataValueType.TEXT));
select.getEntityCollection(function(result) {
                if (result.success) {
                               var collection = result.collection;
                               collection.each(function(item) {
console.log(item.get("Name"));
                                               console.log(item.get("Count"));
                               }, this);
                }
}, this);

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

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

Объясню бизнес-логику. Есть раздел "Vacancies", на ней есть деталь "Vacancy candidates", куда можно добавить 0 или более кандидатов на эту вакансию. У каждого кандидата есть лукап "Candidate status". В процессе работы с кандидатом он проходит определённые стадии в определённом порядке, поэтому каждая запись из соответствующей таблицы UsrCandStat содержит целочисленную колонку "Value", где хранится число, определяющее эту стадию, и "StatusName". Допустим, 10 стадий, 0 - нашли кандидата, 1 - связались с ним, 2 - кандидат послал своё резюме, ..., 9 - кандидат успешно прошел интервью с заказчиком, 10 - кандидат взят на работу. В разделе "Vacancies" есть вычисляемое виртуальное поле, которое автоматически должно заполнятся "StatusName" того кандидата из списка кандидатов детали "Vacancy candidates", который прошел в своём нелёгком пути наиболее далеко, т.е. с максимальным значением "Value".

Допустим, в детали добавили 3 кандидата и у ВСЕХ заполнили поле "Candidate status". В этом случае всё более менее работает - возвращаются 3 записи, отсортированные по максимальному значению Value, т.е. можно взять элемент с индексом 0 и это и будет максимум. Хотя, опять же вопрос - почему 3, а не одна максимальная?

Ситуация похуже. В детали 3 кандидата, у 2х заполнили "Candidate status", а у третьего оставили пустым, допустим, поле необязательное. В итоге возвращаются 3 записи, при этом под индексом 0 будет запись с Value равным 0 и пустым "StatusName". Это корректное поведение? Как мне теперь найти запись с максимальным индексом? Искать Макс среди того, что вернул Макс?

Записал видео это всего:
youtube video

setUsrMostFarthestCandStatusVirtualValue: function ()
{
	var vacancyId = this.get("Id");;
	var esqCand = this.Ext.create("Terrasoft.EntitySchemaQuery", { rootSchemaName: "UsrVacancyCandidates" });
	esqCand.addAggregationSchemaColumn("UsrCandStat.Value", Terrasoft.AggregationType.MAX, "Value");
	esqCand.addColumn("UsrCandStat.Name", "CandidateStatusName");
	esqCand.filters.add("ByUsrVacanciesFilter", this.Terrasoft.createColumnInFilterWithParameters("UsrVacancies", [ vacancyId ]));
	esqCand.getEntityCollection(function(response)
	{
		if (response.success)
		{
			var recruitmentStageNameWithMaxValue = "";
			var collection = response.collection;
			if (collection && collection.getCount() > 0)
			{
				Terrasoft.each(collection.getItems(), function(item) {
					console.log(item.get("CandidateStatusName") + ': ' + item.get("Value"));
				}, this);
				var recruitmentStageNameWithMaxValue = collection.getByIndex(0).get("CandidateStatusName");
			}
			this.set("UsrMostFarthestCandStatusVirtual", recruitmentStageNameWithMaxValue);
		}
	}, this);
},

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

Потому, что Вы формируете неправильный агрегированный запрос.
У Вас получается в итоге что-то на подобии этого:

Select MAX(Value), CandidateStatusName from 

А надо просто Select MAX(Value) from

Спасибо, Вы правы, протупил. Еще вопросы:
1. Если у Кандидата не выбран статус, то в "Value" (числовой тип) содержится 0, хотя я ожидаю, допустим, null или undefined, а 0 уже используется как индекс определённой стадии. Как это побороть? Хотя, как вариант, можно начинать индексацию стадий с 1, а значение 0 использовать как индикацию, что ничего не выбрано. Но всё же.
2. Почему запрос функция срабатывает несколько раз? Это сказывается на производительности. Как это исправить? Я вызываю её так, как Вы советовали

updateDetail: function(config) {
   config.reloadAll = true;
   this.callParent(arguments);
   this.setUsrMostFarthestCandStatusVirtualValue();
},

3. Я сделал виртуальное поле, куда записываю вычисленное значение. Как мне его сделать readonly?

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

1. Добавил виртуальную (вычисляемую) колонку. Вопрос - Как сделать её readonly?

attributes:
{
	"UsrMostFarthestCandStatusVirtual":
	{
		type: Terrasoft.ViewModelColumnTypeVIRTUAL_COLUMN,
		referenceSchemaName: "UsrVacancies",
		dataValueType: Terrasoft.DataValueTypeTEXT,
		customConfig:
		{
			readonly: true // не работает
		},
	}
}

2. Связал с diff (разметка?). Вопрос - Как сделать её readonly? Кстати, колонка отобразилась в визуальном редакторе страницы на нужном месте, но, когда выделяешь её, пропадает кнопка "Edit". Так и надо?

{
	"operation": "insert",
	"name": "UsrMostFarthestCandStatusVirtual",
	"values":
	{
		"layout":
		{
			"column": 0,
			"row": 0,
			"colSpan": 12,
			"rowSpan": 1
		},
		"bindTo": "UsrMostFarthestCandStatusVirtual",
		"caption":
		{
			"bindTo": "Resources.Strings.UsrMostFarthestCandStatusVirtualCaption"
		},
		"textSize": 0,
		"contentType": 3,
		"labelConfig":
		{
			"visible": true
		},
		"enabled": true
	},
	"parentName": "group_gridLayout",
	"propertyName": "items",
	"index": 0
}

3. Создал функцию, которая вычисляет значение виртуальной колонки. Пришлось делать 2 EntitySchemaQuery запроса. Один - для получения макс значения Value, второй - для получения Name по уже известному макс значению Value. Вопросы - можно ли обойтись 1-м запросом? Почему метод вызывается несколько раз (см ниже)? Между переходом к другой вакансии и появлением значения в поле виртуальной колонки приходит около 2 секунд, что много.

setUsrMostFarthestCandStatusVirtualValue: function()
{
	var vacancyId = this.get("Id");;
	var esqCand = this.Ext.create("Terrasoft.EntitySchemaQuery",
	{
		rootSchemaName: "UsrVacancyCandidates"
	});
	esqCand.addAggregationSchemaColumn("UsrCandStat.Value", Terrasoft.AggregationType.MAX, "Value");
	esqCand.filters.add("ByUsrVacanciesFilter", this.Terrasoft.createColumnInFilterWithParameters("UsrVacancies", [vacancyId]));
	esqCand.getEntityCollection(function(response)
	{
		if (response.success)
		{
			var collection = response.collection;
			if (collection && collection.getCount() > 0)
			{
				var maxStageValue = collection.getByIndex(0).get("Value");
				if (maxStageValue === 0)
				{
					this.set("UsrMostFarthestCandStatusVirtual", "");
					return;
				}
				var esqCandStatName = this.Ext.create("Terrasoft.EntitySchemaQuery",
				{
					rootSchemaName: "UsrCandStat"
				});
				esqCandStatName.addColumn("Name", "StatusName");
				esqCandStatName.filters.add("ByValueFilter", this.Terrasoft.createColumnInFilterWithParameters("Value", [maxStageValue]));
				esqCandStatName.getEntityCollection(function(responseCandStatus)
				{
					var recruitmentStageNameWithMaxValue = "";
					if (responseCandStatus.success)
					{
						var candStatCollection = responseCandStatus.collection;
						if (candStatCollection && candStatCollection.getCount() > 0)
						{
							recruitmentStageNameWithMaxValue = candStatCollection.getByIndex(0).get("StatusName");
						}
						this.set("UsrMostFarthestCandStatusVirtual", recruitmentStageNameWithMaxValue);
					}
				}, this)
			}
		}
	}, this);
}

4. Дёргаю функцию на updateDetail событие, как вы и советовали. Почему-то она дергается несколько раз. Я вижу это в дебагере и в закладке Network инспектора. Как исправить?

updateDetail: function(config)
{
	config.reloadAll = true;
	this.callParent(arguments);
	this.setUsrMostFarthestCandStatusVirtualValue();
},

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

{
        "operation": "insert",
        "name": "UsrMostFarthestCandStatusVirtual",
        "values":
        {
                ...
                "enabled": false
        }
...

3. Т.к. вам важна скорость обновления значения (а основное время уходит не на SQL-запрос, а на общение между браузером, сервером и ядром) то рекомендую минимизировать запрос, для этого лучше всего имхо подойдет представление в БД (с Id вакансии и теми колонками, что вам нужны - я так понимаю это Имя кандидата) с использованием (на вскидку) cross join

Спасибо, звучит разумно. Правда нет ответа на вопрос, почему функция дёргается несколько раз, и нормально ли это.

"trickbz" написал:функция дёргается несколько раз, и нормально ли это.

с помощью метода updateDetail обновляются все детали (если не ошибаюсь, то в т.ч. и при их инициализации):

this.updateDetail({detail: "Название детали из блока details, например, Files"});

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

updateDetail: function(config) {
	if (config.detail && config.detail === "VacancyCandidates") { //проверьте имя
		config.reloadAll = true;
		this.callParent(arguments);
		this.setUsrMostFarthestCandStatusVirtualValue();
	} else {
		this.callParent(arguments);
	}
}

Спасибо большое за наводку, проверю! Мог бы сам догадаться добавить параметр в функцию и проинспектировать, или arguments в консоле написатЬ, но как то мозги отключаются по началу знакомства с BPM :)

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

Хочется забить последний гвоздь. Мне хочется, чтобы значение вышесозданного виртуального поля обновлялось, когда добавляется / изменяется новый кандидат в деталь, или меняется значение Candidate Status одного из кандидатов детали. Сейчас, чтобы увидеть изменения, нужно перезайти на страницу редактирования детали.

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

Вам необходимо добавить обработчик ниже приведенного сообщения на вашей странице.

/**
* @message DetailChanged
* Срабатывает когда изменились данные на детали
*/
"DetailChanged": {
mode: this.Terrasoft.MessageMode.PTP,
direction: this.Terrasoft.MessageDirectionType.SUBSCRIBE
},

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

Здравствуйте.
Использую 5 версию системы.
Подскажите можно ли в ней настраивать права доступа на отображения "детали" аналитика в каждом отдельном разделе?

Нравится

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

Здравствуйте.
Из интерфейса такая возможность не предусмотрена. Разве, что программно.

А как это программно сделать?

Здравствуйте,
Данная реализация может выглядеть следующим образом:
В процессе схемы BaseModulePage есть ScriptTask CreateAnalyticsTab.
В данном скрипте, перед созданием закладки, идет проверка параметра HasAnalytics
Добавляем проверку
bool canSeeAnalytic = UserConnection.DBSecurityEngine.GetCanExecuteOperation("CanSeeAnalytic",
UserConnection.CurrentUser.Id);
if (HasAnalytics && canSeeAnalytic) {
...
}
Далее, создаем Операцию в разделе администрирования и раздаем на нее права.

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

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

Пытаюсь использовать поле IsDefault для собственного раздела "Размещения" (Store). Это поле должно принимать значение true только для одной записи из таблицы Store. Естественно возникает вопрос где такую функциональность можно реализовать.
На вскидку у меня 3-и варианта:
1. Триггеры на уровне ms sql server
2. События объекта
3. События карточки редактирования

1 пункт мне не нравится :). 3 пункт на мой взгляд не реализуем.
Остается п. 2.
При реализации функциональности у меня возникло множество проблемы :).

1. Не очень хорошо понимаю как работать с результатами коллекции GetEntityCollection(UserConnection). В частности непонятно как получить значение любого поля из каждого экземпляра в коллекции. В отладчике вот такой вариант почему-то в столбце "Value" выдает ошибку:

entitys[0].GetTypedColumnValueGuid>("Id")

2. Каким образом я могу сделать Update для конкретной записи с фильтром по полю Id?
Заодно хотелось бы увидеть примеры использования и операций Insert и Delete (на будущее).

3. Есть ощущение, что я создам бесконечный цикл, если в объекте для события обновления записи выполню код по обновлению записи для этого же объекта. Как быть в этом случае?

4. Работая с отладчиком серверного кода я не вижу методы объектов. С чем это связано и как можно их отобразить в том объеме в котором они есть в sdk.

5. Где я могу найти класс Entity? Судя по sdk GetEntity() возвращает объект именно этого типа/класс. В sdk такого класса в Terrasoft.Core.Entitys нету (по крайней мере в версии 7.5.0).

6. У меня что-то с браузером? Я использую хром и в ветке "Библиотека классов ядра платформы" у меня постоянно какие-то зависания при раскрытии структуры. Кроме этого, я не понимаю как пользоваться поиском. Результаты его запросов всегда выполняют редирект не на конкретную страничку, а на главную страничку sdk. Так же заметил 2-а неприятных эффекта. Во-первых, при раскрытии структуры sdk размер левой части не подбирается под содержим, что в отдельных случаях не позволяет видеть полное название класс, перечисления и прочих полезных "штучек". Во-вторых контент "прыгает" в буквальном смысле и при раскрытии структуры и очень часто возникает ситуация когда нужно мотать вверх и вниз для того чтобы определить свое местоположение.

Нравится

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

Есть другое решение - создать системный параметр (System settings) DefaultStore, в который выбирать тот самый Store по умолчанию. Тогда не нужны проверки

Владимир, спасибо!

Очень помогли. Действительно перенос в системную переменную очень все упрощает.

Тем не менее все 6 вопросов актуальны.

1. После выполнения метода entitySchemaQuery.GetEntityCollection(UserConnection) формируется запрос к БД, возвращается коллекция объектов, которая содержит только те колонки, которые были заданы для entitySchemaQuery с помощью метода AddColumn(). Пример: entitySchemaQuery.AddColumn(entitySchema.PrimaryColumn.Name). Данный пример показывает, как добавить в запрос первичную колонку для entitySchema. Эквивалент - entitySchemaQuery.AddColumn("Id"), но запись entitySchema.PrimaryColumn.Name - корректней.

2.
Чтобы добавить запись, необходимо:
2.1 Получить менеджер

var entitySchemaManager = UserConnection.EntitySchemaManager;

2.2 Получить схему объекта

var entitySchema = entitySchemaManager.GetInstanceByName("Contact");

или

Guid entitySchemaUId = "0bff4bc9-1a80-42f8-aa43-628a031dc6c0";
var entitySchema = entitySchemaManager.GetInstanceByUId(entitySchemaUId);

2.3 Создать экзмепляр объекта

var entity = entitySchema.CreateEntity(UserConnection);

2.4 Заполнить колонки по умолчанию

entity.SetDefColumnValues();

2.5 Заполнить необходимые колонки (обычно это первичная колонка и первичная для отображения колонка)

entity.SetColumnValue(entitySchema.PrimaryColumn.Name, Guid.NewGuid());
entity.SetColumnValue(entitySchema.PrimaryDisplayColumn.Name, "New entity");

2.6 Вызвать сохранение записи

entity.Save();

Чтобы удалить запись, необходимо:
- Вызвать метод Delete()

entity.Delete();

Чтобы изменить запись, необходимо:
- Вызвать метод FetchFromDB()
Guid entityId = "0bff4bc9-1a80-42f8-aa43-628a031dc6c0";
entity.FetchFromDB(entityId);
- Если метод вернет true, то запись присутствует в БД и в entity запишиться вся информация из записи.

3. В таком случае нужно выполнить обновление только необходимых полей объекта.

4. Если Вы имеете ввиду отладчик в Visual Studio и серверный код в конфигурации, то причина в том, что такой серверный код является автогенерируемый и не входит не в один проект.

5. SDK находится в процессе разработки. Спасибо, что обратили внимание на отсутствия класса Entity. Он будет добавлен в ближайшее время.

6. SDK находится в процессе разработки и постоянно развивается поэтому могут возникнут сложности при работе.

Немного добавлю

"Березкин Геннадий" написал:В частности непонятно как получить значение любого поля из каждого экземпляра в коллекции.

  EntitySchema schema = UserConnection.EntitySchemaManager.GetInstanceByName("Contact");
    EntitySchemaQuery esq = new EntitySchemaQuery(schema);
    esq.AddAllSchemaColumns();
//Фильтр для таблицы
    esq.Filters.Add(esq.CreateFilterWithParameters(FilterComparisonType.Equal, "Name", "FilterName"));
    EntityCollection entities = esq.GetEntityCollection(UserConnection);
//перебираем все записи коллекции, полученной в запросе
    foreach (Entity entity in entities)
    {
        //установить значение
        entity.SetColumnValue("Name", "NewName");
        //получить значение колонки Name
        entity.GetTypedColumnValue<string>("Name");
        //Сохраняем, при необходимости
        entity.Save();
    }

Спасибо за помощь.

Я не совсем понял с

entity.FetchFromDB(entityId);

Save() и Delete() помечают, что запись подлежат либо обновлению, либо удалению соответственно? FetchFromDB(entityId) - это запуск соответствующего запроса?
И еще вопрос. Как в entitys добавить запись?

entity.FetchFromDB(entityId) Возвращает true, если запись с указанным Id найдена в БД. В entity приходит вся информация из записи БД. Если пришло false, значит запись с таким Id не найдена.

Save() - сразу сохраняет entity в БД.
Delete() - сразу удаляет entity из БД.

В общем случае запись добавляется по указанной EntitySchema. Чтобы добавить запись достаточно получить EntitySchema этого объекта. Затем необходимо создать entity.
var entity = entitySchema.CreateEntity(UserConnection);
Для entity вызвать метод Save.
entity.Save();

Если необходимо просто добавить экземпляр в коллекцию, то можно воспользоваться методом AddLast для entityCollection. В него передать entity

Спасибо за помощь. Разобрался!

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