Добрый день.

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

Пытаюсь реализовать функционал расчета сроков в обращении используя пример https://academy.terrasoft.ru/documents/technic-sdk/7-12/dobavlenie-novogo-pravila-rascheta-srokov-v-obrashchenii

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

Получить Категорию обращения я попытался таким способом:

public class UsrMKtermsStrategy: BaseTermStrategy<CaseTermInterval, CaseTermStates>
    {
        // Класс-контейнер для хранения данных, полученных из точки входа.
        protected class StrategyData
        {
            public Guid ServiceItemId {
                get;
                set;
            }
            public Guid PriorityId {
                get;
                set;
            }
            public Guid CategoryId {
                get;
                set;
            }
 
        }
        // Поле, хранящее данные, полученные из точки входа.
        protected StrategyData _strategyData;
        // Параметризированный конструктор, необходимый для корректной
        // инициализации классом-селектором.
        public UsrMKtermsStrategy(UserConnection userConnection, Dictionary<string, object> args)
            : base(userConnection) {
            _strategyData = args.ToObject<StrategyData>();
        }



В результате при добавлении фильтра в esq.CreateFilterWithParameters(FilterComparisonType.Equal, "UsrCategory", _strategyData.CategoryId); у меня передается пустой GUID.

Я так понимаю, причина в том, что не передается аргумент CategoryId

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

Нравится

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

Здравствуйте, Антон!

Убедитесь, что вы добавили Категорию в созданный объект (по п.1 из инструкции).

Так как в инструкции указано, что поля, по которым будет выполнятся фильтрация, должны быть добавлены в объект (скриншоты из инструкции - http://prntscr.com/jgj5av, http://prntscr.com/jgj5i5).

Спасибо. Ссылки на Категорию в созданный объект добавил первым делом.

Удалось разобраться самостоятельно:

1) В клиентскую схему схему CasePage добавил пересоздание метода "prepareCaseTermCalculatorConditions" добавив "this.addToConditions(conditions, "Category");

prepareCaseTermCalculatorConditions: function() {
	var conditions = [];
	this.addToConditions(conditions, "ServiceItem");
	this.addToConditions(conditions, "ServicePact");
	this.addToConditions(conditions, "Priority");
	this.addToConditions(conditions, "Id");
	this.addToConditions(conditions, "SolutionOverdue");
	this.addToConditions(conditions, "Category");
	return conditions;
},

2) Исправил ошибки в исходном коде статьи, а именно:

a) Фильтры в EntitySchemaQuery создавались, но не добавлялись. Т.е. небыло esq.Filters.Add(esqFirstFilter); 

b) Не использовался «using Terrasoft.Configuration.SLMExtensions;» и перевод результата в минуты («result.ResponseTerm = term.ConvertToMinutes();»). В итоге сильнос сбивалась логика калькуляции паузы при переоткрытии обращения.



В итоге мой серверный исходный код получился следующий:

 

namespace Terrasoft.Configuration
{
	using System;
	using System.Collections.Generic;
	using Terrasoft.Common;
	using Terrasoft.Configuration.Calendars;
	using Terrasoft.Configuration.SLMExtensions;
	using Terrasoft.Core;
	using Terrasoft.Core.Entities;
	using CalendarsTimeUnit = Calendars.TimeUnit;
	using SystemSettings = Terrasoft.Core.Configuration.SysSettings;
	//
	using System.Collections.ObjectModel;
	using System.Data;
	using Terrasoft.Core.DB;
	public class UsrMKtermsStrategy: BaseTermStrategy&lt;CaseTermInterval, CaseTermStates&gt;
	{
		// Класс-контейнер для хранения данных, полученных из точки входа.
		protected class StrategyData
		{
			public Guid ServiceItemId {
				get;
				set;
			}
			public Guid CaseId {
				get;
				set;
			}
			public Guid PriorityId {
				get;
				set;
			}
			public Guid CategoryId {
				get;
				set;
			}
 
		}
		// Поле, хранящее данные, полученные из точки входа.
		protected StrategyData _strategyData;
		// Параметризированный конструктор, необходимый для корректной 
		// инициализации классом-селектором.
		public UsrMKtermsStrategy(UserConnection userConnection, Dictionary&lt;string, object&gt; args)
			: base(userConnection) {
			_strategyData = args.ToObject&lt;StrategyData&gt;();
		}
		// Метод, который получает данные и возвращает их в экземпляре класса CaseTermInterval. 
		public override CaseTermInterval GetTermInterval(CaseTermStates mask) {
			var result = new CaseTermInterval();
			// Создание EntitySchemaQuery запроса.
			var esq = new EntitySchemaQuery(UserConnection.EntitySchemaManager, "UsrTimeCalc");
			// Добавление колонок в запрос.
			string reactionTimeUnitColumnName = esq.AddColumn("UsrReactionTimeUnit.Code").Name;
			string reactionTimeValueColumnName = esq.AddColumn("UsrReactionTimeValue").Name;
			string solutionTimeUnitColumnName = esq.AddColumn("UsrSolutionTimeUnit.Code").Name;
			string solutionTimeValueColumnName = esq.AddColumn("UsrSolutionTimeValue").Name;
			string calendarColumnName = esq.AddColumn("UsrCalendarId.Id").Name;
			// Добавление фильтров в запрос.
 
			var esqFirstFilter = esq.CreateFilterWithParameters(FilterComparisonType.Equal, "UsrPriority", _strategyData.PriorityId);
			//var esqSecondFilter = esq.CreateFilterWithParameters(FilterComparisonType.Equal, "UsrCategory", categoryGuid);
			var esqSecondFilter = esq.CreateFilterWithParameters(FilterComparisonType.Equal, "UsrCategory", _strategyData.CategoryId);
 
			esq.Filters.Add(esqFirstFilter);
			esq.Filters.Add(esqSecondFilter);
 
			// Выполнение и обработка результатов запроса.
			EntityCollection entityCollection = esq.GetEntityCollection(UserConnection);
			if (entityCollection.IsNotEmpty()) {
				// Добавление к возвращаемому значению времени реакции.
				if (!mask.HasFlag(CaseTermStates.ContainsResponse)) {
					var term  = new TimeTerm {
						Type = entityCollection[0].GetTypedColumnValue&lt;CalendarsTimeUnit&gt;(reactionTimeUnitColumnName),
						Value = entityCollection[0].GetTypedColumnValue&lt;int&gt;(reactionTimeValueColumnName),
						CalendarId = entityCollection[0].GetTypedColumnValue&lt;Guid&gt;(calendarColumnName)
					};
					result.ResponseTerm = term.ConvertToMinutes();
				}
				// Добавление к возвращаемому значению времени разрешения.
				if (!mask.HasFlag(CaseTermStates.ContainsResolve)) {
					var term  = new TimeTerm {
						Type = entityCollection[0].GetTypedColumnValue&lt;CalendarsTimeUnit&gt;(solutionTimeUnitColumnName),
						Value = entityCollection[0].GetTypedColumnValue&lt;int&gt;(solutionTimeValueColumnName),
						CalendarId = entityCollection[0].GetTypedColumnValue&lt;Guid&gt;(calendarColumnName)
					};
					result.ResolveTerm = term.ConvertToMinutes();
				}
			}
			return result;
		}
	}
}

 

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

Посл создания очереди по обращению(версия 7.12) и нажатия на действие Выполнить наполнение очереди выходит ошибка .

Процесс обработки очереди : БП Обработка обращений из очереди в Едином окне

'OperatorSingleWindow.UpdateQueuesJobTrigger' for 'OperatorSingleWindow.UpdateQueuesJob' job: Invalid column name 'TIME_ZONE_ID'.

где установить часовой пояс?

Нравится

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

Попробуйте выполнить этот скрипт:



IF NOT EXISTS (

    SELECT 1 FROM sys.columns

    WHERE [Name] = N'TIME_ZONE_ID'

    AND [Object_ID] = Object_ID(N'[dbo].[QRTZ_SIMPROP_TRIGGERS]')

)

BEGIN

    ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] ADD [TIME_ZONE_ID] [NVARCHAR] (80);

END;

Евгений Манько,

Скрипт выполнил. Помогло.

Но теперь при переходе в Единое окно не отображаются колонки, но записи есть. Скрин.

В консоли ошибка:

file: undefined

 line: undefined

 column: undefined

 message: config.items.filter is not a function 

 date: Thu May 10 2018 08:44:29 GMT+0300 (RTZ 2 (зима))

 moduleId: OperatorSingleWindowModule_SingleWindow_OperatorQueuesModule_QueueModule

 moduleName: QueueModule

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

VladKapitanchyk,

Это не пользовательское замещение. 

Проблема во ViewgeneratorV2

getGridRowColumns: function(config, rowIndex) {

                return config.items.filter(function(column) {

                    var position = column.position;

                    return (position.row === rowIndex);

                });

            },

доработок с очередями вообще не делали еще. в какую сторону капать?

Евгений Волоцкой,

Посмотрите, что приходит в config. Сравните с параметрами которые приходят в других рабочих гридах. Сравните callStack.

VladKapitanchyk,

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

Евгений Волоцкой,

Если смотреть по callstack(метод reloadGridColumnsConfig) то можно увидеть, что конфиг подтягивается из профайла. Попробуйте перенастроить отображение колонок.

VladKapitanchyk,

я писал в пред. сообщении, что при настройке колонок аналогичная ошибка. как настроить sysprofiledata для единого окна?

В общем случае надо дебажить. Но был случай когда подтягивались не правильные настройки с профиля, что приводило к похожему результату. В профиле в настройках в «items» хранился объект «{…}», а должен список «[…]»

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

Добрый день. 

Переопределил UserCasesListModule для главной страницы портал.

Добавил в него дополнительные поля.

Также необходимо отфильтровать реестр по контакту текущего пользователя портала.

Для этого модифицирую "filterData" в moduleConfig 

{\"className\":\"Terrasoft.FilterGroup\",\"items\":{"
+ this.getCreatedOnFilter()
+ "},\"logicalOperation\":0," 
+ "\"isEnabled\":true,\"filterType\":6,\"rootSchemaName\":\"Case\",\"key\":\"\"}"

Сам метод getCreatedOnFilter (возвращает сериализованный фильтр):

getCreatedOnFilter: function(){
   var filterGroup = Terrasoft.createFilterGroup();
   filterGroup.add("CreatedOnFilter", 
        Terrasoft.createColumnFilterWithParameter(Terrasoft.ComparisonType.EQUAL, "CreatedBy", 
        Terrasoft.SysValue.CURRENT_USER_CONTACT.value)
   );
   var fgSerializeInfo = filterGroup.getDefSerializationInfo();
   fgSerializeInfo.serializeFilterManagerInfo = true;
   var serializeFilter = filterGroup.serialize(fgSerializeInfo); 
   return Ext.encode(serializeFilter);
}

В итоге получаю ошибку {message: "Ошибка декодирования значения из JSON-строки"}

Нравится

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

Добрый день

Я вижу, что в вашей строке ошибка

....key\":\"\"}"

если десериализировать эту строку, то объект получиться таким:

{
   ...
   key:
}

что некорректно. Нужно либо убрать поле key, либо дать ему значение

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

Добрый день!

мне нужно получить названия всех полей определенной сущности. сделала вот такой запрос

select 

(case  when [key] like 'columns%' then replace(replace([key],'Columns.',''),'.Caption','') else '' end),

Value

 from SysLocalizableValue

where SysSchemaId in (select id from SysSchema where Name='ContactAddress')

and ([key] Like 'Columns%')

and SysCultureId='1A778E3F-0A8E-E111-84A3-00155D054C03'

order by (case when [key] like 'columns%' then replace(replace([key],'Columns.',''),'.Caption','') else '' end)

но столкнулась с проблемой наследования. данный запрос мне возвращает только 1 строку - Contact, так как остальные поля наследуются из Address.

как можно получить итоговый список полей сущности с учетом всех изменений из разных пакетов?

 

Нравится

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

Можно посмотреть поля в таблице базы через системные таблицы:

SELECT  t.name AS [Table Name], c.name AS [Column Name], value AS [Extended Property]
FROM sys.extended_properties AS ep
INNER JOIN sys.tables AS t ON ep.major_id = t.object_id 
INNER JOIN sys.columns AS c ON ep.major_id = c.object_id AND ep.minor_id = c.column_id
WHERE class = 1 and t.name = 'ContactAddress'
AND CAST(value AS VARCHAR) LIKE 'ru-RU|%'

 

 

Зверев Александр,

большое спасибо!

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

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

Подскажите, пожалуйста, как мы можем отключить возможность пользователям системы выделять и копировать значения из полей детали на странице редактирования записи? 

Стоит задача: предотвратить возможность пользователей копировать значения из полей детали "Средства связи" на странице Контакта. Кто-то занимался когда-нибудь подобной задачей?

Нравится

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

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

Заблокировать копирование средствами bpm'online вряд ли получиться. но вы можете использовать стандартные средства JavaScript, например:

  • событие oncopy разрешает выделение текста, но полностью зарпещает копирование (сам пробовал, не нашел способа реально скопировать текст, как по мне - один из самых простых и достаточно надежных способов). Единственный недостаток - на некоторых  браузерах может работать не так, как ожидается (в Edge, Chrome, Mozilla, Opera работает точно хорошо).
  • отмена события ​onmousedown​ (для IE - onselectstart) - можно поставить return false для этих событий, что запртетит выделение. Проблема с этим событием в том, что если начать выделение за пределами контейнера, для которого отменены эти событие, текст все-таки выделится.
  • CSS свойство xxx-user-select, где xxx - префиксы браузеров (например, -webkit-, -moz-, -ms-) запрещает выделение, но оно так и не было внесено в стандарты, хотя и поддерживается большинством браузеров (кроме IE9-)

Можете попробовать один из способов или их комбинации.

Золотарев Артем Андреевич,

Артем, добрый день!

Подскажите, пожалуйста, по коду:

define("ContactCommunicationDetail", ["terrasoft"],
	function(Terrasoft) {
		return {
			entitySchemaName: "ContactCommunication",
			methods: {
				init: function() {
					this.callParent(arguments);
					document.body.oncopy = function() { return false; };
				}
			},
			diff: /**SCHEMA_DIFF*/[]/**SCHEMA_DIFF*/
		};
	});

Как видите, я вставил его на деталь, но копирование теперь не работает на всей странице контакта + на всех последующих страницах. Как мне сделать так, чтоб копирование не работало конкретно в полях детали?

В любом случае пользователь сможет нажать в браузере F12 и скопировать всё нужное из HTML-кода страницы.

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

Разве что заставить пользователей работать с системой в браузере на изолированном компьютере по  RDP без общего буфера обмена и расшаренных папок.

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

Добрый день!

есть ли возможность обратиться к BpmOnline из клиентской части стороннего приложения?

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

можно ли вызвать DataService из js стороннего приложения?

если можно, то где можно посмотреть примеры

если я правильно понимаю, то можно работать при помощи oData из js стороннего приложения, но примеров тоже не нашла. 

 

 

Нравится

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

Добрый день

Из клиентского приложения можно легко работать с DataService. Для этого нужно:

1. Авторизоваться в bpmonline

2. Отправлять данные

Здесь есть описание по авторизации https://academy.terrasoft.ua/documents/technic-sdk/7-12/integraciya-s-sistemoy-i-vneshniy-api

С клиентской стороны вы можете организовать "общение" c помощью ajax или XmlHttpRequest

Так как это будут кросс-доменные запросы, то вам нужно это учитывать

https://www.html5rocks.com/en/tutorials/cors/

https://stackoverflow.com/questions/298745/how-do-i-send-a-cross-domain-post-request-via-javascript

год назад писали, что это невозможно

https://community.terrasoft.ru/questions/cors-i-avtorizacia-iz-klientsk…

получается, теперь эта возможность доступна?

у вас нет примера подключения, пока возникают сложности с подключением

Коллеги, кто сталкивался с подобной проблемой?

В конфиг сайта добавил <httpProtocol>

      <customHeaders>

        <add name="X-Frame-Options" value="SAMEORIGIN" />

<add name="Access-Control-Allow-Origin" value="*" />

<add name="Access-Control-Allow-Methods" value="GET, PUT, POST, DELETE, OPTIONS" />

<add name="Access-Control-Allow-Credentials" value="true" />

<add name="Access-Control-Allow-Headers" value="X-PINGOTHER, Origin, Cache-Control, Content-Type, Authorization, X-Requested-With, Accept" />

      </customHeaders>

    </httpProtocol>

 

С помощью JS отправляю запрос

$.ajax({
   url: serviceUrl + 'ContactCollection',
   headers: {
      'Content-Type': 'application/json;odata=verbose',
      'Authorization': 'Basic ' + btoa('Юзер:Пароль')
   },
   dataType: "json",
   data:  JSON.stringify({"Name": "123"}),
   method: 'POST'
});

Возвращается 401 ошибка, права Юзер в права доступ на операции к OData предоставил, в объекте Contact к OData также дал. Запросы кросс-доменные. в чем проблема?

Евгений Волоцкой,

Евгений, если зайти под пользователем в систему, он может перейти в раздел контакты? 

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

Добрый день! 

Для работы с тикетной системой в нашей Компании (******) используются почтовые ящики - с них приходят заявки.  Были настроены следующие процессы:

1. Подключены почтовые ящики

2. Настроена система регистрации обращений из email

3. Настроены группы пользователей и доступы к почтовым ящикам

4 Настроены очереди и их наполнение из данных обращений (по принадлежности оператора группе)

5 Настроено единое окно в которое поступают обращения и из которого операторы работают с ними

 

Столкнулись со следующими проблемами:

1 актуализация вкладки "Единое окно" не происходит автоматически, по мере поступления новых обращений. При этом, все обращения поступают в раздел "Обращения" своевременно. 

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

 

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

 

Просим помочь.

Нравится

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

Добрый день!

отвечу на ваши вопросы по порядку:

1) Процесс наполнения очередей и Единого Окна запускается автоматически согласно времени указанному в системной настройке "Интервал обновления очередей единого окна" Рекомендую проверить данную настройку, 

Так же настройку - "Количество одновременно добавляемых записей в элементы очереди"

Так же если во время работы процесса наполнения очереди возникли ошибку Вы их увидите в "Журнале процессов". 

2) В очередь попадают записи раздела, которые соответствуют фильтру указанному при настройке/создании очереди. Таким образом, Вы можете перейти в раздел Обращения и построить в нем фильтр, который используется для очереди. В результате Вы увидите все записи, которые попадут в очередь. Если в этом случае появятся лишние записи, значить необходимо переделать фильтр.

Действительно помогло выставление параметров как описано в п.1. Спасибо

33SLONA пишет:

(******)

Ээээээээх. Вот угадайте, почему нельзя палить адрес сайта, когда не изменён стандартный доступ для Системного пользователя? Просто всю почту напоказ и в интернет....

Варфоломеев Данила,

Спасибо за наблюдательность. 

33SLONA,

Будьте внимательней. Рекомендую изменить стандартный пароль: 

https://academy.terrasoft.ru/documents/sales-enterprise/7-11/stranica-p…;

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

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

Подскажите, нигде не могу найти ответ. Есть страница для двухуровневого справочника (По аналогии со справочником "Результат активности по категориям" в интерфейсе 5.х). Из дизайнера страниц запускается, работает (добавляет, удаляет). Как привязать страницу к самому справочнику, чтобы она открывалась из раздела "Справочники"? 

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

Нравится

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

Здравствуйте, Лидия!

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

 

Вы можете посмотреть, как устроен справочник:

SELECT * FROM [Lookup] WHERE Name = 'Communication option types'

И увидеть у него в последней колонке SysLookupId ссылку на:

SELECT * FROM SysLookup WHERE Id = 'BE01987D-37FE-DF11-B154-001D60E938C6'

И по примеру создать свою, такую же запись в SysLookup, с ссылками как на страницу составного справочника, так страницу редактирования родительской записи.

 

Также aspx страницы не рекомендуются к использованию, если вам необходим справочник на старом интерфейсе, смотрите, как устроен 'Communication option types', но лучше придумать обходное решение на новом интерфейсе.

 

Ниже примерный алгоритм реализации:

 

1. создаете новый справочник, у Вас должна появиться запись в Lookup;

2. добавляете в старую таблицу SysLookup запись:

                -  в SysGridPageSchemaUid это Uid схемы из таблицы SysSchema;

                -  в SysEntitySchemaUid это Uid объекта, который выступает в качестве справочника;

                - в другие колонки заполняете советующими значениями;

3. в таблице Lookup нужного справочника в колонке SysLookupId заполняете Id из пункта 2.

 

Также подобная тема была на community - https://community.terrasoft.ru/questions/sloznyi-spravocnik-v-bpmonline…

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

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

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

Я например встречал класс Terrasoft.Router и также sandbox.publish() для навигации.

Может есть что почитать по этому поводу?

 

Нравится

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

Хм, а в контексте какой задачи появился такой вопрос?

У меня после закрытия преднастроенной страницы в процессе происходит возврат не туда, куда бы хотелось. Так как используется BasePageV2.onCloseCardButtonClick() и CustomProcessPageV2Utilities.acceptProcessElement().

Если используется только BasePageV2.onCloseCardButtonClick(), то поведение правильное.

Кисловский Михаил Андреевич,

Если еще более конкретно, то я бы хотел чтобы последовательно отработал такой код

1. this.acceptProcessElement();

2. this.sandbox.publish("PushHistoryState", {hash: "GoToDirection"})

 

но this.acceptProcessElement() переадресует меня в другое расположение и вторая строчка не отрабатывает.

Всем спасибо!

Подошло Terrasoft.Chain()

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