Добрый день!

От имени пользователя добавляю запись в таблицу OpportunityFile с помощью POST запроса - все успешно. При попытке загрузить файл в поле Data с помощью запроса PUT, запрос возвращает ошибку 404. Если у пользователя повысить права до администратора, запрос успешно выполняется. Не могу понять что надо настроить чтобы запрос выполнялся от имени простого пользователя.

Подскажите, пожалуйста.

Нравится

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

Руслан, вообще, довольно странно. Если бы там было ограничено по правам то отдавало бы 403 для пользователя. Можете показать примеры запросов? Точно в обоих случаях одинаковые?

 

Также проверьте, нет ли ограничений прав на объекте OpportunityFile: на операции, записи, поле Data.

Вот такой код запроса на изменение поля Data:

curl --location --request PUT 'https://my.company.com/0/odata/OpportunityFile(f7cb87e9-dea4-4609-95f6-5ce7ca499544)/Data' \
--header 'BPMCSRF: qR7NHdxnvLVamFiF4QnCsu' \
--header 'Content-Type: application/pdf' \
--header 'Cookie: BPMLOADER=hhv02mucokh1hdvnnrrna43e; UserName=99|97|98|105|110|101|116|68|105|115|116|114|105|98|117|116|111|114; BPMSESSIONID=zxfuxvecmcjbg0vteh1ibpvh; .ASPXAUTH=45F3530449360B52EC8592892D0588D74F78D2F37E2A91893700AB56ED32E3589FE972BF81199EE6EF9DBA8D6F3A0341586187B1D5C03358262C3E0DF181FE5DE68AB65946E18A742A83EAB74BD1A5DDE5BE6A99CEA0F5EC1574FE9A746D193CB161A330A4732121728AA5F43D2F4D252D844867C0CEDD6E9BC507012C6FF202A540A311D80980E6C4290C8426EF0E0E37B73543828236BFC59E8EABB3158132B0BA2566D72649E0942BC708DDA1264793A6302875345757E32AA1F8E8688C1C8DD9A192CA06BF8B3BC077C420F46C6211B3D37BEB674450533514FF33AA21D72B616E22CEDBBC999D5AB7B66F0BF45E10290E814024AB1550A8455978A60BC124CDE2458A99E26613202F55BA2BF2381FC057CAAF0669170605291C61DD6764A25A3694999D88BE8E3ACA371C225B88D02D46F40791CB11B6928C7587231A3E04CED54910D82498580AA62AC2DBF66088F43B35E09B07ABE3F8182A9250501969C958DBC9464682AE07DEF13C7D1D4C910AEAFBD8A6C2CEE558DA3087EB956F8EE24346; BPMCSRF=qR7NHdxnvLVamFiF4QnCsu' \
--data-binary '@/D:/Docs/Pictures/kyoScan-‎4‎.‎23‎.‎2020-‎14‎.‎07‎.‎08.pdf'

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

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

Ограничения по колонкам не настроены.

У пользователя есть право на системную операцию CanUseODataService? Хотя, выше пишете, что POST у него прошёл...

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

Да, есть. 

Можно было бы понять если бы были ошибки типа 401, 403 или 406, но никак не 404. Руслан, а если завести демо-сайт в облаке и отправлять такие запросы на него, тоже воспроизводится? 

Спасибо, Александр, за подсказку. Развернул тестовую базу в облаке с версией 7.17, у нас 7.16.4. Боялся что из-за разницы в версиях ошибка не воспроизведется. Но, нет, ошибка такая же. 

Руслан, у меня на демо-сайте 7.17 не воспроизвелось: и под админом, и под пользователем файл успешно обновился. Пробовал не в curl, а в Fiddler, и на тестовом сайте не была включена проверка BPMCSRF. Пользователя в группы не включал, права на чтение и изменение записи о продаже дал вручную. Файл создал в интерфейсе, то есть на момент запроса поле Data уже было со старым содержимым.

Значит, ошибка не в «коробке», а что-то неправильно отправляете к серверу. 

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

Что не правильно может отправляться на сервер, если от администратора запрос успешно отрабатывает, а от пользователя нет? Повторю - запрос не меняется, значит запрос правильный.

У меня и под администратором, и под пользователем файл обновляется. Без точного описания, как воспроизвести, не могу сказать, что неправильно.

 

А если, как и в моём примере, файл на момент запуска PUT уже есть, у Вас тоже такая ошибка?

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

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

Вот результат запроса от имени администратора (200 Ок):

Вот результат от имени пользователя 404 (Not Found):

Выполнено на тестовой платформе - https://041470-sales-enterprise.terrasoft.ru/

Руслан, попробуйте не в Postman, а в Fiddler, как на моих скриншотах. Сейчас приведенного тестового сайта уже нет, проверить на нём не могу.

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

Ссылка на тестовую платформу устарела, сгенерировал новое тестовое приложение

Все таки дело было в правах, но оказалось что права проверялись не на таблицу OpportunityFile, а на Opportunity. Интересно получается, создать запись в таблице OpportunityFile разрешено, а изменить нет. Неожиданно.

Руслан, то есть на нескольких разных тестовых базах было одинаково настроено с правами? А как именно? После выдачи пользователю прав на изменение продажи заработало?

Хороший вопрос, Александр. Проверял на двух площадках: тестовое приложение компании и тестовая, которая дается в рамках тест-драйва от Террасофт. Права, конечно же, были настроены по разному. На тот момент я не думал что дело было в правах, и на тестовой площадке ни чего кроме того что добавил тестового пользователя не делал. Сегодня проверил как было настроено на тестовой платформе - там вообще ни чего не было настроено по правам для тестового пользователя (у создателя тестовой площадки права администратора. Продажа принадлежит администратору). К тому же, самое важное, что я сегодня обнаружил, это то что запрос на изменение данных начинает работать если добавить права на ЧТЕНИЕ продажи, а не на изменение. Т.е. я сначала сразу добавил права на изменение продажи - не работает, добавил на чтение - заработало, убрал права на изменение (запрещающих прав нет) - все равно работает. Как то не логично, если же есть логика, дайте, пожалуйста, объяснение.

Руслан, пожалуйста, дайте точные параметры воспроизведения: под какими пользователями, какую запись, какой запрос. Я выше пробовал на своей тестовой, у меня работает и без прав админа.

Либо напишите всё это в поддержку.

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

Добрый день!

Пытаюсь по инструкции создать запись в таблице OpportunityFile. 

При отправке запрос сервер возвращает 500 ошибку Internal Server Error. 

Подскажите, пожалуйста, что не так делаю?

Код запроса:

curl --location --request POST 'https://my.domain.com/0/odata/opportunityfile' \
--header 'BPMCSRF: mbaZawvWrbrf0jub0ayr7O' \
--header 'Content-Type: application/json' \
--header 'Cookie: UserName=username; BPMLOADER=5vurytjra4qxcy2ym1xjsfji; .ASPXAUTH=8FFC3373A07416E90889F9B924E2FFB16B7A75335673C6A585B4FBD7941A36C709548056A1C53030AA0A65C6BC5B9C1F4D5B58195B65BA3BDBFF5F34542A423746FE1329F390C67190FD230C47E8834F89E7D448FC2C14D32C46FF77C6BB64D7A47057BA009B80E4F31093730B41DFDADEF0F70DC3A1A7130D6450ACC136080AB6DACE828A8B68420B4701C983818AFD1CF38573D7DC5E127180D93C8B8D86314CBCFCB86CCEE9E7B5EB1D30248EA25B3AC465E7ADFFA0E891FAB31B2A72627E88A5520BFC625E8701DCF9F7756E1287EA7535F445C13633DE89166AB35BE431C315AD66A10F583CD0D4E31FCC6092701635AAC199D74E9B807A632D9A3BBA2AF3BF0893BB09C9CBDBD3BA110E434B9BFEC744B464D1BF833579317AE62D59D99E58C7C3E40FC03C37BB1BFC401CD958E69A12C3B132DDF1AF7DE58176B26DC6F1DCE91B10CDBDBF98464C7D4047C512010D423A074E4A9EBEC2656F2C45CC76B77EF82C241564FE95494EA754421B45B9BDF2FFAE60853A6F6B44CDA4229AB1FF557FD4; BPMCSRF=mbaZawvWrbrf0jub0ayr7O; BPMSESSIONID=0tt2ubyrc4mo2c4ci5dpwg0w' \
--data-raw '{
    "Name": "FieldValue"
}'

 

Нравится

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

Руслан, ошибка 500 означает, что более подробная информация будет в логах приложения на веб-сервере.

Но, скорее всего, просто не заполнено обязательное поле OpportunityId в объекте, по которому должна осуществляться связь с объектом раздела, куда добавляется файл.

Руслан, ошибка 500 означает, что более подробная информация будет в логах приложения на веб-сервере.

Но, скорее всего, просто не заполнено обязательное поле OpportunityId в объекте, по которому должна осуществляться связь с объектом раздела, куда добавляется файл.

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

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

Такого нет, логи общие для всего сайта. Могут писаться в файлы (Error.log и подобные) или в базу, если так настроено логирование.

В файле Error.log имеется единственная запись:

2020-12-23 08:24:08,710 [1] ERROR IIS APPPOOL\BPMonline Terrasoft.Core.Entities.Events.EntityEventListenersLoader CreateListenerInstance - Error while creating entity event instance of type FileSecurityExcludedUriEventListener
Terrasoft.Core.InstanceActivationException: Error creating an instance of the "Terrasoft.Web.FileSecurity.IFileSecurityExcludedUrisProvider" class ---> Ninject.ActivationException: Error activating IFileSecurityExcludedUrisProvider using binding from IFileSecurityExcludedUrisProvider to method
Provider returned null.
Activation path:
  1) Request for IFileSecurityExcludedUrisProvider
 
Suggestions:
  1) Ensure that the provider handles creation requests properly.
 
   at Ninject.Activation.Context.ResolveInternal(Object scope)
   at Ninject.Activation.Context.Resolve()
   at Ninject.KernelBase.Resolve(IRequest request, Boolean handleMissingBindings)
   at Ninject.ResolutionExtensions.Get[T](IResolutionRoot root, IParameter[] parameters)
   at Terrasoft.Core.Factories.ClassFactory.GetInstance[T](Func`1 action)
   --- End of inner exception stack trace ---
   at Terrasoft.Core.Factories.ClassFactory.GetInstance[T](Func`1 action)
   at DynamicInjector7f791e96d519466d85d3463387ed1a0f(Object[] )
   at Ninject.Activation.Context.ResolveInternal(Object scope)
   at Ninject.Activation.Context.Resolve()
   at Ninject.KernelBase.Resolve(IRequest request, Boolean handleMissingBindings)
   at Ninject.ResolutionExtensions.Get[T](IResolutionRoot root, String name, IParameter[] parameters)
   at Terrasoft.Core.Factories.ClassFactory.GetInstance[T](Func`1 action)
   at Terrasoft.Core.Entities.Events.EntityEventListenersLoader.CreateListenerInstance(Type classType)

Имеет ли она отношение к проблеме?

К проблеме, скорее всего, относится то, что в запросе нет значения поля OpportunityId, которое в объекте обязательное.

Или с ним будет то же самое?

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

Добрый день!

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

 

Спасибо!

Нравится

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

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

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

Алексей Следь, спасибо большое!))) В 7.17 не могу её найти((( 

"Удалять записи" не помогает.

 

 

Екатерина, непонятно, почему у Вас подписи переключателей такие, а не «Блокировать удаление, если есть связанные записи в текущем объекте с этим значением» и «Удалять записи из текущего объекта с этим значением», как написано при открытии в дизайнере стандартной детали, вроде «Контрагент в группе». Там у поля связи с разделом выбрано второе значение.

На всякий случай, старая конфигурация доступна на /0/dev_old и можно включить каскадную связь там.

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

Cпасибо большое! Помогло переключение на старую конфигурацию!)

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

Добавляю данные в бизнес-процессе через задание сценарий. Делаем многострочный Insert

var insCertificateData = new Insert(UserConnection).Into("NavCertificateData");
foreach (var entity in navCertificateData)
{
	insCertificateData.Values()
		.Set("NavCertificate", Column.Parameter(certificateId))
		.Set("NavProduct", Column.Parameter(entity.NavProduct ))
		.Set("NavDiametr", Column.Parameter(entity.NavDiametr))
		.Set("NavMarka", Column.Parameter(entity.NavMarka,))
		.Set("NavNippelAssembly", Column.Parameter(entity.NavNippelAssembly))
		.Set("NavQuantity", Column.Parameter(entity.NavQuantity))
		.Set("NavWeight", Column.Parameter(entity.NavWeight))
		.Set("NavMinUES", Column.Parameter(entity.NavMinUES))
		.Set("NavMaxUES", Column.Parameter(entity.NavMaxUES))
		.Set("NavMinLength", Column.Parameter(entity.NavMinLength))
		.Set("NavMaxLength", Column.Parameter(entity.NavMaxLength))
		.Set("NavDK", Column.Parameter(entity.NavDK))
		.Set("NavBend", Column.Parameter(entity.NavBend))
		.Set("NavKTP", Column.Parameter(entity.NavKTP))
		.Set("NavCompanyGrafit", Column.Parameter(entity.NavCompanyGrafit));
}
insCertificateData.Execute();

В основном все типа string. В значении NavNippelAssembly иногда приходит EmptyString. И в этом случае получаем ошибку 

"Для параметра \"P5\" со значением null необходимо указать тип данных"

Собственно есть Column.Parameter(Object,DataValueType). Полагаю, что надо его использовать, но как указать этот самый DataValuType? Тип строка  

Нравится

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

Алексей Следь,

Посмотрите обсуждение в этой теме.

Для установки null можно использовать Column.Const(null)

Николай Кузьмин,

Так у меня ж не всегда null и 5 полей, где может быть string.Empty

Алексей Следь,

Посмотрите обсуждение в этой теме.

Алла, спасибо, я там как раз приводил похожий пример, только в нынешнем случае сравнение будет не с Guid.Empty, а с String.Empty.

 .Set("IndustryId", ((IndustryIdParameter!=Guid.Empty)?Column.Parameter(IndustryIdParameter):Column.Const(null)))

Ну, или проверять условие и если не выполняется, не производить добавление к формируемому запросу этого Set, тогда в SQL-запросе вообще не будет упоминания этого пустого поля.

Мда, как всегда решение на поверхности лежало. 

Александр, по мне правильней тогда делать сравнение не со string.Empty, а проверять stringIsNullOrEmpty(). А т.к. табличка у меня нуллы не поддерживает, то все пришло к такой конструкции

.Set("NavProduct", (string.IsNullOrEmpty(entity.NavProduct)? Column.Parameter(string.Empty) : Column.Parameter(entity.NavProduct)))

 

Алексей, если всё так, как описали, то зачем вообще два раза писать Column.Parameter и проверять на Empty? Можно только на null при помощи «??», как предложили тут:

.Set("NavProduct", Column.Parameter(entity.NavProduct ?? string.Empty))

 

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

Так пустая строка в итоге и пишется, её же не надо на такую же заменять, можно сразу в параметр.

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

Добрый день!

Подскажите, пожалуйста, где используется пользователь с именем SysPortalConnection и можно ли его отключить?

Нравится

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

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

Логин и пароль этого пользователя при этом используются в ядре при обращении к AppConnection, там они считываются из внутреннего Web.config из значений параметров UserManagementSauName и UserManagementSauPassword.

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

 

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

Установлено дополнение Excel reports builder for Creatio

https://marketplace.terrasoft.ru/app/excel-reports-builder-creatio

При установке ошибок не было, дополнение установлено успешно.

 

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

Изображение удалено.

Ну и далее ничего сделать нельзя.

Версия системы: 7.16.4.1731

 

Прошу подсказать как исправить.

Спасибо.

 

Нравится

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

Сергей, добрый день!

 

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

 

Также уточните продукт Creatio для воспроизведения ошибки.

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

Продукт: bpmonline sales enterprise & marketing & service enterprise

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

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

Добрый день

 

Есть интересная задача. Надо в БП по событию подключится к стороннему серверу MS-SQL и выполнить хранимую процедуру с двумя параметрами, которая вернет нам таблицу данных. Далее эту таблицу данных сохранить в кастомном разделе в системе и настроить связи с другими объектами.

Затык именно в подключении к стороннему серверу БД и вызове процедуры.

В MSSMS это выглядит как:

EXECUTE [dbo].[GetDataForCRM] @nomer,@date 

 

Нравится

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

Добрый день!

Пробовали ли настраивать линкованные серверы? Если получится подключить сторонний сервер, то можно будет отправлять на него sql-запросы.

Добрый день!

Пробовали ли настраивать линкованные серверы? Если получится подключить сторонний сервер, то можно будет отправлять на него sql-запросы.

Указать имя сервера

Логин и пароль

Разрешить RPC для вызова ХП

Спасибо, помогло, частично. в MSSMS могу вызвать как

EXEC [LinkedServer].[LinkedDB].[dbo].[GetDataForCRM] @nomer,@date 

Как передать что надо вызывать на связанном сервере? Если я указываю полный путь процедуры, а не только имя, то получаю

EXEC [dbo].[[LinkedServer].[LinkedDB].[dbo].[GetDataForCRM]] @nomer,@date 

 

Алексей, функцию на связанном сервере запускают специальным образом, через OPENQUERY или sp_executesql, см. примеры тут.

SELECT SomeField
    FROM OPENQUERY([YOURSERVER], 'SELECT * FROM [SOMEDB].dbo.fn_SomeRemoteFunction(NULL)') tst

 

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

т.е. через

StoredProcedure getDataForCRM = new StoredProcedure(UserConnection, "GetDataForCRM")
				.WithParameter("nomer", numberDoc)
				.WithParameter("date", dateDoc) as StoredProcedure;

не выйдет получить данные?

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

 

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

 

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

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

В ActivtiyPageV2 для элемента StartDate определён генератор "TimezoneGenerator.generateTimezoneButton"

 

{
 "operation": "insert",
 "parentName": "Header",
 "propertyName": "items",
 "name": "StartDate",
 "values": {
  "bindTo": "StartDate",
  "name": "StartDate",
  "generator": "TimezoneGenerator.generateTimezoneButton",
  "layout": {"column": 0, "row": 1, "colSpan": 12}
 }
},



Определил, что данное свойство не должно быть строкой или функцией (см. ViewGeneratorV2 методы hasItemCustomGenerator и generateItem).

НО если устанавливаю значение например 0 или null для для свойства generator,

то получаю ошибку: 

Свойство generator не было определено в классе Terrasoft.controls.Label

 

Как в замещающей схеме отключить свойство generator для данного элемента?

Нравится

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

В EmailTemplatePageV2 это свойство — именно функция:

{
	"operation": "insert",
	"parentName": "TemplateContaner",
	"propertyName": "items",
	"name": "Body",
	"values": {
		"generator": function() {
			return {
				"className": "Terrasoft.IframeControl",
				"id": "preview-content-iframe",
				"iframeContent": {"bindTo": "BodyToDisplay"}
			};
		}
	}
}

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

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

Добрый день,

 

столкнулась с такой проблемой. Мы используем для црм глобальный поиск elasticsearch:5.6.9.

 

тут полное описание, как было сделано

https://academy.terrasoft.ru/documents/administration/7-16/nastroyka-se…

OS: Ubuntu 18.04

 

Все работало наверное около 2х месяцев. В один прекрасный что-то сломалось. Как раз хотела перевести на Ubuntu 20.04. Разбираться не стала - переустановила 2 машины. И опять счастье на 1,5-2 месяца. Утром позвонили и сказали вчера работало - сегодня уже нет.

 

На первой машине оба раза вылезает ошибка

[2020-12-03T11:26:14,489][WARN ][o.e.d.i.m.TypeParsers    ] field [include_in_all] is deprecated, as [_all] is deprecated, and will be disallowed in 6.0, use [copy_to] instead.

 

Сколько я понимаю, что обновляется что-то.. скорее всего, что лежит в контейнере на второй машине до версии 6,0 (индексы, база данных). А сам elasticsearch версии 5.6.9 .

 

Помогите пожалуйста разобраться.

 

С уважением,

Екатерина.

Нравится

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

Сообщение «[2020-12-03T11:26:14,489][WARN ][o.e.d.i.m.TypeParsers    ] field [include_in_all] is deprecated, as [_all] is deprecated, and will be disallowed in 6.0, use [copy_to] instead.» не является ошибкой, это предупреждение, что какая-то функциональность является устаревшей и не будет работать в новой версии 6.0. Если у Вас используется 5.6.9, то влиять на работу в момент показа сообщения ещё до обновления это не должно.

О причинах лучше уточнить у администраторов сервера. Может, действительно автоматически обновляют.

А если у Вас глобальный поиск версии 1.7, то там в инструкции вообще явно ElasticSearch версии 5.6.8 рекомендуют.

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

спасибо большое. Нашли ошибку. Память кончилась. Когда ему не хватает память почему-то ломается индексация. Увеличение памяти решило проблему

 

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

В публикации 2 РАЗДЕЛА ОТ ОДНОГО ОБЪЕКТА рассказано как вывести объекты другого раздела.

А мне бы хотелось в новом разделе выводить записи старого в Состояние="Завершено". Есть ли способ это сделать ? Например на раздел выводить содержимое не table БД, а собственного view ?  

Нравится

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

Добрый день.

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

Из минусов:

1) раздел пришлось регистрировать вручную, так как используется представление, а не таблица

2) некоторые функции раздела пришлось отключить или изменить.

За пример можете взять ещё базовый раздел 'Библиотека процессов' ('Process library').

Добрый день.

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

Из минусов:

1) раздел пришлось регистрировать вручную, так как используется представление, а не таблица

2) некоторые функции раздела пришлось отключить или изменить.

За пример можете взять ещё базовый раздел 'Библиотека процессов' ('Process library').

Для данной задачи достаточно быстрого фильтра, как мне кажется

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