Перейти к основному содержимому

Гибкое планирование производства

Данный вариант расширяет базовую задачу планирования производства, добавляя возможность выбора альтернативных способов выполнения каждой операции. В реальном производстве одна и та же операция часто может быть выполнена на разных станках с различной производительностью. Например, фрезеровка детали может производиться как на универсальном станке за 5 часов, так и на специализированном оборудовании за 3 часа, или на высокоточном станке за 4 часа.

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

Постановка задачи

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

Работа 0:

  • Операция 0: станок 0 (3 ед.) / станок 1 (4 ед.) / станок 2 (5 ед.);
  • Операция 1: станок 0 (4 ед.) / станок 1 (2 ед.) / станок 2 (3 ед.);
  • Операция 2: станок 0 (3 ед.) / станок 1 (3 ед.) / станок 2 (2 ед.).

Работа 1:

  • Операция 0: станок 0 (2 ед.) / станок 1 (3 ед.) / станок 2 (4 ед.);
  • Операция 1: станок 0 (2 ед.) / станок 1 (1 ед.) / станок 2 (1 ед.);
  • Операция 2: станок 0 (5 ед.) / станок 1 (4 ед.) / станок 2 (3 ед.).

Работа 2:

  • Операция 0: станок 0 (3 ед.) / станок 1 (4 ед.) / станок 2 (2 ед.);
  • Операция 1: станок 0 (4 ед.) / станок 1 (2 ед.) / станок 2 (3 ед.).

Ограничения:

  • Для каждой операции должен быть выбран ровно один вариант выполнения;
  • Операции в рамках одной работы выполняются последовательно;
  • На одном станке операции не могут пересекаться во времени;
  • Требуется минимизировать общее время завершения всех работ.

Решение задачи

1. Выбор модели

Как и в базовом варианте, используется модель ограничений. Однако теперь задача включает дискретный выбор между альтернативами, что делает модель ограничений ещё более подходящей — она естественным образом работает с условными интервалами и булевыми переменными выбора.

2. Создание модели

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

// Создание модели ограничений
Модель = О2
.Модели()
.МодельОграничений()
.СоздатьМодель();

3. Ввод данных

Данные имеют более сложную структуру: для каждой операции задаётся массив альтернативных вариантов, каждый из которых содержит номер станка и длительность.

// Входные данные по работам и операциям
ДанныеРабот = Новый Массив;

// Работа 0
Работа = Новый Массив;

ВариантыОперации = Новый Массив;
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 0, 3));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 1, 4));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 2, 5));
Работа.Добавить(ВариантыОперации);

ВариантыОперации = Новый Массив;
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 0, 4));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 1, 2));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 2, 3));
Работа.Добавить(ВариантыОперации);

// ... остальные операции и работы
ДанныеРабот.Добавить(Работа);

// Определяем количество станков
НомерПоследнегоСтанка = 0;
Для Каждого Работа Из ДанныеРабот Цикл
Для Каждого ВариантыОперации Из Работа Цикл
Для Каждого ВариантОперации Из ВариантыОперации Цикл
Если ВариантОперации.Станок > НомерПоследнегоСтанка Тогда
НомерПоследнегоСтанка = ВариантОперации.Станок;
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
КоличествоСтанков = НомерПоследнегоСтанка + 1;

ВсеСтанки = Новый Массив;
Для К = 0 По КоличествоСтанков - 1 Цикл
ВсеСтанки.Добавить(К);
КонецЦикла;

Горизонт планирования вычисляется с учётом максимальной длительности среди всех вариантов каждой операции.

// Вычисляем горизонт планирования
ГоризонтПланирования = 0;
Для Каждого Работа Из ДанныеРабот Цикл
Для Каждого ВариантыОперации Из Работа Цикл
МаксимальнаяДлительность = 0;
Для Каждого ВариантОперации Из ВариантыОперации Цикл
Если ВариантОперации.Длительность > МаксимальнаяДлительность Тогда
МаксимальнаяДлительность = ВариантОперации.Длительность;
КонецЕсли;
КонецЦикла;
ГоризонтПланирования = ГоризонтПланирования + МаксимальнаяДлительность;
КонецЦикла;
КонецЦикла;

4. Регистрация переменных

Ключевое отличие от базового варианта — для каждой операции создаётся несколько условных интервалов (по одному на каждый вариант выполнения) и булевы переменные выбора. Интервал активируется только если соответствующая булева переменная равна 1.

// Регистрируем переменные
ВсеЗадачи = Новый Массив(ДанныеРабот.Количество());
ИнтервалыПоСтанкам = Новый Соответствие;

Для Каждого Станок Из ВсеСтанки Цикл
ИнтервалыПоСтанкам.Вставить(Станок, Новый Массив);
КонецЦикла;

Для НомерРаботы = 0 По ДанныеРабот.Количество() - 1 Цикл
Работа = ДанныеРабот[НомерРаботы];
ВсеЗадачи[НомерРаботы] = Новый Массив(Работа.Количество());

Для НомерОперации = 0 По Работа.Количество() - 1 Цикл
ВариантыОперации = Работа[НомерОперации];

Начало = Модель.ПеременнаяДиапазона(0, ГоризонтПланирования);
Конец = Модель.ПеременнаяДиапазона(0, ГоризонтПланирования);

ИнтервалыВариантов = Новый Массив;
БулевыВыборы = Новый Массив;

Для ИндексВарианта = 0 По ВариантыОперации.Количество() - 1 Цикл
ВариантОперации = ВариантыОперации[ИндексВарианта];
Станок = ВариантОперации.Станок;
ДлительностьОперации = ВариантОперации.Длительность;

ВыборВарианта = Модель.БулеваПеременная();
БулевыВыборы.Добавить(ВыборВарианта);

// Условный интервал, активный только при выборе этого варианта
Интервал = Модель.Ограничения().Интервал(Начало, ДлительностьОперации, Конец, ВыборВарианта);
ИнтервалыВариантов.Добавить(Интервал);

МассивИнтервалов = ИнтервалыПоСтанкам.Получить(Станок);
МассивИнтервалов.Добавить(Интервал);
КонецЦикла;

// Должен быть выбран ровно один вариант
Модель.Ограничения().ТолькоОдин(БулевыВыборы);

ВсеЗадачи[НомерРаботы][НомерОперации] = Новый Структура(
"Начало,Конец,Варианты,БулевыВыборы",
Начало, Конец, ВариантыОперации, БулевыВыборы
);
КонецЦикла;
КонецЦикла;

5. Описание ограничений

Ограничения аналогичны базовому варианту, но теперь работают с условными интервалами.

Запрет перекрытий на станках. Операции, выполняемые на одном станке, не могут пересекаться во времени.

// Ограничение: операции на одном станке не пересекаются
Для Каждого Станок Из ВсеСтанки Цикл
ИнтервалыНаСтанке = ИнтервалыПоСтанкам.Получить(Станок);
Если ИнтервалыНаСтанке.Количество() > 0 Тогда
Модель.Ограничения().ЗапретПерекрытий(ИнтервалыНаСтанке);
КонецЕсли;
КонецЦикла;

Технологическая последовательность операций. В рамках одной работы операции должны выполняться строго последовательно: каждая следующая операция начинается не раньше, чем закончится предыдущая.

// Ограничение: операции в рамках одной работы выполняются последовательно
Для НомерРаботы = 0 По ДанныеРабот.Количество() - 1 Цикл
Работа = ДанныеРабот[НомерРаботы];

Для НомерОперации = 0 По Работа.Количество() - 2 Цикл
Текущая = ВсеЗадачи[НомерРаботы][НомерОперации];
Следующая = ВсеЗадачи[НомерРаботы][НомерОперации + 1];

Модель.Ограничения().ЗначениеМеньшеИлиРавно(Текущая.Конец, Следующая.Начало);
КонецЦикла;
КонецЦикла;

6. Описание целевой функции

Целевая функция не изменилась — минимизация времени завершения всех работ.

// Целевая функция: минимизируем время завершения всех работ
МаксимальноеВремя = Модель.ПеременнаяДиапазона(0, ГоризонтПланирования, "МаксимальноеВремя");

Для НомерРаботы = 0 По ДанныеРабот.Количество() - 1 Цикл
Работа = ДанныеРабот[НомерРаботы];
НомерПоследнейОперации = Работа.Количество() - 1;

ДанныеПоследнейОперации = ВсеЗадачи[НомерРаботы][НомерПоследнейОперации];
КонецПоследнейОперации = ДанныеПоследнейОперации.Конец;

Модель.Ограничения().ЗначениеБольшеИлиРавно(МаксимальноеВремя, КонецПоследнейОперации);
КонецЦикла;

Модель.Минимизировать(МаксимальноеВремя);

7. Решение модели

Запускаем процесс оптимизации методом Решить.

// Решение модели
Решение = Модель.Решить();

8. Вывод результатов

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

// Вывод результатов
Если Решение.РешениеНайдено() Тогда
НазначенныеЗадачи = Новый Соответствие;
Для Каждого Станок Из ВсеСтанки Цикл
НазначенныеЗадачи.Вставить(Станок, Новый Массив);
КонецЦикла;

Для НомерРаботы = 0 По ДанныеРабот.Количество() - 1 Цикл
Работа = ДанныеРабот[НомерРаботы];

Для НомерОперации = 0 По Работа.Количество() - 1 Цикл
ДанныеОперации = ВсеЗадачи[НомерРаботы][НомерОперации];
ВремяНачала = Решение.ЗначениеПеременной(ДанныеОперации.Начало);

ВыбранныйСтанок = -1;
ДлительностьВыполнения = 0;

// Определяем выбранный вариант
Для ИндексВарианта = 0 По ДанныеОперации.Варианты.Количество() - 1 Цикл
ВыборВарианта = ДанныеОперации.БулевыВыборы[ИндексВарианта];
Если Решение.ЗначениеПеременной(ВыборВарианта) = 1 Тогда
ВыбранныйСтанок = ДанныеОперации.Варианты[ИндексВарианта].Станок;
ДлительностьВыполнения = ДанныеОперации.Варианты[ИндексВарианта].Длительность;
Прервать;
КонецЕсли;
КонецЦикла;

Если ВыбранныйСтанок >= 0 Тогда
Задача = Новый Структура(
"Начало, Работа, Операция, Длительность",
ВремяНачала, НомерРаботы, НомерОперации, ДлительностьВыполнения
);

МассивЗадач = НазначенныеЗадачи.Получить(ВыбранныйСтанок);
МассивЗадач.Добавить(Задача);
КонецЕсли;
КонецЦикла;
КонецЦикла;

// Сортировка и вывод расписания
Для Каждого Станок Из ВсеСтанки Цикл
МассивЗадач = НазначенныеЗадачи.Получить(Станок);

Для i = 0 По МассивЗадач.Количество() - 2 Цикл
Для j = i + 1 По МассивЗадач.Количество() - 1 Цикл
Если МассивЗадач[i].Начало > МассивЗадач[j].Начало Тогда
ВременнаяЗадача = МассивЗадач[i];
МассивЗадач[i] = МассивЗадач[j];
МассивЗадач[j] = ВременнаяЗадача;
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;

ОбщееВремя = Решение.ЗначениеПеременной(МаксимальноеВремя);

Сообщение = "Оптимальное расписание работ" + Символы.ПС + Символы.ПС;
Сообщение = Сообщение + "Общее время выполнения: " + ОбщееВремя + Символы.ПС + Символы.ПС;

Для Каждого Станок Из ВсеСтанки Цикл
МассивЗадач = НазначенныеЗадачи.Получить(Станок);

Сообщение = Сообщение + "Станок " + Станок + ":" + Символы.ПС;

Для Каждого Задача Из МассивЗадач Цикл
ВремяНачала = Задача.Начало;
ВремяОкончания = ВремяНачала + Задача.Длительность;

Сообщение = Сообщение + СтрШаблон(
" Работа %1, Операция %2: начало %3, окончание %4 (длительность: %5)%6",
Задача.Работа,
Задача.Операция,
ВремяНачала,
ВремяОкончания,
Задача.Длительность,
Символы.ПС
);
КонецЦикла;

Сообщение = Сообщение + Символы.ПС;
КонецЦикла;

Сообщить(Сообщение);
Иначе
Сообщить("Решение не найдено!");
КонецЕсли;

Полный код решения задачи

Ниже представлен полный код решения с выбором альтернативных станков. Вы также можете скачать пример в виде готовой обработки.

// Создание модели ограничений
Модель = О2
.Модели()
.МодельОграничений()
.СоздатьМодель();

// Входные данные по работам и операциям
ДанныеРабот = Новый Массив;

// Работа 0
Работа = Новый Массив;

ВариантыОперации = Новый Массив;
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 0, 3));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 1, 4));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 2, 5));
Работа.Добавить(ВариантыОперации);

ВариантыОперации = Новый Массив;
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 0, 4));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 1, 2));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 2, 3));
Работа.Добавить(ВариантыОперации);

ВариантыОперации = Новый Массив;
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 0, 3));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 1, 3));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 2, 2));
Работа.Добавить(ВариантыОперации);

ДанныеРабот.Добавить(Работа);

// Работа 1
Работа = Новый Массив;

ВариантыОперации = Новый Массив;
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 0, 2));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 1, 3));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 2, 4));
Работа.Добавить(ВариантыОперации);

ВариантыОперации = Новый Массив;
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 0, 2));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 1, 1));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 2, 1));
Работа.Добавить(ВариантыОперации);

ВариантыОперации = Новый Массив;
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 0, 5));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 1, 4));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 2, 3));
Работа.Добавить(ВариантыОперации);

ДанныеРабот.Добавить(Работа);

// Работа 2
Работа = Новый Массив;

ВариантыОперации = Новый Массив;
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 0, 3));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 1, 4));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 2, 2));
Работа.Добавить(ВариантыОперации);

ВариантыОперации = Новый Массив;
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 0, 4));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 1, 2));
ВариантыОперации.Добавить(Новый Структура("Станок, Длительность", 2, 3));
Работа.Добавить(ВариантыОперации);

ДанныеРабот.Добавить(Работа);

// Определяем количество станков
НомерПоследнегоСтанка = 0;
Для Каждого Работа Из ДанныеРабот Цикл
Для Каждого ВариантыОперации Из Работа Цикл
Для Каждого ВариантОперации Из ВариантыОперации Цикл
Если ВариантОперации.Станок > НомерПоследнегоСтанка Тогда
НомерПоследнегоСтанка = ВариантОперации.Станок;
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
КоличествоСтанков = НомерПоследнегоСтанка + 1;

ВсеСтанки = Новый Массив;
Для К = 0 По КоличествоСтанков - 1 Цикл
ВсеСтанки.Добавить(К);
КонецЦикла;

// Вычисляем горизонт планирования
ГоризонтПланирования = 0;
Для Каждого Работа Из ДанныеРабот Цикл
Для Каждого ВариантыОперации Из Работа Цикл
МаксимальнаяДлительность = 0;
Для Каждого ВариантОперации Из ВариантыОперации Цикл
Если ВариантОперации.Длительность > МаксимальнаяДлительность Тогда
МаксимальнаяДлительность = ВариантОперации.Длительность;
КонецЕсли;
КонецЦикла;
ГоризонтПланирования = ГоризонтПланирования + МаксимальнаяДлительность;
КонецЦикла;
КонецЦикла;

// Регистрируем переменные
ВсеЗадачи = Новый Массив(ДанныеРабот.Количество());
ИнтервалыПоСтанкам = Новый Соответствие;

Для Каждого Станок Из ВсеСтанки Цикл
ИнтервалыПоСтанкам.Вставить(Станок, Новый Массив);
КонецЦикла;

Для НомерРаботы = 0 По ДанныеРабот.Количество() - 1 Цикл
Работа = ДанныеРабот[НомерРаботы];
ВсеЗадачи[НомерРаботы] = Новый Массив(Работа.Количество());

Для НомерОперации = 0 По Работа.Количество() - 1 Цикл
ВариантыОперации = Работа[НомерОперации];

Начало = Модель.ПеременнаяДиапазона(0, ГоризонтПланирования);
Конец = Модель.ПеременнаяДиапазона(0, ГоризонтПланирования);

ИнтервалыВариантов = Новый Массив;
БулевыВыборы = Новый Массив;

Для ИндексВарианта = 0 По ВариантыОперации.Количество() - 1 Цикл
ВариантОперации = ВариантыОперации[ИндексВарианта];
Станок = ВариантОперации.Станок;
ДлительностьОперации = ВариантОперации.Длительность;

ВыборВарианта = Модель.БулеваПеременная();
БулевыВыборы.Добавить(ВыборВарианта);

// Условный интервал, активный только при выборе этого варианта
Интервал = Модель.Ограничения().Интервал(Начало, ДлительностьОперации, Конец, ВыборВарианта);
ИнтервалыВариантов.Добавить(Интервал);

МассивИнтервалов = ИнтервалыПоСтанкам.Получить(Станок);
МассивИнтервалов.Добавить(Интервал);
КонецЦикла;

// Должен быть выбран ровно один вариант
Модель.Ограничения().ТолькоОдин(БулевыВыборы);

ВсеЗадачи[НомерРаботы][НомерОперации] = Новый Структура(
"Начало,Конец,Варианты,БулевыВыборы",
Начало, Конец, ВариантыОперации, БулевыВыборы
);
КонецЦикла;
КонецЦикла;

// Ограничение: операции на одном станке не пересекаются
Для Каждого Станок Из ВсеСтанки Цикл
ИнтервалыНаСтанке = ИнтервалыПоСтанкам.Получить(Станок);
Если ИнтервалыНаСтанке.Количество() > 0 Тогда
Модель.Ограничения().ЗапретПерекрытий(ИнтервалыНаСтанке);
КонецЕсли;
КонецЦикла;

// Ограничение: операции в рамках одной работы выполняются последовательно
Для НомерРаботы = 0 По ДанныеРабот.Количество() - 1 Цикл
Работа = ДанныеРабот[НомерРаботы];

Для НомерОперации = 0 По Работа.Количество() - 2 Цикл
Текущая = ВсеЗадачи[НомерРаботы][НомерОперации];
Следующая = ВсеЗадачи[НомерРаботы][НомерОперации + 1];

Модель.Ограничения().ЗначениеМеньшеИлиРавно(Текущая.Конец, Следующая.Начало);
КонецЦикла;
КонецЦикла;

// Целевая функция: минимизируем время завершения всех работ
МаксимальноеВремя = Модель.ПеременнаяДиапазона(0, ГоризонтПланирования, "МаксимальноеВремя");

Для НомерРаботы = 0 По ДанныеРабот.Количество() - 1 Цикл
Работа = ДанныеРабот[НомерРаботы];
НомерПоследнейОперации = Работа.Количество() - 1;

ДанныеПоследнейОперации = ВсеЗадачи[НомерРаботы][НомерПоследнейОперации];
КонецПоследнейОперации = ДанныеПоследнейОперации.Конец;

Модель.Ограничения().ЗначениеБольшеИлиРавно(МаксимальноеВремя, КонецПоследнейОперации);
КонецЦикла;

Модель.Минимизировать(МаксимальноеВремя);

// Решение модели
Решение = Модель.Решить();

// Вывод результатов
Если Решение.РешениеНайдено() Тогда
НазначенныеЗадачи = Новый Соответствие;
Для Каждого Станок Из ВсеСтанки Цикл
НазначенныеЗадачи.Вставить(Станок, Новый Массив);
КонецЦикла;

Для НомерРаботы = 0 По ДанныеРабот.Количество() - 1 Цикл
Работа = ДанныеРабот[НомерРаботы];

Для НомерОперации = 0 По Работа.Количество() - 1 Цикл
ДанныеОперации = ВсеЗадачи[НомерРаботы][НомерОперации];
ВремяНачала = Решение.ЗначениеПеременной(ДанныеОперации.Начало);

ВыбранныйСтанок = -1;
ДлительностьВыполнения = 0;

// Определяем выбранный вариант
Для ИндексВарианта = 0 По ДанныеОперации.Варианты.Количество() - 1 Цикл
ВыборВарианта = ДанныеОперации.БулевыВыборы[ИндексВарианта];
Если Решение.ЗначениеПеременной(ВыборВарианта) = 1 Тогда
ВыбранныйСтанок = ДанныеОперации.Варианты[ИндексВарианта].Станок;
ДлительностьВыполнения = ДанныеОперации.Варианты[ИндексВарианта].Длительность;
Прервать;
КонецЕсли;
КонецЦикла;

Если ВыбранныйСтанок >= 0 Тогда
Задача = Новый Структура(
"Начало, Работа, Операция, Длительность",
ВремяНачала, НомерРаботы, НомерОперации, ДлительностьВыполнения
);

МассивЗадач = НазначенныеЗадачи.Получить(ВыбранныйСтанок);
МассивЗадач.Добавить(Задача);
КонецЕсли;
КонецЦикла;
КонецЦикла;

// Сортировка и вывод расписания
Для Каждого Станок Из ВсеСтанки Цикл
МассивЗадач = НазначенныеЗадачи.Получить(Станок);

Для i = 0 По МассивЗадач.Количество() - 2 Цикл
Для j = i + 1 По МассивЗадач.Количество() - 1 Цикл
Если МассивЗадач[i].Начало > МассивЗадач[j].Начало Тогда
ВременнаяЗадача = МассивЗадач[i];
МассивЗадач[i] = МассивЗадач[j];
МассивЗадач[j] = ВременнаяЗадача;
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;

ОбщееВремя = Решение.ЗначениеПеременной(МаксимальноеВремя);

Сообщение = "Оптимальное расписание работ" + Символы.ПС + Символы.ПС;
Сообщение = Сообщение + "Общее время выполнения: " + ОбщееВремя + Символы.ПС + Символы.ПС;

Для Каждого Станок Из ВсеСтанки Цикл
МассивЗадач = НазначенныеЗадачи.Получить(Станок);

Сообщение = Сообщение + "Станок " + Станок + ":" + Символы.ПС;

Для Каждого Задача Из МассивЗадач Цикл
ВремяНачала = Задача.Начало;
ВремяОкончания = ВремяНачала + Задача.Длительность;

Сообщение = Сообщение + СтрШаблон(
" Работа %1, Операция %2: начало %3, окончание %4 (длительность: %5)%6",
Задача.Работа,
Задача.Операция,
ВремяНачала,
ВремяОкончания,
Задача.Длительность,
Символы.ПС
);
КонецЦикла;

Сообщение = Сообщение + Символы.ПС;
КонецЦикла;

Сообщить(Сообщение);
Иначе
Сообщить("Решение не найдено!");
КонецЕсли;
  Скачать пример