Собственные A01/3 и B02/2 связаны с ценой только A01/500. Меняем настоящий ТипСвязи и проверяем, что внешняя сохраняет обе продажи, а внутренняя исключает B02.
Где и как запускать
Полный серверный общий модуль УчебныеСвязиМакета85 собственного нового стенда; полный отдельный модуль формы ниже.
Подготовка
НОВАЯ пустая файловая база 8.5.1.1343 с корневым Именем=УчебныеСвязиМакета85, совместимость НеИспользовать и управляемое приложение. Не рабочая КА2/УНФ/УТ/БП и не их копия. Без бизнес-метаданных, расширений, подписок, заданий и внешних действий. Здесь база не создавалась и примеры не выполнялись: runtimeVerifiedScenarios=0.
Создайте общий модуль УчебныеСвязиМакета85: Сервер=Да, Клиент=Нет, Вызов сервера=Нет, Глобальный=Нет, Привилегированный=Нет. Вставьте ВЕСЬ основной код одного урока. Не объединяйте уроки с одинаковыми именами процедур. Все таблицы, схемы, настройки и макеты создаются самим модулем.
В своей новой конфигурации создайте обработку УчебныеПродажиИЦены85 → управляемая форма Пульт → команда Выполнить с действием Выполнить и кнопкой. Полный модуль формы ниже. F7, затем обновление только собственного нового стенда перед будущим отдельно разрешённым тестом.
Обычный стенд: источник A01/3 и B02/2, приёмник цен только A01/500. Параметрический стенд: внешняя таблица источника только A01/3, запрос приёмника содержит собственные буквальные A01/500 и B01/700. Эти таблицы не являются документами и не записываются в базу.
Источник всегда объектный, поэтому это реальное связывание самостоятельных наборов, а не один SQL JOIN, который компоновщик мог бы получить слиянием двух запросов. Внешняя структура передаётся вторым аргументом Инициализировать. Макет создаёт генератор коллекции значений; настройки только детальные записи без таблиц и диаграмм.
Будущий серверный просмотр: точки останова в ПроверитьСвоюСвязьМакета и ПроверитьСвойВыводСвязи. Читайте фактические имена и выражения макета, настоящий запрос параметрического приёмника и все реальные строки Результат. Таблицы и макеты не возвращайте в тонкий клиент.
Редакторский урок переносит собственный полный макет через XDTO в строку памяти. Файлы, архивы и резервные копии не создаются. Дублированные черновые связи и очищенный каталог не исполняются. Для повторного вывода восстанавливается весь исходный макет из своей строки.
Процедура ВыполнитьПример() Экспорт
ПроверитьСвойПустойСтенд();
Если ПривилегированныйРежим() Тогда
ВызватьИсключение "Привилегированный режим не нужен";
КонецЕсли;
Стенд = СоздатьСвоюОсновуСвязи(Истина);
Схема = Стенд.Схема;
СоздатьСвойОбъектныйНаборСвязи(Схема, "Приемник85", СтрРазделить("КодПриемника,ЦенаПриемника", ","));
Стенд.ВнешниеДанные.Вставить("Приемник85", СоздатьСвоиДанныеСвязи(Ложь, Ложь));
ДобавитьСвоиНастройкиИСвязь(Схема, Ложь, Ложь);
Макет = СкомпилироватьСвоюСвязь(Схема);
Связь = ПроверитьСвоюСвязьМакета(Макет, Ложь, Ложь);
Связь.ТипСвязи = ТипСвязиНаборовДанныхКомпоновкиДанных.Внешняя;
ПроверитьСвойВыводСвязи(Макет, Стенд.ВнешниеДанные, Истина);
Связь.ТипСвязи = ТипСвязиНаборовДанныхКомпоновкиДанных.Внутренняя;
ПроверитьСвойВыводСвязи(Макет, Стенд.ВнешниеДанные, Ложь);
Связь.ТипСвязи = ТипСвязиНаборовДанныхКомпоновкиДанных.Внешняя;
ПроверитьСвойВыводСвязи(Макет, Стенд.ВнешниеДанные, Истина);
Сообщить("Внешняя связь сохранила две продажи и 5 единиц; внутренняя оставила A01/3/500 и 3 единицы.");
КонецПроцедуры
Функция СвойТипПоляПараметра(ИмяПоля)
Если ИмяПоля = "КодИсточника" Или ИмяПоля = "КодПриемника" Тогда
Возврат Новый ОписаниеТипов("Строка");
ИначеЕсли ИмяПоля = "КоличествоИсточника" Тогда
Возврат Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(1, 0, ДопустимыйЗнак.Неотрицательный));
ИначеЕсли ИмяПоля = "ЦенаПриемника" Тогда
Возврат Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(3, 0, ДопустимыйЗнак.Неотрицательный));
КонецЕсли;
ВызватьИсключение "В собственной связи используются только четыре явно заданных поля";
КонецФункции
Процедура ПроверитьСвоиКвалификаторыПараметра(Описание, Разрядность)
Если ТипЗнч(Описание) <> Тип("ОписаниеТипов") Тогда
ВызватьИсключение "Нужно собственное описание числового поля параметрической связи";
КонецЕсли;
Квалификаторы = Описание.КвалификаторыЧисла;
Если ТипЗнч(Квалификаторы) <> Тип("КвалификаторыЧисла") Или Квалификаторы.Разрядность <> Разрядность
Или Квалификаторы.РазрядностьДробнойЧасти <> 0
Или Квалификаторы.ДопустимыйЗнак <> ДопустимыйЗнак.Неотрицательный Тогда
ВызватьИсключение "Не совпал полный собственный числовой контракт количества или цены";
КонецЕсли;
КонецПроцедуры
Процедура ДобавитьСвоиОписанияСвязи(Набор, Имена)
Для Каждого Имя Из Имена Цикл
Поле = Набор.Поля.Добавить(Тип("ПолеНабораДанныхСхемыКомпоновкиДанных"));
Поле.Поле = Имя;
Поле.ПутьКДанным = Имя;
Поле.ТипЗначения = СвойТипПоляПараметра(Имя);
Если Имя = "КоличествоИсточника" Тогда
ПроверитьСвоиКвалификаторыПараметра(Поле.ТипЗначения, 1);
ИначеЕсли Имя = "ЦенаПриемника" Тогда
ПроверитьСвоиКвалификаторыПараметра(Поле.ТипЗначения, 3);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Функция СоздатьСвоиДанныеСвязи(Источник, СПропущеннойЦеной)
Если Не Источник И СПропущеннойЦеной Тогда
ВызватьИсключение "Отсутствующую цену моделируем только второй строкой источника";
КонецЕсли;
Таблица = Новый ТаблицаЗначений;
ИмяКода = ?(Источник, "КодИсточника", "КодПриемника");
ИмяЧисла = ?(Источник, "КоличествоИсточника", "ЦенаПриемника");
Таблица.Колонки.Добавить(ИмяКода, СвойТипПоляПараметра(ИмяКода));
Таблица.Колонки.Добавить(ИмяЧисла, СвойТипПоляПараметра(ИмяЧисла));
СтрокаДанных = Таблица.Добавить();
СтрокаДанных[ИмяКода] = "A01";
СтрокаДанных[ИмяЧисла] = ?(Источник, 3, 500);
Если СПропущеннойЦеной Тогда
СтрокаДанных = Таблица.Добавить();
СтрокаДанных[ИмяКода] = "B02";
СтрокаДанных[ИмяЧисла] = 2;
КонецЕсли;
Возврат Таблица;
КонецФункции
Функция СоздатьСвойОбъектныйНаборСвязи(Схема, Имя, ИменаПолей)
Набор = Схема.НаборыДанных.Добавить(Тип("НаборДанныхОбъектСхемыКомпоновкиДанных"));
Набор.Имя = Имя;
Набор.ИмяОбъекта = Имя;
Набор.ИсточникДанных = "СвоиСвязиМакета85";
ДобавитьСвоиОписанияСвязи(Набор, ИменаПолей);
Возврат Набор;
КонецФункции
Функция СоздатьСвоюОсновуСвязи(СПропущеннойЦеной)
Схема = Новый СхемаКомпоновкиДанных;
Источник = Схема.ИсточникиДанных.Добавить();
Источник.Имя = "СвоиСвязиМакета85";
Источник.ТипИсточникаДанных = "Local";
СоздатьСвойОбъектныйНаборСвязи(Схема, "Источник85", СтрРазделить("КодИсточника,КоличествоИсточника", ","));
ВнешниеДанные = Новый Структура;
ВнешниеДанные.Вставить("Источник85", СоздатьСвоиДанныеСвязи(Истина, СПропущеннойЦеной));
Возврат Новый Структура("Схема,ВнешниеДанные", Схема, ВнешниеДанные);
КонецФункции
Процедура ДобавитьСвоиНастройкиИСвязь(Схема, ЧерезПараметр, СписокРазрешен)
Связь = Схема.СвязиНаборовДанных.Добавить();
Связь.НаборДанныхИсточник = "Источник85";
Связь.НаборДанныхПриемник = "Приемник85";
Связь.ВыражениеИсточник = "КодИсточника";
Связь.ВыражениеПриемник = "КодПриемника";
Связь.Обязательная = Ложь;
Связь.УсловиеСвязи = "КоличествоИсточника > 0";
Если ЧерезПараметр Тогда
Связь.Параметр = "КодыСвязи85";
Связь.РазрешенСписокПараметров = СписокРазрешен;
КонецЕсли;
Для Каждого Путь Из СтрРазделить("КодИсточника,КоличествоИсточника,ЦенаПриемника", ",") Цикл
Выбранное = Схема.НастройкиПоУмолчанию.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
Выбранное.Поле = Новый ПолеКомпоновкиДанных(Путь);
Выбранное.Использование = Истина;
КонецЦикла;
Детали = Схема.НастройкиПоУмолчанию.Структура.Добавить(Тип("ГруппировкаКомпоновкиДанных"));
Детали.Использование = Истина;
ВыборДеталей = Детали.Выбор.Элементы.Добавить(Тип("АвтоВыбранноеПолеКомпоновкиДанных"));
ВыборДеталей.Использование = Истина;
КонецПроцедуры
Функция СкомпилироватьСвоюСвязь(Схема)
Компоновщик = Новый КомпоновщикМакетаКомпоновкиДанных;
Возврат Компоновщик.Выполнить(Схема, Схема.НастройкиПоУмолчанию, , , Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
КонецФункции
Функция ПроверитьСвоюСвязьМакета(Макет, ЧерезПараметр, СписокРазрешен)
Если ТипЗнч(Макет) <> Тип("МакетКомпоновкиДанных") Или Макет.НаборыДанных.Количество() <> 2 Тогда
ВызватьИсключение "Нужны два реальных независимых набора полного своего макета";
КонецЕсли;
Каталог = Макет.СвязиНаборовДанных;
Если ТипЗнч(Каталог) <> Тип("СвязиНаборовДанныхМакетаКомпоновкиДанных") Или Каталог.Количество() <> 1 Тогда
ВызватьИсключение "Нужен один настоящий скомпилированный элемент связи";
КонецЕсли;
Связь = Каталог.Получить(0);
Если ТипЗнч(Связь) <> Тип("СвязьНаборовДанныхМакетаКомпоновкиДанных") Тогда
ВызватьИсключение "Нужна связь макета, не исходной схемы";
КонецЕсли;
НаборИсточник = Макет.НаборыДанных.Найти(Связь.НаборДанныхИсточник);
НаборПриемник = Макет.НаборыДанных.Найти(Связь.НаборДанныхПриемник);
Если НаборИсточник = Неопределено Или НаборПриемник = Неопределено Тогда
ВызватьИсключение "Оба фактических имени связи должны находить настоящие наборы макета";
КонецЕсли;
Если НаборИсточник = НаборПриемник Или ТипЗнч(НаборИсточник) <> Тип("НаборДанныхОбъектМакетаКомпоновкиДанных") Тогда
ВызватьИсключение "Здесь две разные стороны, источник — собственная внешняя таблица";
КонецЕсли;
Если НаборИсточник.ИмяОбъекта <> "Источник85" Или Связь.ВыражениеИсточник = "" Или Связь.ВыражениеПриемник = "" Тогда
ВызватьИсключение "Нужны свой внешний ключ источника и оба настоящих выражения";
КонецЕсли;
Если Связь.ТипСвязи <> ТипСвязиНаборовДанныхКомпоновкиДанных.Внешняя
И Связь.ТипСвязи <> ТипСвязиНаборовДанныхКомпоновкиДанных.Внутренняя Тогда
ВызватьИсключение "Допустимы только два native-варианта типа связи";
КонецЕсли;
Если ЧерезПараметр Тогда
Если ТипЗнч(НаборПриемник) <> Тип("НаборДанныхЗапросМакетаКомпоновкиДанных") Или Связь.Параметр = ""
Или Связь.РазрешенСписокПараметров <> СписокРазрешен Тогда
ВызватьИсключение "Параметрическая связь должна вести в реальный запрос со своим режимом значения";
КонецЕсли;
Если СтрНайти(ВРег(НаборПриемник.Запрос), ВРег("&" + Связь.Параметр)) = 0 Тогда
ВызватьИсключение "Фактический параметр связи должен использоваться в настоящем запросе приёмника";
КонецЕсли;
Иначе
Если ТипЗнч(НаборПриемник) <> Тип("НаборДанныхОбъектМакетаКомпоновкиДанных") Тогда
ВызватьИсключение "Обычный приёмник — собственная таблица цен";
КонецЕсли;
Если НаборПриемник.ИмяОбъекта <> "Приемник85" Или Связь.Параметр <> "" Или Связь.РазрешенСписокПараметров Тогда
ВызватьИсключение "В обычной объектной связи нет параметра запроса";
КонецЕсли;
КонецЕсли;
Сообщить("Источник=" + Связь.НаборДанныхИсточник + "; приёмник=" + Связь.НаборДанныхПриемник);
Сообщить("Выражения=" + Связь.ВыражениеИсточник + " / " + Связь.ВыражениеПриемник + "; условие=" + Связь.УсловиеСвязи);
Сообщить("Тип=" + Строка(Связь.ТипСвязи) + "; параметр=" + Связь.Параметр + "; список=" + Строка(Связь.РазрешенСписокПараметров));
Возврат Связь;
КонецФункции
Процедура ПроверитьСвойВыводСвязи(Макет, ВнешниеДанные, ОжидаетсяB02)
Процессор = Новый ПроцессорКомпоновкиДанных;
Процессор.Инициализировать(Макет, ВнешниеДанные);
Вывод = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
Приемник = Новый ТаблицаЗначений;
Вывод.ОтображатьПроцентВывода = Ложь;
Вывод.УстановитьОбъект(Приемник);
Результат = Вывод.Вывести(Процессор, Ложь);
Если ТипЗнч(Результат) <> Тип("ТаблицаЗначений") Или Результат.Количество() <> ?(ОжидаетсяB02, 2, 1)
Или Результат.Колонки.Количество() <> 3 Тогда
ВызватьИсключение "Не совпали реальные число продаж и три колонки детального вывода";
КонецЕсли;
Для Каждого Имя Из СтрРазделить("КодИсточника,КоличествоИсточника,ЦенаПриемника", ",") Цикл
Если Результат.Колонки.Найти(Имя) = Неопределено Тогда
ВызватьИсключение "Потеряно собственное выбранное поле " + Имя;
КонецЕсли;
КонецЦикла;
ЕстьA01 = Ложь;
ЕстьB02 = Ложь;
Для Каждого Продажа Из Результат Цикл
Если Продажа.КодИсточника = "A01" Тогда
Если ЕстьA01 Или Продажа.КоличествоИсточника <> 3 Или Продажа.ЦенаПриемника <> 500 Тогда
ВызватьИсключение "Нужна ровно одна связанная продажа A01/3/500";
КонецЕсли;
ЕстьA01 = Истина;
ИначеЕсли Продажа.КодИсточника = "B02" Тогда
Если Не ОжидаетсяB02 Или ЕстьB02 Или Продажа.КоличествоИсточника <> 2 Тогда
ВызватьИсключение "Продажа B02/2 допускается только один раз во внешней связи";
КонецЕсли;
ТипЦены = ТипЗнч(Продажа.ЦенаПриемника);
Если ТипЦены <> Тип("Неопределено") И ТипЦены <> Тип("Null") И ТипЦены <> Тип("Число") Тогда
ВызватьИсключение "Отсутствующая цена не должна заменяться чужим типом";
КонецЕсли;
Если ЗначениеЗаполнено(Продажа.ЦенаПриемника) Тогда
ВызватьИсключение "Для B02 нет строки цены: нельзя подставлять 500 от A01";
КонецЕсли;
ЕстьB02 = Истина;
Иначе
ВызватьИсключение "Лишняя продажа в реальном результате";
КонецЕсли;
КонецЦикла;
Если Не ЕстьA01 Или ЕстьB02 <> ОжидаетсяB02 Или Результат.Итог("КоличествоИсточника") <> ?(ОжидаетсяB02, 5, 3) Тогда
ВызватьИсключение "Нужен правильный состав продаж и итог количества 5 или 3";
КонецЕсли;
Сообщить("Настоящий вывод: A01/3/500; B02 присутствует=" + Строка(ЕстьB02) + "; количество=" + Строка(Результат.Итог("КоличествоИсточника")));
КонецПроцедуры
Процедура ПроверитьСвойПустойСтенд()
Если Метаданные.Справочники.Количество() <> 0 Или Метаданные.Документы.Количество() <> 0 Или Метаданные.Перечисления.Количество() <> 0 Или Метаданные.РегистрыСведений.Количество() <> 0 Или Метаданные.РегистрыНакопления.Количество() <> 0 Или Метаданные.РегистрыБухгалтерии.Количество() <> 0 Или Метаданные.РегистрыРасчета.Количество() <> 0 Или Метаданные.ПланыВидовХарактеристик.Количество() <> 0 Или Метаданные.ПланыВидовРасчета.Количество() <> 0 Или Метаданные.ПланыСчетов.Количество() <> 0 Или Метаданные.ПланыОбмена.Количество() <> 0 Или Метаданные.Последовательности.Количество() <> 0 Или Метаданные.БизнесПроцессы.Количество() <> 0 Или Метаданные.Задачи.Количество() <> 0 Или Метаданные.ОбщиеРеквизиты.Количество() <> 0 Или Метаданные.ПодпискиНаСобытия.Количество() <> 0 Или Метаданные.РегламентныеЗадания.Количество() <> 0 Или Метаданные.ВнешниеИсточникиДанных.Количество() <> 0 Тогда
ВызватьИсключение "Нужен только свой новый пустой учебный стенд";
КонецЕсли;
Если Метаданные.ЕстьИзмененияРасширениямиКонфигурации() Тогда
ВызватьИсключение "Свой сеанс не должен изменяться расширениями";
КонецЕсли;
Источники = Новый Массив;
Источники.Добавить(ИсточникРасширенийКонфигурации.БазаДанных);
Источники.Добавить(ИсточникРасширенийКонфигурации.СеансАктивные);
Источники.Добавить(ИсточникРасширенийКонфигурации.СеансОтключенные);
Для Каждого Источник Из Источники Цикл
Расширения = РасширенияКонфигурации.Получить(, Источник);
Если ТипЗнч(Расширения) <> Тип("Массив") Тогда
ВызватьИсключение "Native список расширений должен иметь тип Массив";
КонецЕсли;
Если Расширения.Количество() <> 0 Тогда
ВызватьИсключение "В своём новом стенде нет расширений базы или сеанса";
КонецЕсли;
КонецЦикла;
Если ТранзакцияАктивна() Тогда
ВызватьИсключение "Чужая или вложенная активная транзакция не допускается";
КонецЕсли;
КонецПроцедурыУчебныеПродажиИЦены85.Пульт
УчебныеСвязиМакета85 → Обработки → УчебныеПродажиИЦены85 → Формы → Пульт → Модуль
Полный модуль собственной управляемой формы; здесь не выполнялся.
&НаКлиенте
Процедура Выполнить(Команда)
ВыполнитьНаСервере();
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ВыполнитьНаСервере()
УчебныеСвязиМакета85.ВыполнитьПример();
КонецПроцедурыРазбор по шагам
СвойТипПоляПараметра и ПроверитьСвоиКвалификаторыПараметра переиспользуют точные описания четырёх полей: два строковых кода, количество Число(1,0) и цена Число(3,0), оба неотрицательные. Описания передаются колонкам при добавлении и полям схемы; readonly-тип колонки не заменяется.
Исходная СвязьНаборовДанныхСхемыКомпоновкиДанных сопоставляет КодИсточника и КодПриемника между собственными Источник85 и Приемник85. Условие КоличествоИсточника > 0 истинно для своих положительных продаж; пустое условие означало бы безусловное осуществление связи.
После компиляции берём настоящий элемент СвязьНаборовДанныхМакетаКомпоновкиДанных. Его фактические имена разрешаются через Макет.НаборыДанных.Найти и проверяются по типам и внешним ключам. Имена генератора не угадываются и не сравниваются с именами исходной схемы вслепую.
ВыражениеИсточник и ВыражениеПриемник — строки выражений, не значения кодов и не ссылки на сами таблицы. УсловиеСвязи — отдельное выражение допуска связи. Их фактические значения печатаются из настоящего макета; затем реальный состав результата подтверждает сопоставление.
Вывод.Вывести возвращает настоящий результат. Проверка сверяет точные выбранные колонки, все строки без дублей, связанную A01/3/500, наличие/отсутствие B02/2 и независимый итог количества. Для отсутствующей цены B02 допускается только незаполненное число/Null/Неопределено, а не цена A01.
Внешняя даёт A01/3/500 и B02/2 без цены, итог 5. Внутренняя на том же макете даёт только A01/3/500, итог 3. После возврата Внешняя заново выводится исходный полный состав. Каждый вывод создаёт свежий процессор.
Как проверить результат
Нужны два настоящих набора и одна настоящая связь макета. Отсутствующее фактическое имя одной из сторон должно остановить пример до инициализации процессора. Не подставляйте первый набор коллекции вместо результата nullable-поиска.
Будущий отрицательный контроль своего результата: заменить цену у B02 на 500 без изменения количества строк и суммы количества. Проверка должна отказать, хотя итог 5 сохранится. Второй контроль — дублировать A01 вместо B02: проверки состава и повторов должны отказать. Здесь контроль не запускался.
В 1С опыт не выполнялся. Эти проверки — код будущего опыта в собственном стенде, не подтверждение реальной компиляции, XDTO round-trip или работы процессора. Не заменяйте настоящий Результат таблицей ожидаемых строк ради прохождения проверки.
Ожидаются три самостоятельных вывода: две строки/5, одна строка/3, снова две строки/5. Проверяется не только тип перечисления, но и фактическое участие продажи без цены.
Ожидаемый результат
Ожидается реальная связанная A01/3/500. Обычный внешний режим дополнительно сохраняет B02/2 без цены, итог 5; внутренний и параметрические режимы дают только A01, итог 3. Здесь в 1С не запускалось.
На что обратить внимание
runtimeVerifiedScenarios=0. Все результаты ожидаемые, статически проверенные по установленной справке, но не измеренные запуском в 1С. Рабочие конфигурации и базы не меняются.
Обязательная у связи исходной схемы отвечает за обязательность включения связи при компиляции и не тождественна ТипСвязи макета. Внешняя сохраняет запись без приёмника; Внутренняя её исключает. В обычном опыте меняется именно ТипСвязи настоящего макета, остальные определения сохраняются.
Параметр означает имя параметра запроса приёмника, а не пользовательское значение. РазрешенСписокПараметров согласуется с операцией В; для равенства список не разрешается. Имя параметра из макета проверяется в фактическом запросе с учётом регистра, а не предполагается равным исходному имени.
НачальноеВыражение предназначено для рекурсивной связи набора с самим собой. Этот набор уроков её не демонстрирует и не засчитывает это свойство. Присваивание пустой строки в обычной связи не является обучением рекурсии.
СвязиНаборовДанных макета — readonly-ссылка на редактируемую коллекцию. У неё нет метода Найти. Добавить() и Вставить(Индекс) не принимают Тип; Сдвинуть и Удалить получают настоящий элемент той же коллекции. Не исполняйте дублированные или неполные черновые связи.
В пустой колонке коллекции значений представление отсутствующей цены может зависеть от установленного типа. Урок не считает нулевую цену найденной строкой: A01 проверяется как точные 500, B02 — отсутствие заполненной цены и правильное участие продажи во внешнем режиме.
Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.