Как в 1С реализовать расчет графика приема с условием каждый третий день и выводом итогов по неделям

Программист 1С v8.3 (Управляемые формы)
← На главную

В практике программирования на платформе 1С:Предприятие часто возникают задачи, связанные с планированием графиков, расчетом периодических событий и накоплением итогов за определенные интервалы времени (например, недели или месяцы). Рассмотрим типичную задачу: необходимо рассчитать потребление определенного продукта (в нашем примере — протеина) на период 21 день, при условии, что прием осуществляется строго каждый третий день, а нормы потребления различаются для будней и выходных. При этом все данные должны быть сохранены в массив, а результаты выведены с группировкой по неделям.

Анализ исходных данных и постановка задачи

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

  1. Точка отсчета: Расчет всегда начинается с понедельника (это 1-й день цикла).
  2. Периодичность: Прием осуществляется каждый третий день (1-й — нет, 2-й — нет, 3-й — да, 4-й — нет и т.д.).
  3. Дифференциация норм: В будние дни (пн-пт) норма одна, в выходные (сб-вс) — другая.
  4. Хранение: Все данные о каждом дне (номер дня, флаг приема, количество) должны храниться в коллекции Массив.
  5. Отчетность: Программа должна выводить промежуточный итог за каждую неделю и общий итог за весь период.

Проанализируем ситуацию: для решения нам потребуется организовать цикл по количеству дней и внутри него реализовать проверку условий. Рассмотрим пошагово, как это сделать правильно с точки зрения архитектуры 1С.

Шаг 1. Подготовка структуры хранения данных

В 1С Массив может хранить значения любого типа. Чтобы хранить "всю информацию" об одном дне, лучше всего использовать объект Структура. Рассмотрим подробнее, почему это удобно: структура позволяет обращаться к данным по именам полей (например, День.Количество), что делает код читаемым. Выясним причину, по которой обычный массив массивов менее удобен — в нем легко запутаться в индексах (0, 1, 2...).

Посмотрим на пример инициализации базовых переменных:


КоличествоДней = 21;
ЧастотаПриема = 3;
ПротеинВБудни = 100; // грамм
ПротеинВВыходные = 150; // грамм

МассивДанных = Новый Массив;
ОбщийИтог = 0;
ИтогЗаНеделю = 0;

Шаг 2. Реализация основного цикла расчета

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

Проанализируем расчет типа дня (будни или выходные). Поскольку мы начинаем с понедельника, мы можем использовать простую арифметику: дни с 1 по 5 — будни, 6 и 7 — выходные. Для любого дня номер дня в рамках недели можно вычислить как ((НомерДня - 1) % 7) + 1.


Для НомерДня = 1 По КоличествоДней Цикл
    
    // 1. Определяем, является ли день днем приема
    ЭтоДеньПриема = (НомерДня % ЧастотаПриема = 0);
    
    // 2. Определяем день недели (с учетом старта с понедельника)
    ДеньНедели = ((НомерДня - 1) % 7) + 1;
    ЯвляетсяВыходным = (ДеньНедели = 6 Или ДеньНедели = 7);
    
    // 3. Расчитываем количество
    ТекущееКоличество = 0;
    Если ЭтоДеньПриема Тогда
        ТекущееКоличество = ?(ЯвляетсяВыходным, ПротеинВВыходные, ПротеинВБудни);
    КонецЕсли;
    
    // 4. Сохраняем данные в структуру и массив
    ДанныеДня = Новый Структура;
    ДанныеДня.Вставить("Номер", НомерДня);
    ДанныеДня.Вставить("Прием", ЭтоДеньПриема);
    ДанныеДня.Вставить("Количество", ТекущееКоличество);
    
    МассивДанных.Добавить(ДанныеДня);
    
КонецЦикла;

Шаг 3. Группировка итогов по неделям

Теперь рассмотрим задачу вывода еженедельных итогов. Как было верно замечено в обсуждении на форуме, нам необходим дополнительный счетчик ИтогЗаНеделю. Мы будем накапливать в нем значения и выводить их в тот момент, когда заканчивается очередная семидневка. Выясним, как это реализовать элегантно внутри того же самого цикла, чтобы не плодить лишние итерации.

Разберем по шагам логику вывода:

  1. На каждой итерации прибавляем количество протеина к еженедельному счетчику.
  2. Проверяем, является ли текущий день воскресеньем (ДеньНедели = 7) или последним днем всего периода (НомерДня = КоличествоДней).
  3. Если условие выполняется, выводим результат и обнуляем счетчик для следующей недели.

НомерНедели = 1;
ИтогЗаНеделю = 0;
ОбщийИтог = 0;

Для Каждого Элемент Из МассивДанных Цикл
    
    ИтогЗаНеделю = ИтогЗаНеделю + Элемент.Количество;
    ОбщийИтог = ОбщийИтог + Элемент.Количество;
    
    // Определяем день недели для текущего элемента
    ТекущийДеньНедели = ((Элемент.Номер - 1) % 7) + 1;
    
    Если ТекущийДеньНедели = 7 Или Элемент.Номер = КоличествоДней Тогда
        Сообщить("Неделя " + НомерНедели + " - " + ИтогЗаНеделю + "гр");
        ИтогЗаНеделю = 0; // Обнуляем для новой недели
        НомерНедели = НомерНедели + 1;
    КонецЕсли;
    
КонецЦикла;

Сообщить("Всего - " + ОбщийИтог + "гр");

Шаг 4. Профессиональный подход с использованием функций даты

В реальных задачах 1С мы редко оперируем просто абстрактными числами (1, 2, 3...). Чаще всего работа идет с объектами типа Дата. Рассмотрим, как сделать наш алгоритм более универсальным, используя встроенные функции платформы, такие как ДеньНедели() и ДобавитьМесяц(). Это позволит избежать ручного вычисления выходных по номеру дня.

Посмотрим на пример кода, который учитывает реальные календарные даты:


ДатаНачала = НачалоНедели(ТекущаяДата()); // Всегда начинаем с понедельника текущей недели
ВсегоДней = 21;
МассивРезультатов = Новый Массив;

Для Индекс = 0 По ВсегоДней - 1 Цикл
    ТекущаяДатаЦикла = ДатаНачала + (Индекс * 86400); // Прибавляем секунды (в сутках 86400 сек)
    
    НомерДняВГрафике = Индекс + 1;
    ЭтоДеньПриема = (НомерДняВГрафике % ЧастотаПриема = 0);
    
    ЧислоДняНедели = ДеньНедели(ТекущаяДатаЦикла);
    
    Если ЭтоДеньПриема Тогда
        Расход = ?(ЧислоДняНедели > 5, ПротеинВВыходные, ПротеинВБудни);
    Иначе
        Расход = 0;
    КонецЕсли;
    
    // Хранение данных
    Запись = Новый Структура("Дата, Количество, Прием", ТекущаяДатаЦикла, Расход, ЭтоДеньПриема);
    МассивРезультатов.Добавить(Запись);
КонецЦикла;

Анализ возможных ошибок

Проанализируем ситуацию, упомянутую в сообщении 23 форума: "нельзя внутри цикла менять переменную, являющуюся счетчиком". Это золотое правило разработки. Если вы используете цикл Для Счетчик = 1 По 21, попытка изменить Счетчик внутри тела цикла может привести к непредсказуемому поведению или бесконечному циклу. Если вам нужно гибкое управление итерациями, используйте цикл Пока.

Также стоит предостеречь от использования функции Выполнить(), предложенной в некоторых сообщениях. В 1С это считается плохим тоном (антипаттерном) для простых математических задач. Это снижает производительность и затрудняет отладку кода. Всегда отдавайте предпочтение явным вычислениям и обращению к свойствам структур или колонкам ТаблицыЗначений.

Заключение

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

← На главную