Неканонический способ спускания параметра нижестоящим процедурам

UPDATE: лучше спускать параметры через параметры сеанса, но во Fresh они недоступны в расширениях, поэтому там через регистр сведений с измерение по пользователям. Потому что код в этом примере в многопользовательском режиме может привести к коллизиям, когда оба пользователя запишут в один справочник.

Дорабатывал одному клиенту модуль по расчету зарплаты в УТ от Codestar. Модуль ставится как добавка в конфигурацию, ставится через сравнение-объединение.

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

В самом модуле нет видов времени. Т.е. чтобы рассчитать аванс, нужно использовать плановое время, вообще-то.

Ну не суть, в общем, мне нужно было при расчете базы начисления учитывать, плановое это время или фактическое.

Я добавил реквизит «Вид рабочего времени» в документ «Начисление зарплаты».

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

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

Я было попытался начать. Вот первая процедура:

Но вот тут я понял, что придется или менять количество параметров функции или как-то менять тип значения параметров на структуру, чтобы впихнуть туда вид рабочего времени:

На этом этапе я отказался от этой сложной конструкции и применил другой, не канонический способ, но простой и надежный, как АК-47.

Я создал в расширении справочник дор_ЗПСлужебный с единственным реквизитом Значение типа «Хранилище значения».

Написал две функции по установке и выдаче его значения по идентификатору:

Процедура ПоместитьВСлужебныйСправочник(Имя, Значение) Экспорт
	УстановитьПривилегированныйРежим(Истина);
	ТекИмя = Лев(Имя, 150);
	СправочникСсылка = Справочники.дор_ЗПСлужебный.НайтиПоНаименованию(ТекИмя);
	Если ЗначениеЗаполнено(СправочникСсылка) Тогда
		СправочникОбъект = СправочникСсылка.ПолучитьОбъект();
	Иначе
		СправочникОбъект = Справочники.дор_ЗПСлужебный.СоздатьЭлемент();
		СправочникОбъект.Наименование = ТекИмя;
	КонецЕсли;
	СправочникОбъект.Значение = Новый ХранилищеЗначения(Значение);
	СправочникОбъект.Записать();
	
КонецПроцедуры

Функция ПолучитьИзСлужебногоСправочника(Имя) Экспорт
	УстановитьПривилегированныйРежим(Истина);
	ТекИмя = Лев(Имя, 150);
	СправочникСсылка = Справочники.дор_ЗПСлужебный.НайтиПоНаименованию(ТекИмя);
	Если ЗначениеЗаполнено(СправочникСсылка) Тогда
		Возврат СправочникСсылка.Значение.Получить();
	Иначе
		Возврат Неопределено;
	КонецЕсли;
КонецФункции

Теперь перед расчетом я устанавливаю значение вида времени:

&НаСервере
&Перед("РассчитатьНаСервере")
Процедура дор_РассчитатьНаСервере()
	дор_ЗПСервер.ПоместитьВСлужебныйСправочник("ВидРабочегоВремени", Объект.ВидРабочегоВремени);
КонецПроцедуры

А в процессе получения базы получаю значение вида времени:

&Вместо("ПолучитьБазуДляРасчетаТарифа")
Функция дор_ПолучитьБазуДляРасчетаТарифа(НачалоПериода, КонецПериода, Сотрудник, Показатель)
	Запрос = Новый Запрос;
	Запрос.Текст = "ВЫБРАТЬ
	|	естьNULL(СУММА(_ОтработанноеВремяОбороты."+Показатель+"Оборот),0) КАК Результат
	|ИЗ
	|	РегистрНакопления._ОтработанноеВремя.Обороты(&НачалоПериода, &КонецПериода, , Сотрудник = &Сотрудник) КАК _ОтработанноеВремяОбороты";
	//Осипов 2021-08-24 только факт считаем +++
	Запрос.Текст = Запрос.Текст + " ГДЕ _ОтработанноеВремяОбороты.ВидРабочегоВремени = &ВидРабочегоВремени";
	ВидРабочегоВремени = дор_ЗПСервер.ПолучитьИзСлужебногоСправочника("ВидРабочегоВремени");
	Запрос.УстановитьПараметр("ВидРабочегоВремени", ВидРабочегоВремени);
	//---
	Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
	Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
	Запрос.УстановитьПараметр("Сотрудник", Сотрудник);
	
	Выборка = Запрос.Выполнить().Выбрать();
	
	Если Выборка.Следующий() Тогда
		Результат = Выборка.Результат;
	Иначе
		Результат = 0;
	КонецЕсли;
	
	Возврат Результат;
КонецФункции

Касательно этого метода хочу сказать, что большинство его не одобрят, т.к. он не очень прозрачный. Это как оператор GOTO, при злоупотреблении можно нарваться на проблемы, но в гомеопатических дозах — то, что нужно. Конкретно описанную мною задачу решает замечательно!

Ну и еще хочу похвастаться аккуратными добавленными мною полями:

Вот код:

&НаСервере
Процедура дор_ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка)
	ПередЭлемент = Элементы.ГруппаСтраницы;
	ТекГруппа = Элементы.Вставить("дор_Группа", Тип("ГруппаФормы"), ПередЭлемент.Родитель, ПередЭлемент);
	ТекГруппа.Вид = ВидГруппыФормы.ОбычнаяГруппа;
	ТекГруппа.Группировка = ГруппировкаПодчиненныхЭлементовФормы.ГоризонтальнаяВсегда;
	
	
	Элемент = Элементы.Добавить("ВидРабочегоВремени", Тип("ПолеФормы"), ТекГруппа);
	Элемент.Вид = ВидПоляФормы.ПолеВвода;
	Элемент.ПутьКДанным = "Объект.ВидРабочегоВремени";
	Элемент.Вид = ВидПоляФормы.ПолеВвода;
	Элемент.АвтоМаксимальнаяШирина = ложь;
	Элемент.РастягиватьПоГоризонтали = ложь;
	Элемент.Ширина = 10;
	
	
	Элемент = Элементы.Добавить("ПроцентУменьшения", Тип("ПолеФормы"), ТекГруппа);
	Элемент.Вид = ВидПоляФормы.ПолеВвода;
	Элемент.ПутьКДанным = "Объект.ПроцентУменьшения";
	Элемент.Вид = ВидПоляФормы.ПолеВвода;
	Элемент.АвтоМаксимальнаяШирина = ложь;
	Элемент.РастягиватьПоГоризонтали = ложь;
	Элемент.Ширина = 10;
	
	ПередЭлемент = Элементы.НачисленияДепозит;
	Элемент = Элементы.Вставить("Начально", Тип("ПолеФормы"), ПередЭлемент.Родитель, ПередЭлемент);
	Элемент.Вид = ВидПоляФормы.ПолеВвода;
	Элемент.ПутьКДанным = "Объект.Начисления.Начально";
	Элемент.ТолькоПросмотр = истина;
	Элемент.Ширина = 10;
	
	Если Не ЗначениеЗаполнено(Объект.ВидРабочегоВремени) Тогда
		Объект.ВидРабочегоВремени = Справочники._ВидыРабочегоВремени.Факт;
	КонецЕсли;
	
	
КонецПроцедуры

Среда: 11.4.13.46. Объем факт: 1 час.

fixin

Программирую на 1С с 1999 года. В 1С просто Гений. В 2020 году ушел из офиса на вольные хлеба фриланса. Принимаю заказы.

Читайте также:

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

  1. Павел:

    Объектная модель 1с устарела невероятно. Любые изменения в модулях умноженные на дурную сложность (из-за дурацкого клиент-сервера и уф) типовых приводят к подобному соплестроению. Из хавна и палок. Чтобы все не развалилось, из-за стека вызовов длиной с ногу, нужно доп реквизит вываливать во внешнее хранилище, а потом его считывать.

    • А как это делается в нормальных языках программирования? можно пример? Насколько я понимаю, передача контента не особо решена в этих языках.
      Не, ну можно мембера в класс прописать, как вариант, но это если это метод класса.

  2. Павел:

    Какого контента? В нормальных языках методов вне классов не существует. И избегают прямых зависимостей, везде где взаимодействие не конкретные классы, а интерфейсы

    • ну вот как бы ты решал подобную задачу в нормальном языке, что бы у тебя было классом?

      • rzd:

        Исходный код был бы например МенеджерРасчетаТарифа
        Тогда либо наследуем его в лоб и переопределяем соответствующий метод и добавляем свойство
        Либо, реализуя тот же интерфейс, оборачиваем исходный объект и делаем класс-обертку
        Второе более универсально (можно любой менеджер обернуть, реализующий интерфейс), но в текущем исполнении проще первое, т.к. внедриться в код доступа не удастся — придется метод переписать

        • мне это ничего не сказало. В общем допустим автор реализовал расчет начисления без учета вида времени. А тут нужно реализовать с учетом вида времени.
          Как бы это было в ООП?
          ну допустим, в простейшем случае:

          РасчитаннаяЗарплата = ОтработанноеВремя * Тариф

          Нужно заменить на

          РасчитаннаяЗарплата = ОтработанноеВремя[ВидВремени] * Тариф

          Вот я смог заменить процедуру расчета на свою и передать в ее контент ВидВремени.

          А как это делается в серьезных языках?

          • rzd:

            из строки
            РасчитаннаяЗарплата = ОтработанноеВремя * Тариф
            я делаю вывод, что ОтработанноеВремя — это число
            а из строки
            РасчитаннаяЗарплата = ОтработанноеВремя[ВидВремени] * Тариф
            делаю вывод, что это массив (коллекция какая-то)

            но как?

          • ОтработанноеВремя — это функция
            ОтработанноеВремя () и ОтработанноеВремя (ВидВремени)

      • bob32:

        Решение это комбинация паттернов dependency injection, inversion of control, state, interpreter (в зависимости от того насколько гибкое решение получить надо)
        В принципе, помещение состояние объекта во внешние хранилище (будь то БД или in-memory) вполне нормальное. Просто с таким подходом надо все параметры выносить в него, а не как ты сделал. Но как костыль сойдет.

        • А что дает вынесение всех параметров во внешнее хранилище?

          • bob32:

            state-less сервис. Можно поднимать несколько экземпляров. Можно обеспечивать версионность состояний и проигрывания сценария заново с нужной версии. В таком духе работают workflow движки (BPMN одна из частных вариаций)

          • Не понимаю, как это решит озвученные вами выше проблемы.

  3. rzd:

    «чтобы не потерять обновления» не используют аннотацию &Вместо без ПродолжитьВызов, иначе нет смысла сохранять наследие

    • ну тут я слишком сильно поменял логику, хотя согласен, можно было бы изменение с контролем использовать. 😉

  4. Павел:

    В метод класса подавался бы объект реализующий интерфейс. Из которого можно получить время и на что-то умножить. Как в нем время хранится — как реквизит или как соответствие по видам — пофигу. Интерфейс формирует «договор», что из класса, который этот интерфейс реализует торчат вот эти свойства наружу и есть метод «получить время» которое возвращает значение определенного типа. И не надо еще городить как в 1с кучу дебильного кода «для универсальности» — а ты точно не список? А ты точно не структура?

  5. Павел:

    Принцип кисс в 1с нереализуем. Иьо нет ооп в явном виде. Прстотота 1с заканчивается за пределами 2+2

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *