Движения документа 1С — это записи в регистрах, у которых в поле Регистратор стоит ссылка на документ. Документ создаёт их при проведении, заменяет при перепроведении и удаляет при отмене проведения: остатки, обороты, цены, проводки. Ниже — где их посмотреть, как добавить регистр в движения документа, а также как сформировать, проверить, прочитать и исправить записи кодом.
Имена документов и регистров в примерах условные (ПеремещениеТоваров, ОстаткиТоваров, Продажи и т. д.) — подставьте свои. Код рассчитан на платформу 8.3 и управляемый режим блокировок. Устройство самого регистра описано в статье о регистре накопления.
ВЫБРАТЬ * ИЗ РегистрНакопления.ОстаткиТоваров ГДЕ Регистратор = &Документ.В конфигураторе откройте свойства документа, закладку «Движения», и отметьте регистр флажком. Этот список и есть регистры движения документа. Связь двусторонняя: документ сразу попадёт в «Регистраторы» регистра. В модуле объекта появится набор Движения.ИмяРегистра, а кнопка «Конструктор движений» сгенерирует заготовку обработчика. Там же — свойства, от которых зависит поведение движений:
| Свойство | Значения | На что влияет |
|---|---|---|
| Оперативное проведение | Разрешить / Запретить | Бывает ли режим «Оперативный» |
| Удаление движений | Удалять автоматически / Удалять автоматически при отмене проведения / Не удалять автоматически | Кто стирает старые записи: платформа перед перепроведением и при отмене, только при отмене или ваш код |
| Запись движений при проведении | Записывать выбранные (по умолчанию) / Записывать модифицированные | Какие наборы платформа запишет после обработчика: с флагом Записывать или все изменённые |
| Привилегированный режим при проведении | Флажок | Нужны ли пользователю права на изменение регистров |
Флажок не создаёт записей задним числом: старые документы попадут в новый регистр только после перепроведения или заполнения обработкой.
Движения формируют в обработчике ОбработкаПроведения модуля объекта. Движения.ОстаткиТоваров — набор записей регистратора: все записи документа в одном регистре с готовым отбором по документу; платформа читает и пишет его целиком. Вид движения есть только у регистров остатков: ВидДвиженияНакопления.Приход увеличивает остаток, Расход уменьшает. Пример — перемещение между складами.
// Модуль объекта документа ПеремещениеТоваров (имена условные).
// Реквизиты: СкладОтправитель, СкладПолучатель; табличная часть Товары (Номенклатура, Количество).
// Регистр накопления ОстаткиТоваров, вид «Остатки»: измерения Склад, Номенклатура; ресурс Количество.
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Набор записей этого документа в регистре ОстаткиТоваров.
НаборЗаписей = Движения.ОстаткиТоваров;
НаборЗаписей.Записывать = Истина;
Для Каждого СтрокаТовары Из Товары Цикл
// Списание со склада-отправителя.
Движение = НаборЗаписей.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Склад = СкладОтправитель;
Движение.Номенклатура = СтрокаТовары.Номенклатура;
Движение.Количество = СтрокаТовары.Количество;
// Поступление на склад-получатель.
Движение = НаборЗаписей.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
Движение.Период = Дата;
Движение.Склад = СкладПолучатель;
Движение.Номенклатура = СтрокаТовары.Номенклатура;
Движение.Количество = СтрокаТовары.Количество;
КонецЦикла;
КонецПроцедуры
Записывать = Истина — в режиме «Записывать выбранные» платформа после обработчика пишет только наборы с этим флагом. Нет флага — движения молча не запишутся.В рабочем коде движения собирают запросом: он сворачивает повторы, подтягивает данные других таблиц и обходится одним обращением к базе. Тот же документ:
// Модуль объекта документа ПеремещениеТоваров: те же движения, но запросом.
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Номенклатура КАК Номенклатура,
| СУММА(Товары.Количество) КАК Количество
|ПОМЕСТИТЬ ВТТовары
|ИЗ
| Документ.ПеремещениеТоваров.Товары КАК Товары
|ГДЕ
| Товары.Ссылка = &Ссылка
|
|СГРУППИРОВАТЬ ПО
| Товары.Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| &Период КАК Период,
| ЗНАЧЕНИЕ(ВидДвиженияНакопления.Расход) КАК ВидДвижения,
| &СкладОтправитель КАК Склад,
| ВТТовары.Номенклатура КАК Номенклатура,
| ВТТовары.Количество КАК Количество
|ИЗ
| ВТТовары КАК ВТТовары
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| &Период,
| ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход),
| &СкладПолучатель,
| ВТТовары.Номенклатура,
| ВТТовары.Количество
|ИЗ
| ВТТовары КАК ВТТовары";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Запрос.УстановитьПараметр("Период", Дата);
Запрос.УстановитьПараметр("СкладОтправитель", СкладОтправитель);
Запрос.УстановитьПараметр("СкладПолучатель", СкладПолучатель);
НаборЗаписей = Движения.ОстаткиТоваров;
НаборЗаписей.Записывать = Истина;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ЗаполнитьЗначенияСвойств(НаборЗаписей.Добавить(), Выборка);
КонецЦикла;
КонецПроцедуры
ОбработкаПроведения документ уже записан в той же транзакции, поэтому запрос видит текущие строки табличной части.СГРУППИРОВАТЬ ПО сворачивает повторы: десять строк одного товара дадут одну запись расхода и одну прихода.ЗНАЧЕНИЕ(ВидДвиженияНакопления.Расход), а ЗаполнитьЗначенияСвойств переносит в запись одноимённые колонки.Второй параметр обработчика — режим проведения. Оперативный — проведение «сейчас»: документ датирован текущим днём, и платформа сдвигает его время позже всех оперативно проведённых. Неоперативный — ввод задним числом. Если оперативное проведение у документа запрещено, обработчик всегда получает Неоперативный.
В форме режим выбирает платформа по свойству формы ИспользоватьРежимПроведения и для документа текущего дня может спросить пользователя. В коде режим передают вторым параметром: Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Оперативный). Классическая схема — строгий контроль остатков только при оперативном проведении, как в следующем примере.
Порядок: заблокировать остатки по товарам документа, записать движения, запросить остаток на момент документа. Минус означает, что товара не хватило: проведение отменяется, а сообщение подсвечивает нужную строку табличной части.
// Модуль объекта документа РеализацияТоваров (имена условные).
// Реквизиты: Склад; табличная часть Товары (Номенклатура, Количество).
// Регистр ОстаткиТоваров — как в примерах выше. Режим блокировок — управляемый.
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Строгий контроль — при оперативном проведении. Если оперативное
// проведение у документа запрещено, режим всегда «Неоперативный»:
// тогда присвойте здесь Истина.
КонтролироватьОстатки = (РежимПроведения = РежимПроведенияДокумента.Оперативный);
Если КонтролироватьОстатки Тогда
// Блокируем до записи: второй документ по тем же товарам
// и складу дождётся конца нашей транзакции.
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиТоваров");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.УстановитьЗначение("Склад", Склад);
ЭлементБлокировки.ИсточникДанных = Товары;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
Блокировка.Заблокировать();
КонецЕсли;
НаборЗаписей = Движения.ОстаткиТоваров;
НаборЗаписей.Записывать = Истина;
Для Каждого СтрокаТовары Из Товары Цикл
Движение = НаборЗаписей.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Склад = Склад;
Движение.Номенклатура = СтрокаТовары.Номенклатура;
Движение.Количество = СтрокаТовары.Количество;
КонецЦикла;
Если Не КонтролироватьОстатки Тогда
Возврат; // набор запишет платформа после обработчика
КонецЕсли;
// Пишем сразу, чтобы запрос увидел остаток после списания.
НаборЗаписей.Записать();
НаборЗаписей.Записывать = Ложь; // иначе платформа запишет набор повторно
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Номенклатура КАК Номенклатура,
| МИНИМУМ(Товары.НомерСтроки) КАК НомерСтроки
|ПОМЕСТИТЬ ВТТовары
|ИЗ
| Документ.РеализацияТоваров.Товары КАК Товары
|ГДЕ
| Товары.Ссылка = &Ссылка
|
|СГРУППИРОВАТЬ ПО
| Товары.Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТТовары.НомерСтроки КАК НомерСтроки,
| ВТТовары.Номенклатура КАК Номенклатура,
| -Остатки.КоличествоОстаток КАК Нехватка
|ИЗ
| ВТТовары КАК ВТТовары
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиТоваров.Остатки(
| &Граница,
| Склад = &Склад
| И Номенклатура В
| (ВЫБРАТЬ
| Т.Номенклатура
| ИЗ
| ВТТовары КАК Т)) КАК Остатки
| ПО ВТТовары.Номенклатура = Остатки.Номенклатура
|ГДЕ
| Остатки.КоличествоОстаток < 0
|
|УПОРЯДОЧИТЬ ПО
| НомерСтроки";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Запрос.УстановитьПараметр("Склад", Склад);
Запрос.УстановитьПараметр("Граница", Новый Граница(МоментВремени(), ВидГраницы.Включая));
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Не хватает товара «" + Выборка.Номенклатура + "»: "
+ Формат(Выборка.Нехватка, "ЧГ=0");
Сообщение.Поле = "Товары[" + Формат(Выборка.НомерСтроки - 1, "ЧН=0; ЧГ=0") + "].Количество";
Сообщение.УстановитьДанные(ЭтотОбъект);
Сообщение.Сообщить();
Отказ = Истина; // откатит транзакцию вместе с записанным набором
КонецЦикла;
КонецПроцедуры
НаборЗаписей.Записать() флаг Записывать сбрасываем, иначе платформа запишет набор повторно.ВидГраницы.Включая — остаток с учётом движений самого документа."ЧН=0; ЧГ=0" нужен для первой строки: Формат(0, "ЧГ=0") вернёт пустую строку, и путь к полю сломается.Отказ = Истина откатывает транзакцию проведения вместе с записанным набором.При отмене проведения платформа вызывает ОбработкаУдаленияПроведения(Отказ). Движения она удалит сама, если не выбрано «Не удалять автоматически»; в этом случае их стирают в коде, записывая пустые наборы. Обработчик полезен и при автоудалении: отмена прихода тоже меняет остаток, и если товар со склада-получателя уже продан, склад уйдёт в минус.
// Модуль объекта документа ПеремещениеТоваров (имена условные).
// Отмена проведения возвращает товар отправителю и снимает приход
// у получателя — проверяем остатки получателя.
Процедура ОбработкаУдаленияПроведения(Отказ)
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиТоваров");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.УстановитьЗначение("Склад", СкладПолучатель);
ЭлементБлокировки.ИсточникДанных = Товары;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
Блокировка.Заблокировать();
// Пустой набор с отбором по документу: запись удаляет его движения.
Движения.ОстаткиТоваров.Записать();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| -Остатки.КоличествоОстаток КАК Нехватка
|ИЗ
| РегистрНакопления.ОстаткиТоваров.Остатки(
| ,
| Склад = &Склад
| И Номенклатура В
| (ВЫБРАТЬ
| Товары.Номенклатура
| ИЗ
| Документ.ПеремещениеТоваров.Товары КАК Товары
| ГДЕ
| Товары.Ссылка = &Ссылка)) КАК Остатки
|ГДЕ
| Остатки.КоличествоОстаток < 0";
Запрос.УстановитьПараметр("Склад", СкладПолучатель);
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Нельзя отменить проведение: остаток «" + Выборка.Номенклатура
+ "» на складе-получателе уйдёт в минус на " + Формат(Выборка.Нехватка, "ЧГ=0");
Сообщение.Сообщить();
Отказ = Истина; // откат вернёт удалённые движения
КонецЦикла;
КонецПроцедуры
Остатки(, …) без периода — текущие остатки.Отказ = Истина отменяет саму отмену: документ остаётся проведённым, движения возвращает откат транзакции.Подчинённые регистратору регистры сведений и бухгалтерии заполняются через ту же коллекцию Движения; отличаются поля записи.
// Модуль объекта документа ПоступлениеТоваров (имена условные).
// Реквизиты: Контрагент; табличная часть Товары (Номенклатура, Цена, Сумма).
// ЦеныПоставщиков — периодический регистр сведений, подчинён регистратору.
// Хозрасчетный — регистр бухгалтерии с корреспонденцией счетов.
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
Движения.ЦеныПоставщиков.Записывать = Истина;
Движения.Хозрасчетный.Записывать = Истина;
Для Каждого СтрокаТовары Из Товары Цикл
// Регистр сведений: вида движения нет, запись — значение на дату.
ЗаписьЦены = Движения.ЦеныПоставщиков.Добавить();
ЗаписьЦены.Период = Дата;
ЗаписьЦены.Поставщик = Контрагент;
ЗаписьЦены.Номенклатура = СтрокаТовары.Номенклатура;
ЗаписьЦены.Цена = СтрокаТовары.Цена;
// Регистр бухгалтерии: проводка Дт «Товары» Кт «Расчеты с поставщиками».
Проводка = Движения.Хозрасчетный.Добавить();
Проводка.Период = Дата;
Проводка.СчетДт = ПланыСчетов.Хозрасчетный.Товары;
Проводка.СчетКт = ПланыСчетов.Хозрасчетный.РасчетыСПоставщиками;
Проводка.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконто.Номенклатура] = СтрокаТовары.Номенклатура;
Проводка.СубконтоКт[ПланыВидовХарактеристик.ВидыСубконто.Контрагенты] = Контрагент;
Проводка.Сумма = СтрокаТовары.Сумма;
КонецЦикла;
КонецПроцедуры
СубконтоДт[ВидСубконто].Движения читают набором записей с отбором по регистратору. Функция обходит регистры с закладки «Движения» и возвращает таблицы только там, где у документа есть записи.
// Общий модуль «РаботаСДвижениями», флаг «Сервер» (имена условные).
// Возвращает соответствие: полное имя регистра -> таблица движений документа.
Функция ДвиженияДокумента(ДокументСсылка) Экспорт
Результат = Новый Соответствие;
Для Каждого МетаРегистр Из ДокументСсылка.Метаданные().Движения Цикл
Если Не ПравоДоступа("Чтение", МетаРегистр) Тогда
Продолжить;
КонецЕсли;
Если Метаданные.РегистрыНакопления.Содержит(МетаРегистр) Тогда
МенеджерРегистра = РегистрыНакопления[МетаРегистр.Имя];
ИначеЕсли Метаданные.РегистрыСведений.Содержит(МетаРегистр) Тогда
МенеджерРегистра = РегистрыСведений[МетаРегистр.Имя];
ИначеЕсли Метаданные.РегистрыБухгалтерии.Содержит(МетаРегистр) Тогда
МенеджерРегистра = РегистрыБухгалтерии[МетаРегистр.Имя];
ИначеЕсли Метаданные.РегистрыРасчета.Содержит(МетаРегистр) Тогда
МенеджерРегистра = РегистрыРасчета[МетаРегистр.Имя];
Иначе
Продолжить;
КонецЕсли;
НаборЗаписей = МенеджерРегистра.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(ДокументСсылка);
НаборЗаписей.Прочитать();
Если НаборЗаписей.Количество() > 0 Тогда
Результат.Вставить(МетаРегистр.ПолноеИмя(), НаборЗаписей.Выгрузить());
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
// Пример вызова:
// ВсеДвижения = РаботаСДвижениями.ДвиженияДокумента(Документ);
// ТаблицаОстатков = ВсеДвижения.Получить("РегистрНакопления.ОстаткиТоваров"); // Неопределено, если записей нет
Метаданные().Движения делает функцию универсальной.ПравоДоступа пропускает закрытые регистры вместо ошибки «Нарушение прав доступа!». При ограничении доступа на уровне записей чтение набора падает, если недоступна хоть одна запись, — для отчётов берите запрос ВЫБРАТЬ РАЗРЕШЕННЫЕ.Движения.ОстаткиТоваров.Прочитать().Нормальный путь — перепровести документ. Править наборы напрямую допустимо в узких случаях: в регистр добавили измерение и его нужно заполнить в старых записях; в закрытом периоде исправляют явный сбой, а перепроведение задело бы себестоимость; разовая миграция данных. Условие: после правки движения должны совпадать с тем, что дал бы текущий код проведения, иначе следующее перепроведение вернёт старое.
// Общий модуль «ИсправлениеДвижений», флаг «Сервер» (имена условные).
// В регистр накопления Продажи добавили измерение Менеджер. Старые движения
// заполняем из реквизита документа, не перепроводя закрытый период.
// Вызывать порциями по списку документов, например из внешней обработки.
Процедура ЗаполнитьМенеджераВДвижениях(ДокументСсылка) Экспорт
НачатьТранзакцию();
Попытка
// Пока набор читается и пишется, документ нельзя перепровести.
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.Продажи.НаборЗаписей");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.УстановитьЗначение("Регистратор", ДокументСсылка);
Блокировка.Заблокировать();
МенеджерДокумента = ДокументСсылка.Менеджер;
НаборЗаписей = РегистрыНакопления.Продажи.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(ДокументСсылка);
НаборЗаписей.Прочитать();
Изменено = Ложь;
Для Каждого Запись Из НаборЗаписей Цикл
Если Запись.Менеджер <> МенеджерДокумента Тогда
Запись.Менеджер = МенеджерДокумента;
Изменено = Истина;
КонецЕсли;
КонецЦикла;
Если Изменено Тогда
НаборЗаписей.Записать(); // замещает движения документа в регистре
КонецЕсли;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("ИсправлениеДвижений", УровеньЖурналаРегистрации.Ошибка,
Метаданные.РегистрыНакопления.Продажи, ДокументСсылка,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
РегистрНакопления.Продажи.НаборЗаписей по регистратору не даёт перепровести документ, пока набор читается и пишется.Движения.ОстаткиТоваров — регистра нет на закладке «Движения» документа или в имени опечатка. Подробнее — в статье «Поле объекта не обнаружено».Записывать = Истина в режиме «Записывать выбранные», обработчик вышел по Возврат раньше цикла или документ загружен обменом с признаком «Проведен», но без проведения.Записать(Ложь) — без замещения записи добавляются к старым; в базе две копии одного документа. Родственный случай — «хвосты»: если движения удаляются не перед каждым проведением, регистр, в который документ больше не пишет, хранит старые записи. Помогает строка Для Каждого НаборЗаписей Из Движения Цикл НаборЗаписей.Записывать = Истина; КонецЦикла; в начале обработчика.ОбработкаПроведения перехватили и продолжили писать наборы. В проведении ошибки не глушат: ставят Отказ = Истина или пробрасывают исключение.Технически да: набор с отбором по регистратору запишется и для непроведённого документа. Но документ не проведён, отчёты видят его записи, а ближайшее проведение их перезапишет.
Да, если новый регистр должен содержать историю, — либо заполните его обработкой. Новые документы получат записи при первом проведении.
Запросом к основной таблице регистра: ВЫБРАТЬ РАЗЛИЧНЫЕ Регистратор ИЗ РегистрНакопления.ОстаткиТоваров ГДЕ Номенклатура = &Товар. Поле Регистратор составного типа, для вывода используйте ПРЕДСТАВЛЕНИЕ(Регистратор).
Добавить движения в типовую конфигурацию, исправить записи в закрытом периоде или найти, откуда на складе минус, лучше поручить программисту, который знает проведение изнутри. Опишите задачу в разделе услуг по доработке 1С или разместите задание на площадке — исполнитель оценит объём до начала работ.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.