Гибкое планирование производства
Данный вариант расширяет базовую задачу планирования производства, добавляя возможность выбора альтернативных способов выполнения каждой операции. В реальном производстве одна и та же операция часто может быть выполнена на разных станках с различной производительностью. Например, фрезеровка детали может производиться как на универсальном станке за 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",
Задача.Работа,
Задача.Операция,
ВремяНачала,
ВремяОкончания,
Задача.Длительность,
Символы.ПС
);
КонецЦикла;
Сообщение = Сообщение + Символы.ПС;
КонецЦикла;
Сообщить(Сообщение);
Иначе
Сообщить("Решение не найдено!");
КонецЕсли;