Расшифровка СКД. Фильтр отчета по диапазону чисел. Переопределение обработки расшифровки. Не модальный режим

08.02.21

Разработка - СКД

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

Скачать исходный код

Наименование Файл Версия Размер
Расшифровка СКД
.erf 15,51Kb
9
.erf 1.1.0 15,51Kb 9 Скачать

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

1 Программное добавление дополнительного пункта меню расшифровки

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

РезультатОбработкаРасшифровки() - нажатие на ячейке отчета правой кнопкой мыши

РезультатОбработкаДополнительнойРасшифровки() - нажатие на ячейке левой кнопкой мыши

Если база создана на основании БСП можно не создавать форму отчета, выполнить доработки через общим модуль ОтчетыКлиентПереопределяемый (не реализовано у меня): 

ОтчетыКлиентПереопределяемый.ОбработкаРасшифровки()

ОтчетыКлиентПереопределяемый.ОбработкаДополнительнойРасшифровки().

 

Код процедур:

&НаКлиенте
Процедура РезультатОбработкаРасшифровки(Элемент, Расшифровка, СтандартнаяОбработка, ДополнительныеПараметры)    
    //вызываем процедуру которая для числа переопределяем обработку расшифровку
    ОбработкаРасшифровкиНачало(Элемент, Расшифровка, СтандартнаяОбработка, ДополнительныеПараметры);    
КонецПроцедуры

&НаКлиенте
Процедура РезультатОбработкаДополнительнойРасшифровки(Элемент, Расшифровка, СтандартнаяОбработка, ДополнительныеПараметры)    
    //вызываем процедуру которая для числа переопределяем обработку расшифровку
    ОбработкаРасшифровкиНачало(Элемент, Расшифровка, СтандартнаяОбработка, ДополнительныеПараметры);    
КонецПроцедуры

В процедуре ОбработкаРасшифровкиНачало получаем объект расшифровки (значение в ячейке табличного документа) с дополнительными проверкам на пустое значение и на тип Число. После проверки данных объекта расшифровки для Числа переопределяем расшифровку (для других типов нет смысла задавать диапазон, хотя можно и для типа "Дата" реализовать)

&НаКлиенте
Процедура ОбработкаРасшифровкиНачало(Элемент, Расшифровка, СтандартнаяОбработка, ДополнительныеПараметры)
    
    //получаем структуру с данными объекта расшифровки
    ОбъектРасшифровки = ПолучитьОбъектРасшифровки(Расшифровка, ДанныеРасшифровки);
    
    Если ОбъектРасшифровки <> Неопределено Тогда
        
        //переопределяем действия обработки расшифровки компоновки данных для числа
        Если ОбъектРасшифровки.ЭтоЧисло ИЛИ ОбъектРасшифровки.ЭтоПустаяСтрока Тогда
            
            //отменяем стандартную обработку расшифровки
            СтандартнаяОбработка = Ложь;
            
            //создаем обработчик расшифровки и инициализируем данными расшифровки отчета и источником доступных настроек для отчета
            ОбработкаРасшифровки = Новый ОбработкаРасшифровкиКомпоновкиДанных(ДанныеРасшифровки, Новый ИсточникДоступныхНастроекКомпоновкиДанных(Отчет));
            
            //задаем список доступных действий расшифровки
            ДоступныеДействия = Новый Массив();
            
            ДоступныеДействия.Добавить(ДействиеОбработкиРасшифровкиКомпоновкиДанных.Отфильтровать);    
            ДоступныеДействия.Добавить(ДействиеОбработкиРасшифровкиКомпоновкиДанных.Упорядочить);    
            ДоступныеДействия.Добавить(ДействиеОбработкиРасшифровкиКомпоновкиДанных.Сгруппировать);
            ДоступныеДействия.Добавить(ДействиеОбработкиРасшифровкиКомпоновкиДанных.Расшифровать);
            ДоступныеДействия.Добавить(ДействиеОбработкиРасшифровкиКомпоновкиДанных.Оформить);
            
            //добавляем дополнительные действия расшифровки
            ДополнительныеПунктыМеню = Новый СписокЗначений;    
            ДополнительныеПунктыМеню.Добавить(ПолучитьИмяДопПунктМеню_ОтфильтроватьДиапазон());    
            
            //Выдаем меню расшифровки, считываем реакцию пользователя 
            ДополнительныеПараметры = Новый Структура("ОбъектРасшифровки, Расшифровка", ОбъектРасшифровки, Расшифровка);
            ОписаниеОповещения         = Новый ОписаниеОповещения("ОбработкаРасшифровкиЗавершение", ЭтаФорма, ДополнительныеПараметры);
            ОбработкаРасшифровки.ПоказатьВыборДействия(ОписаниеОповещения, Расшифровка, ДоступныеДействия, ДополнительныеПунктыМеню);
            
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры


&НаСервереБезКонтекста
Функция ПолучитьОбъектРасшифровки(Расшифровка, ДанныеРасшифровки)
    
    ОбъектРасшифровки = Неопределено;
    
    ДанныеРашифровкиОбъект = ПолучитьИзВременногоХранилища(ДанныеРасшифровки);
    
    ЭлементРасшифровки             = ДанныеРашифровкиОбъект.Элементы.Получить(Расшифровка);
    ПоляЭлементаРасшифровки        = ЭлементРасшифровки.ПолучитьПоля();
    КоличествоПолейРасшифровки     = ПоляЭлементаРасшифровки.Количество();
    
    Если КоличествоПолейРасшифровки Тогда
        
        ОбъектРасшифровки = Новый Структура();
        
        Индекс = 0;
        Для Каждого ПолеЭлементаРасшифровки ИЗ ПоляЭлементаРасшифровки Цикл
            
            //Значение
            ИмяЗначения = "Значение" + ?(Индекс = 0, "", Индекс);                
            ОбъектРасшифровки.Вставить(ИмяЗначения, ПолеЭлементаРасшифровки.Значение);
            
            //Поле
            ИмяПоля = "Поле" + ?(Индекс = 0, "", Индекс);                
            ОбъектРасшифровки.Вставить(ИмяПоля, ПолеЭлементаРасшифровки.Поле);
            
            //проверка на число
            ИмяЗначения = "ЭтоЧисло" + ?(Индекс = 0, "", Индекс);
            ОбъектРасшифровки.Вставить(ИмяЗначения, ТипЗнч(ПолеЭлементаРасшифровки.Значение) = Тип("Число"));
            
            //проверка на число
            ИмяЗначения = "ЭтоПустаяСтрока" + ?(Индекс = 0, "", Индекс);
            ОбъектРасшифровки.Вставить(ИмяЗначения, ПустаяСтрока(ПолеЭлементаРасшифровки.Значение));
            
            Индекс = Индекс + 1;
        КонецЦикла;            
        
    КонецЕсли;    
    
    Возврат ОбъектРасшифровки;
    
КонецФункции // ПолучитьОбъектРасшифровки()


&НаКлиенте
Функция ПолучитьИмяДопПунктМеню_ОтфильтроватьДиапазон() Экспорт
    Возврат НСтр("ru = 'Отфильтровать диапазон от ... до ...'; uk = 'Відфільтрувати діапазон від ... до ...'");
КонецФункции

 

2 Программная обработка расшифровки

После того как пользователь выбрал действие расшифровки необходимо определить какой из пунктов меню он выбрал:

- созданный программно пункт меню выбора диапазона - нужно вызвать форму выбора двух чисел;

- пункт Отфильтровать - нужно открыть новый отчет с параметрами фильтра (так реализован стандартный механизм);

- для прочих пунктов меню необходимо вызвать ОтработатьРасшифровку (функционал с сайта ИТС).

&НаКлиенте
Функция ОбработкаРасшифровкиЗавершение(ВыполненноеДействие, ПараметрВыполненногоДействия, ДополнительныеПараметры) Экспорт

    ОбъектРасшифровки   = ДополнительныеПараметры.ОбъектРасшифровки;
    Расшифровка         = ДополнительныеПараметры.Расшифровка;
    
    ИмяПоля             = ОбъектРасшифровки.Поле;
    
    Если ВыполненноеДействие = ДействиеОбработкиРасшифровкиКомпоновкиДанных.Нет ИЛИ ВыполненноеДействие = Неопределено Тогда
        //ничего не выполняем, пользователь отменил обработку расшифровки
        
    ИначеЕсли ТипЗнч(ВыполненноеДействие) = Тип("Строка") 
        И ВыполненноеДействие = ПолучитьИмяДопПунктМеню_ОтфильтроватьДиапазон() Тогда 
        
        //пользователь выбрал наш дополнительный пункт меню
        
        //создаем описание оповещения формы выбора двух чисел
        ДополнительныеПараметрыОповещения = Новый Структура("ИмяПоля", ИмяПоля);        
        ОписаниеОповещения     = Новый ОписаниеОповещения("ОтфильтроватьДиапазонНачалоВыбораЗавершение", ЭтотОбъект, ДополнительныеПараметрыОповещения);        
        ПараметрыФормы         = Новый Структура;
        
        //открываем форму выбора двух чисел
        ОткрытьФорму("ВнешнийОтчет.РасшифровкаДиапазона.Форма.ФормаВыбораДвухЧисел",
            ПараметрыФормы,
            ЭтаФорма,
            Новый УникальныйИдентификатор,,,
            ОписаниеОповещения,
            РежимОткрытияОкнаФормы.БлокироватьОкноВладельца); 
                        
    ИначеЕсли ВыполненноеДействие = ДействиеОбработкиРасшифровкиКомпоновкиДанных.Отфильтровать Тогда
        
        //стандартное действие "Отфильтровать" открывается в новой форме отчета, поэтому переопределяем его
        ПараметрыФормы = Новый Структура;        
        ПараметрыФормы.Вставить("СформироватьПриОткрытии", Истина);
        ПараметрыФормы.Вставить("Расшифровка", Новый ОписаниеОбработкиРасшифровкиКомпоновкиДанных(ДанныеРасшифровки, Расшифровка, ПараметрВыполненногоДействия));        
        
        ОткрытьФорму("ВнешнийОтчет.РасшифровкаДиапазона.Форма.ФормаОтчета", ПараметрыФормы, , Истина);
          
    Иначе
        
        //для всех остальных используем процедуру взятую с сайта ИТС 
        ОтработатьРасшифровку(Новый ОписаниеОбработкиРасшифровкиКомпоновкиДанных(ДанныеРасшифровки, Расшифровка, ПараметрВыполненногоДействия), ИмяПоля);
        
    КонецЕсли;
    
КонецФункции // ОбработкаРасшифровкиЗавершение()


//переопределяем обработку расшифровки
&НаСервере 
Процедура ОтработатьРасшифровку(ОписаниеОбработкиРасшифровки, ИмяПоля)    
        
    ДанныеРасшифровкиОбъект = ПолучитьИзВременногоХранилища(ДанныеРасшифровки);    
    ОбработкаРасшифровки    = Новый ОбработкаРасшифровкиКомпоновкиДанных(ДанныеРасшифровкиОбъект, Новый ИсточникДоступныхНастроекКомпоновкиДанных(Отчет));    
    РезультирующиеНастройки = ОбработкаРасшифровки.ПрименитьНастройки(ОписаниеОбработкиРасшифровки.Идентификатор, ОписаниеОбработкиРасшифровки.ПрименяемыеНастройки);
        
    Если ТипЗнч(РезультирующиеНастройки) = Тип("НастройкиКомпоновкиДанных") Тогда
        
        Отчет.КомпоновщикНастроек.ЗагрузитьНастройки(РезультирующиеНастройки);
        
    ИначеЕсли ТипЗнч(РезультирующиеНастройки) = Тип("ПользовательскиеНастройкиКомпоновкиДанных") Тогда
        
        Отчет.КомпоновщикНастроек.ЗагрузитьПользовательскиеНастройки(РезультирующиеНастройки);
        
    КонецЕсли;     
    
    СкомпоноватьРезультат();    
    
КонецПроцедуры

3 Программное добавление отборов НастройкиКомпоновкиДанных. Добавление отборов пользовательских насроек. Перерисовка пользовательских настроек

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


&НаКлиенте
Процедура ОтфильтроватьДиапазонНачалоВыбораЗавершение(Результат, ДополнительныеПараметры) Экспорт
    
    //получаем имя поля из дополнительных параметров
    ИмяПоля =  ДополнительныеПараметры.ИмяПоля;
    
    Если Результат <> Неопределено Тогда
        
        //получаем копию настроек компоновки данных.
        НастройкиКомпоновкиДанных = ПолучитьТекущиеНастройки();    
        
        //удаляем предыдущие отборы по полю
        
        //если конфигурация на основании БСП, то можно использовать функцию общего модуля ОбщегоНазначенияКлиентСервер
        //ОбщегоНазначенияКлиентСервер.УдалитьЭлементыГруппыОтбора(НастройкиКомпоновкиДанных.Отбор, ИмяПоля);
        
        УдалитьЭлементыГруппыОтбора(НастройкиКомпоновкиДанных.Отбор, ИмяПоля);
                                                                          
        //добавляем новые отборы по полю
        
        //если конфигурация на основании БСП, то можно использовать функцию общего модуля ОбщегоНазначенияКлиентСервер
        //ДобавленныйЭлементЗначениеОт = ОбщегоНазначенияКлиентСервер.ДобавитьЭлементКомпоновки(НастройкиКомпоновкиДанных.Отбор, ИмяПоля, ВидСравненияКомпоновкиДанных.БольшеИлиРавно, Результат.ЗначениеОт, , Истина, РежимОтображенияЭлементаНастройкиКомпоновкиДанных.БыстрыйДоступ, Новый УникальныйИдентификатор());
        //ДобавленныйЭлементЗначениеДо = ОбщегоНазначенияКлиентСервер.ДобавитьЭлементКомпоновки(НастройкиКомпоновкиДанных.Отбор, ИмяПоля, ВидСравненияКомпоновкиДанных.МеньшеИлиРавно, Результат.ЗначениеДо, , Истина, РежимОтображенияЭлементаНастройкиКомпоновкиДанных.БыстрыйДоступ, Новый УникальныйИдентификатор());
        
        ДобавленныйЭлементЗначениеОт = ДобавитьЭлементКомпоновки(НастройкиКомпоновкиДанных.Отбор, ИмяПоля, ВидСравненияКомпоновкиДанных.БольшеИлиРавно, Результат.ЗначениеОт, , Истина, РежимОтображенияЭлементаНастройкиКомпоновкиДанных.БыстрыйДоступ, Новый УникальныйИдентификатор());
        ДобавленныйЭлементЗначениеДо = ДобавитьЭлементКомпоновки(НастройкиКомпоновкиДанных.Отбор, ИмяПоля, ВидСравненияКомпоновкиДанных.МеньшеИлиРавно, Результат.ЗначениеДо, , Истина, РежимОтображенияЭлементаНастройкиКомпоновкиДанных.БыстрыйДоступ, Новый УникальныйИдентификатор());
        
        
        Если Ложь Тогда //Если нужно открыть отчет в новом окне, тогда проставляем "Истина"            
            ПараметрыФормы = Новый Структура;        
            ПараметрыФормы.Вставить("СформироватьПриОткрытии"    , Истина);
            ПараметрыФормы.Вставить("Вариант"                    , НастройкиКомпоновкиДанных);
            ПараметрыФормы.Вставить("ПользовательскиеНастройки"    , Отчет.КомпоновщикНастроек.ПользовательскиеНастройки);
            
            ОткрытьФорму("ВнешнийОтчет.РасшифровкаДиапазона.Форма.ФормаОтчета", ПараметрыФормы, , Истина);
        Иначе
                        
                        
            //для отображенния добавленных элементов в пользовательских настоек создадим временный компоновщик и загрузим из него настройки
            //так мы сможем заполнить пользовательские настройки
            КомпоновщикВременный = Новый КомпоновщикНастроекКомпоновкиДанных;            
            КомпоновщикВременный.ЗагрузитьНастройки(НастройкиКомпоновкиДанных);
            
            Отчет.КомпоновщикНастроек.ЗагрузитьНастройки(НастройкиКомпоновкиДанных);
            Отчет.КомпоновщикНастроек.ЗагрузитьПользовательскиеНастройки(КомпоновщикВременный.ПользовательскиеНастройки); 
            
            ПользовательскиеНастройки = Отчет.КомпоновщикНастроек.ПользовательскиеНастройки;

            //для наших новых отборов устанавливаем видимость "Быстрый доступ" и отображаем их на форме
            
            //есть конфигурация создана на основании БСП, тогда можно использовать функцию общего модуля ОтчетыКлиентСервер            
            //ПользовательскаяНастройкаЗначениеОт = ОтчетыКлиентСервер.НайтиПользовательскуюНастройку(ПользовательскиеНастройки, ДобавленныйЭлементЗначениеОт.ИдентификаторПользовательскойНастройки);
            //ПользовательскаяНастройкаЗначениеДо = ОтчетыКлиентСервер.НайтиПользовательскуюНастройку(ПользовательскиеНастройки, ДобавленныйЭлементЗначениеДо.ИдентификаторПользовательскойНастройки);
            
            ПользовательскаяНастройкаЗначениеОт = НайтиПользовательскуюНастройку(ПользовательскиеНастройки, ДобавленныйЭлементЗначениеОт.ИдентификаторПользовательскойНастройки);
            ПользовательскаяНастройкаЗначениеДо = НайтиПользовательскуюНастройку(ПользовательскиеНастройки, ДобавленныйЭлементЗначениеДо.ИдентификаторПользовательскойНастройки);
            
            Если  ПользовательскаяНастройкаЗначениеОт <> Неопределено Тогда
                ПользовательскаяНастройкаЗначениеОт.РежимОтображения = РежимОтображенияЭлементаНастройкиКомпоновкиДанных.БыстрыйДоступ;
            КонецЕсли;
            
            Если  ПользовательскаяНастройкаЗначениеДо <> Неопределено Тогда
                ПользовательскаяНастройкаЗначениеДо.РежимОтображения = РежимОтображенияЭлементаНастройкиКомпоновкиДанных.БыстрыйДоступ;
            КонецЕсли;            
            
            СкомпоноватьРезультат();                                        
                        
            //вызываем создание елементов формы пользовательских настроек
            ОбновитьЭлементыПользовательскихНастроек();
            
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры


// Функция возвращает копию настроек компоновки данных.
// 
// Возвращаемое значение:
//   - НастройкиКомпоновкиДанных - настройки компоновки данных
//
&НаСервере
Функция ПолучитьТекущиеНастройки()
    
     Возврат Отчет.КомпоновщикНастроек.ПолучитьНастройки();
     
КонецФункции


// Процедура пересоздает элементы формы для редактирования пользовательских настроек отчета
//
&НаСервере
Процедура ОбновитьЭлементыПользовательскихНастроек()
        
    СоздатьЭлементыФормыПользовательскихНастроек(,РежимОтображенияНастроекКомпоновкиДанных.БыстрыйДоступ);     
    
КонецПроцедуры

 

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

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

Для меня основными проблемами при создании этого механизма были:

- передача НастроекКомпоновкиДанных в новую форму отчета (решилась через использование параметра формы "Вариант")

- добавление отбора в пользовательские настройки (решилась через временный компоновщик данных)

- перерисовка элементов пользовательских настроек на сервере (ну не знал я что нужно использовать СоздатьЭлементыФормыПользовательскихНастроек и с трудом нашел на форумах, хотя возможно что я просто плохо искал))

 

В статье использовались материалы из:

https://its.1c.ru/db/metod8dev/content/3728/hdoc

//infostart.ru/1c/articles/542677/

//infostart.ru/public/165599/

 

Прикрепляю рабочий пример отчета на управляемой форме, платформа 8.3.14. Отчет тянет статические данные, поэтому должен работать на большинстве конфигураций

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

РасшифровкаСКД КомпоновщикНастроек ОбработкаРасшифровкиСКД ОбработкаРасшифровки Отборы Расшифровка СКД Компоновщик Настроек

См. также

Infostart Toolkit: Инструменты разработчика 1С 8.3 на управляемых формах

Инструментарий разработчика Роли и права Запросы СКД Платформа 1С v8.3 Управляемые формы Запросы Система компоновки данных Конфигурации 1cv8 Платные (руб)

Набор инструментов программиста и специалиста 1С для всех конфигураций на управляемых формах. В состав входят инструменты: Консоль запросов, Консоль СКД, Консоль кода, Редактор объекта, Анализ прав доступа, Метаданные, Поиск ссылок, Сравнение объектов, Все функции, Подписки на события и др. Редактор запросов и кода с раскраской и контекстной подсказкой. Доработанный конструктор запросов тонкого клиента. Продукт хорошо оптимизирован и обладает самым широким функционалом среди всех инструментов, представленных на рынке.

10000 руб.

02.09.2020    129745    700    390    

752

Как посмотреть итоговый запрос в отчете СКД

Запросы СКД Система компоновки данных Россия Бесплатно (free)

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

15.05.2024    2962    implecs_team    4    

34

Генератор схемы компоновки данных (СКД), написание кода схемы программно

Инструментарий разработчика СКД Платформа 1С v8.3 Конфигурации 1cv8 1С:Зарплата и Управление Персоналом 3.x Абонемент ($m)

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

3 стартмани

05.02.2024    4782    36    obmailok    19    

74

Набор-объект для СКД по тексту или запросу

Запросы СКД Платформа 1С v8.3 Управляемые формы Конфигурации 1cv8 Абонемент ($m)

Есть список полей в виде текста, или запрос - закидываем в набор СКД.

1 стартмани

31.01.2024    2291    2    Yashazz    0    

32

СКД на JavaScript в 1С

СКД WEB-интеграция Платформа 1С v8.3 Конфигурации 1cv8 Абонемент ($m)

Долгое время поддерживаю web-портал, в котором появилась необходимость создавать отчеты. Просмотрев различные фреймворки на js, я решил сделать свое решение, которое позволяло бы быстро разрабатывать и добавлять новые отчеты на web-портал.

2 стартмани

11.12.2023    8788    20    John_d    25    

124

Использование менеджера временных таблиц в СКД

СКД Платформа 1С v8.3 Система компоновки данных Конфигурации 1cv8 Бесплатно (free)

Рассмотрим еще не получивший широкого распространения способ работы с внешними данным в СКД. В процессе обсуждения работы с СКД выяснилось, что многие не знакомы со способом помещения туда временной таблицы, полученной предварительно. Статья будет полезна разработчикам, знакомым с программным созданием СКД.

05.12.2023    5374    PROSTO-1C    13    

65

Модель СКД

Инструментарий разработчика СКД Платформа 1С v8.3 Система компоновки данных Абонемент ($m)

DSL для работы с СКД.

1 стартмани

15.11.2023    6329    15    kalyaka    5    

89

Пользовательские настройки отчетов 1С. Часть 1. Простые и расширенные настройки

СКД Инструкции пользователю Платформа 1С v8.3 Конфигурации 1cv8 1С:Бухгалтерия 3.0 Россия Бесплатно (free)

Простые приемы работы с отчетами на СКД. Что нужно знать пользователю про настройку отчетов, чтобы использовать их на полную катушку.

18.09.2023    8159    accounting_cons    7    

29
Оставьте свое сообщение