1C-CodeОсновы 1С → Документы и проведение: что случилось

Документы и проведение: что случилось

Документ фиксирует событие, проведение превращает его в движения. Здесь живёт главный код учётной системы — и главные ошибки.

Глава 18 из 36 · чтение примерно 28 минут

Документ отвечает на вопрос «что случилось». Продали, приняли, оплатили, начислили. У него всегда есть дата и номер: событие произошло в конкретный момент, и порядок событий важен.

Если справочник — это существительные учёта, то документ — глаголы.

Шапка и табличная часть

У документа две области, и это устройство повторяется везде.

Шапка — реквизиты, которых по одному на документ: контрагент, склад, организация, комментарий.

Табличная часть — строки: товар, количество, цена, сумма. Их может быть несколько: «Товары» и «Услуги» в одной накладной.

В коде к строкам обращаются циклом:

Обход строк документа

Итого = 0;
Для Каждого Строка Из Док.Товары Цикл
	Итого = Итого + Строка.Сумма;
КонецЦикла;
ЧастьЧто означает
Док.Товарытабличная часть по имени, заданному в конфигураторе
Для Каждого … Изобход строк по порядку; менять его нельзя
Строка.Суммареквизит строки, а не шапки
Итогоитог считаем сами: платформа его не хранит

Три состояния документа

Новый — создан, но не записан. Номера ещё нет.

Записан — лежит в базе, но на учёт не повлиял. В списке выглядит бледным.

Проведён — учтён: остатки изменились, долг вырос, проводки появились.

Разница между «записан» и «проведён» — то, чего не понимают до первой рабочей задачи. Документ можно записать черновиком и провести завтра. Пока он не проведён, для учёта его как бы нет.

Проведение и движения

Проведение — это код, который превращает документ в движения по регистрам. Реализация списывает товар со склада и увеличивает долг покупателя. Ни то, ни другое не хранится в самом документе: документ говорит «что случилось», регистры отвечают «сколько теперь».

Код проведения лежит в модуле объекта документа, в процедуре ОбработкаПроведения. У неё два параметра: Отказ и РежимПроведения.

Отказ = Истина — единственный законный способ не дать провести документ. Не исключение, не сообщение, не «выйти из процедуры»: именно Отказ. Платформа увидит его и откатит всё, что процедура успела написать.

На чём спотыкаются

Пишут проверки после записи движений. Отказ отменит всё, но порядок всё равно неверный: сначала проверяем, потом пишем. Иначе однажды проверка окажется в ветке, до которой не дошли.

Сообщают об ошибке и не ставят Отказ. Пользователь видит красное сообщение, документ при этом проводится. Худший из возможных исходов: и напугали, и пропустили.

Читают остатки без блокировки. Два человека проводят одновременно, оба видят «на складе 5», оба списывают по 3. На складе становится −1. Лечится управляемой блокировкой — про это отдельная глава.

Чистят движения вручную. Движения.Очистить() не нужен: при повторном проведении платформа очищает сама, если стоит свойство «Удаление движений — автоматически».

Отмена проведения и пометка удаления

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

Пометка удаления — заявка на удаление. Сам документ остаётся в базе, пока не запустят «Удаление помеченных объектов». Это не формальность: удалить объект, на который ссылаются другие, нельзя, и контроль ссылочной целостности выполняется именно там.

Нумерация

Номер документа платформа выдаёт сама при записи. Он уникален в пределах периода, заданного свойством «Периодичность номера»: обычно года. Первого января нумерация начинается заново.

Отсюда правило, которое спрашивают: номер не уникален сам по себе. Искать документ только по номеру, без даты или организации, — ошибка.

Записать — не значит провести

Это первое, обо что спотыкается каждый: «я сохранил документ, почему остаток не изменился».

Записать — положить документ в базу. Он получает номер, его видно в списке, его можно открыть. На остатки он при этом не влияет никак.

Провести — выполнить ОбработкаПроведения и записать движения в регистры. Вот теперь документ влияет на остатки, долги и продажи.

Поэтому и в коде это два разных вызова: Док.Записать() и Док.Записать(РежимЗаписиДокумента.Проведение). Первый оставит документ непроведённым, даже если он был проведён до этого.

Три способа записать документ

Док.Записать();                                          // просто записать
Док.Записать(РежимЗаписиДокумента.Проведение);           // записать и провести
Док.Записать(РежимЗаписиДокумента.ОтменаПроведения);     // распровести
Док.Записать(РежимЗаписиДокумента.Запись);               // то же, что просто Записать
ЧастьЧто означает
Записать()по умолчанию — режим «Запись»: движения не трогаются, а проведённый документ СТАНОВИТСЯ непроведённым
Проведениевызывает ОбработкаПроведения и пишет движения
ОтменаПроведенияудаляет движения, документ остаётся в базе
важноу документа, в отличие от справочника, режим записи есть всегда — и выбирать его приходится осознанно

Как выглядит проведение изнутри

ОбработкаПроведения живёт в модуле объекта документа и получает два параметра: Отказ и Режим.

Движения не возвращают и не создают «новый объект движений»: у документа есть коллекция Движения, в наборы которой добавляют записи. Платформа сама запишет их после процедуры — если сказать ей, что набор надо записывать.

Проведение по регистру остатков

Процедура ОбработкаПроведения(Отказ, Режим)
	Движения.ТоварыНаСкладах.Записывать = Истина;

	Для Каждого Строка Из Товары Цикл
		Движение = Движения.ТоварыНаСкладах.Добавить();
		Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
		Движение.Период      = Дата;
		Движение.Номенклатура = Строка.Номенклатура;
		Движение.Склад       = Склад;
		Движение.Количество  = Строка.Количество;
	КонецЦикла;
КонецПроцедуры
ЧастьЧто означает
Записывать = Истинабез этой строки движения не попадут в базу — молча: ни ошибки, ни движений
Движения.…Добавить()возвращает новую запись набора; заполняем её поля
Период = Датадата движения — это дата документа, а не текущая
ВидДвиженияНакопленияприход или расход; в регистре оборотов вида движения нет вовсе
без Записать()набор записывает платформа после процедуры — вручную вызывать не надо

Забыли Записывать — движений нет, и ошибки тоже нет

Самая тихая ошибка в проведении. Код отработал, документ проведён, а в регистре пусто. Никто ничего не сказал.

Причина: Движения.Регистр.Записывать по умолчанию Ложь. Платформа записывает только те наборы, которые об этом попросили.

Второй вид той же беды — Движения.Регистр.Очистить() без последующего заполнения при перепроведении: старые движения удалены, новые не добавлены.

Контроль остатка: почему нельзя просто посмотреть

Задача звучит просто: не дать продать больше, чем есть. Наивное решение — прочитать остаток, сравнить, отказать — неверно, и это классический вопрос собеседования.

Причина в том, что между «прочитали остаток» и «записали движения» проходит время, и за это время другой пользователь успевает продать тот же товар. Оба прочитали по 10, оба списали по 7 — остаток стал минус 4.

Правильный порядок — сначала записать движения, потом проверить остаток, уже внутри той же транзакции. Если ушло в минус — Отказ = Истина, и транзакция откатится вместе с движениями.

✓ Так — проверка до движений

Процедура ОбработкаПроведения(Отказ, Режим)
	// прочитали остаток
	Если Остаток < Строка.Количество Тогда
		Отказ = Истина;
	КонецЕсли;
	// и только потом пишем движения
КонецПроцедуры

Между чтением и записью влезает второй пользователь. На однопользовательской базе работает годами — и разваливается на боевой.

✕ Так — движения, потом контроль

Процедура ОбработкаПроведения(Отказ, Режим)
	// 1. управляемая блокировка нужных строк
	// 2. движения
	// 3. запрос остатков ПОСЛЕ движений
	// 4. отрицательный остаток → Отказ = Истина
КонецПроцедуры

Движения и проверка в одной транзакции. Ушло в минус — откатывается всё сразу, и чужие движения уже учтены.

Управляемые блокировки — половина ответа

Полная схема контроля остатков включает управляемую блокировку: перед чтением остатков ставят БлокировкаДанных на нужные номенклатуру и склад.

Смысл не в том, чтобы «запретить другим», а в том, чтобы второе проведение по тем же товарам подождало, пока первое закончится. Блокируются строки, а не таблица целиком: продажи разных товаров идут параллельно.

В типовых конфигурациях это спрятано в общие модули БСП и в общие процедуры проведения — но спрашивают об этом на собеседовании почти всегда.

Оперативное и неоперативное проведение

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

Оперативное — документ проводится «сейчас», текущей датой. Это работа кладовщика в реальном времени, и именно здесь имеет смысл контроль остатка: нельзя отгрузить то, чего нет на складе прямо сейчас.

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

Отсюда и типовой код: Если Режим = РежимПроведенияДокумента.Оперативный Тогда — и только внутри этого условия проверка остатков.

Почему документ «задним числом» ведёт себя иначе

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

Из этого следует практическое: одна и та же кнопка «Провести» ведёт себя по-разному на сегодняшнем и вчерашнем документе. Человек этого не знает и приходит с вопросом «почему вчера ругалось, а сегодня нет».

Упражнение 2. Сколько документов не проведено

Напишите Непроведённые(): пройдите по всем документам РеализацияТоваров и верните, сколько из них не проведено. В демо-базе таких пять.

Решается в тренажёре: код запускается и проверяется сразу.

Упражнение 3. Самый крупный документ

Напишите СамыйКрупный(): верните номер документа с наибольшей суммой по строкам (количество × цена, округляя по строке). Перебирать надо все документы, включая непроведённые.

Решается в тренажёре: код запускается и проверяется сразу.

События документа и их порядок

Кроме проведения у документа есть ещё четыре события, и путаница между ними — частая причина «код не сработал».

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

ПередЗаписью — последняя возможность проверить и отказаться. Документ ещё не в базе.

ПриЗаписи — документ уже записан, но транзакция не закрыта. Здесь пишут связанные данные: отказ тут тоже работает, и всё откатится.

ОбработкаУдаленияПроведения — при отмене проведения. Обычно пуста: движения платформа удаляет сама.

ПередЗаписью срабатывает и при проведении

ПередЗаписью вызывается при любой записи: и когда просто сохранили, и когда провели, и когда распровели.

Поэтому проверки «нельзя проводить без склада» пишут не в ПередЗаписью, а в ОбработкаПроведения — иначе документ нельзя будет даже сохранить черновиком.

А если проверка нужна именно при записи, смотрят параметр РежимЗаписи: он говорит, что именно сейчас происходит.

Перепроведение и последовательность

Исправили документ недельной давности — и дальше начинается то, из-за чего в 1С существуют «закрытие месяца» и «восстановление последовательности».

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

Платформа сама этого не исправляет: она перепроводит только тот документ, который вы записали. Восстановление последовательности — отдельная операция, и в типовых её запускают при закрытии месяца.

Проведение целиком в транзакции

Всё, что происходит при проведении, — одна транзакция: запись документа, движения по всем регистрам, связанные записи из ПриЗаписи.

Поэтому Отказ = Истина в любой момент откатывает всё, а не только текущий регистр. И поэтому же в проведении нельзя делать долгие вещи: обращение к внешнему сервису внутри транзакции держит блокировки всё время ожидания.

Упражнение 1. Сумма документа по строкам

Функция СуммаДокумента(Номер) должна найти документ РеализацияТоваров по номеру и вернуть сумму по всем строкам табличной части Товары: количество × цену, округлённое до копеек по каждой строке. Документа нет — вернуть 0.

Решается в тренажёре: код запускается и проверяется сразу.

Короткий свод

Документ — событие с датой и номером. Шапка по одному полю, табличная часть по строке. Записан и проведён — разные вещи: до проведения для учёта документа нет. Проведение пишет движения в регистры, код лежит в ОбработкаПроведения. Не дать провести можно только через Отказ = Истина, и проверки идут до записи движений, а не после. Номер уникален в пределах года, не сам по себе.

Как закрепить

Читать про код и писать код — разные умения. В тренажёре к этой главе идут упражнения: вы пишете решение, оно запускается на настоящем интерпретаторе 1С и проверяется сразу, без установки платформы.

Открыть эту главу в тренажёре

Справочники: списки того, что существует
Регистры накопления: сколько и где