В какой-то момент (пока не получилось отследить после каких изменений) перестал открываться мастер разделов для редактирования страницы. Висит вечная загрузка, в консоли выдаётся ошибка:
TypeError: rootItem is undefined 1 ViewModelSchemaValidationMixin.js:52:1
И дальше перечисление стека вызовов.
Подскажите, пожалуйста, если кто-то сталкивался с подобным - что это и в какую сторону смотреть, чтобы исправить?
Как правило, данная ошибка возникает по причине того, что мастер разделов не может разобрать иерархию и построить страницу редактирования. Если у Вас в системе присутствуют пользовательские пакеты, то необходимо обратить внимание на корректность зависимостей пакетов.
Также, необходимо проверить какой пакет указан в системной настройке "Текущий пакет".
Вероятно, что там указан пакет, который имеет не правильную зависимость.
Добрый день. У меня такая проблема: по БП пользователю ставиться задача, но ее может закрыть любой другой пользователь, а нужно, чтобы только ответственный мог ее закрыть. В правах доступа настроено следующем образом: Раздел Активность: администрирование по записям(права на изменение) - указано кто создает и кому дается право. Если зайти в раздел "Активности" и там начать закрывать задачу, то Система не даст это сделать, а если это делать через объект "Продажа" через БП, то все возможно. Может кто-то подскажет как решить данную проблему? Я не программист, поэтому много не понимаю, но решить как-то хочу проблему. Заранее спасибо!
По ходу БП ставит задачи, которые отражаются в Продаже. При наведении мышкой на задачу, появляется кнопка "Завершить", соответственно нажав на нее и выбрав один из вариантов завершения задачи ( в зависимости от категории задачи) - именно так и закрывается активность.
Смотрите в сторону настройки прав доступа - необходимо настроить администрирование по записям для объекта "Активность".
Решением будет включение администрирования (по умолчанию оно включено) и удаление всех строк из настроек по умолчанию для изменения - это будет значит, что менять задачу может только автор/ответственный.
"Сорокоумова Татьяна" написал:но ее может закрыть любой другой пользователь, а нужно, чтобы только ответственный мог ее закрыть.
Если закрытие происходит в рамках бизнесс-логики только через UI
(графический интерфейс, т.е. не бывает/пердусмотрено, так что закрывается например в другом БП)
То задачу можно решить на уровне JS-логики карточки Активности (Задачи).
Для того чтобы не "мучаться" определяя входит ли пользователь в необходимую функц.роль или организационный юнит и т.д. можете использовать разработанный мной миксин
Добрый день. Занимаюсь реализацией интеграции с BPM'Online 7.10 через DataService.
Возникла проблема которая выражается в том, что никак не удается создать запись с заполненной датой и временем или обновить поле дату и время в имеющейся записи.
Код примерно такой
var list =new Dictionarystring, ColumnExpression>(); foreach(....) {
list.Add(c1.Attribute.FieldName, new ColumnExpression() {
ExpressionType = EntitySchemaQueryExpressionType.Parameter,
Parameter =new Parameter() {
Value = value,
DataValueType = c1.Attribute.DataType } }); }
1) Подскажите, пожалуйста, как работать с полями дата и время?
2) Есть ли способ чтобы DataService выдавал более UserFrendly ошибку чем "Удаленный сервер возвратил ошибку: (500) Внутренняя ошибка сервера."? Т.к. выяснение того, что ошибка связана именно с типом DataTime заняло много времени, причем формат запроса корректен и "вроде бы" DataService не должен ругаться на него 500 ошибкой...
"Габбазов Искандер Рустемович" написал:На сколько я понимаю ваш пример это кусок "правильного" JSON'а.
нет, JSON тут совсем не при чем.
Это текстовое представление формата TimeStamp,
Оператор @ - интерпритирует строку без управляющих последовательностей, ну и двойные кавычки "схлопываются" по две в одну, на выходе имеем
Илья, нет, формат все же верный, то есть тайм-стамп в экранированных кавычках, и в кавычках. Я не имел ввиду, что это должен быть валидный json, это просто формат, в виде которого по определенным архтиктурным причинам передается дата в приложении.
Те кто все-таки забрел в эту тему, достаточно передать DateTime обернутым в экранированные кавычки.
var date = DateTime.Now;
insertQuery.ColumnValues.Items.Add("....", new ColumnExpression(){
ExpressionType = EntitySchemaQueryExpressionType.Parameter,
Parameter =new Parameter(){
Value ="\""+date+""\""""
При эскалации обращения открывается отдельная страница, её схема EscalationEditPage, объект - то же самый Case.
При этом доступны не все колонки объекта Case. В частности, не доступна колонка Id, а также пользовательские колонки (при попытке вывести значение в консоль после onEntityInitialized выводится undefined).
Как получить данные пользовательских колонок?
Задача в общем: в зависимости от значения одной из пользовательских колонок автоматически проставлять группу ответственных.
Зачастую требуется корректировать набор фильтров для справочных полей карточки как говорится "на лету", в зависимости от тех или иных условий/событий/действий пользователя
Собственно к коллекции фильтров можно подступиться через this.columns.{ИмяКолонки}.lookupListConfig
Но основной вопрос - корректно ли манипулировать этой коллекцией через непосредственный доступ - вот таким образом, или разработчик предусмотрел другую методику корректировки набора фильтров для справочных полей ?
Специализированные методы возможно,
Возможно состояние this.columns.{ИмяКолонки}.lookupListConfig в жизненном цикле контекста может быть сброшено - и необходимо иметь вспомогательные сущности, для повторного его построения. (Т.к. изначально тот-же lookupListConfig задается например при описании атрибута одноименного колонке, но в конечном итоге среди полей получаемых из модели по имени поля - не содержится) ?
Не встречал такого подхода работы в рамках базовых конфигураций.
Если вам необходимо анализировать внешние асинхронные условия пере формированием той или иной логики фильтр метода, то рекомендуем вам:
1. Сделать подписку на изменения всех колонок, которые могут влиять на логику формирования фильтрации колонки в lookupListConfig;
2. При изменении данных колонок кешировать асинхронный результат, который должен повлиять на логику фильтрации в атрибутах;
3. В фильтр методе lookupListConfig-а: анализировать кешированные атрибуты, и формировать согласно вашей логики нужный вам esq, блоками if else.
Открываем карточку Лида в Combined режиме,
Устанавливаем точки останова внутри метода depMethod в карточке и секции соответственно,
Открываем консоль, поехали
document.LeadSectionScope.set("firstAttr",true); //НИЧЕГО НЕ ПРОИСХОДИТ ! //Исполнение в метод depMethod секции не переходит.
document.LeadPageScope.set("firstAttr",true); //ОТЛАДЧИК ВСТАЕТ ВНУТРИ МЕТОДА depMethod карточки !
И как это понимать ?
Двойные стандарты для функциональности атрибутов в отношении зависимостей ?
Зависимости атрибутов представляют собой зависимости между колонками конкретной записи. При изменении значения колонки в карточке редактирования срабатывает зависимость.
Раздел в свою очередь работает с коллекцией записей. Нет конкретной колонки, изменения которой должно вызвать срабатываение зависимости.
Ну в рамках карточки мы не обязательно привязаны к колонкам,
мы можем просто создать атрибуты (свои, пользовательские не связанные с колонками - что собственно и показано в примере) и на их изменение - зависимости выраженные в вызове методов текущего контекста.
Почему то же самое с атрибутами не доступно в рамках карточки секции ?
Возможно есть иной способ привязки логики к изменению атрибута ?
Например биндинг value на метод:
Что в таком случае будет происходить -
При установке значения в аттрибут - каждый раз будет вызываться забинженный метод - в аргументы которого будет поступать устанавливаемое значение, а его return значение - будет установлено в качестве текущего value ?
Потому что applyDependencies из BusinessRulesApplierV2 работает только для BasePageV2 и её наследников. По сути это сахар над обычным бекбоновским this.on("change: ...
так что в секции можете в ините просто написать:
this.on("change:MyAttrColumn", this.myMethod, this);
где MyAttrColumn имя атрибута\колонки для которой будет тригерится по изменению метод myMethod описанный в блоке методов.
"Максим Шевченко" написал:По сути это сахар над обычным бекбоновским this.on("change
ну вот именно это я и подразумевал :)
Спасибо что уточнили.
А можно подключить applyDependencies из BusinessRulesApplierV2 в качестве зависимостей схемы секции - в таком случае тоже будет работать ?
Просто интересует однообразие.
Прям её не подключить, все же в базовой реализации этого метода анализируется атрибут IsEntityInitialized которого нет в секции. Но, вы можете попробовать написать свой миксин который будет пробегать по атрибутам, искать у них свойство "зависимости" и делать в этом форе this.on на указанные методы.
Все красиво, и работает корректно. Открывается окно выбора ().
Но предположим, что мне необходимо добавить дополнительную кнопку, либо изменить страницу LookupPage.
Что я делаю - наследую LookupPage.
В итоге не смог разобраться, а официальный саппорт отклоняет подобные запросы по SLA, хотя, здесь явно ошибка в стандартной конфигурации. Вобщем - ничего нового...
Предлагаю поступить немного иначе.
Не наследоваться через зависимости (в карточку), А унаследоваться от схемы справочника (LookupPageV2) (создать свою схему наследника, не замещающую),
там в схеме "играться" с "дифами" и замещенеим, так как мы это делаем при классическом замещении клиентских схем.
И уже своего наследника подключать в зависимости.
(Мне кажется это более каноничный подход)
PS:У меня в ближайшем будущем есть аналогичная задача - скастомить окно на базе типового окна справочника, со своими кнопками и т.д.
Я планировал действовать так.
Прошу Вас отписаться если предложенный мною вариант окажется рабочим, чтобы душа была спокойна :)
Вообщем Вы были изначально на правильном пути, но упустили несколько моментов, по сути Ваши начинания "в купе с моей идеей" и есть итоговое решение, т.к. наследование на уровне схем все равно оказалось необходимым условием (правда через зависимости, а не механизм наследования)
Разобраться помогла схема "MultiLookupModule" входящая в базовую поставку
1) Вы реализуете модуль, зависимости которого необходимо указать не только в исходном коде но и в меню структуры
2) Сам модуль не должен быть чьим либо наследником, всё необходимое идет в его зависимости, в т.ч. и LookupPage который мы идеологически наследуем, но архитектурно через наследование в схемах - это почему-то не работает (Если у казать "Выбор из справочника" как родителя - получаем очень много разного рода ошибок при вызове).
3) Далее вот вообщем-то пример самого кода
перчим солим, замещаем, дополняем по вкусу и
ну и вызываем его через LookupUtilites или же есть более простой способ через this.openLookup (что само собой является оберткой над вышеуказанным и "сахаром" по сути).
Уже в таком виде - Ваш "отнаследованный" lookupPage можно вызвать следующим образом
var config ={
entitySchemaName:"Lead",
columns:["Id"],
multiSelect:false,
actionsButtonVisible:false,
lookupPageName:"MyCustomLookupPage"};this.openLookup(config, function(result){console.log(result)})
Вот.
PS:
Были попытки отнаследовать через механизм наследования схем, насколько хватило фантазии и времени - но все безрезультатны.
Хотя очень хотелось бы вот так вот взять и в diff все свои вынести изменения.
Тем не менее описанный способ, предположительно - позволяет делать то что нужно...
Как оказалось - решение было близко)
Спасибо большое, что поделились окончательной реализацией! Я то уже переключился на более приоритетные задачи, так как посчитал эту затею бесперспективной.
На самом деле - решение не окончательное... бой продолжается :)
Есть прям несколько моментов - которые весьма и весьма удивляют... например что кнопки по факту "захардкожены" в LookupPageViewGenerator и собственно не зависят от каких либо конфигурационных параметров вызова этого самого генератора, что приводит к необходимости наследовать и его, с переопределением некоторых функций, в свою очередь "копия" генератора подобным образом не работает полностью или частично, т.к. используя наследника в качестве зависимости - получаем ряд ошибок про "sunbox of undefined" из core.js (что практически убивает надежду "по быстрому разобраться") при попытке выбора записей из такого справочника и т.д.
Кстати по какой-то причине не удается подключить к карточке страницы LookupUtilities
В конечном итоге в контексте карточки нет объекта "LookupUtilities"
Кастом окна справочника через наследование его базового Ext-класса возможен, и даже, как оказалось не сложен.
В основном наверное в таком случае требуется юзкейз по управлению панелью с кнопками такого окна - переименование, сокрытие, добавление своих, стилизация и т.д. HowTo по порядку
1) Создаем пользовательский модуль например "MyCustomLookupWindow" [color=red]Внимание![/color]
Модуль не должен являться чьим либо наследником
В структуре метаданных модуля, дублируем зависимости, которые объявим в самом листинге
схемы при создании необходимо выбирать по заголовкам (давняя боль), облегчаю задачу: LookupPage - Выбор из справочника ( NUI ) LookupPageCSS - Стили модуля справочника ( NUI ) LookupPageViewGenerator, LookupUtilities, MaskHelper - имеют такие же заголовки как и их имена
2) Для вызова lookup-наследника используйте в вашей логике this.openLookup метод (присутствует в контексте любой карточки/схемы/детали), если же случилось так что его нет - то см.выше - надо будет подключить LookupUtilitesV2 в зависимости схемы, и использовать метод Open предоставляемый объектом зависимости. (Примеры использования см.в исходниках и в начале темы)
//Подготовим минимальный конфигурационный объект
var config ={//Для примера справочник "нацелен" на раздел "Лиды"
entitySchemaName:"Lead",
columns:["Id"],
multiSelect:true,
actionsButtonVisible:true,
//Вот сюда передаем имя модуля-наследника
lookupPageName:"MyCustomLookupWindow"};//пример вызоваthis..openLookup(//наш конфигурационный объект
config,
//callback принимающий результат/результаты выбора в виде коллекции
function(selected){console.log(selected)},
//контекст для коллбэкаthis)
3) Для того чтобы закастомить кнопки необходимо в нашем наследнике переопределить метод renderLookupView
в который произвести инъекцию своей логики
define("MyCustomLookupWindow", ["LookupPage", "LookupPageViewGenerator", "LookupUtilities", "css!LookupPageCSS"],
function(LookupPage, LookupPageViewGenerator){return Ext.define("Terrasoft.configuration.MyCustomLookupWindow", {
alternateClassName:"Terrasoft.MyCustomLookupWindow",
extend:"Terrasoft.LookupPage",
gridWrapClasses:["km-gms-lookup-control"],
//Переопределяем метод в котором мы можем управлять сформированной конфигурацией до рендеринга.
renderLookupView: function(schema, profile){
var config =this.getLookupConfig(schema, profile);
var topPanelConfig = LookupPageViewGenerator.generateFixed(config);//----------------------- инъекция логики (начало) ----------------------//переменная для хранения ссылки на массив объектов-конфигурайий кнопок
var buttonsConfig;//Получаем ссылку на аттрибут-массив конфигурационных объектов-кнопок//Используем Underscore.some с возможностью прерывания переборы по возврату от предиката "true"
_.some(topPanelConfig.items, function(target){//выделяем объект группы кнопок (Wrapper) по id контейнераif(target.id==="selectionControlsContainerLookupPage"){//в нем ищем подчиненные объекты являющиеся массивом
_.some(target, function(target){//согласно структуры конфигурационного объекта панели//"чистым" массивом является только объект с конфигами кнопокif(Array.isArray(target)){//сохраняем ссылку на него в переменной для дальнейшего использования
buttonsConfig = target;//Прерываем переборreturntrue;}});//Прерываем переборreturntrue;}});//Создаем новую кнопку, клонируя любую, первой как правило идет кнопка "Отмена"
var newButton = Terrasoft.deepClone(buttonsConfig[0]);//Используем Underscore.extend для объединения склонированного объекта//с анонимным объектом-разницы в котором опишем необходимые изменения
_.extend(
newButton,
{
caption:"Добавленная кнопка",
click:{
bindTo:"AddTender"},
//Внимаение! свойство "tag" должено быть уникальным для каждой кнопки
tag:"AddTender",
//меняем стиль
style:"green"});//Добавляем конфигурационный объект кнрпки в массив конф.объектов
buttonsConfig.push(newButton);//Поиск конфигурационного объекта кнопки "Выбрать" в искомом массиве по caption
_.some(buttonsConfig, function(target){if(target.caption==="Выбрать"){//В найденном объекте меняем значение аттрибута caption на "Создать тендеры".
target.caption="Переименованная кнопка";returntrue;}});//Поиск конфигурационного объекта кнопки "Добавить" в искомом массиве по caption
_.some(buttonsConfig, function(target){if(target.caption==="Действия"){//В найденном объекте меняем значение аттрибута visible на "false"//тем самым скрывая кнопку-меню
target.visible=false;returntrue;}});//----------------------- инъекция логики (конец) ----------------------this.renderLookupControls(config, topPanelConfig);}});});
Собственно вопрос: Существует ли способ определить, есть ли подписчики у события, до его публикации ?
Чтобы разворачивать свою логику по принципу:
"Если есть подписчики - публикуем сообщение, ловим ответы, передаем коллбеки и т.д., Если нет - поехали по другому пути"
Прошу прощения за поднятую панику :)
Это собственно "костыль" собственного производства...
Достался от предшественников, они разблокировали схемы типовой поставки и вносили в них изменения (зачем-то :) )