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