ESQ
Filter
BuildEsqFilter
filterGroup
ParameterExpression
Expected_hex_0x_in_'{0}'
Sales_Creatio
#7.18

Добрый день, требуется вытащить в коде фильтр из группы.



Вытащил json следующего фильтра из бд

{
  "className": "Terrasoft.FilterGroup",
  "items": {
    "572334d2-7d75-43e0-b60f-c2cdfca071ea": {
      "className": "Terrasoft.InFilter",
      "filterType": 4,
      "comparisonType": 3,
      "isEnabled": true,
      "trimDateTimeParameterToDate": false,
      "leftExpression": {
        "className": "Terrasoft.ColumnExpression",
        "expressionType": 0,
        "columnPath": "TestColumn"
      },
      "isAggregative": false,
      "key": "572334d2-7d75-43e0-b60f-c2cdfca071ea",
      "dataValueType": 10,
      "leftExpressionCaption": "TestColumn",
      "referenceSchemaName": "TestColumn",
      "rightExpressions": [
        {
          "className": "Terrasoft.ParameterExpression",
          "expressionType": 2,
          "parameter": {
            "className": "Terrasoft.Parameter",
            "dataValueType": 10,
            "value": {
              "Name": "Да",
              "Id": "3631ec86-e4cd-490c-9614-cea3bbf71187",
              "value": "3631ec86-e4cd-490c-9614-cea3bbf71187",
              "displayValue": "Да"
            }
          }
        }
      ]
    }
  },
  "logicalOperation": 0,
  "isEnabled": true,
  "filterType": 6,
  "rootSchemaName": "Contact",
  "key": "FolderFilters"
}

Далее с помощью этого метода пытаюсь создать esq фильтр

 

public static EntitySchemaQuery GetEsqByFilterData(UserConnection userConnection, string filterData)
        {
            if (userConnection is null) { throw new ArgumentNullException(nameof(userConnection)); }
 
            if (filterData is null) { throw new ArgumentNullException(nameof(filterData)); }
 
            var filters = Terrasoft.Common.ServiceStackTextHelper.Deserialize<Terrasoft.Nui.ServiceModel.DataContract.Filters>(filterData);
 
            string rootSchemaName = filters.RootSchemaName;
            if (string.IsNullOrEmpty(rootSchemaName))
            {
                return null;
            }
            IEntitySchemaQueryFilterItem esqFilters = filters.BuildEsqFilter(rootSchemaName, userConnection);
            var queryFilterCollection = esqFilters as EntitySchemaQueryFilterCollection;
            var esq = new EntitySchemaQuery(userConnection.EntitySchemaManager, rootSchemaName);
 
            if (queryFilterCollection != null)
            {
                if (queryFilterCollection.Count == 0)
                {
                    return esq;
                }
                esq.Filters.LogicalOperation = queryFilterCollection.LogicalOperation;
                esq.Filters.IsNot = queryFilterCollection.IsNot;
                esq.Filters.IsEnabled = queryFilterCollection.IsEnabled;
                foreach (IEntitySchemaQueryFilterItem filter in queryFilterCollection)
                {
                    esq.Filters.Add(filter);
                }
            }
            else
            {
                esq.Filters.Add(esqFilters);
            }
 
            return esq;
        }

При запуске получаю ошибку Expected hex 0x in '{0}'.

Проблему нашёл, она заключается в том что в json "value" является объектом, а не guid. Если "value": "3631ec86-e4cd-490c-9614-cea3bbf71187", то метод отрабатывает корректно. 



Единственный вариант исправления этой проблемы пока только замена с помощью регулярной строки value объект на value guid. Есть ли другие варианты решения данной проблемы.

Нравится

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

Добрый день,

 

В базовой реализации уже есть логика, которая вытаскивает из группы (папки) фильтры. Она находится в CommonUtilities, нужно смотреть в метод GetFolderEsqFilters, он в конце возвращает esqFilters (типа IEntitySchemaQueryFilterItem). Думаю это то что Вам нужно.

Добрый день,

 

В базовой реализации уже есть логика, которая вытаскивает из группы (папки) фильтры. Она находится в CommonUtilities, нужно смотреть в метод GetFolderEsqFilters, он в конце возвращает esqFilters (типа IEntitySchemaQueryFilterItem). Думаю это то что Вам нужно.

Показать все комментарии
установка
значений
колонок
через
ESQ
на
клиентской
части
Studio_Creatio
7.16

Добрый день, есть такая задача:

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

Я реализовала кнопку у детали "Документы по заказу", при нажатии на которую, обновляется значение определенной колонки у этой детали и должно обновляться значение определенной колонки (имя колонки прописано в коде, пусть будет "NameColumn") в детали "Позиции заказа".

Вопрос: Как обновить значение колонки (какой-либо) в первой детали "позиции заказа"? 

Пыталась сделать это так:

Колонка в детали "Документы по заказу" обновляется через UpdateQuery, нашла инструкцию в документации.

С обновлением колонок детали в первой вкладке наткнулась на трудность. 

Я пыталась сделать коллекцию через esq по позициям заказа. Устанавливаю значение через инструкцию this.set("NameColumn",value);

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

Нравится

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

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

 

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

var value = this.get('UsrChange') – новое значение, которое вы хотите записать в необходимую колонку;

updateQuery.setParameterValue("Name", value, this.Terrasoft.DataValueType.TEXT) – в значение "Name" ("Notes" во втором запросе) подставьте название поля из объекта, в которое хотите записать изменения;

Удостоверьтесь, что вы добавили в дизайнере нужные поля на страницу. "detailColumn" - “Where detail column”, "masterColumn" - “Equals to page column”, "entitySchemaName"  - “Detail”.

 

...
details: /**SCHEMA_DETAILS*/{"UsrTabSecond": {
        "schemaName": "OpportunityHistoryActivityDetail",
        "entitySchemaName": "Activity",
        "filter": {
        "detailColumn": "Priority",
        "masterColumn": "UsrLookup2"
        }
    },
    "UsrTabFirst": {
        "schemaName": "AccountContactsDetailV2",
        "entitySchemaName": "Contact",
        "filter": {
        "detailColumn": "Job",
        "masterColumn": "UsrLookup1"
        }
    }
}/**SCHEMA_DETAILS*/,
    businessRules: /**SCHEMA_BUSINESS_RULES*/{}/**SCHEMA_BUSINESS_RULES*/,
        methods: {
             onMyButtonClick: function() {
                   var value = this.get('UsrChange');
                   var IdValue = this.get('UsrLookup1');
                   var updateQuery = Ext.create("Terrasoft.UpdateQuery", {
                       rootSchemaName: "Contact"});
                   var filters = updateQuery.filters;
                   filters.addItem(this.Terrasoft.createColumnFilterWithParameter(
                       this.Terrasoft.ComparisonType.EQUAL, "Job", IdValue));
                   updateQuery.setParameterValue("Name", value, this.Terrasoft.DataValueType.TEXT);
                   updateQuery.execute(function(result){
                       if(result.success) this.updateDetail({detail: "UsrTabFirst"});}, this);                                 
                                                          
                   var value1 = this.get('UsrChange');
                   var IdValue1 = this.get('UsrLookup2');
                   var updateQuery1 = Ext.create("Terrasoft.UpdateQuery", {
                       rootSchemaName: "Activity"});
                   var filters1 = updateQuery1.filters;
                   filters1.addItem(this.Terrasoft.createColumnFilterWithParameter(
                       this.Terrasoft.ComparisonType.EQUAL, "Priority", IdValue1));
                   updateQuery1.setParameterValue("Notes", value1, this.Terrasoft.DataValueType.TEXT);
                   updateQuery1.execute(function(result){
                       if(result.success) this.updateDetail({detail: "UsrTabSecond"});}, this);
        }
},
...

 

С уважением,

Ангелина

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

 

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

var value = this.get('UsrChange') – новое значение, которое вы хотите записать в необходимую колонку;

updateQuery.setParameterValue("Name", value, this.Terrasoft.DataValueType.TEXT) – в значение "Name" ("Notes" во втором запросе) подставьте название поля из объекта, в которое хотите записать изменения;

Удостоверьтесь, что вы добавили в дизайнере нужные поля на страницу. "detailColumn" - “Where detail column”, "masterColumn" - “Equals to page column”, "entitySchemaName"  - “Detail”.

 

...
details: /**SCHEMA_DETAILS*/{"UsrTabSecond": {
        "schemaName": "OpportunityHistoryActivityDetail",
        "entitySchemaName": "Activity",
        "filter": {
        "detailColumn": "Priority",
        "masterColumn": "UsrLookup2"
        }
    },
    "UsrTabFirst": {
        "schemaName": "AccountContactsDetailV2",
        "entitySchemaName": "Contact",
        "filter": {
        "detailColumn": "Job",
        "masterColumn": "UsrLookup1"
        }
    }
}/**SCHEMA_DETAILS*/,
    businessRules: /**SCHEMA_BUSINESS_RULES*/{}/**SCHEMA_BUSINESS_RULES*/,
        methods: {
             onMyButtonClick: function() {
                   var value = this.get('UsrChange');
                   var IdValue = this.get('UsrLookup1');
                   var updateQuery = Ext.create("Terrasoft.UpdateQuery", {
                       rootSchemaName: "Contact"});
                   var filters = updateQuery.filters;
                   filters.addItem(this.Terrasoft.createColumnFilterWithParameter(
                       this.Terrasoft.ComparisonType.EQUAL, "Job", IdValue));
                   updateQuery.setParameterValue("Name", value, this.Terrasoft.DataValueType.TEXT);
                   updateQuery.execute(function(result){
                       if(result.success) this.updateDetail({detail: "UsrTabFirst"});}, this);                                 
                                                          
                   var value1 = this.get('UsrChange');
                   var IdValue1 = this.get('UsrLookup2');
                   var updateQuery1 = Ext.create("Terrasoft.UpdateQuery", {
                       rootSchemaName: "Activity"});
                   var filters1 = updateQuery1.filters;
                   filters1.addItem(this.Terrasoft.createColumnFilterWithParameter(
                       this.Terrasoft.ComparisonType.EQUAL, "Priority", IdValue1));
                   updateQuery1.setParameterValue("Notes", value1, this.Terrasoft.DataValueType.TEXT);
                   updateQuery1.execute(function(result){
                       if(result.success) this.updateDetail({detail: "UsrTabSecond"});}, this);
        }
},
...

 

С уважением,

Ангелина

Anhelina, 

большое спасибо за помощь!

Показать все комментарии
SQL
MSSQL
ESQ
SQL запрос
запрос
класс
select
selectquery
8.0

Просьба помочь собрать такой запрос при помощи класса select

select count(ID)

   from [dbo].[Case] AS TI

WHERE DATEPART(HOUR, DATEADD(hh, 6, TI.[UsrDueDate])) >=9  and DATEPART(HOUR, DATEADD(hh, 6, TI.[UsrDueDate])) <12

and FORMAT(DATEADD(hh, 6, TI.[UsrDueDate]), N'yyyy.MM.dd') = FORMAT(cast(GETDATE() +1 as date), N'yyyy.MM.dd')

Заранее спасибооо! 

Нравится

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

Добрый день, Адилет.

 

Вы можете настроить SQL-представление (view) с нужным условием.

 

А потом из этого представления уже делать выборку с помощью класса select.

 

Или же попробовать сделать по аналогии с примерами запросов по этой ссылке.

Alla Savelieva,

Благодарю, будем пробовать.

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

В БД формируется такой запрос:

SELECT Name, Data FROM SysModuleReportTable

 

Колонки Data нет в таблице SysModuleReportTable, но это уже детали.

 

Как найти откуда в коде этот запрос генерируется?

Какие есть варианты?

Нравится

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

Евгений, добрый день!

Исходя из таблицы SysModuleReportTable, этот запрос связан с печатными формами. Не могу точно сказать когда именно он формируется, однако, можно поступить следующим образом. Если вы используете mssql, то в меню Manegement->Extended Events->Session и там создать свою сессию, которая будет отлавливать вес запросы уходящие в бд. После запуска этой сессии повыполняйте действия с печатными формами и посмотрите. после чего в бд появляется этот запрос.

Показать все комментарии
ESQ
хранимая функция
7.17

Доброе день, возник вопрос как из функции, которая возвращает таблицу передать параметры и вернуть её в формате ESQ (либо же DataTable, который можно преобразовать в ESQ)?

Нравится

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

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

Ответил на Ваш предыдущий вопрос.

Нужно реализовывать фильтрацию сразу на ESQ без применения хранимых процедур и функций.

Показать все комментарии
Хранимая процедура
SQL
бизнес-процесс
ESQ
storedprocedure
7.17

Добрый вечер, возникла задача:

Нужно отфильтровывать SQL-запрос и создавать из него xlsx отчёт.

Фильтрация реализована с помощью хранимой процедуры, в которую из БП передаются значения, выбранные пользователем, но выгрузка в xlsx с помощью ExportToExcel выгружает только esq.

Можно ли как-нибудь перевести StoredProcedure в ESQ? Если нет, то как выгрузить StoredProcedure в xlsx?

 

Нравится

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

Добрый вечер.

 

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

 

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

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

Спасибо. Не подскажите, а как потом хранимую функцию вызвать в БП и передать в esq?

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

И стандартными механизмами работать дальше с ESQ.

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

var esq = new EntitySchemaQuery(UserConnection.EntitySchemaManager, "EDOSystem");

var edoSystemIdColumn = esq.AddColumn("Id");

esq.Filters.Add(esq.CreateFilterWithParameters(FilterComparisonType.Equal, "Name", "SomeVAlue"));

Как можно сделать подобный фильтр по колонке Name без учитывания регистра?

 

Нравится

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

А найти записи по другому условию не выйдет?

Там же не одно поле, может есть какие то связи вввиде справочников . которые на странице

 

 

Ну стандартного типа я не нашел для сравнения без регистра, но наверное вы можеет создать группу фильтров

var esq = this.Ext.create("Terrasoft.EntitySchemaQuery", {
	rootSchemaName: "qrtEventAddRule"
});
esq.addColumn("qrtEventShipping.Id", "idEvent");
 
 
 
var filterGroup1 = Ext.create("Terrasoft.FilterGroup");
var esqSecondFilter = esq.createColumnFilterWithParameter(Terrasoft.ComparisonType.EQUAL, "Name", value);
filterGroup1.add("qrtIsInsuranceTrue", esqSecondFilter);
 
var esqFirstFilter = esq.createColumnFilterWithParameter(Terrasoft.ComparisonType.EQUAL, "qrtService", value.toLowerCase());
filterGroup2.add("esqFirstFilter", esqFirstFilter);	
filterGroup2.logicalOperation = Terrasoft.LogicalOperatorType.OR;
 
esq.filters.add("1",filterGroup1);
}

Я не уверен но можно посмотреть также в сторону CONTAINS. он может без регистра сравнивает

 

самый извращенный и долгий вариант это написать в теле выборки свой фильтр и пройтись по все записям

 

// Определение конфигурационного объекта.
				var config = {
					// Название схемы сущности.
					entitySchemaName: "Contact",
					// Убирать дубли в результирующем наборе данных.
					isDistinct: true
				};
				// Получение данных.
				Terrasoft.DataManager.select(config, function (collection) {
					// Сохранение полученных записей в локальное хранилище.
					collection.each(function (item) {
                       //тут пишешь свое условие
						//this.console.log(item.viewModel.values)//поля записи
						Terrasoft.DataManager.addItem(item);
					});
				}, this);

 

Показать все комментарии
using System;
using System.Collections.Generic;
using Terrasoft.Core;
using Terrasoft.Core.DB;
using Terrasoft.Core.Entities;
 
namespace Norbit.Crm.EsPlus.Common
{
    /// <summary>
    /// Методы расширения для ESQ.
    /// </summary>
    public static class EsqExtensions
    {
        /// <summary>
        /// Постраничное чтение ESQ запроса.
        /// </summary>
        /// <param name="esq">Запрос.</param>
        /// <param name="userConnection">Пользовательское подключение.</param>
        /// <param name="rowCount">Количество считываемых записей за один раз(по умолчанию 1000).</param>
        /// <returns>Коллекция объектов из запроса.</returns>
        public static List<Entity> GetEntityCollectionPaginatedReading(
            this EntitySchemaQuery esq,
            UserConnection userConnection,
            EntitySchemaQueryOptions options = null,
            int rowCount = 1000)
        {
            if (esq == null)
            {
                throw new ArgumentNullException(nameof(esq));
            }
 
            if (userConnection == null)
            {
                throw new ArgumentNullException(nameof(userConnection));
            }
 
            var entityCollection = new List<Entity>();
            esq.UseOffsetFetchPaging = true;                              /// Использовать постраничное чтение.
 
            var esqOptions = options ?? new EntitySchemaQueryOptions                     /// Настройки получения коллекции.
            {
                PageableRowCount = rowCount,                             /// Количество считываемых записей.
                RowsOffset = 0,                                          /// Количество записей, которые необходимо пропустить
                PageableDirection = PageableSelectDirection.Next,
                PageableConditionValues = new Dictionary<string, object>()
            };
 
            var entities = esq.GetEntityCollection(userConnection, esqOptions);
 
            while (entities.Count > 0)
            {
                foreach (var entity in entities)
                {
                    entityCollection.Add(entity);
                }
                esqOptions.RowsOffset += esqOptions.PageableRowCount;
                esq.ResetSelectQuery();                                     /// Обнуление результата запроса.
                entities = esq.GetEntityCollection(userConnection, esqOptions);
            }
 
            return entityCollection;
        }
 
    }
}

 

Нравится

Поделиться

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

Есть подозрение. что поможет 

PageableDirection = PageableSelectDirection.First  при первом запросе.

потом сменить на Next

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

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

where encode("InputArguments", 'escape') like '%identifierNo=cardnumber123%';



По той же логике пытался сделать фильтр через ESQ такого формата:

esq.Filters.Add(
    esq.CreateFilterWithParameters(
        FilterComparisonType.Contain,
        "InputArguments",
        Encoding.UTF8.GetBytes(inputArguments)
));



В результате получаю исключение:  "42883: function upper(bytea) does not exist". Есть ли обходные пути для реализации данного фильтра?

Нравится

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

Добрый день, Александр.

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

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

Добрый день,  коллеги. Помогите пожалуйста сделать ESQ запрос с фильтрацией. Вывести 

TOP1 номер который соответсвует критерию. 

Читал статьи так и не понял.  Данный запрос в разделе контакты.

Select T1."Number" From  "ContactCommunication" T1

LEFT JOIN "Contact" T2 ON T1.Contact=T2.Id

Where T1.CommunicationType='3DDDB3CC-53EE-49C4-A71F-E9E257F59E49'

 

onEntityInitialized: function() {

        this.callParent(arguments);

        this.helping();

    },

    helping: function() {

     // Получаем [Id] объекта карточки.

      // Получаем [Id] объекта карточки.

var recordId = this.get("Id");

// Создаем экземпляр класса Terrasoft.EntitySchemaQuery с корневой схемой [Contact].

var esq = this.Ext.create("Terrasoft.EntitySchemaQuery", {

    rootSchemaName: "Contact"

});

esq.addColumn("[ContactCommunication:Contact:Id].Number", "Number");

esq.addColumn("[ContactCommunication:Contact:Id].CommunicationType", "CommunicationType");

var esqFirstFilter= esq.createColumnFilterWithParameter(Terrasoft.ComparisonType.EQUAL, "CommunicationType", "3DDDB3CC-53EE-49C4-A71F-E9E257F59E49");

// Добавление созданных фильтров в коллекцию запроса. 

esq.filters.add("esqFirstFilter", esqFirstFilter);

// В данную коллекцию попадут объекты - результаты запроса, отфильтрованные по двум фильтрам.

esq.getEntityCollection(function (result) {

    if (result.success) {

      result.collection.each(function (item) {

      this.showInformationDialog(result.entity.get("Number"));

        });

    }

}, this);

        }

Нравится

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

Вот мой пример по копированию всех адресов электронной почты контакта, по аналогии можно выбрать топ1 нужного типа

/** Копирование почты */
copyEmail: function() {
	if (this.get("Contact")) {
		var contact = this.get("Contact");
		contact = contact.value;
		var esq = Ext.create("Terrasoft.EntitySchemaQuery", {
			rootSchemaName: "ContactCommunication"
		});
		var email = "";
		esq.addColumn("Contact");
		esq.addColumn("CommunicationType");
		esq.addColumn("Number");
		// Создание экземпляра первого фильтра.
		var esqFirstFilter = esq.createColumnFilterWithParameter(Terrasoft.ComparisonType.EQUAL, "Contact", contact);
		// Создание экземпляра второго фильтра.
		var esqSecondFilter =	esq.createColumnFilterWithParameter(Terrasoft.ComparisonType.EQUAL,
							"CommunicationType", "ee1c85c3-cfcb-df11-9b2a-001d60e938c6");
		// Фильтры в коллекции фильтров запроса будут объединяться логическим оператором OR. 
		esq.filters.logicalOperation = Terrasoft.LogicalOperatorType.AND;
		// Добавление созданных фильтров в коллекцию запроса. 
		esq.filters.add("esqFirstFilter", esqFirstFilter);
		esq.filters.add("esqSecondFilter", esqSecondFilter);
		// В данную коллекцию попадут объекты - результаты запроса, отфильтрованные по двум фильтрам.
		esq.getEntityCollection(function(result) {
			if (result.success) {
				result.collection.each(function(item) {
					// Обработка элементов коллекции.
					email = email + " " + this.get("Number");
				});
				this.set("UsrEmail", email);
			}
		}, this);
}

 

Вот мой пример по копированию всех адресов электронной почты контакта, по аналогии можно выбрать топ1 нужного типа

/** Копирование почты */
copyEmail: function() {
	if (this.get("Contact")) {
		var contact = this.get("Contact");
		contact = contact.value;
		var esq = Ext.create("Terrasoft.EntitySchemaQuery", {
			rootSchemaName: "ContactCommunication"
		});
		var email = "";
		esq.addColumn("Contact");
		esq.addColumn("CommunicationType");
		esq.addColumn("Number");
		// Создание экземпляра первого фильтра.
		var esqFirstFilter = esq.createColumnFilterWithParameter(Terrasoft.ComparisonType.EQUAL, "Contact", contact);
		// Создание экземпляра второго фильтра.
		var esqSecondFilter =	esq.createColumnFilterWithParameter(Terrasoft.ComparisonType.EQUAL,
							"CommunicationType", "ee1c85c3-cfcb-df11-9b2a-001d60e938c6");
		// Фильтры в коллекции фильтров запроса будут объединяться логическим оператором OR. 
		esq.filters.logicalOperation = Terrasoft.LogicalOperatorType.AND;
		// Добавление созданных фильтров в коллекцию запроса. 
		esq.filters.add("esqFirstFilter", esqFirstFilter);
		esq.filters.add("esqSecondFilter", esqSecondFilter);
		// В данную коллекцию попадут объекты - результаты запроса, отфильтрованные по двум фильтрам.
		esq.getEntityCollection(function(result) {
			if (result.success) {
				result.collection.each(function(item) {
					// Обработка элементов коллекции.
					email = email + " " + this.get("Number");
				});
				this.set("UsrEmail", email);
			}
		}, this);
}

 

Быстров Сергей, Спасибо, сейчас попробуем. 

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