Вопрос

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

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

1. Создается объект UsrVwTest , который наследуется от базового, в нем не добавляется ни одного поля (в view будет 1 колонка с идентификаторами контакта), у него устанавливается признак, что это представление в базе, он сохраняется и публикуется.

2. После этого, через расширение SqlExecutor выполняется что-то в духе

create view UsrVwTest as select top 1 Id as 'Id' from Contact

3. После это прямо из консоли пробую посчитать записи, которые там есть, выполняя следующее:

var esq = this.Ext.create("Terrasoft.EntitySchemaQuery", { rootSchemaName: "UsrVwTest" });
esq.addColumn("Id");
esq.getEntityCollection(function (result) {
	if (!result.success) {
		// обработка/логирование ошибки, например
		this.showInformationDialog("Ошибка запроса данных");
		return;
	}
	console.info(result.collection.getCount());
}, this);

Консоль выдает мне 1, то есть проблем с доступом к view нет, там одна строка, как и должно быть.

После этого я повторяю тот же алгоритм на on-site проекте 7.11, в том же порядке. После этого я на аналогичное обращение к view из консоли получаю 500 (InvalidObjectStateException). Запрос в Network падает также с кодом "ErrorCode":"InvalidObjectStateException". Обработка результата падает в ветку с !result.success. Пробовал дополнительно компилировать конфигурацию на разных этапах алгоритма, создавать view различными способами (напрямую в базе, через установку скрипта в конфигурации и т.д.), получаю тот же ответ. Названия view в базе и в конфигурации совпадают, название и количество колонок тоже.  Кроме того, на проекте уже есть view, реализованные ранее, с которыми ведется работа, они реагируют по-разному. Некоторые так же выдают 500, некоторые послушно пишут количество записей внутри себя. 

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

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

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

Поправка:

По всей видимости, все уже существующие представления ошибок не вызывают. Случаи, когда запрос к ним из консоли выдает 500 - превышение 20 000 записей на выгрузку. 

 

Все получилось. Оказалось, что view нужно создавать именно как dbo.ViewName, а не просто ViewName, иначе InvalidObjectName

 

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

Добрый день.

Подскажите, пожалуйста где хранятся настройки представления Аналитика конкретного раздела? Интересует способ быстро перенести (по типу экспорт-импорт md файлов) эти настройки с одного сервера на другой.

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

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

Делал когда то так:
смотрите файлы

Спасибо, Илья.
Перенес из одной БД в другую. Графики отображаются корректно. Однако, если перейти в меню Показать данные, то ни в одном графике ничего не отображается. Все настройки отображаемых колонок утеряны. Хотелось бы перенести вместе с этими настройками, так как их повторная настройка займет ничуть не меньше времени, чем настройка графиков, да и вероятность ошибок существенна.

del

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

Добрый день!
Есть задача добавить представление в разделе, в котором будет редактируемый реестр другого объекта с собственными фильтрами (фильтры аналогичны с фильтрами раздела).
Подскажите, пожалуйста, как реализовать данный функционал?

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

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

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

Правильно ли я понимаю, что Вы хотите создать еще одно представление раздела, выведя в реестр детали, и добавив в созданное представление "Фильтр", который будет фильтровать записи на деталях?

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

Добрый день!
Вашу задачу нужно разделить на 2 этапа:
1. Создать представление
2. Создать свой модуль отображения реестра

По 1-му:
Начать нужно искать с BaseSection, в котором есть функция initDataViews(). Далее по аналогии с, например, представлением "Аналитика"

По 2-му:
Вам нужно создать свой модуль, который будет отображать что угодно (ваш реестр, графики...). После этого проследите как работает функция LoadView из BaseSection, на примере того же представления Аналитика

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

Все блоги автора

Задача: необходимо реализовать возможность переключения реестра в разделе. То есть, находясь в разделе "Контрагенты" переключать реестры с физическими и юридическими лицами, причём у каждого реестра свои определенные колонки.

Решение состоит из 2х частей:

  • Добавить кнопки переключения между представлениями
  • Собственно сделать страницы представлений

Добавление кнопок переключения реестров в раздел

Подготовлены спрайты кнопок:


Размер 30*60. Смысл в том, чтобы при наведении мыши фон кнопки съезжал на 30 пикселей, и как бы подсвечивался. Создан модуль, назвать, например SmrCustomButtonsCSS и определить LESS:

.blue-button-wrapper {
     width: 30px;
     height: 30px;
     padding: 0px;
     margin: 0px 5px 0px 5px;
}
.blue-button-image {
     display: inline;
     background-position: 0px 0px;
     width: 30px;
     height: 30px;
     padding: 0px;
     margin: 0px;
}
.blue-button-image:hover {
     display: inline;
     background-position: 0px -30px;
}
.blue-button-pressed {
     display: inline;
     background-position: 0px -30px;
     width: 30px;
     height: 30px;
     padding: 0px;
     margin: 0px;
}

Создана замещающая страница "AccountSectionV2":
 

define("AccountSectionV2", ["ConfigurationConstants", "css!SmrCustomButtonsCSS"],
    function(ConfigurationConstants) {
        return {
            entitySchemaName: "Account",
            attributes: {  },
            methods: {
                showLegalView: function(){
                    this.sandbox. publish( "PushHistoryState", { hash : "SectionModuleV2/LegalAccountSectionV2" });
                },
                showIndividualView: function(){
                    this.sandbox. publish( "PushHistoryState", { hash : "SectionModuleV2/IndividualAccountSectionV2" });
                },
                showAllView: function(){
                    this.sandbox. publish( "PushHistoryState", { hash : "SectionModuleV2/AccountSectionV2" });
                },
                isSwitchButtonsContainerVisible: function() {
                    var isCardVisible = this.get("IsCardVisible");
                    return !isCardVisible;
                }
 
 
            },
            diff: /**SCHEMA_DIFF*/[
 
                {
                    "operation": "insert",
                    "name": "SwitchViewButtonsContainer",
                    "parentName": "ActionButtonsContainer",
                    "propertyName": "items",
                    "values": {
                        "itemType": Terrasoft.ViewItemType.CONTAINER,
                        "visible": {
                            "bindTo": "isSwitchButtonsContainerVisible"
                        },
                        "items": []
                    }
                },
                {
                    "operation": "insert",
                    "parentName": "SwitchViewButtonsContainer",
                    "propertyName": "items",
                    "name": "SwitchToAllDataView",
                    "values": {
                        "layout": {
                            "column": 23,
                            "row": 0,
                            "colSpan": 1,
                            "rowSpan": 1
                        },
                        "itemType": Terrasoft.ViewItemType.BUTTON,
                        "style": Terrasoft.controls.ButtonEnums.style.TRANSPARENT,
                        //"iconAlign": Terrasoft.controls.ButtonEnums.iconAlign.RIGHT,
                        iconAlign: Terrasoft.controls.ButtonEnums.iconAlign.TOP,
                        pressed: {bindTo: "LegalGridActive"},
                        "imageConfig": {
                            "source": Terrasoft.ImageSources.SOURCE_CODE_SCHEMA,
                            "params": {
                                "schemaName": "SmrCustomButtonsCSS",
                                "resourceItemName": "GridDataViewIconBlue"
                            }
                        },
                        classes: {
                            wrapperClass: "blue-button-wrapper",
                            imageClass: "blue-button-pressed"
                        },
                        "click": {"bindTo": "showAllView"}
                    }
                },
                {
                    "operation": "insert",
                    "parentName": "SwitchViewButtonsContainer",
                    "propertyName": "items",
                    "name": "SwitchToPersonalDataView",
                    "values": {
                        "layout": {
                            "column": 23,
                            "row": 0,
                            "colSpan": 1,
                            "rowSpan": 1
                        },
                        "itemType": Terrasoft.ViewItemType.BUTTON,
                        "style": Terrasoft.controls.ButtonEnums.style.TRANSPARENT,
                        //"iconAlign": Terrasoft.controls.ButtonEnums.iconAlign.RIGHT,
                        iconAlign: Terrasoft.controls.ButtonEnums.iconAlign.TOP,
                        pressed: {bindTo: "LegalGridActive"},
                        "imageConfig": {
                            "source": Terrasoft.ImageSources.SOURCE_CODE_SCHEMA,
                            "params": {
                                "schemaName": "SmrCustomButtonsCSS",
                                "resourceItemName": "GridPersonalDataViewIconBlue"
                            }
                        },
                        classes: {
                            wrapperClass: "blue-button-wrapper",
                            imageClass: "blue-button-image"
                        },
                        "click": {"bindTo": "showIndividualView"}
                    }
                },
                {
                    "operation": "insert",
                    "parentName": "SwitchViewButtonsContainer",
                    "propertyName": "items",
                    "name": "SwitchToLegalDataView",
                    "values": {
                        "layout": {
                            "column": 23,
                            "row": 0,
                            "colSpan": 1,
                            "rowSpan": 1
                        },
                        "itemType": Terrasoft.ViewItemType.BUTTON,
                        "style": Terrasoft.controls.ButtonEnums.style.TRANSPARENT,
                        //"iconAlign": Terrasoft.controls.ButtonEnums.iconAlign.RIGHT,
                        iconAlign: Terrasoft.controls.ButtonEnums.iconAlign.TOP,
                        pressed: {bindTo: "LegalGridActive"},
                        "imageConfig": {
                            "source": Terrasoft.ImageSources.SOURCE_CODE_SCHEMA,
                            "params": {
                                "schemaName": "SmrCustomButtonsCSS",
                                "resourceItemName": "GridLegalDataViewIconBlue"
                            }
                        },
                        classes: {
                            wrapperClass: "blue-button-wrapper",
                            imageClass: "blue-button-image",
                            pressedClass: "blue-button-pressed"
                        },
                        "click": {"bindTo": "showLegalView"}
                    }
                }
 
            ]/**SCHEMA_DIFF*/
        };
    }
);

Страницы разделов

Созданы 2 страницы разделов. Код для реестра физических лиц:

define("IndividualAccountSectionV2", ["ConfigurationConstants", "css!SmrCustomButtonsCSS"],
    function(ConfigurationConstants) {
        return {
            entitySchemaName: "Account",
            methods: {
                /**
                 * Инициализирует  фильтры экземпляра запроса
                 * @protected
                 * @overridden
                 * @param {Terrasoft.EntitySchemaQuery} esq Запрос, в котором будут инициированы фильтры
                 */
                initQueryFilters: function (esq) {
                    this.callParent(arguments);
                    esq.filters.removeByKey("ClientType");
                    esq.filters.add("ClientType", this.Terrasoft.createColumnFilterWithParameter(
                        this.Terrasoft.ComparisonType.EQUAL, "SmrClientType.Id", "80FD3F74-0AF4-4F8A-9F0B-F626E4F05147"));
                },
                /**
                 * Возвращает представления раздела по умолчанию.
                 * Реестр, Аналитика не создаётся
                 * @overridden
                 * @return {Object} Представления раздела по умолчанию
                 */
                getDefaultDataViews: function() {
                    var gridDataView = {
                        name: this.get("GridDataViewName"),
                        caption: this.getDefaultGridDataViewCaption(),
                        icon: this.getDefaultGridDataViewIcon()
                    };
                    var analyticsDataView = {
 
                    };
                    return {
                        "GridDataView": gridDataView,
                        "AnalyticsDataView": analyticsDataView
                    };
                },
                /**
                 * Получает пункты меню кнопки "Вид"
                 * @overridden
                 * @virtual
                 * @return {Terrasoft.BaseViewModelCollection} Возвращает пункты меню кнопки "Вид"
                 */
                getViewOptions: function() {
                    var viewOptions = this.Ext.create("Terrasoft.BaseViewModelCollection");
                    viewOptions.addItem(this.getButtonMenuItem({
                        "Caption": {"bindTo": "Resources.Strings.SortMenuCaption"},
                        "Items": this.get("SortColumns")
                    }));
                    viewOptions.addItem(this.getButtonMenuItem({
                        "Caption": {"bindTo": "Resources.Strings.OpenGridSettingsCaption"},
                        "Click": {"bindTo": "openGridSettings"}
                    }));
                    return viewOptions;
                }
            },
            diff: /**SCHEMA_DIFF*/[
                {
                    "operation": "merge",
                    "name": "SwitchToAllDataView",
                    "values": {
                        classes: {
                            wrapperClass: "blue-button-wrapper",
                            imageClass: "blue-button-image"
                        }
                    }
                },
                {
                    "operation": "merge",
                    "name": "SwitchToPersonalDataView",
                    "values": {
                        classes: {
                            wrapperClass: "blue-button-wrapper",
                            imageClass: "blue-button-pressed"
                        }
                    }
                },
                {
                    "operation": "merge",
                    "name": "SwitchToLegalDataView",
                    "values": {
                        classes: {
                            wrapperClass: "blue-button-wrapper",
                            imageClass: "blue-button-image"
                        }
                    }
                }
            ]/**SCHEMA_DIFF*/
        };
    }
);

Код страницы для реестра юридических лиц:

define("LegalAccountSectionV2", ["RightUtilities", "ConfigurationConstants", "css!SmrCustomButtonsCSS"],
    function (RightUtilities, ConfigurationConstants) {
        return {
            entitySchemaName: "Account",
            attributes: {},
            messages: {},
            methods: {
                /**
                 * Инициализирует  фильтры экземпляра запроса
                 * @protected
                 * @overridden
                 * @param {Terrasoft.EntitySchemaQuery} esq Запрос, в котором будут инициированы фильтры
                 */
                initQueryFilters: function (esq) {
                    this.callParent(arguments);
                    esq.filters.removeByKey("ClientType");
                    esq.filters.add("ClientType", this.Terrasoft.createColumnFilterWithParameter(
                        this.Terrasoft.ComparisonType.EQUAL, "SmrClientType.Id", "DD4E6E34-21D3-4F09-A417-6FC4116876B5"));
                },
                /**
                 * Возвращает представления раздела по умолчанию.
                 * Реестр, Аналитика не создаётся
                 * @overridden
                 * @return {Object} Представления раздела по умолчанию
                 */
                getDefaultDataViews: function() {
                    var gridDataView = {
                        name: this.get("GridDataViewName"),
                        caption: this.getDefaultGridDataViewCaption(),
                        icon: this.getDefaultGridDataViewIcon()
                    };
                    var analyticsDataView = {
 
                    };
                    return {
                        "GridDataView": gridDataView,
                        "AnalyticsDataView": analyticsDataView
                    };
                },
                /**
                 * Получает пункты меню кнопки "Вид"
                 * @overridden
                 * @virtual
                 * @return {Terrasoft.BaseViewModelCollection} Возвращает пункты меню кнопки "Вид"
                 */
                getViewOptions: function() {
                    var viewOptions = this.Ext.create("Terrasoft.BaseViewModelCollection");
                    viewOptions.addItem(this.getButtonMenuItem({
                        "Caption": {"bindTo": "Resources.Strings.SortMenuCaption"},
                        "Items": this.get("SortColumns")
                    }));
                    viewOptions.addItem(this.getButtonMenuItem({
                        "Caption": {"bindTo": "Resources.Strings.OpenGridSettingsCaption"},
                        "Click": {"bindTo": "openGridSettings"}
                    }));
                    return viewOptions;
                }
            },
            diff: /**SCHEMA_DIFF*/[
                {
                    "operation": "merge",
                    "name": "SwitchToAllDataView",
                    "values": {
                        classes: {
                            wrapperClass: "blue-button-wrapper",
                            imageClass: "blue-button-image"
 
                        }
                    }
                },
                {
                    "operation": "merge",
                    "name": "SwitchToPersonalDataView",
                    "values": {
                        classes: {
                            wrapperClass: "blue-button-wrapper",
                            imageClass: "blue-button-image"
                        }
                    }
                },
                {
                    "operation": "merge",
                    "name": "SwitchToLegalDataView",
                    "values": {
                        classes: {
                            wrapperClass: "blue-button-wrapper",
                            imageClass: "blue-button-pressed"
                        }
                    }
                }
            ]/**SCHEMA_DIFF*/
        };
    });

Что нужно отметить в этих страницах:

/**
 * Инициализирует  фильтры экземпляра запроса
 * @protected
 * @overridden
 * @param {Terrasoft.EntitySchemaQuery} esq Запрос, в котором будут инициированы фильтры
 */
initQueryFilters: function (esq) {
        this.callParent(arguments);
        esq.filters.removeByKey("ClientType");
        esq.filters.add("ClientType", this.Terrasoft.createColumnFilterWithParameter(
                this.Terrasoft.ComparisonType.EQUAL, "SmrClientType.Id", "DD4E6E34-21D3-4F09-A417-6FC4116876B5"));
}

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

 

Проблемы

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

P.S.: Очень ждем возможности использовать такую возможность базовыми средствами, кейсов много - это и деление на сотрудники/контакты и физ/юр лица. В ситуациях, когда набор колонок одинаковый, конечно рекомендуется пользоваться стандартными динамическими группами.

Поделиться

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

Эх, еще бы это в Мастер раздела :)

Кстати, а кейс ведения физических и юридических лиц в Контрагентах давно стал практикой?

Это практика при условиях, что:

  • Клиенту нужно смотреть суммарную разнообразную аналитику по всем клиентам физики+юрики, создаваемую через стандартные средства (в представлении "Аналитика")
  • Клиента бесит, когда ему каждый раз заходя в раздел Контакты, нужно отфильтровывать своих сотрудников и сотрудников подрядчиков, чтобы поработать с клиентами:smile:

Александр, золотой вы человек :twisted:
Как раз ломал голову как бы не убить кучу времени и реализовать представления с переключением, а тут пример живой.

Рад помочь, для этого и нужно комьюнити:smile:. Если есть еще вопросы в стиле "В 3ке это делалось в 5 кликов аналитиком, а как это сделать это в 7ке?" - спрашивайте, наверняка найдем проектное решения в закромах, пусть и не всегда очевидное:lol:

"Александр Свистунов" написал:спрашивайте, наверняка найдем проектное решения в закромах

Я ж воспользуюсь :)
Case: отфильтровать в разделе Счета все счета, к которым прикреплен продукт = "ххх". То есть фильтр по вхождению некой информации в деталь - аналогично в любом разделе. Так как штатно доступны только фильтры по детали с агрегатной функцией, то видимо надо как-то добавлять свой фильтр?
В 3х это делалось через модификацию sq_, в 5х вроде как вообще работало штатно через обратные связи.
Раздел итоги элемент список не предлагать :) там делается посредством списка на основании Продукт в счете - пробовал, работает.

Александр, если я правильно понял ваш Кейс, то это делается через подобный фильтр

Продукт в счете

"Толмачев Дмитрий Юрьевич" написал:делается через подобный фильтр

Вот только сейчас сумел поменять count на exists - после того как выбрал хоть какую-то колонку при фильтрации по связанной таблице.
Именно это и было нужно, спасибо
Ну вот как надо было догадаться сначала выбрать произвольное поле типа Количество, чтобы попасть обратно в настройки фильтра... Идем и ищем (с)

Чтобы темы не плодить... товарищи, как бы попроще скрыть из меню Edit, Copy и особенно Delete для детали, основанной на вьюшке?
Деталь самодельная, родитель BaseGridDetailV2.

Александр,

Для решения вашей задачи достаточно переопределить функцию addRecordOperationsMenuItems на созданной вами странице реестра

methods: {
	/**
	 * @overridden
	 */
	addRecordOperationsMenuItems: Ext.emptyFn
	//...
}

"Толмачев Дмитрий Юрьевич" написал:Ext.emptyFn

Спасибо, попробую так.
Я ее нашел, только наивно переопределял как

addRecordOperationsMenuItems: function() {
}

То есть пытался заполнить "ничем"...

А я где-то такую функцию нашёл

getAddRecordButtonVisible: function() {
	return false;
},

Добрый день!

Александр, спасибо за предоставленной пример, остался один непонятный для меня момент, какие действия нужно выполнить, что бы новая страница раздела (например, IndividualAccountSectionV2) стала доступна по URL "http://localhost/0/Nui/ViewModule.aspx#SectionModuleV2/IndividualAccoun…"?

Сергей,

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

В данном случае, насколько я помню, у страниц LegalAccountSectionV2 и IndividualAccountSectionV2 в качестве родительской использовалась "AccountSectionV2", т.к. по сути это дочерние ветки.

Александр, большое спасибо за то что делитесь отличным материалом!

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

1. Правильно ли я понимаю, что через свойства Diff:

pressed: {bindTo: "LegalGridActive"},

и

classes: {
                            wrapperClass: "blue-button-wrapper",
                            imageClass: "blue-button-image",
                            pressedClass: "blue-button-pressed"
                        },

Вы указываете, будет ли элемент содержать класс "blue-button-pressed" ?
Если да, то что в этом случае должна возвращать функция "LegalGridActive" - true или false?

У меня на версии 7.7 не получилось сделать так, чтобы к елементу "прицепился" мой класс указанный в pressedClass (как у Вашем примере: pressedClass: "blue-button-pressed"). Вместо него появился "дефолтный" класс - t-btn-pressed.
вот код DIFF:

	{
		"operation": "insert",
		"parentName": "SignButtonsContainer",
		"propertyName": "items",
		"name": "SignBtnVip",
		"values": {
			"itemType": Terrasoft.ViewItemType.BUTTON,
			"caption": {"bindTo": "sign_Vip"},
			"hint": {"bindTo": "Resources.Strings.sign_Vip"},
			"imageConfig" : {"bindTo": "Resources.Images.sign_vip_Image"},
			//"click": {"bindTo": "onVipButtonClick"},
			pressed : {bindTo : "VipButtonPressed"},
			classes : {
				wrapperClass : "sign-btn",
				imageClass   : "sign-img",
				pressedClass : "sign-img-pressed"
			},
			"visible": true
		}
	},

В итоге вместо ожидаемого css-класса "sign-img-pressed" - появляется "t-btn-pressed". Не могу понять что я сделал не так...

2. Можно ли какимто образом отключить для моего елемента генерацию стандрартных css-классов?
(для элемента Terrasoft.ViewItemType.BUTTON сгенерировались - t-btn-wrapper, t-btn-no-text-padding, t-btn-style-default )

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

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

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

Здравствуйте, возникла проблема.
Есть объект с некоторыми полями и представление наследуемое от этого объекта, я создал замещающий объект который наследуется от этого объекта и такое же представление.
Проблема в том что в замещающем объекте я добавил поля, но они не отображаются в представлении в MS SQL Server. И я не могу удалять данные из этого объекта, так как отсутствуют поля которые я добавил самостоятельно.

BPM 7.1.0.172

Скрин из BPMa
Скрин из MS SQL Server

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

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

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

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

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

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

Попробовал дебажить. Выбивает ошибку "View or function 'dbo.VwInvoiceProduct' is not updatable because the modification affects multiple base tables"

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

Никаких новых связей я не создавал. Просто добавил два новых поля (UsrComment, UsrCompleted).
Без них данные удаляются без проблем.

Можете профайлером отловить запрос на удаление?

Тарас,

Судя по всему, ваше представление не является обновляемым с точки зрения СУБД (объединяет в себе данные более, чем из одной таблицы, содержит агрегирующие функции и т.д.). Более подробно про то, какие представления обновляемые представления, изложено здесь.

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

Также Вам возможно будет полезна информация из статьи SDK про работу с представлениями. Не смотря на то, что в ней описан кейс для версии 5.Х, общий принцип работы с представлениями как с объектами в BPMonline остался неизменным и для 7-ки.

Спасибо за помощь. Проблему решил, добавив триггер.

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

Здравствуйте!
Я решил сделать в продажах деталь продукты по аналогии с разделом документы.
Для этого создал представление на основе таблицы "Продукты в продаже" чтобы имя подставлялось в зависимости от заполненного поля(продукт, произвольный продукт). Все работает, но при удалении продукта из детали, возникает ошибка: "Выбранные элементы удалить невозможно, так как они используются в других объектах.".
Так же в события Страницы реестра интересов к продуктам я добавил два событийных подпроцесса(по аналогии с продуктами в документах): OfferingChanged и DeleteYesMessage. Правда не понял откуда они вызываются и где их нужно регистрировать, чтобы они работали. Но они вроде как и нужны для того, чтобы удаление работало.
Подскажите, пожалуйста, что можно сделать, чтобы удаление работало?
Спасибо!

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

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

Здравствуйте, Павел!

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

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

Добрый день, Павел!

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

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

Если Вы когда-либо создавали запросы SelectQuery с CustomSQL-колонками, Вы наверняка обратили внимание на то, что запрос, прекрасно работающий под учетной записью администратора может оказаться нефункциональным под учетной записью пользователя, если в CustomSQLColumn используются таблицы, администрируемые по записям.

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

Дело в том, что если таблица администрируется по записям, дизайнер запросов при работе под пользователем автоматически подставляет вместо нее представление. А поскольку CustomSQL-колонки вставляются в запрос как есть, в результате у пользователя нет доступа к таблице, и есть доступ к предоставлению.

Схема работы:
selectquery

Для того, чтобы запрос работал корректно, следует в CustomSQLColumn использовать не таблицу, а ее алиас, заданный в блоке FROM. В случае необходимости - присоединить таблицу в одном из JOIN-ов и также обращаться по алиасу.

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

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

Поделиться

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

"Alimova Anna" написал:

Есть еще один способ - сразу указать в CustomSQLColumn представление таблицы. Однако такой способ медленнее


Кроме того что он будет медленнее для Администратора, он еще почти всегда будет работать некорректно - так как будет проверяться доступ на записи, а как правило Администратору никто его не дает, он и так все "видит".

Ну, а самое забавное начинается, когда таблица начинает администрироваться по полям =)

Максим, и что же начнется? :)

В случае добавления безобидной колонки Account.Name
И удалении прав на чтение этой колонки:

TSObjectLibrary.DBDataset: Ошибка открытия источника данных "ds_Account". 
Оригинальное сообщение об ошибке: The SELECT permission was denied on the column 'Name' of the object 'vw_Account", database 'XXX', schema 'dbo'

Это потому, что она ключевая? о_О

И еще: как это обойти?

Нет, это потому, что на нее доступ запрещен на уровне БД в самой таблице(и как следствие во вьюхе). Ядро в таком случае колонки заменяет на NULL, чтоб к ним вообще обращение не шло.
Пока мне известен один способ обхода - создавать отдельную функцию и использовать в CustomSQLColumn:

fn_GetAccountNameByID(Account.ID)
Войдите или зарегистрируйтесь, чтобы комментировать
Публикация

Добрый день!

Как Вы знаете, в карточке редактирования может быть представлено несколько представлений. Иногда требуется ограничить доступ к этому представлению некоторой группе пользователей. Этот функционал можно дополнительно реализовать средствами Terrasoft Administrator. Ниже приведу сам алгоритм, на примере карточки редактирования раздела "Продукты", в которой существует представление "Движение по складу", доступ к которому мы ограничим.

Итак, прежде всего необходимо открыть скрипт wnd_OfferingEditScript и в конец скрипта добавить следующую функцию:

function IsUserInGroup(GroupID)
{
        var Dataset = Services.GetSingleItemByUSI('ds_AdminUnit');
        ApplyDatasetFilter(Dataset, 'UserContactID', Connector.CurrentUser.ContactID, true);
        Dataset.Open();
        var UserID = Dataset.ValAsGUID(IDFieldName);
        Dataset.Close();
        var Dataset = Services.GetSingleItemByUSI('ds_UserInGroup');
        ApplyDatasetFilter(Dataset, 'GroupID', GroupID, true);
        ApplyDatasetFilter(Dataset, 'UserID', UserID, true);
        Dataset.Open();
        var Is = (Dataset.RecordsCount > 0);
        Dataset.Close();
        return Is;
}

Приведенная выше функция, в случае если текущий пользователь системы входит в указанную нами группу пользователей, возвращает значение true, в ином случае - false.

Далее необходимо отредактировать функцию function wnd_OfferingEditOnPrepare(Window). В ней добавим следующую проверку:

function wnd_OfferingEditOnPrepare(Window) {
        scr_BaseDBEdit.wnd_BaseDBEditOnPrepare(Window);
        Initialize(Window);            
        if(!Connector.CurrentUser.IsAdmin)
        {
                var UsrDataset = Services.GetSingleItemByUSI('ds_UserInGroup');                  
                var GroupName = 'Название';
                //где 'Название' - имя группы пользователей, для которых нужно ограничить доступ к представлению         
                ApplyDatasetFilter(UsrDataset, 'GroupName', GroupName, true);
                //тут следует не забыть создать фильтр сравнения в сервисе sq_UserInGroup (см. скриншот ниже)
                UsrDataset.Open();             
                var GroupID = UsrDataset.Values('GroupID');
                if(IsUserInGroup(GroupID))
                {
                        //скрываем само представление, установив свойству IsVisible значение false
                        pgOfferingAnalytic.IsVisible = false;  
                }
                UsrDataset.Close();
        }
       
}

Так же, перед тестированием результатов, следует создать фильтр сравнения в сервисе sq_UserInGroup:

2
3

После этого не забудьте сохранить изменения и перезапустить клиентское приложение Terrasoft.

Поделиться

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

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

    var GroupDataset = Services.GetSingleItemByUSI('ds_UserInGroup');
    ApplyDatasetFilter(GroupDataset, 'UserID', UserID, true);
	     GroupDataset.Open();
	      GroupDataset.GotoNext();     //берем второе значение, так как первая по порядку группа 'Все пользователи'                 
         var GroupID = GroupDataset.ValAsStr('GroupID'); 
         GroupDataset.Close();

Большое спасибо за Ваш пример, очень пригодился!

В принципе есть базовая функция в scr_Access называется GetIsUserInGroup, делает примерно тоже самое только немного написана по другому

function GetIsUserInGroup(UserName, GroupID) {
	var sqGetIsUserInGroup = GetSingleItemByCode('sq_GetIsUserInGroup');
	SetParameterValue(sqGetIsUserInGroup.Parameters, 'GroupID', GroupID);
	SetParameterValue(sqGetIsUserInGroup.Parameters, 'UserName', UserName);
	var dsRes = sqGetIsUserInGroup.Open();
	try {
		return dsRes('IsExists') != 0;
	} finally {
		dsRes.Close();
	}
}
Войдите или зарегистрируйтесь, чтобы комментировать
Вопрос

Реестр записей содержит несколько представлений. На нажатие кнопки "Добавить" в форме создания новой записи необходимо проставить некоторые значения в зависимости от выбраного фильтра. Скажите пожалуйста, можно ли узнать какое представление выбрано в обработчике OnPrepare окна создания записи? Или же необходимо записывать его в глобальную переменную в обработчике события ActiveViewChange грида?

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

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

SDK
Смотрите в имя, например :wink:

Спасибо за ответ, но я немного не то спрашивал.
Я имел ввиду можно ли в методах обработки событий формы создания новой записи вытянуть это значение, то есть можно ли доступиться до грида реестра записей. Но я уже сам разобрался как мне реализовать задачу :smile:

хотя, конечно, не уверен насколько я правильно и корректно это сделал, я только начинаю изучать XRM.
В обработчике нажатия кнопки "добавить" я прописал

BaseGridArea.AddDataDefaultValues('ActiveView') = Self.ComponentsByName('grdData').ActiveView.Name;

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

var ActiveView = GetAttribute(this.scr_WindowUtils.WinCache_Windows[0].Instance, 'DefaultValues')('ActiveView');

Для какого раздела делаете функционал?
Правильнее действовать по следующей схеме:
1) Находим функцию, где устанавливаются значения по умолчанию для окна редактирования. (DefaultValues)
2) В ней проверить какая вкладка была активна. Но лучше использовать метод, когда окно реестра устанавливает еще не открытому окну редактирования дополнительный атрибут, а не наоборот.

Уточните раздел, пожалуйста, тогда ответить будет проще.

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

Attributes('ActiveView') = Self.ComponentsByName('grdData').ActiveView.Name;

и в той же функции установки дефолтных значений

var ActiveView = Window.Attributes('ActiveView');

при условии, что эти атрибуты были переданы объекту Window.

Олег Лабьяк,
разработчик,
3-я линия Службы поддержки Terrasoft.

Раздел - Счета.
Функцию нашёл.
Это

FillInvoiceDefaultValues(Dataset)

в файле ds_InvoiceScript

Перекройте обработчик события btnAddOnClick (только не забудьте вызвать базовый) и в нем передавайте значения по умолчанию через BaseGridArea.AddDataDefaultValues, все остальное сделают базовые скрипты.

FillInvoiceDefaultValues(Dataset) изменять в Вашем случае нерационально.

"Агутин Алексей" написал:Перекройте обработчик события btnAddOnClick (только не забудьте вызвать базовый) и в нем передавайте значения по умолчанию через BaseGridArea.AddDataDefaultValues, все остальное сделают базовые скрипты.

Да, таким образом работает. Спасибо :)
Хотя не хотелось бы нагружать каждый обработчик (в том числе и если добавляю счета через детали) похожей логикой.

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

Спасибо огромное.

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

Думаю, особенно полезна эта информация будет тем, кто использует MS SQL Server Express (ввиду отсутствия настройки Analysis Services, необходимой для моделирования куба) и желает работать с разделом OLAP.
Для того, чтобы работать с разделом OLAP можно также использовать View-представления.

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

Итак, показываю на практике как строится View и настраивается его соединение с разделом OLAP:
1. Запускаем MS SQL Server. Выбираем рабочую базу и открываем папку Views.
2. При помощи контекстного меню NewView добавляем новое представление:

3.  Предварительно необходимо определиться, по каким полям нужно настроить OLAP, а также в какие таблицы эти поля входят. В новом появившемся окне создаем набор таблиц базы (это может быть либо одна таблица, либо несколько - в зависимости от поставленных Вами задач). После того, как таблицы будут выбраны, нажмите на  кнопку  "Close".

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

5. Здесь же, используя кнопки панели инструментов Вы можете добавить фильтры, группировки, ограничения и т.д.
6. Когда все будет сформировано, закрываем созданный View и даем ему название.
7. Далее запускаем рабочее приложение Terrasoft CRM, переходим в раздел OLAP. Слева при помощи опции контекстного меню "Добавить куб" добавляем новую запись.
8. Нажимаем на кнопку "Параметры подключения". В появившемся окне выбираем опцию "+Подключение к новому источнику данных.odc":

9. В качестве мастера подключения данных выбираем Microsoft SQL Server.
10. Далее вводим параметры, необходимые для подключения к серверу БД.
11. Выбираем нужную базу и созданный View. Также советую Вам предварительно позаботиться о том, чтобы название Вашей базы не содержало никаких знаков препинания (точек, запятых, подчеркиваний и т.д.), а также цифры, поскольку по этой причине список таблиц и представлений в нижеуказанном списке не будет отображаться.

12. Нажимаем на кнопку "Готово".
13. Теперь Вы можете вытягивать с списка поля в нужные Вам области для отображения данных в удобном для Вас виде.

Заметьте, что при изменении данных в таблицах, эти данные будут меняться в View, и следовательно в разделе OLAP.

Желаю удачи!

С уважением,
Мельникова Екатерина

Поделиться

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

то же самое относится и к Firebird?

Владимир, дело в том, что создать View на Firebird - не является проблемой. Все настройки аналогичны построению View на MS SQL Server (во всяком случае при использовании утилиты IBExpert). Но вся проблема заключается в том, что в Terrasoft предусмотрена настройка соединения только с СУБД MS SQL.
Но думаю, что со временем такая возможность в приложении Terrasoft CRM будет реализована :)
http://community.terrasoft.ua/node/3097

Мельникова Екатерина

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