1C-Code → Основы 1С → Документы и проведение: что случилось
Документ фиксирует событие, проведение превращает его в движения. Здесь живёт главный код учётной системы — и главные ошибки.
Глава 18 из 36 · чтение примерно 28 минут
Документ отвечает на вопрос «что случилось». Продали, приняли, оплатили, начислили. У него всегда есть дата и номер: событие произошло в конкретный момент, и порядок событий важен.
Если справочник — это существительные учёта, то документ — глаголы.
У документа две области, и это устройство повторяется везде.
Шапка — реквизиты, которых по одному на документ: контрагент, склад, организация, комментарий.
Табличная часть — строки: товар, количество, цена, сумма. Их может быть несколько: «Товары» и «Услуги» в одной накладной.
В коде к строкам обращаются циклом:
Итого = 0;
Для Каждого Строка Из Док.Товары Цикл
Итого = Итого + Строка.Сумма;
КонецЦикла;
| Часть | Что означает |
|---|---|
Док.Товары | табличная часть по имени, заданному в конфигураторе |
Для Каждого … Из | обход строк по порядку; менять его нельзя |
Строка.Сумма | реквизит строки, а не шапки |
Итого | итог считаем сами: платформа его не хранит |
Новый — создан, но не записан. Номера ещё нет.
Записан — лежит в базе, но на учёт не повлиял. В списке выглядит бледным.
Проведён — учтён: остатки изменились, долг вырос, проводки появились.
Разница между «записан» и «проведён» — то, чего не понимают до первой рабочей задачи. Документ можно записать черновиком и провести завтра. Пока он не проведён, для учёта его как бы нет.
Проведение — это код, который превращает документ в движения по регистрам. Реализация списывает товар со склада и увеличивает долг покупателя. Ни то, ни другое не хранится в самом документе: документ говорит «что случилось», регистры отвечают «сколько теперь».
Код проведения лежит в модуле объекта документа, в процедуре ОбработкаПроведения. У неё два параметра: Отказ и РежимПроведения.
Отказ = Истина — единственный законный способ не дать провести документ. Не исключение, не сообщение, не «выйти из процедуры»: именно Отказ. Платформа увидит его и откатит всё, что процедура успела написать.
На чём спотыкаются
Пишут проверки после записи движений. Отказ отменит всё, но порядок всё равно неверный: сначала проверяем, потом пишем. Иначе однажды проверка окажется в ветке, до которой не дошли.
Сообщают об ошибке и не ставят Отказ. Пользователь видит красное сообщение, документ при этом проводится. Худший из возможных исходов: и напугали, и пропустили.
Читают остатки без блокировки. Два человека проводят одновременно, оба видят «на складе 5», оба списывают по 3. На складе становится −1. Лечится управляемой блокировкой — про это отдельная глава.
Чистят движения вручную. Движения.Очистить() не нужен: при повторном проведении платформа очищает сама, если стоит свойство «Удаление движений — автоматически».
Отмена проведения убирает движения, но документ остаётся. Так поступают, когда событие оказалось ошибочным, но след о нём нужен.
Пометка удаления — заявка на удаление. Сам документ остаётся в базе, пока не запустят «Удаление помеченных объектов». Это не формальность: удалить объект, на который ссылаются другие, нельзя, и контроль ссылочной целостности выполняется именно там.
Номер документа платформа выдаёт сама при записи. Он уникален в пределах периода, заданного свойством «Периодичность номера»: обычно года. Первого января нумерация начинается заново.
Отсюда правило, которое спрашивают: номер не уникален сам по себе. Искать документ только по номеру, без даты или организации, — ошибка.
Это первое, обо что спотыкается каждый: «я сохранил документ, почему остаток не изменился».
Записать — положить документ в базу. Он получает номер, его видно в списке, его можно открыть. На остатки он при этом не влияет никак.
Провести — выполнить ОбработкаПроведения и записать движения в регистры. Вот теперь документ влияет на остатки, долги и продажи.
Поэтому и в коде это два разных вызова: Док.Записать() и Док.Записать(РежимЗаписиДокумента.Проведение). Первый оставит документ непроведённым, даже если он был проведён до этого.
Док.Записать(); // просто записать
Док.Записать(РежимЗаписиДокумента.Проведение); // записать и провести
Док.Записать(РежимЗаписиДокумента.ОтменаПроведения); // распровести
Док.Записать(РежимЗаписиДокумента.Запись); // то же, что просто Записать
| Часть | Что означает |
|---|---|
Записать() | по умолчанию — режим «Запись»: движения не трогаются, а проведённый документ СТАНОВИТСЯ непроведённым |
Проведение | вызывает ОбработкаПроведения и пишет движения |
ОтменаПроведения | удаляет движения, документ остаётся в базе |
важно | у документа, в отличие от справочника, режим записи есть всегда — и выбирать его приходится осознанно |
ОбработкаПроведения живёт в модуле объекта документа и получает два параметра: Отказ и Режим.
Движения не возвращают и не создают «новый объект движений»: у документа есть коллекция Движения, в наборы которой добавляют записи. Платформа сама запишет их после процедуры — если сказать ей, что набор надо записывать.
Процедура ОбработкаПроведения(Отказ, Режим)
Движения.ТоварыНаСкладах.Записывать = Истина;
Для Каждого Строка Из Товары Цикл
Движение = Движения.ТоварыНаСкладах.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Номенклатура = Строка.Номенклатура;
Движение.Склад = Склад;
Движение.Количество = Строка.Количество;
КонецЦикла;
КонецПроцедуры
| Часть | Что означает |
|---|---|
Записывать = Истина | без этой строки движения не попадут в базу — молча: ни ошибки, ни движений |
Движения.…Добавить() | возвращает новую запись набора; заполняем её поля |
Период = Дата | дата движения — это дата документа, а не текущая |
ВидДвиженияНакопления | приход или расход; в регистре оборотов вида движения нет вовсе |
без Записать() | набор записывает платформа после процедуры — вручную вызывать не надо |
Забыли Записывать — движений нет, и ошибки тоже нет
Самая тихая ошибка в проведении. Код отработал, документ проведён, а в регистре пусто. Никто ничего не сказал.
Причина: Движения.Регистр.Записывать по умолчанию Ложь. Платформа записывает только те наборы, которые об этом попросили.
Второй вид той же беды — Движения.Регистр.Очистить() без последующего заполнения при перепроведении: старые движения удалены, новые не добавлены.
Задача звучит просто: не дать продать больше, чем есть. Наивное решение — прочитать остаток, сравнить, отказать — неверно, и это классический вопрос собеседования.
Причина в том, что между «прочитали остаток» и «записали движения» проходит время, и за это время другой пользователь успевает продать тот же товар. Оба прочитали по 10, оба списали по 7 — остаток стал минус 4.
Правильный порядок — сначала записать движения, потом проверить остаток, уже внутри той же транзакции. Если ушло в минус — Отказ = Истина, и транзакция откатится вместе с движениями.
✓ Так — проверка до движений
Процедура ОбработкаПроведения(Отказ, Режим)
// прочитали остаток
Если Остаток < Строка.Количество Тогда
Отказ = Истина;
КонецЕсли;
// и только потом пишем движения
КонецПроцедурыМежду чтением и записью влезает второй пользователь. На однопользовательской базе работает годами — и разваливается на боевой.
✕ Так — движения, потом контроль
Процедура ОбработкаПроведения(Отказ, Режим)
// 1. управляемая блокировка нужных строк
// 2. движения
// 3. запрос остатков ПОСЛЕ движений
// 4. отрицательный остаток → Отказ = Истина
КонецПроцедурыДвижения и проверка в одной транзакции. Ушло в минус — откатывается всё сразу, и чужие движения уже учтены.
Управляемые блокировки — половина ответа
Полная схема контроля остатков включает управляемую блокировку: перед чтением остатков ставят БлокировкаДанных на нужные номенклатуру и склад.
Смысл не в том, чтобы «запретить другим», а в том, чтобы второе проведение по тем же товарам подождало, пока первое закончится. Блокируются строки, а не таблица целиком: продажи разных товаров идут параллельно.
В типовых конфигурациях это спрятано в общие модули БСП и в общие процедуры проведения — но спрашивают об этом на собеседовании почти всегда.
Второй параметр ОбработкаПроведения — Режим. Значений два, и разница между ними не техническая, а смысловая.
Оперативное — документ проводится «сейчас», текущей датой. Это работа кладовщика в реальном времени, и именно здесь имеет смысл контроль остатка: нельзя отгрузить то, чего нет на складе прямо сейчас.
Неоперативное — документ проводится задним числом или перепроводится пачкой. Контролировать остаток бессмысленно: мы восстанавливаем историю, а не решаем, отгружать или нет. Отказ здесь заблокировал бы закрытие месяца.
Отсюда и типовой код: Если Режим = РежимПроведенияДокумента.Оперативный Тогда — и только внутри этого условия проверка остатков.
Почему документ «задним числом» ведёт себя иначе
Оперативность определяется не вашим желанием, а датой: платформа считает проведение оперативным, если дата документа близка к текущей — в пределах так называемой оперативной отметки времени.
Из этого следует практическое: одна и та же кнопка «Провести» ведёт себя по-разному на сегодняшнем и вчерашнем документе. Человек этого не знает и приходит с вопросом «почему вчера ругалось, а сегодня нет».
Упражнение 2. Сколько документов не проведено
Напишите Непроведённые(): пройдите по всем документам РеализацияТоваров и верните, сколько из них не проведено. В демо-базе таких пять.
Решается в тренажёре: код запускается и проверяется сразу.
Упражнение 3. Самый крупный документ
Напишите СамыйКрупный(): верните номер документа с наибольшей суммой по строкам (количество × цена, округляя по строке). Перебирать надо все документы, включая непроведённые.
Решается в тренажёре: код запускается и проверяется сразу.
Кроме проведения у документа есть ещё четыре события, и путаница между ними — частая причина «код не сработал».
ОбработкаЗаполнения — при создании нового документа на основании чего-то. Здесь заполняют реквизиты по умолчанию. Записи ещё нет.
ПередЗаписью — последняя возможность проверить и отказаться. Документ ещё не в базе.
ПриЗаписи — документ уже записан, но транзакция не закрыта. Здесь пишут связанные данные: отказ тут тоже работает, и всё откатится.
ОбработкаУдаленияПроведения — при отмене проведения. Обычно пуста: движения платформа удаляет сама.
ПередЗаписью срабатывает и при проведении
ПередЗаписью вызывается при любой записи: и когда просто сохранили, и когда провели, и когда распровели.
Поэтому проверки «нельзя проводить без склада» пишут не в ПередЗаписью, а в ОбработкаПроведения — иначе документ нельзя будет даже сохранить черновиком.
А если проверка нужна именно при записи, смотрят параметр РежимЗаписи: он говорит, что именно сейчас происходит.
Исправили документ недельной давности — и дальше начинается то, из-за чего в 1С существуют «закрытие месяца» и «восстановление последовательности».
Дело в том, что документы зависят друг от друга по времени: себестоимость сегодняшней продажи считается по партиям, пришедшим раньше. Изменили приход задним числом — все продажи после него посчитаны по старым данным.
Платформа сама этого не исправляет: она перепроводит только тот документ, который вы записали. Восстановление последовательности — отдельная операция, и в типовых её запускают при закрытии месяца.
Проведение целиком в транзакции
Всё, что происходит при проведении, — одна транзакция: запись документа, движения по всем регистрам, связанные записи из ПриЗаписи.
Поэтому Отказ = Истина в любой момент откатывает всё, а не только текущий регистр. И поэтому же в проведении нельзя делать долгие вещи: обращение к внешнему сервису внутри транзакции держит блокировки всё время ожидания.
Упражнение 1. Сумма документа по строкам
Функция СуммаДокумента(Номер) должна найти документ РеализацияТоваров по номеру и вернуть сумму по всем строкам табличной части Товары: количество × цену, округлённое до копеек по каждой строке. Документа нет — вернуть 0.
Решается в тренажёре: код запускается и проверяется сразу.
Документ — событие с датой и номером. Шапка по одному полю, табличная часть по строке. Записан и проведён — разные вещи: до проведения для учёта документа нет. Проведение пишет движения в регистры, код лежит в ОбработкаПроведения. Не дать провести можно только через Отказ = Истина, и проверки идут до записи движений, а не после. Номер уникален в пределах года, не сам по себе.
Читать про код и писать код — разные умения. В тренажёре к этой главе идут упражнения: вы пишете решение, оно запускается на настоящем интерпретаторе 1С и проверяется сразу, без установки платформы.
← Справочники: списки того, что существует
→ Регистры накопления: сколько и где