Проведение документа и регистр накопления — walkthrough

artifacts/provedenie-dokumenta-registry.html · domain: platform · week: 1

Регистр накопления — это учётное ядро 1С: документы пишут в него движения, а платформа отдаёт по ним остатки и обороты. Главный неочевидный механизм — как платформа считает остаток на произвольную дату быстро, не суммируя все движения с начала времён. Ответ: таблица итогов хранит предрассчитанный остаток на границах периодов, а виртуальная таблица досчитывает только движения между ближайшей границей и нужной датой.

Модель данных регистра накопления

Регистр вида «остатки» состоит из измерений (по чему учитываем), ресурсов (что накапливаем) и хранит две таблицы под капотом:

Измерения

Номенклатура, Склад — координаты, по которым считаем остаток. Платформа индексирует их.

Ресурсы

Количество (и/или Сумма) — то, что накапливается. Складывается по движениям.

Таблица движений

Каждая запись: период, измерения, ресурсы, ВидДвижения (Приход/Расход). Пишется документом при проведении.

Таблица итогов

Предрассчитанный остаток на границах периодов (по умолчанию помесячно). Кэш для быстрого чтения.

Проведение: ОбработкаПроведения

При проведении документ в обработчике ОбработкаПроведения формирует набор записей движений и просит платформу их записать:

Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    // 1. Разрешаем запись набора движений по регистру
    Движения.ОстаткиТоваров.Записывать = Истина;

    Для Каждого ТекСтрока Из Товары Цикл
        Движение = Движения.ОстаткиТоваров.Добавить();
        Движение.ВидДвижения  = ВидДвиженияНакопления.Приход;
        Движение.Период       = Дата;
        Движение.Номенклатура = ТекСтрока.Номенклатура;
        Движение.Склад        = Склад;
        Движение.Количество   = ТекСтрока.Количество;
    КонецЦикла;
    // 2. Запись произойдёт автоматически в конце транзакции проведения,
    //    платформа сама обновит таблицу итогов.
КонецПроцедуры
1. Подготовка. Пользователь заполнил документ и нажал «Провести». Открывается транзакция, набор движений по регистру очищается.
2. ОбработкаПроведения. Код формирует движения из табличной части — приход/расход по номенклатуре и складу.
3. Контроль (если нужен). Для оперативного проведения — читаем остатки под управляемой блокировкой и проверяем на отрицательные. Без блокировки два параллельных проведения уведут остаток в минус.
4. Запись. Движения пишутся в таблицу движений, платформа обновляет таблицу итогов, транзакция фиксируется.

Остаток на дату — ядро механизма

Покрути дату запроса. Внизу видно, как платформа собирает остаток: берёт итог на ближайшей границе периода и прибавляет движения после этой границы до даты запроса. Это и есть РегистрНакопления.ОстаткиТоваров.Остатки(&Период).

Остаток `Количество` на 15.03 = 40 шт
Из таблицы итогов (на 01.03)
80
Досчёт из движений после границы
−40
= Остаток на дату
40

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

Вот почему чтение остатков — это O(движений после границы), а не O(всех движений). При помесячных итогах платформа суммирует максимум один месяц движений, а не всю историю. Отключишь расчёт итогов — каждое чтение остатка станет полным сканированием таблицы движений.

Виртуальные таблицы: Остатки vs Обороты

Остатки(Период, Условие)

Состояние ресурса на момент. «Сколько лежит на складе на дату».

Обороты(Начало, Конец, ...)

Сумма движений за интервал. «Сколько пришло и ушло за месяц».

ОстаткиИОбороты(...)

И начальный/конечный остаток, и обороты за период — в одной таблице.

Отбор задавай в параметрах виртуальной таблицы (Остатки(&Дата, Склад = &Склад)), а не в ГДЕ снаружи. В параметрах он уходит внутрь вложенного запроса и сужает данные до расчёта итогов; снаружи — платформа сначала посчитает всё, потом отфильтрует.

Контроль остатков и блокировки

Оперативное проведение должно гарантировать неотрицательный остаток даже при параллельных проведениях. Правильный порядок:

// 1. Записать движения (расход)
Движения.ОстаткиТоваров.Записывать = Истина;
// ... заполнение движений ...
Движения.ОстаткиТоваров.Записать();

// 2. Под управляемой блокировкой прочитать остатки ПОСЛЕ записи
Блокировка = Новый БлокировкаДанных;
ЭлБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиТоваров");
ЭлБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();

// 3. Проверить виртуальную таблицу Остатки на отрицательные
// если есть минус — Отказ = Истина
Если читать остатки без блокировки — две транзакции одновременно увидят «10 шт в наличии», обе спишут по 7, и остаток уйдёт в −4. Управляемая блокировка сериализует их: вторая ждёт, видит уже −7 и получает отказ.

Типичные ошибки (code review)