Вопрос

Помогите разобраться: имею список названий колонок схемы, нужно на уровне Entity их очистить, чтобы гарантировать, что они не будут видны не только в конкретной форме, но и нигде. Предположил, что можно пройтись по GetColumnValueNames(), но для колонок-справочников имена отличаются, типа <имя>Id (Я так понимаю, как в БД), <имя>Value (lookup value из связанной таблицы). Подскажите, как гарантировано и эффективно очистить колонки по имени? Насколько быстрым будет вариант через Entity - Schema - EntitySchemaColumn? Может ли помочь знание не только ColumnName, но и UId?

У меня такой же вопрос

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

Нужно просто настроить запрещающий доступ по колонкам.

Так же вы на уровне можете скрыть поле на уроне Entity (будет существовать в БД, невозможно будет создать колонку с таким же именем и тд), но тогда обращение к нему стандартными методами будет затруднено. Для этого необходимо изменить режим использования 

Запрещающий доступ не подходит, поскольку администрировать нужно по каждой записи отдельно. Сценарий - индивидуальный менеджер видит все по своему контрагенту, любой менеджер - только часть. Может, есть где-то нормальная документация по sdk с комментариями чуть полнее, чем Overloaded :) ?

Вопрос комбинации доступа по записям и полям обсуждался неоднократно, простого решения нет, только доработка.

 

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

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

Наличие и отсутствие Id для всех справочных колонок работает одинаково. Посмотрите у одной и для остальных сделайте так же.

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

Если у справочной колонки уже есть «Id» как часть названия, то в базе будет, соответственно, «IdId». Но лучше таких названий избегать.

Исправить первый пост не могу, там не <имя>Value, а <имя>Name, что впрочем не принципиально. Принципиально, что о гглупости, или незнанию, или умышленно созданная колонка ContactName не будет отличаться от Contact.Name, а потом такую ошибку черта лысого найдешь.

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

Спасибо, мы все это смотрели, но мы делаем более-менее generic решение, с тем, чтобы оно работало без дополнительных ограничений и не только в момент сдачи, поэтому варианты посмотреть, перечислить явно и т.п. никак не подходят. Нужна или аутентичная процедура получения одного из другого (или хотя бы документированное API, по которому не приходится догадываться, что имеется в виду), или официальній документ о требованиях к именованию, или (что хуже всего) авторитетное утверждение, что єти колонки каждій именует как Бог на душу положит и задача не решается в принципе.

Требования к именованию публиковались ещё во времена 3.Х.

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

Мы так и делаем, правда там хранятся не Id, а UId, и я пытаюсь от имеющихся UId перейти к columnName, которые можно передать в Entity.SetColumnValue. Думал, что нашел, но см. первый пост этой ветки.

Поскольку на https://community.terrasoft.ru/questions/spisok-ne-administriruemyh-kol… никто не отвечает по сути вопроса, я просто пробую другой подход, основная часть задачи решена, а то, что казалось очевидным при беглом взгляде на имеющийся API, оказалось проблемой.

Обычно имена в базе и схеме совпадают, отличие только для справочных. Можно проверять, справочник ли это и затем добавлять или удалять «Id».

Также можно получить в виде JSON метаданные объекта и анализировать их: для каждой  справочной колонки там в соответствующих полях есть тройка названий: для поля-объекта, поля с Guid и поля для отображения. Например, "Type", "TypeId" и "TypeName".

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

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

Можно ли как-то унаследовать правила? Ибо, копи-паста - это как-то не хорошо...

У меня такой же вопрос

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

Насколько мне известно, унаследовать правила нельзя.

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

Но, как показывает практика, правила обязательности всегда работают корректно, а вот с собственной реализацией могут возникнут нюансы laugh

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

вот, да, но копипастить не хочется. Может кто из разрабов ответит?

Если стандартными средствами настроить правила не получится, можно делать кодом, привязав им всем «isRequired» к логике какой-либо функции. Например, такой код есть в EmailTemplateUserTaskPropertiesPage.

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

Сущности в системе идентифицируются Guid. который, будучи преобразованным в строку, имеет вид типа "846d8d33-004b-4a3e-b778-10cfd163f3bb" (буквы строчные, в таком виде он фигурирует, например, в параметрах запросов http)

С другой стороны в БД сущности хранятся с первичным ключем, построенным на id varchar2(38), но содержиное там заключено в фигурные скобки и буквы заглавные.

Есть ли стандартная функция преобразования одного в другое? В запрос нужно передать Id текущего контакта, но "{"+UserConnection.CurrentUser.ContactId.ToUpper()+"}" выглядит достаточно неуклюже.

У меня такой же вопрос

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

Могу предложить "элегантный костыль":
string contact = "846d8d33-004b-4a3e-b778-10cfd163f3bb";
string result = String.Join(String.Empty, "{", contact.ToUpper(), "}");

Если используете механизмы EntityShchemaQuery или Select/Insert/Update/Delete, то ничего преобразовывать не нужно, в функции передаётся переменная типа Guid и при генерации SQL в нужном формате подставится само. Если же самостоятельно создаёте SQL, воспользуйтесь своим кодом или советом выше.

Эелегантность костыля в виде сокращения записи обращения к переменной иррелевантна задаче :)

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

Причина в том, что основная поддерживаемая база  — MS SQL, где есть тип «uniqueidentifier» и база поймёт вставку в любом формате. А поддержка Oracle добавлена опционально.

В этом и была суть вопроса: если в Oracle используется просто текст, какая разница, что туда писать? Зачем эти скобочки? Чтобы что-то в базе посмотреть, скопировать Id из URL не получится, это минус. Как и при склеивании ESQ и Select. А плюсы вообще есть?

Этот текст, хоть и не является отдельным типом «uniqueidentifier», но служит в качестве первичного или внешнего ключа. Соответственно, если писать в двух полях один GUID разным способом, связи между ними не получится.

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

В каких 2-х полях??? Почему не писать ОДНИМ способом, в нижнем регистре и без скобок? Везде.

В версии для MS SQL пишите как вам нужно, а в Oracle — именно требуемым способом.

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

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

Дмитрий, оно и приведено к единому знаменателю — с фигурными скобками в верхнем регистре. Нет смысла в версиях с 3.0 по 7.13 использовать один формат, а потом внезапно менять просто потому, что не нравятся скобки.

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

При обычной работе с форматом написания не сталкиваются никак, все C#-классы принимают значения типа Guid и преобразуют в нужный формат при генерации SQL автоматически. В чём именно негативное влияние фигурных скобок, Вы так и не объяснили. 

Вероятно, первоисточник именно такого написания — стандартная функция CreateGuid в Delphi, на котором была написана система Terrasoft 3.X. Она генерирует именно в таком формате. И в таблицу базы Firebird и Oracle, где нет встроенных типов для хранения GUID, так и записывали.

В Microsoft для C# рекомендуют для получения нужного формата использовать для переменной типа Guid метод ToString("N") в сочетании с String.ToUpper.

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

Хочу отловить двойной клик по записи в разделе.

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

Вопрос как?

У меня такой же вопрос

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

Проверьте метод onGridDoubleClick, возможно это то, что вам нужно

Пример работы с этим методом есть на детали «Пользователи» раздела «Организационные роли». По двойному клику открывается карточка пользователя. Логика реализована в схеме UsersDetailV2.

Добрый день. В 7.13 или 7.13.1 это есть уже в коробке. Можете посмотреть, как реализовано там. Работает и в разделах, и в деталях.

 

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

Здравствуйте, что-то я запутался((

Есть колонка(дробное число) необходимо её заполнить одинаковым значением - 1000000 для всех контрагентов, как это сделать не импортом... Пробовал  БП, он вроде проходит, но не заполняет(

У меня такой же вопрос

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

Колосов Алексей,

Если Вам нужно это выполнить единоразово, то проще всего cделать, действительно, sql-запросом вида:

update Account

set Field1 = value

where IsNull(Field1, 0) = 0

где Field1 - это название Вашего поля, value - его значение.

А выполнить этот запрос можно либо предложенным в первом комментарии способом, либо установив бесплатное расширение SQL Executor.

 

Выполнять прямой запрос к БД или через Microsoft Management Studio если у вас On-Site или через SQL Script в Админки BPM'Online. Нужна помощь в написании SQL скрипта пишите поможем.

а средствами BPM?

В С#-коде можно сформировать и запустить Update-запрос. Пример запроса:

Query update = new Update(UserConnection, "SysEntitySchemaRecordDefRight")
	.Set("AuthorSysAdminUnitId", Column.Parameter(authorId))
	.Where("Id").IsEqual(Column.Parameter(RedactedRecordId));
update.Execute();

В Вашем случае будет ещё и без условия Where.

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

Данная конструкция на C# может не сработать, так как ключевые входные данные это 1 миллион записей. Есть вероятность что по Таймауту ошибка выйдет.

Здесь лучше чтобы Автор поста описал свои знания и от этого лучше отталкиваться чтобы дать совет.

Если много записей, можно порциями, where Id in select top 1000 Id from...

1000000 это не записи, а значение в колонку... Знаний на уровня С# нету....(( Хотелось бизнес-процессом...

В  БП есть элемент «Изменить данные», который делает примерно то же.  Возможно, медленнее, за счёт отрабатывания событий сохранения по каждой записи.

Зверев Александр, этим элементом можно только значения из справочника передать, а у меня дробное число((

Числовые тоже можно.

Колосов Алексей,

Если Вам нужно это выполнить единоразово, то проще всего cделать, действительно, sql-запросом вида:

update Account

set Field1 = value

where IsNull(Field1, 0) = 0

где Field1 - это название Вашего поля, value - его значение.

А выполнить этот запрос можно либо предложенным в первом комментарии способом, либо установив бесплатное расширение SQL Executor.

 

Спасибо, разобрался... наверное - понедельник был)))

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

Пытаюсь в коде сбросить справочник

this.set("UsrDealerAccount", {value: null, displayValue: null});

но тогда не срабатывает правило REQUIRED, т.е. звездочка * рядом с полем есть, поле пустое, но страница может быть сохранена (в поле в базе сохраняется NULL). 

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

Что я делаю не так?

У меня такой же вопрос

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

Вы в справочное поле вставляет "Пустой объект", поэтому в вашем случае с точки зрения валидатора поле заполнено.
Делайте так: 

this.set("UsrDealerAccount", null);

Литвинко Павел,Там потом ошибки в консоли валятся

Алексей-Карягин пишет:

Литвинко Павел,Там потом ошибки в консоли валятся

В каком случае и какие ошибки? 

Литвинко Павел,

у меня фильтрация этого поля правилом, когда туда плюхаешь нул, он ругается на этот нул.

Алексей-Карягин пишет:

Попробуйте тогда, вместо null использовать  Terrasoft.GUID_EMPTY может поможет

Литвинко Павел,

к сожалению, нет

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

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

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

Точнее, функция this.callParent(arguments) никак не работает из

esq.getEntity(Id, function(result) {
    this.callParent(arguments);
}, this);

Уже пробовал сделать var self = this и вызывать от self, пробовал сохранить arguments до esq. Ничего не помогает, задача не сохраняется. 

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

Есть способ?

У меня такой же вопрос

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

Добрый день, нужно либо смотреть в сторону asyncValidate (пример есть в DocumentPageV2), либо переопределить Save.

save: function(config,flag) {
    if (flag) {
        this.callParent(arguments);
    } else {
        //Validation Block
        Terrasoft.chain(
            this.checkPhonesCount,
            function (callback, scope) {
                this.save(config||{}, true),
                    callback.call(scope || this);
            },
            this
        );
    }
 
},
/**
 * Проверка количества телефонов
 * */
checkPhonesCount: function (callback, scope) {
    var leadId = this.get("Id");
    var esq = this.Ext.create("Terrasoft.EntitySchemaQuery", {
        rootSchemaName: "UsrLeadPhone"
    });
    esq.addAggregationSchemaColumn("Id", Terrasoft.AggregationType.COUNT, "Count");
 
    esq.filters.addItem(Terrasoft.createColumnFilterWithParameter(
        Terrasoft.ComparisonType.EQUAL, "UsrLead", leadId));
    esq.filters.addItem(Terrasoft.createColumnFilterWithParameter(
        Terrasoft.ComparisonType.NOT_EQUAL, "UsrPhone", ""));
    esq.filters.addItem(Terrasoft.createColumnIsNotNullFilter("UsrPhone"));
    esq.getEntityCollection(function (response) {
        if (response && response.success) {
            var collection = response.collection;
            if (collection && collection.getCount() > 0) {
                var count = collection.getByIndex(0).get("Count");
                if (count >= 1) {
                    callback.call(scope || this);
                    return;
                }
            }
            var msg = this.get("Resources.Strings.PhonesErrorMessage");
            msg = this.Ext.String.format(msg, 1);
            Terrasoft.showInformation(msg);
        }
    }, this);
},

 

Я делаю так: 

1) у кнопки сохранить bind меняю на другую функцию (пр. onBeforeSave)

2) в onBeforeSave реализую логику, а дальше вызываю/не вызываю save-метод

Также можно в save зашить проверку какого-либо параметра. Сохранять arguments, проверять параметр, если false-> вызывать свою функцию, в конце своей функции параметр = true и вызвать save с  сохр. аргументами. Тоже в принципе вариант

Добрый день, нужно либо смотреть в сторону asyncValidate (пример есть в DocumentPageV2), либо переопределить Save.

save: function(config,flag) {
    if (flag) {
        this.callParent(arguments);
    } else {
        //Validation Block
        Terrasoft.chain(
            this.checkPhonesCount,
            function (callback, scope) {
                this.save(config||{}, true),
                    callback.call(scope || this);
            },
            this
        );
    }
 
},
/**
 * Проверка количества телефонов
 * */
checkPhonesCount: function (callback, scope) {
    var leadId = this.get("Id");
    var esq = this.Ext.create("Terrasoft.EntitySchemaQuery", {
        rootSchemaName: "UsrLeadPhone"
    });
    esq.addAggregationSchemaColumn("Id", Terrasoft.AggregationType.COUNT, "Count");
 
    esq.filters.addItem(Terrasoft.createColumnFilterWithParameter(
        Terrasoft.ComparisonType.EQUAL, "UsrLead", leadId));
    esq.filters.addItem(Terrasoft.createColumnFilterWithParameter(
        Terrasoft.ComparisonType.NOT_EQUAL, "UsrPhone", ""));
    esq.filters.addItem(Terrasoft.createColumnIsNotNullFilter("UsrPhone"));
    esq.getEntityCollection(function (response) {
        if (response && response.success) {
            var collection = response.collection;
            if (collection && collection.getCount() > 0) {
                var count = collection.getByIndex(0).get("Count");
                if (count >= 1) {
                    callback.call(scope || this);
                    return;
                }
            }
            var msg = this.get("Resources.Strings.PhonesErrorMessage");
            msg = this.Ext.String.format(msg, 1);
            Terrasoft.showInformation(msg);
        }
    }, this);
},

 

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

Вообще, лучше не делать такие проверки только лишь в клиентской логике. Чисто теоретически, пользователь сможет отловить выполнение функции и в отладчике перешагнуть проверку. Хорошо бы и на объекте в обработчике Saving проверять соблюдение условия.

1. Если не вчитываться в смысл задачи, а только подсказать как сделать желаемое то вот этот способ:

save: function() {
   ...
   var args = arguments;
   var parentMethod = this.getParentMethod();
   ....
   esq.GetEntity(function() {
       parentMethod.apply(this, args)
   }, this);
}

2. По смыслу же задачи, вам нужно делать валидацию, а не переопределять методы сохранения. посмотрите примеры в коде с использованием методов addColumnValidator, validate, asyncValidate. 

addColumnValidator, validate делают синхронные проверки. asyncValidate асинхронная валидация, используется когда нужно сделать например запрос в БД для проверки.

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

Добрый всем день

Работаем над кастомным определением прав доступа (доступность полей в зависимости от роли по отношеню к контрагенту). Для этого в AccountLoaded проверяем у пользователя наличие роли по отношению к контрагенту (типа "индивидуальный менеджер", хранится в кастомном объекте, не суть), находим в VwSysEntitySchemaColumnRight доступные роли "индивидуальный менеджер"  поля Account, но они там хранятся только в виде SubjectColumnUId. Для самого же Account доступен список полей через Entity.GetColumnValueNames().

Возникло 2 вопроса:

1. Как эффективно получить список не администрируемых полей чтобы лишний раз их не проверять и не тащить методом исключения?

2. Как имея список SubjectColumnUId эффективно получить названия полей, чтобы скрыть те, на которые у роли нет прав? Через SysPackageSchemaDataColumn или как-то иначе?

 

У меня такой же вопрос

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

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

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

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

Например, называлось моё поле Цена, стало называться Стоимость. Я имею ввиду стандартными средствами, без html хаков?

У меня такой же вопрос

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

Добрый день, да можно, для этого при добавлении поля необходимо реализовать метод, который будет возвращать имя вашей колонки. И свойство Caption забиндить на него, или на какой-то атрибут (смысл тот же)
{
                "operation": "merge",
                "name": "Owner",
                "values": {
                    "caption": {
                        "bindTo": "getDetailCaption"
                    },
                    "layout": {
                        "colSpan": 12,
                        "rowSpan": 1,
                        "column": 12,
                        "row": 1
                    }
                }
            }

getDetailCaption: function() {
                var caption = this.get("Resources.Strings.GantDetailCaption");
                return caption;
            },

Добрый день, да можно, для этого при добавлении поля необходимо реализовать метод, который будет возвращать имя вашей колонки. И свойство Caption забиндить на него, или на какой-то атрибут (смысл тот же)
{
                "operation": "merge",
                "name": "Owner",
                "values": {
                    "caption": {
                        "bindTo": "getDetailCaption"
                    },
                    "layout": {
                        "colSpan": 12,
                        "rowSpan": 1,
                        "column": 12,
                        "row": 1
                    }
                }
            }

getDetailCaption: function() {
                var caption = this.get("Resources.Strings.GantDetailCaption");
                return caption;
            },

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

Хочу дополнительно по определенным сложным условиям фильтровать Результат в активности.

Единственным способом мне видится lookupListConfig filters. 

Вопрос - как корректно вызвать родительские фильтры, чтобы не копипастить их в свой код?

У меня такой же вопрос

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

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

Не так давно уже обсуждали подобную тему здесь.

Если бы родительские фильтры были вынесены в отдельный метод, тогда можно было бы просто Ваш метод унаследовать от родительского и в нём вызвать callParent.

Но так как, к сожалению, это не так, Вам прийдется дублировать родительские фильтры в своем коде sad

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

Не так давно уже обсуждали подобную тему здесь.

Если бы родительские фильтры были вынесены в отдельный метод, тогда можно было бы просто Ваш метод унаследовать от родительского и в нём вызвать callParent.

Но так как, к сожалению, это не так, Вам прийдется дублировать родительские фильтры в своем коде sad

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