Временно создаём собственный приход 3 в 10:00 и расход 1 в 11:00. Проверяем 0, 3 и 2 на разных границах, затем отменяем все записи.
Где и как запускать
Учебная база: создайте серверный общий модуль УчебныеЗадачи (флаг Сервер). Код без директив вставьте в тело процедуры ВыполнитьПример() Экспорт. Вызовите её из серверного обработчика команды учебной обработки. Не запускайте в рабочей базе.
Подготовка
Создайте НОВУЮ пустую файловую базу, имя корневой конфигурации УчебныеОстатки85. Не добавляйте эти объекты в УТ/КА2/УНФ/БП. Режим управления блокировкой данных конфигурации — Управляемый.
Документы → Добавить: УчебноеДвижение85. Номер: Строка длиной 40, автонумерация выключена. Модули объекта и менеджера оставьте пустыми; не добавляйте обработчики, подписки и обязательные пользовательские реквизиты.
Регистры накопления → Добавить: УчебныеОстатки85. Вид регистра Остатки. Единственное измерение КодТовара: Строка 50, индексировать, проверка заполнения Показывать ошибку. Единственный ресурс Количество: Число 12,3, неотрицательное. Дополнительных реквизитов нет.
В списке регистраторов выберите только собственный документ УчебноеДвижение85. Оставьте модули регистра пустыми, использование итогов включённым. Планы обмена, разделители, историю данных и фоновые задания для урока не создавайте.
Обновите конфигурацию базы данных. Создайте серверный общий модуль УчебныеЗадачи и команду собственной обработки. Фрагмент вставьте внутрь ВыполнитьПример() Экспорт; запускайте задачи по отдельности.
Первые задачи работают с незаписанными наборами. Только задачи с пометкой «в собственной транзакции» временно записывают два собственных документа и движения, затем обязательно отменяют транзакцию. В рабочей базе этот код не запускайте.
Убедитесь, что модули документа и набора записей, подписки и планы обмена действительно пустые: отмена транзакции НЕ отменяет уже отправленные HTTP-запросы, письма или запись внешних файлов.
Запускайте как отдельную серверную команду, не из проведения документа и не внутри чужой транзакции. Здесь нет ЗафиксироватьТранзакцию: временные учебные данные должны исчезнуть.
Если Метаданные.Документы.Количество() <> 1
Или Метаданные.РегистрыНакопления.Количество() <> 1
Или Метаданные.Документы.Найти("УчебноеДвижение85") = Неопределено Тогда
ВызватьИсключение "Разрешены только собственный учебный документ и регистр";
КонецЕсли;
Описание = Метаданные.РегистрыНакопления.Найти("УчебныеОстатки85");
Если Описание = Неопределено Тогда
ВызватьИсключение "Сначала создайте собственный учебный регистр";
КонецЕсли;
Если Описание.ВидРегистра <> Метаданные.СвойстваОбъектов.ВидРегистраНакопления.Остатки
Или Описание.Измерения.Количество() <> 1
Или Описание.Ресурсы.Количество() <> 1
Или Описание.Реквизиты.Количество() <> 0
Или Описание.Измерения.Найти("КодТовара") = Неопределено
Или Описание.Ресурсы.Найти("Количество") = Неопределено Тогда
ВызватьИсключение "Нужен регистр остатков с КодТовара и Количество";
КонецЕсли;
Менеджер = РегистрыНакопления.УчебныеОстатки85;
Если ТранзакцияАктивна() Тогда
ВызватьИсключение "Не запускайте урок внутри чужой транзакции";
КонецЕсли;
Если Не Менеджер.ПолучитьИспользованиеИтогов() Тогда
ВызватьИсключение "В учебном регистре должны быть включены итоги";
КонецЕсли;
Код = "TRAIN-" + Строка(Новый УникальныйИдентификатор);
ОтборТовара = Новый Структура("КодТовара", Код);
Начало = Дата(2026, 9, 15, 0, 0, 0);
Конец = Дата(2026, 9, 16, 0, 0, 0);
НачатьТранзакцию();
Попытка
ДокументПрихода = Документы.УчебноеДвижение85.СоздатьДокумент();
ДокументПрихода.Дата = Дата(2026, 9, 15, 10, 0, 0);
ДокументПрихода.Номер = "P-" + Строка(Новый УникальныйИдентификатор);
ДокументПрихода.Записать();
ДокументРасхода = Документы.УчебноеДвижение85.СоздатьДокумент();
ДокументРасхода.Дата = Дата(2026, 9, 15, 11, 0, 0);
ДокументРасхода.Номер = "E-" + Строка(Новый УникальныйИдентификатор);
ДокументРасхода.Записать();
НаборПрихода = Менеджер.СоздатьНаборЗаписей();
НаборПрихода.Отбор.Регистратор.Установить(ДокументПрихода.Ссылка);
Приход = НаборПрихода.ДобавитьПриход();
Приход.Период = ДокументПрихода.Дата;
Приход.Регистратор = ДокументПрихода.Ссылка;
Приход.Активность = Истина;
Приход.КодТовара = Код;
Приход.Количество = 3;
НаборПрихода.Записать(Истина);
НаборРасхода = Менеджер.СоздатьНаборЗаписей();
НаборРасхода.Отбор.Регистратор.Установить(ДокументРасхода.Ссылка);
Расход = НаборРасхода.ДобавитьРасход();
Расход.Период = ДокументРасхода.Дата;
Расход.Регистратор = ДокументРасхода.Ссылка;
Расход.Активность = Истина;
Расход.КодТовара = Код;
Расход.Количество = 1;
НаборРасхода.Записать(Истина);
НаНачалоДня = Менеджер.Остатки(Дата(2026, 9, 15, 11, 0, 0), ОтборТовара, "КодТовара", "Количество");
Сообщить(НаНачалоДня.Количество() = 0);
МоментРасхода = Новый МоментВремени(ДокументРасхода.Дата, ДокументРасхода.Ссылка);
ДоРасхода = Менеджер.Остатки(МоментРасхода, ОтборТовара, "КодТовара", "Количество");
Сообщить(ДоРасхода.Итог("Количество") = 3);
ПослеРасхода = Менеджер.Остатки(Новый Граница(МоментРасхода, ВидГраницы.Включая), ОтборТовара, "КодТовара", "Количество");
Сообщить(ПослеРасхода.Итог("Количество") = 2);
ЗаДень = Менеджер.Обороты(Начало, Конец, ОтборТовара, "КодТовара", "Количество");
Сообщить(ЗаДень.Итог("КоличествоПриход") = 3);
Сообщить(ЗаДень.Итог("КоличествоРасход") = 1);
Сообщить(ПослеРасхода.Итог("Количество") = ЗаДень.Итог("КоличествоПриход") - ЗаДень.Итог("КоличествоРасход"));
ОтменитьТранзакцию();
Исключение
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
ВызватьИсключение;
КонецПопытки;
Сообщить(Не ТранзакцияАктивна());
ПослеОтмены = Менеджер.Выбрать(Начало, Конец, ОтборТовара);
Сообщить(Не ПослеОтмены.Следующий());
Сообщить("Временные движения отменены. Учебная транзакция завершена.");Разбор по шагам
Проверка запрещает старт внутри чужой транзакции. Записываются только два своих новых документа с уникальными номерами и один новый код товара.
Каждый набор имеет отбор по своему сохранённому регистратору; Записать(Истина) замещает только его собственные движения.
Простая Дата 15 сентября с временем 11:00 всё равно задаёт Остатки на начало дня. До первого прихода собственного кода строк нет.
МоментВремени расходного документа исключает его движение: остаётся приход 3. Граница(тот же момент, Включая) включает расход и даёт 2.
Обороты показывают приход 3 и расход 1. Затем ОтменитьТранзакцию вызывается и при успехе, и при исключении.
После отмены отдельная выборка по собственному коду пуста. Внешние побочные действия не создаются: поэтому модули и подписки должны быть пустыми.
Как проверить результат
Внутри транзакции шесть проверок Истина: начало дня пусто, до расхода 3, после расхода 2, приход 3, расход 1 и согласованность итогов.
После отмены ещё две проверки Истина: активной транзакции нет, сохранённых движений по собственному коду нет.
Повторный запуск должен снова дать те же значения без накопления учебных документов.
Ожидаемый результат
Границы расчёта разобраны на конкретной паре собственных движений, сохранение всегда завершается откатом.
На что обратить внимание
Не заменяйте ОтменитьТранзакцию на ЗафиксироватьТранзакцию и не переносите урок в рабочую конфигурацию.
Откат базы не отменяет HTTP, письма и файлы, уже созданные обработчиками: в подготовке они запрещены.
Дата и МоментВремени в Остатки — разные границы. При нескольких документах в одну секунду ссылка является частью момента.
Пример не проверяет конкурентное резервирование товара, отрицательные остатки или права реального пользователя.
Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.