Полный own fixture для современного bot-only API: фактические два IBUsers, первое создание нового bot-profile с собственным90-byte avatar, непредопределенный bot-description и native ProcessBots. Никаких fake ID/default-user impersonation/direct fake event calls.
Где и как запускать
COMMON целиком в серверный server-call общий модуль УчебныйБот85 + полный MODULE_INSPECTION в тот же модуль. BOT_MODULE отдельно только metadata.Боты.УчебныйПроверяющий85.Модуль; APPLICATION отдельно модуль управляемого приложения; CLIENT отдельно модуль управляемой формы. Own УчебныйБотКниги85/localhost:9094/two actualIBUsers/one nonpredefined bot.
Подготовка
Новая собственная пустая CF УчебныйБотКниги85 на8.5.1.1343: русский язык, управляемый интерфейс, совместимость Не использовать. Без предметных объектов/подписок/регламентных заданий/расширений. Ровно один создаваемый вручную metadata-бот и свои модули/роль; не рабочие конфигурации/сервера.
Свой отдельный новый абонент/сервер взаимодействия в изолированной VM, строго http://localhost:9094. У абонента ровно одно приложение УчебныйБотКниги85. Без SMTP/внешнего сайта/мессенджеров/интеграций/общих приложений/чужих пользователей. Установка сервера берется из документации поставки; здесь не придумывается серверный контракт.
В конфигураторе вручную создайте ровно двух IBUsers: Уч85Получатель_<ваш новый ненулевой канонический UUID> — человек, полное имя Учебный ученик; Уч85Отправитель_<другой ваш UUID> — исполняющий botIBUser, полное имя Учебный бот книги. UUID имени только метка; реальные IB UUID код получает из actual objects.
Для собственных учебных пользователей вручную назначьте необходимые права Администрирование данных/системы взаимодействия/доступа к своим модулям. Для named background execution бот должен иметь права исполнять свой модуль и серверные методы; не включайте глобальный privilegedMode и не переносите права в production.
Сначала войдите только под человеком Получателем и синхронизируйте его внутренний collaboration profile: exact name/full name/IBUUID, без почты/телефона/внешних ID/ключей сопоставления, не заблокирован. Пока НЕ входите под botIBUser и НЕ синхронизируйте его автоматически: его actual profile впервые создаст documented CreateUser(BotIBUser). Если уже существует, урок честно откажет, не удалит/не заменит его.
Конфигуратор → Общие → Боты → Добавить → имя УчебныйПроверяющий85, Предопределенный=Нет. Откройте именно Модуль этого объекта Бот и вставьте полный код bot-модуля из supportingModules/отдельного урока панели. Обычный общий модуль НЕ подходит: native Row.Add/Insert требуют модуль бота. Metadata.Модуль всегда Неопределено, это не текст/возможность записать модуль из BSL.
Создайте серверный общий модуль УчебныйБот85: Сервер/Вызов сервера включены, Клиент/Привилегированный/Глобальный выключены. Вставьте COMMON целиком и добавьте полный MODULE_INSPECTION из supportingModules в тот же общий модуль. Экспортируемые server-call процедуры сами проверяют текущую own role/metadata/users/app/localhost; клиент не может подменить цель произвольным MessageID.
Конфигуратор → Конфигурация → Открыть модуль управляемого приложения: вставьте полный клиентский native event ПриНажатииКнопкиПанелиКнопокСообщенияСистемыВзаимодействия(Сообщение,Кнопка) из supportingModules. Это событие нужно только собственной кнопке ОбработатьНаКлиенте; не вызывайте его программно и не устанавливайте старые action-hooks параллельно.
В своей внешней обработке/управляемой форме создайте строковые ПодтверждениеПодготовки/ПодтверждениеОтправки/ПодтверждениеКликов/ПодтверждениеСнятия длиной100, СвойКлючОбсуждения длиной100 и команды ПодготовитьБота/ОтправитьЗапросБоту/ПроверитьПанель/ПроверитьКлики/ОбработатьКлики/СнятьСвоегоБота с одноименными обработчиками. Вставьте полный supporting form module. Автоматического запуска при открытии нет.
Из своего human-сеанса сначала пустая фраза должна отказать. Затем сознательно введите СВОЙ БОТ И КАРТОЧКА750 и нажмите подготовку один раз. CreateUser требует краткий0→1→0 privilegedMode и действительно записывает только свой новый profile/маленький собственный PNG90 байт. При коллизии/ошибке остановитесь и проверяйте частичный результат вручную.
Bot description не предопределенный: User=реальный новый botID, Metadata=точный own metadata.Боты.УчебныйПроверяющий85, ExecuteProcessingInfoBaseUserName=actual имя своего botIBUser, AdditionalParameters=own serialized strings/IDs/UUID/opaque internal invite. Не оставляйте execution name пустым: это иной default-user контекст; SetCurrentUser обычного сеанса здесь не используется.
Задайте ОДИН ЗАПРОС СВОЕМУ БОТУ и нажмите ОтправитьЗапросБоту один раз. Current human отправит единственное own сообщение только actualbotID, затем ProcessBots запустит native фоновое получение. Ни COMMON, ни форма НЕ вызывают процедуру ОбработкаСообщения... напрямую.
Дождитесь ответа через ручную ПроверитьПанель, без повторной отправки/ProcessBots. Оставьте только свой human-сеанс, без параллельных очередей/регламентных запусков/изменения модулей/профилей. В native background session реально исполняется named botIBUser; это проверит роль Отправитель в bot-module guard.
После проверки собственной панели откройте только свое обсуждение по выданному ключу. В 1С8.5 вручную нажмите Показать750, Подтвердить750 и Показать на клиенте. Не активируйте disabled-кнопку внутренней ссылки: URI непрозрачен и код его не открывает. Не используются phone/location/provider tokens/контракты.
После этих реальных GUI-кликов нажмите ПроверитьКлики: нужны ровно два human-запроса и два bot-ответа с actual SourceMessageID. ProcessBots native обещает только запуск фонового получения, не вечную обработку всех будущих сообщений. Если ответы уже получены, нового задания не нужно. Если оба запроса уже существуют, но ответов еще нет, сначала вручную проверьте завершение предыдущего своего worker в администрировании собственного стенда. Лишь после подтвержденного завершения задайте ПРЕЖНИЙ WORKER ЗАВЕРШЕН КЛИКИ750 и нажмите ОбработатьКлики один раз; код не проверяет состояние задания автоматически и не отменяет его. Если worker все еще работает/статус неизвестен — не запускайте второй, ждите и проверяйте только чтением.
После завершения собственного фонового получения и ручных проверок отдельно можно задать СНЯТЬ ТОЛЬКО МОЕГО БОТА750. Удаляется только непредопределенная запись своего бота, не metadata/IBUser/profile/messages. Delete не заявлен как мгновенный Cancel работающего задания; автоматическое удаление при ошибке запрещено.
Модули/fixtures и PNG здесь только исходные данные/статические тесты. Никаких real1C/background/network/DB/account creations/image uploads/files/backup операций при авторстве не выполнено. Не объявляйте этот материал подтвержденным runtime вашего сервера.
Функция ПодготовитьСвоегоБота85(Подтверждение) Экспорт
Фикстура = ПроверитьНачальныеСвоиУчетныеЗаписи85();
Если Подтверждение <> "СВОЙ БОТ И КАРТОЧКА750" Тогда
ВызватьИсключение "Прочитайте подготовку. Профиль/бот/обсуждение не создаются";
КонецЕсли;
Попытка
УстановитьПривилегированныйРежим(Истина);
ПользовательБота = СистемаВзаимодействия.СоздатьПользователя(Фикстура.Бот);
ПользовательБота.ИдентификаторПользователяИнформационнойБазы = Фикстура.Бот.УникальныйИдентификатор;
ПользовательБота.Картинка = Новый Картинка(Base64Значение("iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAIUlEQVR4nGOQi96CFTHIRW/5jwTQJeB8OJtsHSQahdW5AIqqa+GXXcRPAAAAAElFTkSuQmCC"));
ПользовательБота.Записать();
Исключение
УстановитьПривилегированныйРежим(Ложь);
ВызватьИсключение "Создание собственного профиля бота могло произойти частично. PrivilegedMode выключен, повтора/удаления нет. Возможно, профиль уже существовал";
КонецПопытки;
УстановитьПривилегированныйРежим(Ложь);
Пара = ПроверитьСвоюПару85("Получатель");
Если ПользовательБота.Идентификатор <> Пара.Отправитель Тогда
ВызватьИсключение "Real profileID нового собственного бота не совпал с real IBUUID mapping";
КонецЕсли;
ПроверитьСвойАватарБота85(Пара.Отправитель);
Ключ = "Уч85БотКнига_" + Строка(Новый УникальныйИдентификатор);
Сообщить("Свой новый кандидат обсуждения: " + Ключ);
Если СистемаВзаимодействия.ПолучитьОбсуждение(Ключ) <> Неопределено Тогда
ВызватьИсключение "Ключ занят; существующее обсуждение не изменяется";
КонецЕсли;
Обсуждение = СистемаВзаимодействия.СоздатьОбсуждение();
Обсуждение.Ключ = Ключ;
Обсуждение.Заголовок = Ключ + " | Бот: Книга3×250";
Обсуждение.Групповое = Истина;
Обсуждение.Отображаемое = Истина;
Обсуждение.ТолькоПросмотр = Ложь;
Обсуждение.КонтекстОбсуждения = Неопределено;
Обсуждение.Интеграция = Неопределено;
Обсуждение.ВнешнийИдентификатор = "";
Обсуждение.Участники.Добавить(Пара.Получатель);
Обсуждение.Участники.Добавить(Пара.Отправитель);
Обсуждение.Записать();
ИДОбсуждения = Обсуждение.Идентификатор;
Если ТипЗнч(ИДОбсуждения) <> Тип("ИдентификаторОбсужденияСистемыВзаимодействия") Тогда
ВызватьИсключение "Собственный real discussionID после записи не получен";
КонецЕсли;
Приглашение = СистемаВзаимодействия.ПолучитьНавигационнуюСсылкуПриглашенияВОбсуждение(ИДОбсуждения, Ложь);
Если ТипЗнч(Приглашение) <> Тип("Строка") Или Не ЗначениеЗаполнено(Приглашение) Или СтрДлина(Приглашение) > 8192 Тогда
ВызватьИсключение "Внутреннее own-приглашение не получено. URI не открывается";
КонецЕсли;
Бот = СистемаВзаимодействия.СоздатьБота();
Если Бот.Предопределенный Тогда
ВызватьИсключение "Новый own bot должен быть непредопределенным";
КонецЕсли;
Бот.Пользователь = Пара.Отправитель;
Бот.Метаданные = Метаданные.Боты.УчебныйПроверяющий85;
Бот.ИмяПользователяИнформационнойБазыВыполненияОбработки = Фикстура.Бот.Имя;
Бот.ДополнительныеПараметры = Новый Структура("Ключ,Обсуждение,Ученик,Бот,Метка,Приглашение", Ключ, Строка(ИДОбсуждения),
Строка(Пара.Получатель), Строка(Пара.Отправитель), Строка(Новый УникальныйИдентификатор), Приглашение);
Бот.Записать();
Контекст = ПолучитьСвойКонтекстБота85("Получатель");
Если Контекст.Ключ <> Ключ Или Контекст.Обсуждение <> Строка(ИДОбсуждения) Тогда
ВызватьИсключение "Собственное bot-description после записи отдельно не подтверждено";
КонецЕсли;
Возврат Новый Структура("Ключ,Обсуждение", Ключ, Строка(ИДОбсуждения));
КонецФункции
Процедура ОтправитьЗапросИНачатьОбработку85(Подтверждение) Экспорт
Контекст = ПолучитьСвойКонтекстБота85("Получатель");
Если Подтверждение <> "ОДИН ЗАПРОС СВОЕМУ БОТУ" Тогда
ВызватьИсключение "Запрос не отправлен, обработка не запущена";
КонецЕсли;
Если ПолучитьСвойСписокСообщений85(Контекст, "Получатель").Количество() <> 0 Тогда
ВызватьИсключение "Own обсуждение уже содержит сообщения; повтор отправки запрещен";
КонецЕсли;
Сообщение = СистемаВзаимодействия.СоздатьСообщение(Новый ИдентификаторОбсужденияСистемыВзаимодействия(Контекст.Обсуждение));
Сообщение.Текст = Контекст.Ключ + "|Карточка750";
Сообщение.Данные = Контекст.Метка + "|Начать";
Сообщение.ФорматДанныхСтроки = "text/plain";
Сообщение.Получатели.Добавить(Новый ИдентификаторПользователяСистемыВзаимодействия(Контекст.Бот));
Сообщение.Записать();
Сообщения = ПолучитьСвойСписокСообщений85(Контекст, "Получатель");
Если Сообщения.Количество() <> 1 Или Сообщения[0].Идентификатор <> Сообщение.Идентификатор
Или Строка(Сообщения[0].Автор) <> Контекст.Ученик Или Сообщения[0].Данные <> Контекст.Метка + "|Начать" Тогда
ВызватьИсключение "Свой запрос мог записаться, но отдельно не подтвержден";
КонецЕсли;
СистемаВзаимодействия.ВыполнитьОбработкуБотов();
Сообщить("Запущено native фоновое получение сообщений собственного единственного бота. Это не callback/подтверждение готовности панели");
КонецПроцедуры
Функция ПолучитьСвойКонтекстБота85(Роль) Экспорт
ПроверитьСвоюМетадатуБота85();
Пара = ПроверитьСвоюПару85(Роль);
Если СистемаВзаимодействия.ПоддерживаетсяВнешнийДоступ() Тогда
ВызватьИсключение "Внешний сайт в своем bot-стенде должен оставаться выключенным";
КонецЕсли;
ВсеБоты = СистемаВзаимодействия.ПолучитьБотов();
Если ТипЗнч(ВсеБоты) <> Тип("Массив") Или ВсеБоты.Количество() <> 1 Тогда
ВызватьИсключение "Обработка разрешена только при одном собственном боте";
КонецЕсли;
Бот = СистемаВзаимодействия.ПолучитьБота(Пара.Отправитель);
Если ТипЗнч(Бот) <> Тип("БотСистемыВзаимодействия") Тогда
ВызватьИсключение "Бот не записан для реального собственного bot-user ID";
КонецЕсли;
ПользовательБота = СистемаВзаимодействия.ПолучитьПользователя(Пара.Отправитель);
Если ВсеБоты[0].Пользователь <> Пара.Отправитель Или Бот.Пользователь <> Пара.Отправитель
Или Бот.Предопределенный Или Бот.Метаданные <> Метаданные.Боты.УчебныйПроверяющий85
Или Бот.ИмяПользователяИнформационнойБазыВыполненияОбработки <> ПользовательБота.Имя Тогда
ВызватьИсключение "Паспорт собственного непредопределенного metadata-бота изменен";
КонецЕсли;
Контекст = Бот.ДополнительныеПараметры;
Если ТипЗнч(Контекст) <> Тип("Структура") Или Контекст.Ученик <> Строка(Пара.Получатель)
Или Контекст.Бот <> Строка(Пара.Отправитель) Или ТипЗнч(Контекст.Ключ) <> Тип("Строка")
Или Лев(Контекст.Ключ, СтрДлина("Уч85БотКнига_")) <> "Уч85БотКнига_" Тогда
ВызватьИсключение "Own serialized params/real linked IDs не совпали";
КонецЕсли;
UUIDКлюча = Новый УникальныйИдентификатор(Сред(Контекст.Ключ, СтрДлина("Уч85БотКнига_") + 1));
UUIDМетки = Новый УникальныйИдентификатор(Контекст.Метка);
Если Контекст.Ключ <> "Уч85БотКнига_" + Строка(UUIDКлюча) Или Контекст.Метка <> Строка(UUIDМетки)
Или UUIDКлюча = Новый УникальныйИдентификатор("00000000-0000-0000-0000-000000000000")
Или UUIDМетки = Новый УникальныйИдентификатор("00000000-0000-0000-0000-000000000000")
Или ТипЗнч(Контекст.Приглашение) <> Тип("Строка") Или Не ЗначениеЗаполнено(Контекст.Приглашение)
Или СтрДлина(Контекст.Приглашение) > 8192 Тогда
ВызватьИсключение "Own UUID/opaque internal URI не подтвердились";
КонецЕсли;
Обсуждение = СистемаВзаимодействия.ПолучитьОбсуждение(Контекст.Ключ);
Если Обсуждение = Неопределено Тогда
ВызватьИсключение "Own discussion не найдено";
КонецЕсли;
Если Строка(Обсуждение.Идентификатор) <> Контекст.Обсуждение Или Обсуждение.Ключ <> Контекст.Ключ
Или Обсуждение.Заголовок <> Контекст.Ключ + " | Бот: Книга3×250" Или Не Обсуждение.Групповое
Или Не Обсуждение.Отображаемое Или Обсуждение.ТолькоПросмотр Или Обсуждение.КонтекстОбсуждения <> Неопределено
Или Обсуждение.Интеграция <> Неопределено Или Обсуждение.ВнешнийИдентификатор <> ""
Или Обсуждение.Участники.Количество() <> 2 Или Не Обсуждение.Участники.Содержит(Пара.Получатель)
Или Не Обсуждение.Участники.Содержит(Пара.Отправитель) Тогда
ВызватьИсключение "Own discussion exact passport не совпал";
КонецЕсли;
ПроверитьСвойАватарБота85(Пара.Отправитель);
Возврат Контекст;
КонецФункции
Функция ПолучитьСвойСписокСообщений85(Контекст, Роль) Экспорт
Факт = ПолучитьСвойКонтекстБота85(Роль);
Если Контекст.Ключ <> Факт.Ключ Или Контекст.Обсуждение <> Факт.Обсуждение Или Контекст.Метка <> Факт.Метка Тогда
ВызватьИсключение "Чужой/устаревший контекст не принимается";
КонецЕсли;
Отбор = Новый ОтборСообщенийСистемыВзаимодействия;
Отбор.Обсуждение = Новый ИдентификаторОбсужденияСистемыВзаимодействия(Факт.Обсуждение);
Отбор.Количество = 20;
Отбор.НаправлениеСортировки = НаправлениеСортировки.Возр;
Сообщения = СистемаВзаимодействия.ПолучитьСообщения(Отбор);
Если ТипЗнч(Сообщения) <> Тип("Массив") Или Сообщения.Количество() >= 20 Тогда
ВызватьИсключение "Учебный предел20 превышен; список не объявляется полным";
КонецЕсли;
Для Каждого Сообщение Из Сообщения Цикл
Если Строка(Сообщение.Обсуждение) <> Факт.Обсуждение
Или (Строка(Сообщение.Автор) <> Факт.Ученик И Строка(Сообщение.Автор) <> Факт.Бот) Тогда
ВызватьИсключение "В собственном обсуждении другой автор/ID";
КонецЕсли;
КонецЦикла;
Возврат Сообщения;
КонецФункции
Процедура ОстановитьТолькоСвоегоБота85(Подтверждение) Экспорт
Контекст = ПолучитьСвойКонтекстБота85("Получатель");
Если Подтверждение <> "СНЯТЬ ТОЛЬКО МОЕГО БОТА750" Тогда
ВызватьИсключение "Запись собственного бота не удалена";
КонецЕсли;
Сообщения = ПолучитьСвойСписокСообщений85(Контекст, "Получатель");
ОтветНайден = Ложь;
Для Каждого Сообщение Из Сообщения Цикл
Если Строка(Сообщение.Автор) = Контекст.Бот И Сообщение.Текст = Контекст.Ключ + "|Книга:3×250=750"
И Сообщение.Данные = Контекст.Метка + "|Панель750" Тогда
ОтветНайден = Истина;
КонецЕсли;
КонецЦикла;
Если Не ОтветНайден Тогда
ВызватьИсключение "Перед отдельным снятием записи подтвердите собственный ответ бота";
КонецЕсли;
Бот = СистемаВзаимодействия.ПолучитьБота(Новый ИдентификаторПользователяСистемыВзаимодействия(Контекст.Бот));
Если Бот.Предопределенный Или Бот.Метаданные <> Метаданные.Боты.УчебныйПроверяющий85
Или Бот.ДополнительныеПараметры.Метка <> Контекст.Метка Тогда
ВызватьИсключение "Чужого/предопределенного бота не удаляем";
КонецЕсли;
Бот.Удалить();
Если СистемаВзаимодействия.ПолучитьБота(Новый ИдентификаторПользователяСистемыВзаимодействия(Контекст.Бот)) <> Неопределено
Или СистемаВзаимодействия.ПолучитьБотов().Количество() <> 0 Тогда
ВызватьИсключение "Снятие собственной записи бота отдельно не подтверждено";
КонецЕсли;
ПараПосле = ПроверитьСвоюПару85("Получатель");
Если Строка(ПараПосле.Отправитель) <> Контекст.Бот Или Строка(ПараПосле.Получатель) <> Контекст.Ученик Тогда
ВызватьИсключение "Собственный профиль после удаления записи бота не подтвержден";
КонецЕсли;
Сообщить("Удалена только своя непредопределенная запись бота. Metadata, IBUser, collaboration profile и сообщения сохранены; это не гарантия мгновенной остановки уже запущенного фонового задания");
КонецПроцедуры
Процедура ОбработатьСвоиРучныеКлики85(Подтверждение) Экспорт
Панель = ПроверитьСохраненнуюПанель85();
Если Не Панель.Готово Тогда
ВызватьИсключение "Сначала отдельно подтвердите собственную сохраненную панель";
КонецЕсли;
Клики = ПроверитьСвоиРучныеКлики85();
Если Клики.Готово Тогда
Сообщить("Оба own клика уже имеют реальные source-linked ответы750. Дополнительное фоновое задание не запускается");
Возврат;
КонецЕсли;
Если Клики.Запросов <> 2 Или Подтверждение <> "ПРЕЖНИЙ WORKER ЗАВЕРШЕН КЛИКИ750" Тогда
ВызватьИсключение "Нужны два настоящих own GUI-сообщения и ручная проверка завершения предыдущего worker. Новый запуск не выполнен";
КонецЕсли;
СистемаВзаимодействия.ВыполнитьОбработкуБотов();
Сообщить("Отдельно запущено native получение двух уже существующих own запросов. Это не доказательство готовности ответов и не автоматическая проверка завершения прежнего задания");
КонецПроцедуры
Процедура ПроверитьСвойАватарБота85(ИДБота)
КартинкаБота = СистемаВзаимодействия.ПолучитьПользователя(ИДБота).Картинка;
Если ТипЗнч(КартинкаБота) <> Тип("Картинка") Тогда
ВызватьИсключение "Own avatar должен иметь native тип Картинка";
КонецЕсли;
Ширина = КартинкаБота.Ширина();
Высота = КартинкаБота.Высота();
Если ТипЗнч(Ширина) <> Тип("Число") Или ТипЗнч(Высота) <> Тип("Число")
Или Ширина <= 0 Или Высота <= 0 Или Ширина > 512 Или Высота > 512 Тогда
ВызватьИсключение "Own avatar пуст или не укладывается в маленький учебный предел512";
КонецЕсли;
КонецПроцедуры
Функция ПроверитьНачальныеСвоиУчетныеЗаписи85()
ПроверитьСвоюМетадатуБота85();
Если ПривилегированныйРежим() Или Не СистемаВзаимодействия.ИнформационнаяБазаЗарегистрирована()
Или Не СистемаВзаимодействия.ИспользованиеДоступно()
Или СистемаВзаимодействия.АдресСервера() <> "http://localhost:9094"
Или СистемаВзаимодействия.ПоддерживаетсяВнешнийДоступ() Тогда
ВызватьИсключение "Нужен свой непривилегированный стенд localhost без внешнего сайта";
КонецЕсли;
Приложения = СистемаВзаимодействия.ПолучитьПриложенияАбонента();
Если ТипЗнч(Приложения) <> Тип("Массив") Или Приложения.Количество() <> 1
Или Приложения[0].Идентификатор <> СистемаВзаимодействия.ИдентификаторТекущегоПриложения()
Или Приложения[0].Наименование <> "УчебныйБотКниги85" Тогда
ВызватьИсключение "Нужно единственное свое приложение нового абонента";
КонецЕсли;
ВсеБоты = СистемаВзаимодействия.ПолучитьБотов();
Если ТипЗнч(ВсеБоты) <> Тип("Массив") Или ВсеБоты.Количество() <> 0 Тогда
ВызватьИсключение "У нового стенда уже есть бот; повторная подготовка запрещена";
КонецЕсли;
Пользователи = ПользователиИнформационнойБазы.ПолучитьПользователей();
Текущий = ПользователиИнформационнойБазы.ТекущийПользователь();
Если Пользователи.Количество() <> 2 Или Текущий = Неопределено Тогда
ВызватьИсключение "Нужны два реальных собственных IBUsers";
КонецЕсли;
Фикстура = Новый Структура("Ученик,Бот", Неопределено, Неопределено);
Для Каждого Пользователь Из Пользователи Цикл
Если Лев(Пользователь.Имя, СтрДлина("Уч85Получатель_")) = "Уч85Получатель_" Тогда
Префикс = "Уч85Получатель_";
Роль = "Ученик";
ИначеЕсли Лев(Пользователь.Имя, СтрДлина("Уч85Отправитель_")) = "Уч85Отправитель_" Тогда
Префикс = "Уч85Отправитель_";
Роль = "Бот";
Иначе
ВызватьИсключение "Посторонний пользователь не допускается";
КонецЕсли;
Метка = Новый УникальныйИдентификатор(Сред(Пользователь.Имя, СтрДлина(Префикс) + 1));
Если Пользователь.Имя <> Префикс + Строка(Метка)
Или Метка = Новый УникальныйИдентификатор("00000000-0000-0000-0000-000000000000")
Или Фикстура[Роль] <> Неопределено Тогда
ВызватьИсключение "Имена своих ролей должны содержать разные канонические ненулевые UUID";
КонецЕсли;
Фикстура[Роль] = Пользователь;
КонецЦикла;
Если Фикстура.Ученик = Неопределено Или Фикстура.Бот = Неопределено
Или Фикстура.Ученик.УникальныйИдентификатор <> Текущий.УникальныйИдентификатор
Или Фикстура.Ученик.УникальныйИдентификатор = Фикстура.Бот.УникальныйИдентификатор Тогда
ВызватьИсключение "Войдите именно под собственным учеником; botIBUser другой";
КонецЕсли;
ИДУченика = СистемаВзаимодействия.ПолучитьИдентификаторПользователя(Фикстура.Ученик.УникальныйИдентификатор);
Ученик = СистемаВзаимодействия.ПолучитьПользователя(ИДУченика);
Если ИДУченика <> СистемаВзаимодействия.ИдентификаторТекущегоПользователя()
Или СистемаВзаимодействия.ПолучитьИдентификаторПользователяИнформационнойБазы(ИДУченика) <> Фикстура.Ученик.УникальныйИдентификатор
Или Ученик.Идентификатор <> ИДУченика Или Ученик.Имя <> Фикстура.Ученик.Имя
Или Ученик.ПолноеИмя <> Фикстура.Ученик.ПолноеИмя
Или Ученик.ИдентификаторПользователяИнформационнойБазы <> Фикстура.Ученик.УникальныйИдентификатор
Или Ученик.Внешний Или Ученик.Заблокирован Или Ученик.ТипВнешнейСистемы <> ""
Или Ученик.ИдентификаторПользователяВнешнейСистемы <> Неопределено
Или Ученик.АдресЭлектроннойПочты <> "" Или Ученик.АдресЭлектроннойПочтыДляАутентификации <> ""
Или Ученик.НомерТелефона <> "" Или Ученик.КлючСопоставления <> "" Тогда
ВызватьИсключение "Паспорт собственного человека не совпал";
КонецЕсли;
Возврат Фикстура;
КонецФункции
Процедура ПроверитьСвоюМетадатуБота85()
ПроверитьСвойПустойСтенд();
Если Метаданные.Боты.Количество() <> 1 Тогда
ВызватьИсключение "Нужен ровно один собственный metadata-бот";
КонецЕсли;
МетаБота = Метаданные.Боты.УчебныйПроверяющий85;
Если МетаБота.Имя <> "УчебныйПроверяющий85" Или МетаБота.Предопределенный
Или МетаБота.Родитель() <> Метаданные Или МетаБота.ЕстьИзмененияРасширениямиКонфигурации()
Или МетаБота.Модуль <> Неопределено Тогда
ВызватьИсключение "Нужен собственный непредопределенный бот с модулем в точном месте метаданных";
КонецЕсли;
КонецПроцедуры
Функция ПроверитьСвоюПару85(Роль)
ПроверитьСвойПустойСтенд();
Если ПривилегированныйРежим() Или (Роль <> "Получатель" И Роль <> "Отправитель") Тогда
ВызватьИсключение "Роль или привилегированный режим неверны";
КонецЕсли;
Если Не СистемаВзаимодействия.ИнформационнаяБазаЗарегистрирована()
Или Не СистемаВзаимодействия.ИспользованиеДоступно()
Или СистемаВзаимодействия.АдресСервера() <> "http://localhost:9094" Тогда
ВызватьИсключение "Нужно собственное изолированное приложение на localhost:9094";
КонецЕсли;
Приложение = СистемаВзаимодействия.ПолучитьПриложение(СистемаВзаимодействия.ИдентификаторТекущегоПриложения());
Если Приложение.Идентификатор <> СистемаВзаимодействия.ИдентификаторТекущегоПриложения()
Или Приложение.Наименование <> "УчебныйБотКниги85" Тогда
ВызватьИсключение "Наименование своего нового приложения не совпало";
КонецЕсли;
Пользователи = ПользователиИнформационнойБазы.ПолучитьПользователей();
Текущий = ПользователиИнформационнойБазы.ТекущийПользователь();
Если Пользователи.Количество() <> 2 Или Текущий = Неопределено Тогда
ВызватьИсключение "Нужны ровно две собственные UUID-учетные записи";
КонецЕсли;
Пара = Новый Структура("Получатель,Отправитель", Неопределено, Неопределено);
ТекущийПодтвержден = Ложь;
Для Каждого ПользовательБазы Из Пользователи Цикл
Если Лев(ПользовательБазы.Имя, СтрДлина("Уч85Получатель_")) = "Уч85Получатель_" Тогда
ИмяРоли = "Получатель";
ИначеЕсли Лев(ПользовательБазы.Имя, СтрДлина("Уч85Отправитель_")) = "Уч85Отправитель_" Тогда
ИмяРоли = "Отправитель";
Иначе
ВызватьИсключение "Посторонняя учетная запись недопустима";
КонецЕсли;
Префикс = "Уч85" + ИмяРоли + "_";
UUIDИмени = Новый УникальныйИдентификатор(Сред(ПользовательБазы.Имя, СтрДлина(Префикс) + 1));
Если ПользовательБазы.Имя <> Префикс + Строка(UUIDИмени)
Или UUIDИмени = Новый УникальныйИдентификатор("00000000-0000-0000-0000-000000000000") Тогда
ВызватьИсключение "Имя своего участника должно содержать канонический ненулевой UUID";
КонецЕсли;
ИД = СистемаВзаимодействия.ПолучитьИдентификаторПользователя(ПользовательБазы.УникальныйИдентификатор);
Если ТипЗнч(ИД) <> Тип("ИдентификаторПользователяСистемыВзаимодействия")
Или ИД = Новый ИдентификаторПользователяСистемыВзаимодействия
Или ИД = СистемаВзаимодействия.СтандартныеПользователи.ВсеПользователиПриложения
Или СистемаВзаимодействия.ПолучитьИдентификаторПользователяИнформационнойБазы(ИД) <> ПользовательБазы.УникальныйИдентификатор Тогда
ВызватьИсключение "Реальная связка своего пользователя с базой не подтверждена";
КонецЕсли;
Пользователь = СистемаВзаимодействия.ПолучитьПользователя(ИД);
Если Пользователь.Идентификатор <> ИД Или Пользователь.Имя <> ПользовательБазы.Имя
Или Пользователь.ПолноеИмя <> ПользовательБазы.ПолноеИмя
Или Пользователь.ИдентификаторПользователяИнформационнойБазы <> ПользовательБазы.УникальныйИдентификатор
Или Пользователь.Внешний Или Пользователь.Заблокирован Или Пользователь.ТипВнешнейСистемы <> ""
Или Пользователь.ИдентификаторПользователяВнешнейСистемы <> Неопределено
Или Пользователь.АдресЭлектроннойПочты <> "" Или Пользователь.АдресЭлектроннойПочтыДляАутентификации <> ""
Или Пользователь.НомерТелефона <> "" Или Пользователь.КлючСопоставления <> "" Тогда
ВызватьИсключение "Нужен собственный внутренний учебный паспорт без персональных контактов";
КонецЕсли;
Если Пара[ИмяРоли] <> Неопределено Тогда
ВызватьИсключение "Роль продублирована";
КонецЕсли;
Пара[ИмяРоли] = ИД;
Если ПользовательБазы.УникальныйИдентификатор = Текущий.УникальныйИдентификатор Тогда
Если ИмяРоли <> Роль Или ИД <> СистемаВзаимодействия.ИдентификаторТекущегоПользователя() Тогда
ВызватьИсключение "Войдите под своей учетной записью нужной роли";
КонецЕсли;
ТекущийПодтвержден = Истина;
КонецЕсли;
КонецЦикла;
Если Не ТекущийПодтвержден Или Пара.Получатель = Неопределено Или Пара.Отправитель = Неопределено
Или Пара.Получатель = Пара.Отправитель Тогда
ВызватьИсключение "Не получены два разных реальных собственных ID";
КонецЕсли;
Приложения = СистемаВзаимодействия.ПолучитьПриложенияАбонента();
Если ТипЗнч(Приложения) <> Тип("Массив") Или Приложения.Количество() <> 1 Тогда
ВызватьИсключение "У своего отдельного абонента должно быть ровно одно собственное приложение";
КонецЕсли;
Если ТипЗнч(Приложения[0]) <> Тип("ПриложениеСистемыВзаимодействия")
Или Приложения[0].Идентификатор <> СистемаВзаимодействия.ИдентификаторТекущегоПриложения()
Или Приложения[0].Наименование <> "УчебныйБотКниги85" Тогда
ВызватьИсключение "Список собственного абонента содержит другое приложение";
КонецЕсли;
Возврат Пара;
КонецФункции
Процедура ПроверитьСвойПустойСтенд()
Если Метаданные.Справочники.Количество() <> 0 Или Метаданные.Документы.Количество() <> 0 Или Метаданные.Перечисления.Количество() <> 0 Или Метаданные.РегистрыСведений.Количество() <> 0 Или Метаданные.РегистрыНакопления.Количество() <> 0 Или Метаданные.РегистрыБухгалтерии.Количество() <> 0 Или Метаданные.РегистрыРасчета.Количество() <> 0 Или Метаданные.ПланыВидовХарактеристик.Количество() <> 0 Или Метаданные.ПланыВидовРасчета.Количество() <> 0 Или Метаданные.ПланыСчетов.Количество() <> 0 Или Метаданные.ПланыОбмена.Количество() <> 0 Или Метаданные.Последовательности.Количество() <> 0 Или Метаданные.БизнесПроцессы.Количество() <> 0 Или Метаданные.Задачи.Количество() <> 0 Или Метаданные.ОбщиеРеквизиты.Количество() <> 0 Или Метаданные.ПодпискиНаСобытия.Количество() <> 0 Или Метаданные.РегламентныеЗадания.Количество() <> 0 Или Метаданные.ВнешниеИсточникиДанных.Количество() <> 0 Тогда
ВызватьИсключение "Нужен только свой новый пустой учебный стенд";
КонецЕсли;
Если Метаданные.ЕстьИзмененияРасширениямиКонфигурации() Тогда
ВызватьИсключение "Свой сеанс не должен изменяться расширениями";
КонецЕсли;
Источники = Новый Массив;
Источники.Добавить(ИсточникРасширенийКонфигурации.БазаДанных);
Источники.Добавить(ИсточникРасширенийКонфигурации.СеансАктивные);
Источники.Добавить(ИсточникРасширенийКонфигурации.СеансОтключенные);
Для Каждого Источник Из Источники Цикл
Расширения = РасширенияКонфигурации.Получить(, Источник);
Если ТипЗнч(Расширения) <> Тип("Массив") Тогда
ВызватьИсключение "Native список расширений должен иметь тип Массив";
КонецЕсли;
Если Расширения.Количество() <> 0 Тогда
ВызватьИсключение "В своём новом стенде нет расширений базы или сеанса";
КонецЕсли;
КонецЦикла;
Если ТранзакцияАктивна() Тогда
ВызватьИсключение "Чужая или вложенная активная транзакция не допускается";
КонецЕсли;
КонецПроцедурыПолный настоящий metadata bot-module УчебныйПроверяющий85, вставлять только в объект Бот
Конфигуратор → Общие → Боты → УчебныйПроверяющий85 → Модуль.
Сервер: только настоящий native модуль собственного metadata-бота, вызываемый платформой.
Процедура ОбработкаСообщенияСистемыВзаимодействия(Входящее, ДополнительныеПараметры)
Если ТипЗнч(Входящее) <> Тип("СообщениеСистемыВзаимодействия")
Или ТипЗнч(ДополнительныеПараметры) <> Тип("Структура") Тогда
Возврат;
КонецЕсли;
Контекст = УчебныйБот85.ПолучитьСвойКонтекстБота85("Отправитель");
Если ДополнительныеПараметры.Ключ <> Контекст.Ключ Или ДополнительныеПараметры.Обсуждение <> Контекст.Обсуждение
Или ДополнительныеПараметры.Ученик <> Контекст.Ученик Или ДополнительныеПараметры.Бот <> Контекст.Бот
Или ДополнительныеПараметры.Метка <> Контекст.Метка Или ДополнительныеПараметры.Приглашение <> Контекст.Приглашение Тогда
Возврат;
КонецЕсли;
Если Строка(Входящее.Обсуждение) <> Контекст.Обсуждение Или Строка(Входящее.Автор) <> Контекст.Ученик Тогда
Возврат;
КонецЕсли;
Если Входящее.Текст = Контекст.Ключ + "|Карточка750" И Входящее.Данные = Контекст.Метка + "|Начать" Тогда
ВидОтвета = "Панель750";
ТекстОтвета = Контекст.Ключ + "|Книга:3×250=750";
ИначеЕсли Входящее.Текст = Контекст.Ключ + "|Подтвердить750" И Входящее.Данные = Контекст.Метка + "|Подтверждение750" Тогда
ВидОтвета = "Подтверждено750";
ТекстОтвета = Контекст.Ключ + "|Подтверждено:750";
ИначеЕсли Входящее.Текст = Контекст.Ключ + "|Расчет750" И Входящее.Данные = Неопределено Тогда
ВидОтвета = "Расчет750";
ТекстОтвета = Контекст.Ключ + "|Расчет:3×250=750";
Иначе
Возврат;
КонецЕсли;
СвоиСообщения = УчебныйБот85.ПолучитьСвойСписокСообщений85(Контекст, "Отправитель");
Для Каждого Существующее Из СвоиСообщения Цикл
Если Строка(Существующее.Автор) = Контекст.Бот
И Существующее.ИсходноеСообщение = Входящее.Идентификатор
И Существующее.Данные = Контекст.Метка + "|" + ВидОтвета Тогда
Возврат;
КонецЕсли;
КонецЦикла;
Ответ = СистемаВзаимодействия.СоздатьСообщение(Новый ИдентификаторОбсужденияСистемыВзаимодействия(Контекст.Обсуждение));
Ответ.Текст = ТекстОтвета;
Ответ.Данные = Контекст.Метка + "|" + ВидОтвета;
Ответ.ФорматДанныхСтроки = "text/plain";
Ответ.ИсходноеСообщение = Входящее.Идентификатор;
Ответ.Получатели.Добавить(Новый ИдентификаторПользователяСистемыВзаимодействия(Контекст.Ученик));
Если ВидОтвета = "Панель750" Тогда
СформироватьСвоюПанель85(Ответ, Контекст);
КонецЕсли;
Если Ответ.Вложения.Количество() <> 0 Или Ответ.Действия.Количество() <> 0
Или Ответ.Получатели.Количество() <> 1
Или Не Ответ.Получатели.Содержит(Новый ИдентификаторПользователяСистемыВзаимодействия(Контекст.Ученик)) Тогда
ВызватьИсключение "Own bot reply имеет неправильного адресата/содержимое";
КонецЕсли;
УчебныйБот85.ПолучитьСвойКонтекстБота85("Отправитель");
Ответ.Записать();
СвоиСообщения = УчебныйБот85.ПолучитьСвойСписокСообщений85(Контекст, "Отправитель");
Подтверждено = Ложь;
Для Каждого Сохраненное Из СвоиСообщения Цикл
Если Сохраненное.Идентификатор = Ответ.Идентификатор И Строка(Сохраненное.Автор) = Контекст.Бот
И Сохраненное.Текст = ТекстОтвета И Сохраненное.Данные = Контекст.Метка + "|" + ВидОтвета
И Сохраненное.ИсходноеСообщение = Входящее.Идентификатор Тогда
Подтверждено = Истина;
КонецЕсли;
КонецЦикла;
Если Не Подтверждено Тогда
ВызватьИсключение "Own новый ответ мог записаться, но отдельно не подтвержден. Входящее сообщение не перезаписывается";
КонецЕсли;
КонецПроцедуры
Процедура СформироватьСвоюПанель85(Ответ, Контекст)
Если Строка(Ответ.Обсуждение) <> Контекст.Обсуждение Тогда
ВызватьИсключение "Панель создается только у нового собственного ответа";
КонецЕсли;
Панель = Ответ.ПанельКнопок;
Панель.ВидКнопок = ВидКнопкиПанелиКнопокСообщенияСистемыВзаимодействия.ОбычнаяКнопка;
Ряды = Панель.РядыКнопок;
Если Ряды.Количество() <> 0 Тогда
ВызватьИсключение "Новая собственная панель уже содержит ряды";
КонецЕсли;
Основной = Ряды.Добавить();
Справочный = Ряды.Вставить(0);
Черновой = Ряды.Добавить();
Расчет = Справочный.Добавить(ДействиеКнопкиПанелиКнопокСообщенияСистемыВзаимодействия.ОтправитьСообщение, "Показать750");
Расчет.ИмяДействия = "СвойРасчет750";
Расчет.ТекстСообщения = Контекст.Ключ + "|Расчет750";
Подтверждение = Основной.Добавить(ДействиеКнопкиПанелиКнопокСообщенияСистемыВзаимодействия.ОтправитьСообщениеСДанными, "Подтвердить750");
Подтверждение.ИмяДействия = "СвоёПодтверждение750";
Подтверждение.Данные = Контекст.Метка + "|Подтверждение750";
Подтверждение.ТекстСообщения = Контекст.Ключ + "|Подтвердить750";
Подтверждение.Доступность = Истина;
Подтверждение.Картинка = СистемаВзаимодействия.ПолучитьПользователя(Новый ИдентификаторПользователяСистемыВзаимодействия(Контекст.Бот)).Картинка;
Клиентская = Основной.Вставить(0, ДействиеКнопкиПанелиКнопокСообщенияСистемыВзаимодействия.ОбработатьНаКлиенте, "Показать на клиенте");
Клиентская.ИмяДействия = "СвойКлиент750";
Клиентская.Действие = ДействиеКнопкиПанелиКнопокСообщенияСистемыВзаимодействия.ОбработатьНаКлиенте;
Клиентская.Текст = "Показать на клиенте";
Ссылка = Основной.Добавить(ДействиеКнопкиПанелиКнопокСообщенияСистемыВзаимодействия.ПерейтиПоНавигационнойСсылке, "Своё внутреннее приглашение: выключено");
Ссылка.ИмяДействия = "СвойDisabledURI";
Ссылка.НавигационнаяСсылка = Контекст.Приглашение;
Ссылка.Доступность = Ложь;
Основной.Добавить(ДействиеКнопкиПанелиКнопокСообщенияСистемыВзаимодействия.ОтправитьСообщение, "Лишний черновик");
Если Основной.Количество() <> 4 Или Основной.Индекс(Клиентская) <> 0 Или Основной.Индекс(Подтверждение) <> 1 Тогда
ВызватьИсключение "Own initial button order не совпал";
КонецЕсли;
Основной.Сдвинуть(3, -1);
Если Основной.Получить(2).Текст <> "Лишний черновик" Или Основной.Индекс(Ссылка) <> 3 Тогда
ВызватьИсключение "Сдвиг own draft-кнопки не подтвердился";
КонецЕсли;
Основной.Удалить(2);
Если Основной.Количество() <> 3 Или Основной.Получить(0).ИмяДействия <> "СвойКлиент750"
Или Основной.Получить(1).Данные <> Контекст.Метка + "|Подтверждение750"
Или Основной.Получить(2).НавигационнаяСсылка <> Контекст.Приглашение Или Основной.Получить(2).Доступность Тогда
ВызватьИсключение "Own final buttons не подтвердились";
КонецЕсли;
Если Ряды.Количество() <> 3 Или Ряды.Индекс(Справочный) <> 0 Или Ряды.Индекс(Основной) <> 1 Или Ряды.Индекс(Черновой) <> 2 Тогда
ВызватьИсключение "Own initial row order не совпал";
КонецЕсли;
Ряды.Сдвинуть(2, -1);
Если Ряды.Индекс(Черновой) <> 1 Тогда
ВызватьИсключение "Own draft row shift не подтвердился";
КонецЕсли;
Ряды.Удалить(1);
Ряды.Сдвинуть(Основной, -1);
Если Ряды.Количество() <> 2 Или Ряды.Получить(0) <> Основной Или Ряды.Получить(1) <> Справочный
Или Справочный.Получить(0).ТекстСообщения <> Контекст.Ключ + "|Расчет750" Тогда
ВызватьИсключение "Own final rows не подтвердились";
КонецЕсли;
ЧерновойОтвет = СистемаВзаимодействия.СоздатьСообщение(Новый ИдентификаторОбсужденияСистемыВзаимодействия(Контекст.Обсуждение));
ЧерновыеРяды = ЧерновойОтвет.ПанельКнопок.РядыКнопок;
ЧерновойРяд = ЧерновыеРяды.Добавить();
ЧерновойРяд.Добавить(ДействиеКнопкиПанелиКнопокСообщенияСистемыВзаимодействия.ОтправитьСообщение, "Неотправленный черновик");
Если ЧерновойРяд.Количество() <> 1 Тогда
ВызватьИсключение "Own unsent row не заполнен";
КонецЕсли;
ЧерновойРяд.Очистить();
Если ЧерновойРяд.Количество() <> 0 Тогда
ВызватьИсключение "Own unsent buttons не очищены";
КонецЕсли;
ЧерновыеРяды.Очистить();
Если ЧерновыеРяды.Количество() <> 0 Тогда
ВызватьИсключение "Own unsent rows не очищены";
КонецЕсли;
КонецПроцедурыMODULE_INSPECTION: добавить целиком в тот же серверный общий модуль УчебныйБот85
Конфигуратор → Общие → Общие модули → УчебныйБот85 → Модуль, после COMMON.
Серверный общий server-call модуль: только проверка собственной сохраненной панели, без создания кнопок.
Функция ПроверитьСохраненнуюПанель85() Экспорт
Контекст = ПолучитьСвойКонтекстБота85("Получатель");
Сообщения = ПолучитьСвойСписокСообщений85(Контекст, "Получатель");
Карточек = 0;
ИДКарточки = "";
Для Каждого Сообщение Из Сообщения Цикл
Если Строка(Сообщение.Автор) <> Контекст.Бот Или Сообщение.Данные <> Контекст.Метка + "|Панель750" Тогда
Продолжить;
КонецЕсли;
Если Сообщение.Текст <> Контекст.Ключ + "|Книга:3×250=750" Тогда
ВызватьИсключение "Own card marker/body mismatch";
КонецЕсли;
ПроверитьСвойПаспортПанели85(Сообщение, Контекст);
Карточек = Карточек + 1;
ИДКарточки = Строка(Сообщение.Идентификатор);
КонецЦикла;
Если Карточек > 1 Тогда
ВызватьИсключение "Несколько own replies: не маскируем повтор/конкурентную обработку";
КонецЕсли;
Возврат Новый Структура("Готово,Сообщение", Карточек = 1, ИДКарточки);
КонецФункции
Функция ПроверитьСвоюКлиентскуюКнопку85(ИДОбсужденияСтрокой, ИДСвоейКарточкиСтрокой, ИмяКнопки) Экспорт
Контекст = ПолучитьСвойКонтекстБота85("Получатель");
Если ИДОбсужденияСтрокой <> Контекст.Обсуждение Или ИмяКнопки <> "СвойКлиент750" Тогда
ВызватьИсключение "Только собственный context и client button разрешены";
КонецЕсли;
Результат = ПроверитьСохраненнуюПанель85();
Если Не Результат.Готово Или Результат.Сообщение <> ИДСвоейКарточкиСтрокой Тогда
ВызватьИсключение "Real own card/button не подтверждены";
КонецЕсли;
Возврат Новый Структура("Книга,Количество,Цена,Сумма", "Книга", 3, 250, 3 * 250);
КонецФункции
Функция ПроверитьСвоиРучныеКлики85() Экспорт
Контекст = ПолучитьСвойКонтекстБота85("Получатель");
Сообщения = ПолучитьСвойСписокСообщений85(Контекст, "Получатель");
Запросы = Новый Структура("Расчет,Подтверждение", Неопределено, Неопределено);
Запросов = 0;
Для Каждого Сообщение Из Сообщения Цикл
Если Строка(Сообщение.Автор) <> Контекст.Ученик Тогда
Продолжить;
КонецЕсли;
Если Сообщение.Текст = Контекст.Ключ + "|Расчет750" И Сообщение.Данные = Неопределено Тогда
Маршрут = "Расчет";
ИначеЕсли Сообщение.Текст = Контекст.Ключ + "|Подтвердить750" И Сообщение.Данные = Контекст.Метка + "|Подтверждение750" Тогда
Маршрут = "Подтверждение";
Иначе
Продолжить;
КонецЕсли;
Если Запросы[Маршрут] <> Неопределено Тогда
ВызватьИсключение "Own GUI-кнопка нажата повторно: дубли запросов не маскируются";
КонецЕсли;
Запросы[Маршрут] = Сообщение.Идентификатор;
Запросов = Запросов + 1;
КонецЦикла;
Ответов = 0;
НайденныеОтветы = Новый Структура("Расчет,Подтверждение", Ложь, Ложь);
Для Каждого Сообщение Из Сообщения Цикл
Если Строка(Сообщение.Автор) <> Контекст.Бот Тогда
Продолжить;
КонецЕсли;
Если Сообщение.Данные = Контекст.Метка + "|Расчет750" И Сообщение.Текст = Контекст.Ключ + "|Расчет:3×250=750" Тогда
Маршрут = "Расчет";
ИначеЕсли Сообщение.Данные = Контекст.Метка + "|Подтверждено750" И Сообщение.Текст = Контекст.Ключ + "|Подтверждено:750" Тогда
Маршрут = "Подтверждение";
Иначе
Продолжить;
КонецЕсли;
Если Запросы[Маршрут] = Неопределено Или Сообщение.ИсходноеСообщение <> Запросы[Маршрут] Или НайденныеОтветы[Маршрут] Тогда
ВызватьИсключение "Own bot acknowledgment имеет чужой sourceID или duplicate reply";
КонецЕсли;
НайденныеОтветы[Маршрут] = Истина;
Ответов = Ответов + 1;
КонецЦикла;
Возврат Новый Структура("Запросов,Ответов,Готово", Запросов, Ответов, Запросов = 2 И Ответов = 2);
КонецФункции
Процедура ПроверитьСвойПаспортПанели85(Сообщение, Контекст)
Панель = Сообщение.ПанельКнопок;
Если Панель.ВидКнопок <> ВидКнопкиПанелиКнопокСообщенияСистемыВзаимодействия.ОбычнаяКнопка
Или Панель.РядыКнопок.Количество() <> 2 Тогда
ВызватьИсключение "Own saved panel type/rows mismatch";
КонецЕсли;
Основной = Панель.РядыКнопок.Получить(0);
Справочный = Панель.РядыКнопок.Получить(1);
Если Основной.Количество() <> 3 Или Справочный.Количество() <> 1 Тогда
ВызватьИсключение "Own saved button count mismatch";
КонецЕсли;
Клиентская = Основной.Получить(0);
Подтверждение = Основной.Получить(1);
Ссылка = Основной.Получить(2);
Расчет = Справочный.Получить(0);
Если Клиентская.ИмяДействия <> "СвойКлиент750" Или Клиентская.Текст <> "Показать на клиенте"
Или Клиентская.Действие <> ДействиеКнопкиПанелиКнопокСообщенияСистемыВзаимодействия.ОбработатьНаКлиенте
Или Не Клиентская.Доступность Тогда
ВызватьИсключение "Own client button passport mismatch";
КонецЕсли;
Если Подтверждение.ИмяДействия <> "СвоёПодтверждение750" Или Подтверждение.Текст <> "Подтвердить750"
Или Подтверждение.Действие <> ДействиеКнопкиПанелиКнопокСообщенияСистемыВзаимодействия.ОтправитьСообщениеСДанными
Или Подтверждение.Данные <> Контекст.Метка + "|Подтверждение750"
Или Подтверждение.ТекстСообщения <> Контекст.Ключ + "|Подтвердить750"
Или Не Подтверждение.Доступность Или ТипЗнч(Подтверждение.Картинка) <> Тип("Картинка") Тогда
ВызватьИсключение "Own SendMessageWithData button passport mismatch";
КонецЕсли;
Если Ссылка.ИмяДействия <> "СвойDisabledURI" Или Ссылка.Доступность
Или Ссылка.Действие <> ДействиеКнопкиПанелиКнопокСообщенияСистемыВзаимодействия.ПерейтиПоНавигационнойСсылке
Или Ссылка.НавигационнаяСсылка <> Контекст.Приглашение Тогда
ВызватьИсключение "Own disabled URI button passport mismatch";
КонецЕсли;
Если Расчет.ИмяДействия <> "СвойРасчет750" Или Расчет.Текст <> "Показать750"
Или Расчет.Действие <> ДействиеКнопкиПанелиКнопокСообщенияСистемыВзаимодействия.ОтправитьСообщение
Или Расчет.ТекстСообщения <> Контекст.Ключ + "|Расчет750" Тогда
ВызватьИсключение "Own SendMessage button passport mismatch";
КонецЕсли;
КонецПроцедурыПолный модуль своей управляемой формы: one-shot setup/send/manual poll/own record delete
Собственная внешняя обработка → Управляемая форма → Модуль формы.
Клиент-серверный модуль управляемой формы; мутации и native ProcessBots только на сервере.
Перем НачатаПодготовка85;
Перем НачатаОтправка85;
Перем НачатаОбработкаКликов85;
Перем НачатоСнятие85;
&НаКлиенте
Процедура ПодготовитьБота(Команда)
Если НачатаПодготовка85 = Истина Тогда
ВызватьИсключение "Повтор подготовки запрещен: профиль/бот могли уже создаться";
КонецЕсли;
НачатаПодготовка85 = Истина;
Пакет = ПодготовитьНаСервере85();
СвойКлючОбсуждения = Пакет.Ключ;
Сообщить("Свое обсуждение подготовлено: " + СвойКлючОбсуждения);
КонецПроцедуры
&НаСервере
Функция ПодготовитьНаСервере85()
Возврат УчебныйБот85.ПодготовитьСвоегоБота85(ПодтверждениеПодготовки);
КонецФункции
&НаКлиенте
Процедура ОтправитьЗапросБоту(Команда)
Если НачатаОтправка85 = Истина Тогда
ВызватьИсключение "Повтор отправки/ProcessBots запрещен: смотрите возможный частичный результат";
КонецЕсли;
НачатаОтправка85 = Истина;
ОтправитьНаСервере85();
КонецПроцедуры
&НаСервере
Процедура ОтправитьНаСервере85()
УчебныйБот85.ОтправитьЗапросИНачатьОбработку85(ПодтверждениеОтправки);
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьПанель(Команда)
Результат = ПроверитьНаСервере85();
Если Результат.Готово Тогда
Сообщить("Собственный real ответ750/два ряда/три активные и одна disabled кнопка отдельно проверен. Теперь проверяйте ручные клики в своем обсуждении");
Иначе
Сообщить("Собственный ответ еще не подтвержден. Не повторяйте отправку/ProcessBots; позже нажмите только ПроверитьПанель");
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПроверитьНаСервере85()
Возврат УчебныйБот85.ПроверитьСохраненнуюПанель85();
КонецФункции
&НаКлиенте
Процедура ПроверитьКлики(Команда)
Результат = ПроверитьКликиНаСервере85();
Сообщить("Реальные own GUI-запросы: " + Строка(Результат.Запросов) + "/2; source-linked bot-ответы: " + Строка(Результат.Ответов) + "/2");
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПроверитьКликиНаСервере85()
Возврат УчебныйБот85.ПроверитьСвоиРучныеКлики85();
КонецФункции
&НаКлиенте
Процедура ОбработатьКлики(Команда)
Если НачатаОбработкаКликов85 = Истина Тогда
ВызватьИсключение "Вторая фаза запускается только один раз: нельзя скрывать повтор/частичный результат";
КонецЕсли;
НачатаОбработкаКликов85 = Истина;
ОбработатьКликиНаСервере85();
КонецПроцедуры
&НаСервере
Процедура ОбработатьКликиНаСервере85()
УчебныйБот85.ОбработатьСвоиРучныеКлики85(ПодтверждениеКликов);
КонецПроцедуры
&НаКлиенте
Процедура СнятьСвоегоБота(Команда)
Если НачатоСнятие85 = Истина Тогда
ВызватьИсключение "Повтор удаления записи запрещен; результат проверяйте вручную";
КонецЕсли;
НачатоСнятие85 = Истина;
СнятьНаСервере85();
КонецПроцедуры
&НаСервере
Процедура СнятьНаСервере85()
УчебныйБот85.ОстановитьТолькоСвоегоБота85(ПодтверждениеСнятия);
КонецПроцедурыПолный модуль управляемого приложения: настоящее native client button event
Конфигуратор → Конфигурация → Открыть модуль управляемого приложения.
Клиент: native событие кнопки ОбработатьНаКлиенте, server-call проверка actual собственного messageID.
Процедура ПриНажатииКнопкиПанелиКнопокСообщенияСистемыВзаимодействия(Сообщение, Кнопка)
Если ТипЗнч(Сообщение) <> Тип("СообщениеСистемыВзаимодействия")
Или ТипЗнч(Кнопка) <> Тип("КнопкаПанелиКнопокСообщенияСистемыВзаимодействия") Тогда
Возврат;
КонецЕсли;
Если Кнопка.ИмяДействия <> "СвойКлиент750"
Или Кнопка.Действие <> ДействиеКнопкиПанелиКнопокСообщенияСистемыВзаимодействия.ОбработатьНаКлиенте
Или Не Кнопка.Доступность Тогда
Возврат;
КонецЕсли;
Результат = УчебныйБот85.ПроверитьСвоюКлиентскуюКнопку85(Строка(Сообщение.Обсуждение), Строка(Сообщение.Идентификатор), Кнопка.ИмяДействия);
Если Результат.Количество * Результат.Цена <> Результат.Сумма Или Результат.Сумма <> 750 Тогда
ВызватьИсключение "Own server/client checked sum mismatch";
КонецЕсли;
Сообщить(Результат.Книга + ": " + Строка(Результат.Количество) + " × " + Строка(Результат.Цена) + " = " + Строка(Результат.Сумма));
КонецПроцедурыРазбор по шагам
До первого CreateUser проверяются пустая CF, единственный own metadata.Bot, два real IBUsers, current human, real human profile и sole own app. Проверка полной пары с mapping бота выполняется только после его реального создания, не до него.
CreateUser получает actual объект уже вручную созданного botIBUser. Под privileged0→1 создается новый profile, задается только его actualIBUUID и собственная маленькая Картинка из двоичных PNG-данных; Profile.Write выполняется до возврата режима0.
При исключении режим0 восстанавливается, но профиль может уже существовать: его не удаляют, не перезаписывают, не повторяют создание. Документированный collision error для уже существующего пользователя остается честной границей.
После записи двусторонний realID/realIBUUID и fresh profile подтверждают собственного человека/бота, а полученный avatar имеет native Картинка и небольшие ненулевые размеры. Не обещается совпадение PNG90 байт после серверного преобразования изображения.
Own discussion использует новый canonical UUID key, exact title и два real participants. Нет context/integration/externalID. Native internal invitation получается при External=false и используется только в disabled own-button.
CreateBot возвращает настоящий bot-description, не constructor. User/Metadata/ExecutionInfoBaseUserName/AdditionalParameters устанавливаются для own nonpredefined object, затем Write и отдельный GetBot/GetBots подтверждают one own recorded description.
AdditionalParameters содержит только serializable own strings/real IDs/UUID/internal URI. GetBot takes actualuserID, не имя/произвольный UUID. Предопределенный readonly; его не присваивают, predefined metadata/source не используется.
Send request выполняет собственный human, проверяет own discussion и empty messages, затем пишет только одному actualbot recipient. Отдельный scoped read проверяет messageID/author/data. Повтора при частичном результате нет.
ProcessBots запускает native background fetching и не возвращает панель/результат обработки. Настоящий metadata module вызывается платформой только после получения сообщения; fake прямого вызова handler из COMMON/формы нет.
Execution name задан конкретным actualbotIBUser. В фоновом модуле named session/current collaboration identity проверяются в обе стороны. Default-user SetCurrentUser требует иной контекст и здесь не подменяется privileged-mode переключением.
Manual poll читает actual saved reply/rows/buttons; получение startup signal не считается доказательством работы. Готовность проверяется отдельно после получения own response marker750 и nativebutton passports.
Последующие реальные клики проверяются отдельным scoped read: два human-запроса и два bot-ответа со ссылками именно на их actual IDs. Вечный worker не выдумывается: отдельный conditional ProcessBots разрешается только после вручную подтвержденного завершения предыдущего; код не объявляет текст подтверждения доказательством статуса.
Отдельное Delete разрешено точной фразой после сохраненного собственного ответа, только actual own nonpredefined bot-description с metadata/nonce. Fresh GetBot=Неопределено/GetBots=0 подтверждает снятие записи, profile/IBUser/messages не удаляются.
Already running background job может еще завершать работу после удаления записи: no Cancel claim. Перед финальным шагом собственник вручную проверяет завершение своего опыта; никакого автоматического error cleanup нет.
Как проверить результат
Bot-profile ID действительно получен из Write/CreateUser(actualIBUser), а mapping в обе стороны совпадает. UUID из имени не используется как fake collaboration ID.
CreateUser privileged requirement выполнен только в scoped server block; input privileged mode не принимается, success/catch устанавливают0.
Бот по native GetBots ровно один, GetBot(actualbotID) возвращает nonpredefined description с точным metadata object и named execution user. Кнопки никогда не создаются COMMON/формой.
Native incoming handler имеет ровно2параметра, SourceMessage.Write никогда не вызывается. Текст/сумма750 и30 primary panel/row/button members проверяются в настоящем bot-module/actual saved reply; всего44 новых primary в двух уроках.
Свой avatar реально установлен новому own profile из собственного PNG90байт без чтения чужих файлов; свежая Картинка непустая, размер ограничен, серверная нормализация не называется ошибкой exact-hash.
Delete own record не удаляет metadata/user/profile/messages и не обещает Cancel рабочего BG. Повторные/fake/reference handlers не используются.
Ожидаемый результат
При вашем ручном успешном запуске подготовленного собственного стенда realbot profile/metadata description создаются и native dispatch вызывает настоящий bot-module. Реальные row/button результаты проверяются отдельно. Дополнительное ownDelete снимает только запись непредопределенного бота. Здесь static/native verification, realruntime0.
На что обратить внимание
Row.Add/Insert разрешены именно модулю бота. Его код не вставляется в обычный серверный общий модуль/форму и не вызывается напрямую как учебная функция.
Metadata.Bot.Модуль readonly и всегда Неопределено: это не текст/строка процедуры. Сам модуль устанавливают вручную в точном месте конфигуратора.
CreateUser возможен только privilegedMode и бросает при existing user. Scope guards/actualIB object проверены до включения, состояние0 возвращается обеими ветками.
IBUser создается вручную, collaboration profile — документированным API. Это два разных объекта; нельзя придумать userID или принять canonical nameUUID за realIBUUID.
ExecuteProcessingInfoBaseUserName — настоящая учетная запись исполняющего сеанса. Пустое имя означает default-user путь, которому требуется отдельный безопасный fixture; здесь пустота не используется.
Bot.Delete требует Администрирование данных; собственный guard этого урока отказывается удалять predefined objects. Этот урок создает только собственный explicit nonpredefined description, не утверждая недокументированного запрета платформы.
SourceMessage события имеет особый запрет Write. Ответ создается новым сообщением, author/date не подменяются привилегированно.
ProcessBots запускает background fetch, а не synchronous return готовой панели. Срок получения/GUI-поддержка конкретного клиента здесь не заявлены измеренными.
One own worker/no parallel mutation обязательны: read-before-write duplicate guard не превращает несколько конкурентных workers в атомарную транзакцию.
PNG является собственным маленьким in-memory fixture, не file path. Web-client picture constructor здесь не вызывается: avatar создается server-only.
Internal URI opaque и disabled; не публикуйте/не включайте/не открывайте ссылку. Phone/location/внешние мессенджеры и provider parameters отсутствуют.
Любая ошибка может оставить own partial profile/description/messages. Не повторяйте/не удаляйте автоматически, проверяйте только собственный выделенный стенд вручную. Backups0.
Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.