Неканонический способ спускания параметра нижестоящим процедурам
UPDATE: лучше спускать параметры через параметры сеанса, но во Fresh они недоступны в расширениях, поэтому там через регистр сведений с измерение по пользователям. Потому что код в этом примере в многопользовательском режиме может привести к коллизиям, когда оба пользователя запишут в один справочник.
Дорабатывал одному клиенту модуль по расчету зарплаты в УТ от Codestar. Модуль ставится как добавка в конфигурацию, ставится через сравнение-объединение.
И вот я делал изменения уже в свою очередь через расширение, чтобы не потерять обновления, которые будут от этого поставщика.
В самом модуле нет видов времени. Т.е. чтобы рассчитать аванс, нужно использовать плановое время, вообще-то.
Ну не суть, в общем, мне нужно было при расчете базы начисления учитывать, плановое это время или фактическое.
Я добавил реквизит «Вид рабочего времени» в документ «Начисление зарплаты».
Но столкнулся с тем, что мне нужно было его передавать по стеку через несколько функций в функцию расчета базы.
Для этого пришлось бы каждую функцию добавлять в расширение и существенно их менять. Причем модули были большими, пришлось бы их передавать через расширение с контролем и изменением. Сама ссылка на документ в расчет не передавалась.
Я было попытался начать. Вот первая процедура:
Но вот тут я понял, что придется или менять количество параметров функции или как-то менять тип значения параметров на структуру, чтобы впихнуть туда вид рабочего времени:
На этом этапе я отказался от этой сложной конструкции и применил другой, не канонический способ, но простой и надежный, как АК-47.
Я создал в расширении справочник дор_ЗПСлужебный с единственным реквизитом Значение типа «Хранилище значения».
Написал две функции по установке и выдаче его значения по идентификатору:
Процедура ПоместитьВСлужебныйСправочник(Имя, Значение) Экспорт УстановитьПривилегированныйРежим(Истина); ТекИмя = Лев(Имя, 150); СправочникСсылка = Справочники.дор_ЗПСлужебный.НайтиПоНаименованию(ТекИмя); Если ЗначениеЗаполнено(СправочникСсылка) Тогда СправочникОбъект = СправочникСсылка.ПолучитьОбъект(); Иначе СправочникОбъект = Справочники.дор_ЗПСлужебный.СоздатьЭлемент(); СправочникОбъект.Наименование = ТекИмя; КонецЕсли; СправочникОбъект.Значение = Новый ХранилищеЗначения(Значение); СправочникОбъект.Записать(); КонецПроцедуры Функция ПолучитьИзСлужебногоСправочника(Имя) Экспорт УстановитьПривилегированныйРежим(Истина); ТекИмя = Лев(Имя, 150); СправочникСсылка = Справочники.дор_ЗПСлужебный.НайтиПоНаименованию(ТекИмя); Если ЗначениеЗаполнено(СправочникСсылка) Тогда Возврат СправочникСсылка.Значение.Получить(); Иначе Возврат Неопределено; КонецЕсли; КонецФункции
Теперь перед расчетом я устанавливаю значение вида времени:
&НаСервере &Перед("РассчитатьНаСервере") Процедура дор_РассчитатьНаСервере() дор_ЗПСервер.ПоместитьВСлужебныйСправочник("ВидРабочегоВремени", Объект.ВидРабочегоВремени); КонецПроцедуры
А в процессе получения базы получаю значение вида времени:
&Вместо("ПолучитьБазуДляРасчетаТарифа") Функция дор_ПолучитьБазуДляРасчетаТарифа(НачалоПериода, КонецПериода, Сотрудник, Показатель) Запрос = Новый Запрос; Запрос.Текст = "ВЫБРАТЬ | естьNULL(СУММА(_ОтработанноеВремяОбороты."+Показатель+"Оборот),0) КАК Результат |ИЗ | РегистрНакопления._ОтработанноеВремя.Обороты(&НачалоПериода, &КонецПериода, , Сотрудник = &Сотрудник) КАК _ОтработанноеВремяОбороты"; //Осипов 2021-08-24 только факт считаем +++ Запрос.Текст = Запрос.Текст + " ГДЕ _ОтработанноеВремяОбороты.ВидРабочегоВремени = &ВидРабочегоВремени"; ВидРабочегоВремени = дор_ЗПСервер.ПолучитьИзСлужебногоСправочника("ВидРабочегоВремени"); Запрос.УстановитьПараметр("ВидРабочегоВремени", ВидРабочегоВремени); //--- Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода); Запрос.УстановитьПараметр("КонецПериода", КонецПериода); Запрос.УстановитьПараметр("Сотрудник", Сотрудник); Выборка = Запрос.Выполнить().Выбрать(); Если Выборка.Следующий() Тогда Результат = Выборка.Результат; Иначе Результат = 0; КонецЕсли; Возврат Результат; КонецФункции
Касательно этого метода хочу сказать, что большинство его не одобрят, т.к. он не очень прозрачный. Это как оператор GOTO, при злоупотреблении можно нарваться на проблемы, но в гомеопатических дозах — то, что нужно. Конкретно описанную мною задачу решает замечательно!
Ну и еще хочу похвастаться аккуратными добавленными мною полями:
Вот код:
&НаСервере Процедура дор_ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка) ПередЭлемент = Элементы.ГруппаСтраницы; ТекГруппа = Элементы.Вставить("дор_Группа", Тип("ГруппаФормы"), ПередЭлемент.Родитель, ПередЭлемент); ТекГруппа.Вид = ВидГруппыФормы.ОбычнаяГруппа; ТекГруппа.Группировка = ГруппировкаПодчиненныхЭлементовФормы.ГоризонтальнаяВсегда; Элемент = Элементы.Добавить("ВидРабочегоВремени", Тип("ПолеФормы"), ТекГруппа); Элемент.Вид = ВидПоляФормы.ПолеВвода; Элемент.ПутьКДанным = "Объект.ВидРабочегоВремени"; Элемент.Вид = ВидПоляФормы.ПолеВвода; Элемент.АвтоМаксимальнаяШирина = ложь; Элемент.РастягиватьПоГоризонтали = ложь; Элемент.Ширина = 10; Элемент = Элементы.Добавить("ПроцентУменьшения", Тип("ПолеФормы"), ТекГруппа); Элемент.Вид = ВидПоляФормы.ПолеВвода; Элемент.ПутьКДанным = "Объект.ПроцентУменьшения"; Элемент.Вид = ВидПоляФормы.ПолеВвода; Элемент.АвтоМаксимальнаяШирина = ложь; Элемент.РастягиватьПоГоризонтали = ложь; Элемент.Ширина = 10; ПередЭлемент = Элементы.НачисленияДепозит; Элемент = Элементы.Вставить("Начально", Тип("ПолеФормы"), ПередЭлемент.Родитель, ПередЭлемент); Элемент.Вид = ВидПоляФормы.ПолеВвода; Элемент.ПутьКДанным = "Объект.Начисления.Начально"; Элемент.ТолькоПросмотр = истина; Элемент.Ширина = 10; Если Не ЗначениеЗаполнено(Объект.ВидРабочегоВремени) Тогда Объект.ВидРабочегоВремени = Справочники._ВидыРабочегоВремени.Факт; КонецЕсли; КонецПроцедуры
Среда: 11.4.13.46. Объем факт: 1 час.
Объектная модель 1с устарела невероятно. Любые изменения в модулях умноженные на дурную сложность (из-за дурацкого клиент-сервера и уф) типовых приводят к подобному соплестроению. Из хавна и палок. Чтобы все не развалилось, из-за стека вызовов длиной с ногу, нужно доп реквизит вываливать во внешнее хранилище, а потом его считывать.
А как это делается в нормальных языках программирования? можно пример? Насколько я понимаю, передача контента не особо решена в этих языках.
Не, ну можно мембера в класс прописать, как вариант, но это если это метод класса.
Какого контента? В нормальных языках методов вне классов не существует. И избегают прямых зависимостей, везде где взаимодействие не конкретные классы, а интерфейсы
ну вот как бы ты решал подобную задачу в нормальном языке, что бы у тебя было классом?
Исходный код был бы например МенеджерРасчетаТарифа
Тогда либо наследуем его в лоб и переопределяем соответствующий метод и добавляем свойство
Либо, реализуя тот же интерфейс, оборачиваем исходный объект и делаем класс-обертку
Второе более универсально (можно любой менеджер обернуть, реализующий интерфейс), но в текущем исполнении проще первое, т.к. внедриться в код доступа не удастся — придется метод переписать
мне это ничего не сказало. В общем допустим автор реализовал расчет начисления без учета вида времени. А тут нужно реализовать с учетом вида времени.
Как бы это было в ООП?
ну допустим, в простейшем случае:
РасчитаннаяЗарплата = ОтработанноеВремя * Тариф
Нужно заменить на
РасчитаннаяЗарплата = ОтработанноеВремя[ВидВремени] * Тариф
Вот я смог заменить процедуру расчета на свою и передать в ее контент ВидВремени.
А как это делается в серьезных языках?
из строки
РасчитаннаяЗарплата = ОтработанноеВремя * Тариф
я делаю вывод, что ОтработанноеВремя — это число
а из строки
РасчитаннаяЗарплата = ОтработанноеВремя[ВидВремени] * Тариф
делаю вывод, что это массив (коллекция какая-то)
но как?
ОтработанноеВремя — это функция
ОтработанноеВремя () и ОтработанноеВремя (ВидВремени)
Решение это комбинация паттернов dependency injection, inversion of control, state, interpreter (в зависимости от того насколько гибкое решение получить надо)
В принципе, помещение состояние объекта во внешние хранилище (будь то БД или in-memory) вполне нормальное. Просто с таким подходом надо все параметры выносить в него, а не как ты сделал. Но как костыль сойдет.
А что дает вынесение всех параметров во внешнее хранилище?
state-less сервис. Можно поднимать несколько экземпляров. Можно обеспечивать версионность состояний и проигрывания сценария заново с нужной версии. В таком духе работают workflow движки (BPMN одна из частных вариаций)
Не понимаю, как это решит озвученные вами выше проблемы.
«чтобы не потерять обновления» не используют аннотацию &Вместо без ПродолжитьВызов, иначе нет смысла сохранять наследие
ну тут я слишком сильно поменял логику, хотя согласен, можно было бы изменение с контролем использовать. 😉
В метод класса подавался бы объект реализующий интерфейс. Из которого можно получить время и на что-то умножить. Как в нем время хранится — как реквизит или как соответствие по видам — пофигу. Интерфейс формирует «договор», что из класса, который этот интерфейс реализует торчат вот эти свойства наружу и есть метод «получить время» которое возвращает значение определенного типа. И не надо еще городить как в 1с кучу дебильного кода «для универсальности» — а ты точно не список? А ты точно не структура?
В общем, ничего не понятно, но ладно.
Это понятно, что тебе непонятно. Почитать паттерны проектирования неплохо
зачем? я в 1С, там это не нужно.
Не ненужно, а неприменимо. Только в виде имитации. Ибо — см. первый пост
нет, 1с реализует сполна принцип KISS
Тогда к чему такие вопросы?
Профессиональное любопытство. Ну и хочется понять, если ли у товарища чего сказать, или он просто буквоедически прячется за стеной умных слов и терминов.
Принцип кисс в 1с нереализуем. Иьо нет ооп в явном виде. Прстотота 1с заканчивается за пределами 2+2
это уже «религиозные» споры. я умываю руки.