Все красиво, и работает корректно. Открывается окно выбора ().
Но предположим, что мне необходимо добавить дополнительную кнопку, либо изменить страницу 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);}});});
Здравствуйте! Продукт sales enterprice 7.9.0. Необходимо принудительно сортировать записи на детали по колонке "Дата создания" по возрастанию.
При этом пользователь должен иметь возможность сортировать записи по своему усмотрению с помощью стандартных средств сортировки на детали. Но при добавлении новой записи на детали должна снова применяться принудительная сортировка скриптом записей по возрастанию даты создания. Как это реализовать?
Сомневаюсь, что в фильтре можно задавать сортировку. Я бы смотрел:
1) В сторону метода заполнения коллекции GridData в детали. Желательно докопаться до esq запроса в бд и вставить что-то вроде
var col = esq.addColumn("CreatedOn");
col.orderDirection= Terrasoft.OrderDirection.ASC;
col.orderPosition=0;
2) В описании Grid(опять же в детали) в diff вроде есть
Здравствуйте, коллеги. Пытаюсь построить сервис, который закрывает обращение и дает возможность добавлять комментарии при закрытии.
Скелет класса CaseRatingService (клон CaseRatingManagementService):
namespace Terrasoft.Configuration.CaseManagementService
{
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Activation;
using System.ServiceModel.Channels;
using System.Security.Principal;
using System.Web;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Terrasoft.Common;
using Terrasoft.Common.Json;
using Terrasoft.Core;
using Terrasoft.Core.DB;
using Terrasoft.Core.Entities;
using Terrasoft.Core.Store;
using Terrasoft.Nui.ServiceModel;
using Terrasoft.Nui.ServiceModel.Extensions;
using Terrasoft.UI.WebControls;
#region Class: CaseManagementService
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class CaseManagementService {
[OperationContract]
[WebGet(UriTemplate = "Case/{id}/{rating}")]
public void GetCase(string id, string rating) {
var context = HttpContext.Current;
try{
HttpRequest request = context.Request;
if (id == null || rating == null) {
throw new ArgumentNullOrEmptyException("rating");
}
_appConnection = HttpContext.Current.Application["AppConnection"] as AppConnection;
_sysUserName = _appConnection.SystemUserConnection.CurrentUser.Name;
_sessionId = Guid.NewGuid().ToString("N");
Thread.CurrentPrincipal = new TerrasoftPrincipal(new GenericIdentity(_sysUserName), new string[0], _sessionId);
this._setResponseText(context.Response, "Ok");
} catch (Exception ex) {
this._setResponseText(context.Response, ex.Message);
}
}
Date: 08.12.2016 11:27:01
Date (UTC): 08.12.2016 9:27:01
Exception Message: Не удалось найти тип "Terrasoft.Configuration.CaseManagementService.CaseManagementService", заданный значением атрибута Service в директиве ServiceHost или указанный в элементе конфигурации system.serviceModel/serviceHostingEnvironment/serviceActivations.
Exception Type: System.InvalidOperationException
Exception Source: System.ServiceModel.Activation
Exception Stack Trace:
в System.ServiceModel.Activation.ServiceHostFactory.CreateServiceHost(String constructorString, Uri[] baseAddresses)
в System.ServiceModel.ServiceHostingEnvironment.HostingManager.CreateService(String normalizedVirtualPath, EventTraceActivity eventTraceActivity)
в System.ServiceModel.ServiceHostingEnvironment.HostingManager.ActivateService(ServiceActivationInfo serviceActivationInfo, EventTraceActivity eventTraceActivity)
в System.ServiceModel.ServiceHostingEnvironment.HostingManager.EnsureServiceAvailable(String normalizedVirtualPath, EventTraceActivity eventTraceActivity)
SessionID: 1r4srdi5za0yv132xnjywfzg
Request URL: /WebApp770/0/ServiceModel/CaseManagementService.svc
Request Path: /WebApp770/0/ServiceModel/CaseManagementService.svc
Request Type: GET
User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36
User Host Address: ::1
User: EFrolov
Is Authenticated: True
Authentication Type: Forms
Is Secure Connection: False
Application Version: 7.7.0.0
Application Path: G:\Projects\Core\TSBpm\Src\Lib\Terrasoft.WebApp.Loader\Terrasoft.WebApp\
Application Virtual Path: /WebApp770/0
Application Trust Level: Full
Machine Name: PC-23-N
Is Local: True
Process ID: 10396
Process Name: iisexpress.exe
Process Account Name: INDOORMEDIA\developer
Thread Account Name: INDOORMEDIA\developer
OS Version: Microsoft Windows NT 6.2.9200.0
Net Framework Version: 4.0.30319.42000
DBExecutor Type: MSSqlExecuto
1. в ServiceModel поцепил файлик CaseManagementService.svc:
Добрый день!
Имеется следующий пример реализации.
В конфигурации необходимо создать схему исходного кода c контрактом сервиса:
[ServiceContract]
public interface IService
{
[OperationContract]
SPMClientInfoResponse SPMClientInfo(string Login);
}
[DataContract]
public class SPMClientInfoResponse
{
bool success = true;
string errorText = "";
[DataMember]
public bool Success
{
get { return success; }
set { success = value; }
}
[DataMember]
public string ErrorText
{
get { return errorText; }
set { errorText = value; }
}
}
и схему исходного кода c реализацией сервиса:
public class SPMSUBPService : IService
{
public SPMClientInfoResponse SPMClientInfo(string Login)
{
return new SPMClientInfoResponse();
}
}
Методы конфигурационного веб-сервиса должны быть помечены атрибутами [OperationContract] и
[WebInvoke] с параметрами.
Например, вот так: [OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, BodyStyle
= WebMessageBodyStyle.Wrapped, ResponseFormat = WebMessageFormat.Json)]
Далее, в папке В папке Terrasoft.WebApp\ServiceModel создаем файл CaseManagementService.svc с примерно таким текстом:
<%@ ServiceHost Language="C#" Debug="true" Service="CaseManagementService.CaseManagementService" Factory="System.ServiceModel.Activation.ServiceHostFactory" %>
Добавить в файл Terrasoft.WebApp\ServiceModel\http\services.config описание сервиса:
В разделе активности, в списочном реестре, если вытянуть поле Контрагент, реестр не загружается (Заголовки колонок тоже пропадают). Если поле убрать все работает нормально. Данная проблема наблюдается под одним пользователем, на разных компьютерах и браузерах.
Подскажите на что стоит обратить внимание, в чем может быть проблема? Может кто то сталкивался?
В разделе Документы настроены итоги, записи раздела отфильтрованны по быстрому фильтру даты (например выбран Сегодня), при переходе в другой раздел и возврате в раздел Документы итоги отображаються как если бы фильтра по дате не было, если нажать Сегодня итоги обновляться и отображаться верно.
Подскажите пожалуйста, с чем это может быть связанно и что возможно сделать, что бы исправить проблему?
Подскажите, пожалуйста, каким образом можно добавить клиентским кодом на страницу элементы сторонних сайтов, напр. видео YouTube, либо контейнер iframe?
Здравствуйте,
В системе есть пример уже, это встраивание видео на главной странице.
Суть написания сводится к использованию контейнера, и написанию в нем произвольного html кода, вплоть до iframe.
Еще пример:
Только с iframe стоит учитывать, что встраивать можно только те страницы, которые себя в явном виде позволяют встраивать, к примеру, youtube видео, а также протокол передачи должен совпадать, если ваш сайт работает через https, то и страница фрейма должна работать через https.
А при компиляции он ругается на AppScheduler и SimpleTriggerImpl, типа, они не объявлены.
Пробовал перед ними добавлять альясы Usings, например "Terrasoft.Core.Scheduler.AppScheduler", но это тоже не помогло.
Связано ли это с тем, что БП, создающий Job находится в созданном мной отдельном пакете, в котором из зависимостей только базовые пакеты Base и UIv2?
Добрый день! Нужно создать справочник с страницей редактирования.
Был создан скрытый раздел, и добавлен справочник. Но возникает проблема, при попытке открыть справочник открывает:
как видите в адресной строке пишет: LookupSectionModule/ilayDocReportsSection
и не отображаются записи справочника.
Но после нажатия Вид -> Настроить колонки(или Настроить итоги) и возврата с страницы настройки назад:
и в адресной строке теперь пишет: SectionModuleV2/ilayDocReportsSection
Смотрел в базе как зарегистрирован аналогичный справочник "Библиотека блоков контента" не нашел отличий.
:
Если не ошибаюсь, для реализации подобной задачи справочник необходимо регистрировать как отдельный раздел. В Бд есть хранимые процедуры RegisterPage и RegisterSection.
Как альтернатива - можно создать отдельный раздел мастером, после чего добавить его на необходимую страницу, как справочное поле.
Если не ошибаюсь, для реализации подобной задачи справочник необходимо регистрировать как отдельный раздел. В Бд есть хранимые процедуры RegisterPage и RegisterSection.
Как альтернатива - можно создать отдельный раздел мастером, после чего добавить его на необходимую страницу, как справочное поле.
Добрый день, Илья!
Дело в том что как раз через мастер и был создан раздел, и "страницой реестра" была указана секция созданного мастером раздела.
И проблема в том, что при открытии наполнения справочника сначала открывается LookupSectionModule/ilayDocReportsSection, а после открытия страницы настройки колонок открывается уже SectionModuleV2/ilayDocReportsSection.
Здравствуйте,
Отличный вопрос. Виной всему вот этот метод:
Если посмотреть схему секции контент блока, Вы увидите, что он переопределен, это же необходимо сделать и Вам в Вашей странице секции, а так же добавить зависимость:
getProfileKey: function(){
var currentTabName =this.getActiveViewName();
var schemaName =this.name;return schemaName +this.entitySchemaName+"GridSettings"+ currentTabName;}
Почистить кеш, перезайти на сайт, зайти в наполнение справочника, заново настроить колонки, после чего они удачно сохранятся и будут корректно открываться без всяких манипуляций с «настроить колонки» каждый раз при заходе в наполнение.
На днях озаботились такой проблемой - клиенту надо посмотреть почтовые сообщения за последнюю неделю. И опа :) В стандартном почтовом клиенте средств фильтрации нет никаких, о папках вообще молчу. Решение - выводить в списке активностей еще и e-maily, благо активности и есть.
Реализация занимает 5 минут, поэтому, если кому надо, то вот последовательность действий:
1. Создаем замещающую схему секции активностей (ActivitySectionV2).
2. В ней определяем, что используем. Нам надо по большому счету BaseFiltersGenerateModule и все.
3. В ветке methods сносим фильтр NotEmailFilter, который собственно и убирает из списка активностей e-maily
getFilters:function(){ var filters =this.callParent(arguments); if(filters.contains("NotEmailFilter")){
filters.removeByKey("NotEmailFilter"); } return filters; }
сохряняем, сносим кэш браузера и готово. Можно наслаждаться отбром почты по любым параметрам.
Используется bpm'online sales 7.7 после перехода с Terrasoft, т.е. все данные были перенесены (порядка 6 млн. активностей). По результату Terrasoft отрабатывает быстрее чем bpm'online. При запуске сайта локально на сервере отрабатывает терпимо, при работе на пользовательских машинах иногда данные вообще не отображаются.
Может кто-то может поделится наработками в вопросе увеличения быстродействия bpm'online on-site: где/как искать проблему, как/что оптимизировать.
Здравствуйте.
Не совсем корректно сравнивать быстродействие BPM'online и Terrasoft 3.x. Эти систему имеют абсолютно разную архитектуру. Terrasoft 3.x - это классическая 2-звенная (клиент-сервер) система. BPM'online - 3-звенная (клиент-сервер базы данных-сервер приложения). Если время отклика системы значительно отличается на сервере и на клиентской машине, тогда стоит обратить внимание на сетевую составляющую (стабильность, ширина канала, особенно если это Wi-Fi), также нужно обеспечить достаточные ресурсы клиентской машины (часть кода выполняется, именно, на пользовательском ПК). Также рекомендую в connectionstrings.config в строке подключения к Redis-серверу не использовать в качестве адреса localhost или 127.0.0.1 (указать сетевое имя или реальный IP-адрес). В противном случае игнорируются параметры, отвечающие за многопоточность подключения к Redis. Это параметры maxReadPoolSize=; maxWritePoolSize= (находятся в том же файле в той же строке). По умолчанию равны – 25. Желательно устанавливать по количеству пользователей *10.
Олег, а вы в браузере посмотрите сколько запросов идет и как долго они выполняются. Таким образом сможете выявить где тормозит и падает.
Например, в версии 7.2 по умолчанию стоит таймаут в 30 секунд на запрос. В результате любые запросы более менее тяжелые могут отвалиться, и при этом останется висеть лоадер, либо просто ничего не произойдет. Самый простой вариант, руками поменять таймаут на больший. И как показывает практика, больше времени уходит на обработку уже на серверной стороне.