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