Добрый день! Интересно мнение людей с опытом программирования в 1С.
Впервые написал внешний отчет для конфигурации "Учет в управляющих компаниях ЖКХ, ТСЖ и ЖСК 3.0" (основана на БП 3.0 с надстройкой для ЖКХ). Без СКД.
Хотелось бы понять есть ли какие-нибудь серьезные недочеты, все ли моменты написаны корректно и т.д.
В этом деле новичок.
Отчет позволяет выводить уведомления о задолженности по выбранным пользователем лицевым счетам (эти уведомления у нас заполняют вручную на данный момент).
В уведомлении выводится шапка (реквизиты организации), само уведомление (Текст + параметры, среди которых ЛС, адрес, ФИО, задолженность и дата до которой необходимо внести платеж).
Так же указаны параметры отчета для подключения в качестве внешнего отчета.
Особенно интересует:
- на сколько корректно написан запрос
- на сколько корректен остальной алгоритм работы
- правильно ли сделан вывод областей макета (для каждого ЛС в отдельном цикле)
Исходный код модуля формы:
&НаСервере
Процедура СформироватьНаСервере()
ОбластьОтчета.Очистить();
ОбъектОтчет = РеквизитФормыВЗначение("Отчет");
Макет = ОбъектОтчет.ПолучитьМакет("МакетОтчета");
//ЗАПРОС К ИБ, Отбор сведений по организации
ЗапросОрганизации = Новый Запрос ("ВЫБРАТЬ
|Ссылка КАК Организация,
|НаименованиеПолное,
|НаименованиеСокращенное,
|ИНН,
|КПП,
|ОГРН
|ИЗ
|Справочник.Организации КАК КВП_Организации
|ГДЕ
|Ссылка = &Организация
|;");
ЗапросОрганизации.УстановитьПараметр("Организация", Организация);
РезультатЗапросаОрганизации=ЗапросОрганизации.Выполнить().Выбрать();
РезультатЗапросаОрганизации.Следующий();
//Получаем контактную информацию организации
ТелефонОрганизации = УПЖКХ_ТиповыеМетодыСервер.ПолучитьКонтактнуюИнформацияОбъекта(Организация, Справочники.ВидыКонтактнойИнформации.ТелефонОрганизации);
ЭлПочтаОрганизации = УПЖКХ_ТиповыеМетодыСервер.ПолучитьКонтактнуюИнформацияОбъекта(Организация, Справочники.ВидыКонтактнойИнформации.EmailОрганизации);
АдресОрганизации = УПЖКХ_ТиповыеМетодыСервер.ПолучитьКонтактнуюИнформацияОбъекта(Организация, Справочники.ВидыКонтактнойИнформации.ПочтовыйАдресОрганизации);
//Заполняем Шапку
Шапка = Макет.ПолучитьОбласть("Шапка");
Шапка.Параметры.ДатаФормирования = Формат(ДатаОтчета, "ДФ=""дд.ММ.гггг""");
Шапка.Параметры.ОрганизацияНаименованиеПолное = РезультатЗапросаОрганизации.НаименованиеПолное;
Шапка.Параметры.ИННорганизации = РезультатЗапросаОрганизации.ИНН;
Шапка.Параметры.КППорганизации = РезультатЗапросаОрганизации.КПП;
Шапка.Параметры.ОГРНорганизации = РезультатЗапросаОрганизации.ОГРН;
Шапка.Параметры.АдресОрганизации = АдресОрганизации;
Шапка.Параметры.ПочтаОрганизации = ЭлПочтаОрганизации;
Шапка.Параметры.ТелефонОрганизации = ТелефонОрганизации;
//Формируем список значения для запроса из отмеченных ЛС
ОтобранныеЛицевыеСчета = Новый СписокЗначений;
Для Каждого СтрокаОтбора Из ТабВыбораЛС Цикл
ОтобранныеЛицевыеСчета.Добавить(СтрокаОтбора.ЛицевойСчет);
КонецЦикла;
//ЗАПРОС К ИБ, Отбор сведений по ЛС
ЗапросДанныхЛС = Новый Запрос ("ВЫБРАТЬ
| Ссылка КАК ЛС,
| Адрес.Владелец.Наименование КАК Здание,
| Адрес.Код КАК Квартира
| ПОМЕСТИТЬ ВыборЛС
| ИЗ
| Справочник.КВП_ЛицевыеСчета
| ГДЕ
| Ссылка В (&СписокВыбранныхЛС)
| ;
|
| ВЫБРАТЬ
| УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета.ЛицевойСчет КАК ЛС,
| УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета.ОтветственныйВладелец КАК Собственник,
| УПЖКХ_Жильцы.ФизЛицо.Пол КАК Пол
| Поместить ВыборСобств
| ИЗ
| РегистрСведений.УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета.СрезПоследних(&Дата) КАК УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.УПЖКХ_Жильцы КАК УПЖКХ_Жильцы
| ПО УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета.ОтветственныйВладелец = УПЖКХ_Жильцы.Ссылка
| ГДЕ
| УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета.ЛицевойСчет В (&СписокВыбранныхЛС)
| ;
|
| ВЫБРАТЬ
| УПЖКХ_НачисленияОбороты.ЛицевойСчет КАК ЛицСчет,
| УПЖКХ_НачисленияОбороты.СуммаНачисленияОборот КАК КонечноеСальдо
| Поместить Долг
| ИЗ
| РегистрНакопления.УПЖКХ_Начисления.Обороты(, &Дата, Период) КАК УПЖКХ_НачисленияОбороты
| ;
|
| ВЫБРАТЬ
| ВыборЛС.ЛС КАК ЛС,
| ВыборЛС.Здание КАК Здание,
| ВыборЛС.Квартира,
| ВыборСобств.Собственник КАК Собственник,
| ВыборСобств.Пол КАК Пол,
| Долг.КонечноеСальдо КАК КонечноеСальдо
| ИЗ
| ВыборЛС ЛЕВОЕ СОЕДИНЕНИЕ ВыборСобств
| ПО ВыборЛС.ЛС = ВыборСобств.ЛС
| ЛЕВОЕ СОЕДИНЕНИЕ Долг
| ПО ВыборЛС.ЛС = Долг.ЛицСчет");
ЗапросДанныхЛС.УстановитьПараметр("Дата", КонецДня(ДатаОтчета));
ЗапросДанныхЛС.УстановитьПараметр("СписокВыбранныхЛС", ОтобранныеЛицевыеСчета);
РезультатЗапросаДанныхЛС = ЗапросДанныхЛС.Выполнить().Выгрузить();
Уведомление = Макет.ПолучитьОбласть("Уведомление");
ЛинияОтрыва = Макет.ПолучитьОбласть("ЛинияОтрыва");
//Заполняем общие для всех сведения в параметры области макета
Уведомление.Параметры.СокрНаимОрг = РезультатЗапросаОрганизации.НаименованиеСокращенное;
Уведомление.Параметры.ДатаФорм = Формат(ДатаОтчета, "ДФ=""дд.ММ.гггг""");
Уведомление.Параметры.СрокПогашения = Формат(ДатаПогашения, "ДФ=""дд.ММ.гггг""");
//Заполняем сведения по ЛС в параметры области макета
ПереносСтраницы = ЛОЖЬ;
Для каждого СтрокаЗапроса из РезультатЗапросаДанныхЛС Цикл
ОбращениеКП = "";
ЕСЛИ СтрокаЗапроса.Пол = ПредопределенноеЗначение("Перечисление.ПолФизическогоЛица.Мужской")
ТОГДА ОбращениеКП = "Уважаемый"
ИНАЧЕЕСЛИ СтрокаЗапроса.Пол = ПредопределенноеЗначение("Перечисление.ПолФизическогоЛица.Женский")
ТОГДА ОбращениеКП = "Уважаемая"
ИНАЧЕ ОбращениеКП = "Уважаемый(ая)"
КОНЕЦЕСЛИ;
Уведомление.Параметры.ОбращениеКПотребителю = ОбращениеКП + " " + СтрокаЗапроса.Собственник;
Если НЕ СтрокаЗапроса.Квартира = 0
Тогда Уведомление.Параметры.АдресПотребителя = СтрокаЗапроса.Здание + ", Кв. " + Строка(СтрокаЗапроса.Квартира)
Иначе Уведомление.Параметры.АдресПотребителя = СтрокаЗапроса.Здание
КонецЕсли;
Уведомление.Параметры.ЛицевойСчет = СтрокаЗапроса.ЛС;
Если ЗначениеЗаполнено(СтрокаЗапроса.КонечноеСальдо)
Тогда Уведомление.Параметры.СуммаДолга = СтрокаЗапроса.КонечноеСальдо
Иначе Уведомление.Параметры.СуммаДолга = 0
КонецЕсли;
//После того как все параметры заполнены, выводим обе области для каждого ЛС
ОбластьОтчета.Вывести(Шапка);
ОбластьОтчета.Вывести(Уведомление);
Если ПереносСтраницы = ИСТИНА
ТОГДА ОбластьОтчета.ВывестиГоризонтальныйРазделительСтраниц(); //разделитель страниц
ПереносСтраницы = ЛОЖЬ;
ИНАЧЕ ПереносСтраницы = ИСТИНА;
ОбластьОтчета.Вывести(ЛинияОтрыва); //Выводим линию отрыва
КонецЕсли;
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура Сформировать(Команда)
//БЛОК ПРОВЕРКИ НА ЗАПОЛНЕНИЕ ПАРАМЕТРОВ ОТЧЕТА
Если ДатаОтчета = Дата(1, 1, 1, 0, 0, 0) тогда
ВызватьИсключение "Укажите дату формирования отчета!";
Возврат;
КонецЕсли;
Если ПустаяСтрока(Организация) тогда
ВызватьИсключение "Выберите организацию!";
Возврат;
КонецЕсли;
Если ДатаПогашения = Дата(1, 1, 1, 0, 0, 0) тогда
ВызватьИсключение "Укажите дату, до которой требуется погасить задолженность!";
Возврат;
КонецЕсли;
Если ТабВыбораЛС.Количество() <= 0 тогда
ВызватьИсключение "Выберите хотя бы один лицевой счет!";
Возврат;
КонецЕсли;
СформироватьНаСервере();
КонецПроцедуры
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ДатаОтчета = ТекущаяДата();
СрокПогашенияВДнях = 1;
КонецПроцедуры
&НаКлиенте
Процедура Информация(Команда)
Сообщить("Формирование уведомлений о задолженности на заданную дату по выбранным ЛС");
КонецПроцедуры
&НаКлиенте
Процедура ПодборЛС(Команда)
СтандартнаяОбработка = Ложь;
ФормаПодбора = ПолучитьФорму("Справочник.КВП_ЛицевыеСчета.Форма.ФормаПодбора", Новый Структура("ВыборГруппИЭлементов", ИспользованиеГруппИЭлементов.Элементы), Элементы.ТабЛС);
ФормаПодбора.ЗакрыватьПриВыборе = Ложь;
ФормаПодбора.Открыть();
КонецПроцедуры
&НаКлиенте
Процедура ТабЛСОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
Если ЗначениеЗаполнено(ВыбранноеЗначение)
Тогда
НоваяСтрока = ТабВыбораЛС.Добавить();
НоваяСтрока.ЛицевойСчет = ВыбранноеЗначение;
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура Очистить(Команда)
ТабВыбораЛС.Очистить();
КонецПроцедуры
На сколько корректно так делать? Может правильнее было бы отобрать в отдельные виртуальные таблицы и левым соединением так же все собрать в одну в конце запроса?
Корректно. Платформа в этом случае достраивает неявное левое соединение сама.
Выбор через точку плохо, когда поле составного типа и не используется разыменование.
Вот тут тоже можно было обойтись без левого соединения. Так запрос проще читается.
(1)
| ВЫБРАТЬ
| УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета.ЛицевойСчет КАК ЛС,
| УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета.ОтветственныйВладелец КАК Собственник,
| УПЖКХ_Жильцы.ФизЛицо.Пол КАК Пол
| Поместить ВыборСобств
| ИЗ
| РегистрСведений.УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета.СрезПоследних(&Дата) КАК УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.УПЖКХ_Жильцы КАК УПЖКХ_Жильцы
| ПО УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета.ОтветственныйВладелец = УПЖКХ_Жильцы.Ссылка
| ГДЕ
| УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета.ЛицевойСчет В (&СписокВыбранныхЛС)
Вот тут тоже можно было обойтись без левого соединения. Так запрос проще читается.
Вы имеете ввиду так же, через точку?
Переписал, результат запроса идентичный первому варианту. Думаю так и оставить.
ВЫБРАТЬ
УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета.ЛицевойСчет КАК ЛС,
УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета.ОтветственныйВладелец КАК Собственник,
УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета.ОтветственныйВладелец.ФизЛицо.Пол КАК Пол
ИЗ
РегистрСведений.УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета.СрезПоследних(&Дата) КАК УПЖКХ_ОтветственныйСобственникНанимательЛицевогоСчета
Так же стало интересно, допускается ли обращение к реквизитам подчиненных справочников в запросе.
То есть есть строчка:
| Адрес.Владелец.Наименование КАК Здание,
Где реквизит справочника лицевых счетов "Адрес" - это ссылка к объекту таблицы помещений (так задумано разработчиком).
"Владелец" - это здание, на которое это помещение было создано (помещения - это подчиненный справочник справочника Зданий).
"Наименование" - текстовое поле, содержащее адрес здания.
По сути этой строчкой мы переходим от таблицы лицевых счетов к помещениям, от помещения к зданиям, из зданий берем наименование.
На сколько корректно так делать? Может правильнее было бы отобрать в отдельные виртуальные таблицы и левым соединением так же все собрать в одну в конце запроса?
Процедура Сформировать на клиенте - при проверке заполненности реквизитов не нужно вызывать исключение. достаточно выдать сообщение пользователю.
Организация - это справочник, т.е. надо проверить ЗначениеЗаполнено(Организация), а не заполненность строки.
Далее вы ушли на сервер и там собираете отчет:
получили общую информацию по организации и затем трижды вызываете процедуру получения контактной информации.
подозреваю, что в данную процедуру можно передать структуру полей контактной информации, которые вы хотите узнать. В этом случае процедура один раз сформирует запрос к БД и получит необходимые реквизиты. У вас же получается, что вы три раза формируете запрос к БД через процедуру. Можно вообще посмотреть, откуда процедура получает контактные данные и вставить это в свой запрос и одним запросом получить и наименование и КПП и телефон и адрес и все остальное.
Также в запросе можно применить именование полей, и назвать реквизиты аналогично параметрам в макете. Тогда можно одной строчкой ЗаполнитьЗначенияСвойств(ШапкаПараметры, Выборка) заполнить все параметры в области шапки.
Далее вы запросом перебираете таблицу и собираете лицевые счета. Для таблицы доступен метод ВыгрузитьКолонку(). Получите массив из ссылок лицевых счетов, его и передаете в запрос в качестве параметра.
Далее по запросу данных о ЛС. запрос ВТ ВыборСобств я бы делать не стал, а перенёс это в первую ВТ, и там левым соединением получил собственника, не забывая при этом, что если в РС нет данных о собственнике то будет NULL. В итоговом запросе тоже при левом соединении не отработана ситуация , когда придет NULL. Нужно использовать ЕстьNull()
ну и ошибка общая по запросу. Когда получаете ЛС или Собственника, вы получаете ссылку. Затем передаете (выводите) ссылку в макет. В этот момент на уровне платформы идет формирование запроса, которое по указанной ссылке вернет вам представление. Т.е. опять запрос в цикле. Надо сразу получать либо номер ЛС, либо его код (реквизит типа строки или число) ну и для собственника сразу получать значение реквизита ФИО или наименование (что там имеется в у вас базе).
получили общую информацию по организации и затем трижды вызываете процедуру получения контактной информации.
подозреваю, что в данную процедуру можно передать структуру полей контактной информации, которые вы хотите узнать.
Данную функцию я подсмотрел в типовых отчетах разработчика конфигурации. Они точно так же вызывают ее по отдельности для требуемой информации. Отдельно для телефона, отдельно для адреса. Вот так вот, в две строки. Не буду утверждать что это правильно, разумеется.
Вообще справочник организаций - это типовой справочник из БП 3.0 (напомню, что данная конфигурация для ЖКХ является надстройкой над БП 3.0). Думаю контактная информация должна доставаться стандартными методами для БП. Но Рарус написали свою функцию, зачем-то...
(14) для получения трех реквизитов по организации - это конечно не критично три раза вызвать процедуру и три раза сформировать запрос, который скорее всего небольшой (обращение к одному регистру)
а вот например вы заходите в свой отчет вывести контактную информацию по владельцу лицевого счета (физлицо) и этих лицевых счетов нужно вывести +100500. Если начать вызывать процедуры для каждого владельца ЛС то время формирования вашего отчета возрастет в разы. Вот тут и поможет одним запросом получить всю необходимую контактную информацию для всех владельцев ЛС.
(20) да, это я понимаю. Информация по организации запрашивается единоразово за формирование отчета.
С ЛС по неопытности чуть так и не сделал, когда прикидывал алгоритм. Проскакивала мысль вкорячить запрос в цикл по перебору выбранных ЛС с последующим подтягиванием данных в макет. Разумеется быстро понял что это дикость дикая и сделал один запрос с отбором по списку значений, а заполнение параметров макета и вывод областей завязал на перебор результата запроса. Как и должно быть :)
(1) по остальному.
форматирование кода - жесть полная. чуть залип на месте
Если ПереносСтраницы = ИСТИНА
ТОГДА ОбластьОтчета.ВывестиГоризонтальныйРазделительСтраниц(); //разделитель страниц
ПереносСтраницы = ЛОЖЬ;
ИНАЧЕ ПереносСтраницы = ИСТИНА;
ОбластьОтчета.Вывести(ЛинияОтрыва); //Выводим линию отрыва
КонецЕсли;
так НЕ ПИШУТ!!!
Если ПереносСтраницы Тогда
ОбластьОтчета.ВывестиГоризонтальныйРазделительСтраниц(); //разделитель страниц
ПереносСтраницы = Ложь;
Иначе
ПереносСтраницы = Истина;
ОбластьОтчета.Вывести(ЛинияОтрыва); //Выводим линию отрыва
КонецЕсли;
И не лишним было бы написать в комментарии, что на странице выводится 2 макета.
по шапке.
1 контактную информацию можно получить так же в запросе.
2 имена полей в запросе лучше привести к именам параметров и делать через ЗаполнитьЗначенияСвойств(Шапка.Параметры, РезультатЗапросаОрганизации)
по ЛС.
1. Почему таблица оборотов, а не остатков? Причем нет вообще никаких отборов из виртуальной таблицы.
2. Условия по отбору ЛС лучше поместить в условия виртуальной таблицы остатков (не забываем про границу).
3. Временные таблицы не нужны. Можно сразу к остаткам сделать внутреннее соединение с нужными таблицами по ЛС.
и еще. есть замечательная глобальная функция ЗначениеЗаполнено.
(5) по п.3 - если сразу к остаткам, а по ЛС нет остатков - то оно не попадет в отчет, а если необходимо все-же ЛС вывести. имхо тут в зависимости от требований к отчёту
Вы абсолютно правы )) Но я человек в программировании не совсем опытный. Особенно в 1С.
В C# так и писал, как вы предлагаете. По моему там структура "if then else" максимально короткая.
Даже и не подумал что в 1С можно так же.
2 имена полей в запросе лучше привести к именам параметров и делать через ЗаполнитьЗначенияСвойств(Шапка.Параметры, РезультатЗапросаОрганизации)
по ЛС.
Про это не знал свосем, спасибо. Сделаю.
Почему таблица оборотов, а не остатков?
Если сказать честно - то подсмотрел во внешней обработке выгрузки реестра, которую мы заказывали. Изучу этот вопрос подробнее и переделаю.
и еще. есть замечательная глобальная функция ЗначениеЗаполнено.
1. Почему таблица оборотов, а не остатков? Причем нет вообще никаких отборов из виртуальной таблицы.
Посмотрел как сделано в типовом отчете "Справка о задолженности". Там задолженность собирается из другого регистра накопления - КВП.ВзаиморасчетыПоЛицевымСчетам.
Взял оттуда, убрав отбор по услугам. По сути оставил вывод общей суммы остатка, то есть долга. Так же добавил отбор по выбранным ЛС. А в итоговый запрос в конце добавил проверку ЕСТЬNULL на значении долга, чтобы подставлялся 0 если долга нет.
ВЫБРАТЬ
КВП_ВзаиморасчетыПоЛицевымСчетамОстатки.ЛицевойСчет КАК ЛицСчет,
КВП_ВзаиморасчетыПоЛицевымСчетамОстатки.СуммаНачисленияОстаток КАК КонечноеСальдо
Поместить Долг
ИЗ
РегистрНакопления.КВП_ВзаиморасчетыПоЛицевымСчетам.Остатки(&Дата) КАК КВП_ВзаиморасчетыПоЛицевымСчетамОстатки
ГДЕ
КВП_ВзаиморасчетыПоЛицевымСчетамОстатки.ЛицевойСчет В (&СписокВыбранныхЛС)
Надеюсь верно.
P.s. заметил что уведомления начали формироваться раза в 3 быстрее после этого...
РегистрНакопления.КВП_ВзаиморасчетыПоЛицевымСчетам.Остатки(&Дата) КАК КВП_ВзаиморасчетыПоЛицевымСчетамОстатки
ГДЕ
УПЖКХ_НачисленияОбороты.ЛицевойСчет В (&СписокВыбранныхЛС)
Так выбираются все остатки, на которые потом накладывается отбор. Чтобы сразу выбирать с отбором надо вот так
РегистрНакопления.КВП_ВзаиморасчетыПоЛицевымСчетам.Остатки(&Дата, ЛицевойСчет В (&СписокВыбранныхЛС)) КАК КВП_ВзаиморасчетыПоЛицевымСчетамОстатки
И если выбирать из виртуальной таблицы остатков, то нужно устанавливать Дату как границу
ЗапросДанныхЛС.УстановитьПараметр("Дата", Новый Граница(КонецДня(ДатаОтчета), ВидГраницы.Включая);
И если выбирать из виртуальной таблицы остатков, то нужно устанавливать Дату как границу
Я правильно понял, что речь про ситуацию когда результат запроса помещается в таблицу "Долг" командой "Поместить долг" ? И почему именно как границу?
Еще раз спасибо.
Я правильно понял, что речь про ситуацию когда результат запроса помещается в таблицу "Долг" командой "Поместить долг" ? И почему именно как границу?
нет. при выборе из виртуальной таблицы остатков и передаче параметра как дата, не учитывается последняя секунда. почитайте https://www.uroki-1c.ru/2021/09/1.html
(6) можно и так)
(7) ЗначениеЗаполнено на мой взгляд универсальнее. Если Организация по каким-то причинам будет Неопределено, то получим ошибку.
(8) согласен. и с внутренним я погорячился. надо все таки левое.