В практике разработки на платформе 1С часто возникает задача автоматизации периодических начислений, таких как ежемесячные премии или штрафы. Типичная ситуация: программист создает документ, который при проведении должен сформировать движения в регистре накопления на несколько месяцев вперед. Однако при просмотре отчета пользователь видит только единовременные выплаты, а ежемесячные отсутствуют. В этой статье мы подробно разберем, почему это происходит, проанализируем типичные ошибки в коде и определим наиболее правильную архитектуру для решения таких задач.
Рассмотрим фрагмент кода, который отвечает за формирование движений в регистре НачисленныеШтрафыИПремии. Проблема заключается в том, что при выборе режима "ежемесячно" данные в отчет не попадают. Проанализируем основной цикл обработки табличной части:
Для Каждого ТекСтрокаШтрафы Из Штрафы Цикл
Если ТекСтрокаШтрафы.ШтрафИлиПремия = Перечисления.ШтрафИлиПремия.Надбавка Тогда
Если ТекСтрокаШтрафы.ПериодОплаты = ОбщегоНазначения.ПолучитьВидНачисления(2) Тогда
Для сч = 0 По ТекСтрокаШтрафы.ВыплатаВМесяц - 1 Цикл
Движение = Движения.НачисленныеШтрафыИПремии.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
Движение.Период = ДобавитьМесяц(Дата, сч);
Движение.Сотрудник = ТекСтрокаШтрафы.Сотрудник;
// ... заполнение других реквизитов
Движение.ПроцентОтОбшейСуммы = ТекСтрокаШтрафы.Проценты;
КонецЦикла;
Иначе
// Логика для единовременной выплаты
Движение.Сумма = ТекСтрокаШтрафы.Итог;
КонецЕсли;
КонецЕсли;
КонецЦикла;
При внимательном изучении кода мы можем выделить несколько критических моментов, которые приводят к потере данных:
Сумма, а в цикле для ежемесячных выплат — ресурс ПроцентОтОбшейСуммы. Если ваш отчет настроен на вывод поля Сумма, он покажет пустые строки для всех ежемесячных записей.ОбщегоНазначения.ПолучитьВидНачисления(2) — это крайне ненадежный метод. Если функция вернет значение другого типа или порядок элементов в перечислении изменится, условие всегда будет ложным, и программа уйдет в ветку Иначе (единовременная выплата).ВыплатаВМесяц в табличной части равен нулю или не заполнен, цикл Для сч = 0 По ... не выполнится ни разу. В результате движения просто не будут созданы.Разберем, как правильно проверить работу данного кода. Самый эффективный метод — установка точки останова на строке с началом цикла Для Каждого ТекСтрокаШтрафы. Во время пошагового выполнения (клавиша F10) нам необходимо проконтролировать следующие моменты:
Во-первых, выясним, какое значение возвращает функция ПолучитьВидНачисления(2). Для этого выделим выражение и нажмем Shift+F9. Если результат не совпадает с типом ПеречислениеСсылка.ПериодОплаты, условие никогда не выполнится.
Во-вторых, проверим значение переменной ТекСтрокаШтрафы.ВыплатаВМесяц. Если там 0, то выполнение кода сразу перейдет к следующей строке после КонецЦикла. Важно помнить: в 1С цикл "Для" с параметром "По -1" игнорируется системой.
Часто программисты забывают о том, как работают стандартные отчеты и система компоновки данных (СКД). Рассмотрим ситуацию: вы проводите документ 15 января. Код создает движения на 15 января, 15 февраля, 15 марта и так далее с помощью функции ДобавитьМесяц(Дата, сч).
Когда бухгалтер открывает отчет, по умолчанию период часто установлен как "Текущий месяц" или ограничено текущей датой. В этом случае записи за февраль и март просто не попадают в выборку. Чтобы увидеть данные, необходимо в настройках отчета расширить период до конца года. Это не ошибка кода, а особенность восприятия данных пользователем.
Хотя технически возможно делать записи в регистр накопления "на будущее", в среде 1С это считается "плохим тоном" и может привести к серьезным проблемам в будущем. Проанализируем, почему:
Правильное решение: Разделим задачу на два этапа. На первом этапе создадим Регистр сведений (периодический), в который будем записывать "план": кто, сколько и в течение какого времени должен получать премию или штраф. На втором этапе создадим ежемесячный регламентный документ (например, "Начисление зарплаты"), который при заполнении будет считывать данные из этого регистра сведений и фиксировать фактические движения в регистре накопления за текущий месяц.
Если вы все же решили оставить текущую схему с регистрами накопления, рассмотрим, как сделать код более надежным и читаемым. Заменим сомнительные вызовы функций на прямые ссылки на метаданные:
// Правильное сравнение с перечислением
Если ТекСтрокаШтрафы.ПериодОплаты = Перечисления.ПериодОплаты.Ежемесячно Тогда
// Проверка на заполнение количества месяцев
КоличествоМесяцев = ТекСтрокаШтрафы.ВыплатаВМесяц;
Если КоличествоМесяцев = 0 Тогда
Сообщить("Для сотрудника " + ТекСтрокаШтрафы.Сотрудник + " не указан период выплат!");
Продолжить;
КонецЕсли;
Для сч = 0 По КоличествоМесяцев - 1 Цикл
Движение = Движения.НачисленныеШтрафыИПремии.Добавить();
Движение.ВидДвижения = ?(ТекСтрокаШтрафы.ШтрафИлиПремия = Перечисления.ШтрафИлиПремия.Надбавка,
ВидДвиженияНакопления.Приход,
ВидДвиженияНакопления.Расход);
Движение.Период = ДобавитьМесяц(Дата, сч);
Движение.Сотрудник = ТекСтрокаШтрафы.Сотрудник;
// Обязательно заполняем СУММУ, чтобы отчет ее увидел
Движение.Сумма = ТекСтрокаШтрафы.Итог / КоличествоМесяцев;
Движение.ПроцентОтОбшейСуммы = ТекСтрокаШтрафы.Проценты;
КонецЦикла;
КонецЕсли;
В данном примере мы добавили расчет суммы для каждой итерации и обеспечили правильную установку вида движения (Приход/Расход) в зависимости от типа выплаты. Использование тернарного оператора ?() делает код компактнее и снижает вероятность дублирования ошибок в блоках Если...ИначеЕсли.
Подводя итог, при возникновении подобных проблем всегда начинайте с проверки ресурсов регистра и соответствия типов в условиях. Тщательная отладка каждой строки цикла позволит вам быстро найти место, где логика программы расходится с вашими ожиданиями.