Счётчик в 1С на регистре накопления: создаём с нуля

· 9 мин

Содержание

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

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

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

Какой вид регистра накопления взять

Регистр накопления в 1С бывает двух видов, и это первое решение, от которого всё зависит:

  • 🟢 Остатки — хранит текущее значение, которое можно увеличивать (приход) и уменьшать (расход). Платформа сама считает остаток на любую дату. Это и есть счётчик в привычном смысле.
  • 🔵 Обороты — хранит только накопление «сколько прошло за период», без понятия текущего остатка. Берут его для статистики: сколько продали, сколько начислили за месяц.

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

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

Регистр накопления всегда работает в паре с документом-регистратором. Сам по себе он значения не меняет — только документ пишет в него движения.

Иногда под «счётчиком» понимают не накопитель, а простой изменяемый показатель без прихода и расхода — например, последний использованный порядковый номер. Для такого случая регистр накопления избыточен: берут регистр сведений (периодический или непериодический) и хранят одно значение по ключу. Но как только появляется логика «прибавить/вычесть» и нужен остаток на дату — это именно регистр накопления вида «Остатки», и дальше речь о нём.

Проектируем измерения, ресурсы и реквизиты

Структура регистра — это ответ на вопрос «счётчик в разрезе чего». Три типа полей:

ПолеЧто хранитПример для счётчика баллов
ИзмерениеРазрез, по которому ведём учётКлиент (или Клиент + Карта)
РесурсСамо число, которое накапливаемКоличество (баллы)
РеквизитДоп. информация без влияния на остатокКомментарий, Основание

Правила, которые экономят потом часы отладки:

  1. Измерений — минимум необходимого. Каждое лишнее измерение раздувает таблицу итогов и замедляет запросы. Если счётчик ведётся по клиенту — одно измерение Клиент, и всё.
  2. Ресурс — только числовой тип. Для баллов достаточно длины 10 и точности 0 (целые). Для денег — точность 2.
  3. Порядок измерений важен. Ставьте первым то измерение, по которому чаще всего фильтруете остатки. Платформа строит итоги по порядку измерений слева направо.

Создаётся регистр в дереве конфигурации: правой кнопкой по ветке Регистры накопления → Добавить. На вкладке Данные добавляете измерения и ресурсы, на вкладке Регистраторы позже отметите документ.

Проектирование регистра: что решить до кода
Выполнено: 0 / 5

Документ, который двигает счётчик

Менять значение счётчика будет документ. Сначала свяжите его с регистром: откройте документ, вкладка Движения, в списке регистров накопления установите флажок напротив своего регистра. Платформа предложит сконструировать движения — можно согласиться для черновика, но боевой код пишут руками.

Движения формируются в процедуре ОбработкаПроведения модуля документа. Простой приход (увеличение счётчика):

Процедура ОбработкаПроведения(Отказ, Режим)

	Движения.Счётчик.Записывать = Истина;

	Для Каждого СтрокаТаблицы Из Товары Цикл
		Движение = Движения.Счётчик.Добавить();
		Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
		Движение.Период = Дата;
		Движение.Клиент = Клиент;
		Движение.Количество = СтрокаТаблицы.Количество;
	КонецЦикла;

КонецПроцедуры

Разберём ключевые строки:

  • Движения.Счётчик.Записывать = Истина — говорит платформе записать набор движений при проведении. Без этой строки записи не появятся.
  • ВидДвижения — Приход увеличивает остаток, Расход уменьшает. Для счётчика-накопителя обычно только приход; если значение надо и списывать — добавьте расход.
  • Период — дата, на которую ложится движение. Как правило, это дата самого документа.

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

Если счётчик должен уметь и прибавлять, и убавлять (списание баллов), для расхода меняется только вид движения:

Движение.ВидДвижения = ВидДвиженияНакопления.Расход;

Как узнать текущее значение счётчика запросом

Текущее значение читают не из таблицы движений, а из виртуальной таблицы остатков — её платформа считает сама. Имя таблицы: РегистрНакопления.<Имя>.Остатки. Ресурс превращается в колонку <Ресурс>Остаток — для ресурса Количество это КоличествоОстаток.

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	СчётчикОстатки.Клиент КАК Клиент,
|	СчётчикОстатки.КоличествоОстаток КАК Остаток
|ИЗ
|	РегистрНакопления.Счётчик.Остатки(&Период, Клиент = &Клиент) КАК СчётчикОстатки";

Запрос.УстановитьПараметр("Период", ТекущаяДата());
Запрос.УстановитьПараметр("Клиент", Клиент);

Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
	ТекущееЗначение = Выборка.Остаток;
Иначе
	ТекущееЗначение = 0;
КонецЕсли;

Два момента, на которых спотыкаются чаще всего:

  • Пустой результат — это ноль, а не ошибка. Если по клиенту движений ещё не было, виртуальная таблица вернёт пустую выборку. Поэтому ветка Иначе обязательна: нет строки — значение 0.
  • Параметр периода. Первый параметр таблицы Остатки() — момент, на который нужен остаток. Второй — условие отбора. Передавайте границу периода точкой или Неопределено, если нужен остаток на текущий момент.

Для остатка «прямо сейчас» в реальном времени вместо ТекущаяДата() на сервере корректнее Неопределено в качестве периода — тогда платформа возьмёт самую свежую точку расчёта итогов.

Если текущее значение нужно сразу по многим клиентам (список, отчёт), не гоняйте запрос в цикле. Уберите отбор по конкретному клиенту — виртуальная таблица вернёт остатки по всем разрезам одним запросом:

"ВЫБРАТЬ
|	СчётчикОстатки.Клиент КАК Клиент,
|	СчётчикОстатки.КоличествоОстаток КАК Остаток
|ИЗ
|	РегистрНакопления.Счётчик.Остатки КАК СчётчикОстатки"

Когда вместе с текущим значением нужна и динамика (сколько пришло и ушло за период), есть третья виртуальная таблица — ОстаткиИОбороты. Она отдаёт начальный остаток, приход, расход и конечный остаток в одной выборке, и это дешевле, чем считать то же тремя отдельными запросами.

Блокировки и контроль: чтобы счётчик не ушёл в минус

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

В управляемом режиме блокировок (8.3 по умолчанию) схема такая:

  1. Сформировать движения расхода в ОбработкаПроведения.
  2. Поставить Движения.Счётчик.БлокироватьДляИзменения = Истина — это наложит управляемую блокировку на нужные комбинации измерений.
  3. Записать движения: Движения.Записать().
  4. Только после записи запросить остатки виртуальной таблицы.
  5. Если остаток отрицательный — установить Отказ = Истина и сообщить пользователю.
Движения.Счётчик.Записывать = Истина;
Движения.Счётчик.БлокироватьДляИзменения = Истина;

// ... заполнение движений расхода ...

Движения.Записать();

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	СчётчикОстатки.Клиент КАК Клиент,
|	СчётчикОстатки.КоличествоОстаток КАК Остаток
|ИЗ
|	РегистрНакопления.Счётчик.Остатки(, Клиент = &Клиент) КАК СчётчикОстатки
|ГДЕ
|	СчётчикОстатки.КоличествоОстаток < 0";
Запрос.УстановитьПараметр("Клиент", Клиент);

Если НЕ Запрос.Выполнить().Пустой() Тогда
	Отказ = Истина;
	Сообщить("Недостаточно баллов у клиента " + Клиент);
КонецЕсли;

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

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

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

Контроль остатка живёт в проведении документа. Если кто-то поменяет движения напрямую через набор записей регистра в обход документа, проверка не сработает — оставьте запись движений за документом.

Готовое решение по контролю отрицательных остатков есть в типовых конфигурациях (БСП), но для своего регистра логику пишут под его структуру.

Частые ошибки при создании счётчика

Отдельно собрал то, что не видно из кода, но ломает счётчик на практике:

Диагностика: выберите симптом

Что ускоряет счётчик и итог

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

  • 📉 Минимум измерений. Таблица итогов — это произведение всех значений измерений. Чем их меньше, тем быстрее запрос остатков.
  • 🗓 Итоги и агрегаты. У регистра накопления есть механизм хранения итогов. Он включён по умолчанию; для больших регистров проверьте, что пересчёт итогов не отключён, иначе каждый запрос остатков будет суммировать движения с нуля.
  • 🔍 Отбор в виртуальной таблице, а не во внешнем ГДЕ. Условие передавайте вторым параметром Остатки(, Условие) — платформа применит его до расчёта итогов, это быстрее, чем фильтровать результат.
  • 🧮 Остаток на момент. Для текущего значения период оставляйте пустым (Неопределено) — платформа возьмёт готовую точку итогов, не пересчитывая на произвольную дату.

Итого, чтобы сделать счётчик в 1С: создаёте регистр накопления вида «Остатки», задаёте измерения (разрез учёта) и числовой ресурс, пишете движения в проведении документа, читаете значение запросом к виртуальной таблице остатков и закрываете его от гонки блокировкой БлокироватьДляИзменения с проверкой отрицательного остатка. Этого достаточно для любого счётчика — от бонусных баллов до учёта выданных пропусков.

Частые вопросы

Можно ли сделать счётчик без документа, только на регистре?
Движения в регистр накопления пишет регистратор — обычно документ. Технически можно записать движения набором записей из обработки, но тогда вы теряете контроль остатка в проведении и привязку к первичному документу. Для счётчика с остатком документ — правильный путь.
Чем счётчик на регистре отличается от реквизита с числом в справочнике?
Реквизит хранит одно текущее число и не помнит историю: кто и когда менял, на какую дату сколько было. Регистр накопления даёт остаток на любую дату, движения по каждому документу и корректный пересчёт при перепроведении. Для счётчика с историей берут регистр.
Как обнулить или скорректировать счётчик?
Отдельным документом-корректировкой, который делает движение прихода или расхода на нужную разницу. Править движения напрямую в базе не стоит — потеряете привязку и контроль. Для разового обнуления сделайте расход на величину текущего остатка.
Почему виртуальная таблица остатков пустая, хотя движения есть?
Либо не совпало условие отбора (например, передаёте не того клиента), либо запрошен остаток на дату раньше первого движения. Проверьте параметры запроса и уберите отбор — если данные появились, дело в условии.
Нужен ли нумератор документов — это тоже счётчик?
Нет, это другое. Автонумерация документов и справочников в 1С встроена (поле «Номер», свойства нумератора) и к регистрам накопления отношения не имеет. Регистр накопления нужен, когда считаете количественный показатель с приходом и расходом.