При разработке собственных механизмов учета в 1С:Предприятие 8.3, особенно в таких сложных конфигурациях, как Комплексная автоматизация 2.4, начинающие разработчики часто сталкиваются с проблемой: приход товара в регистр отображается корректно, но после оформления расхода остаток по ресурсу Цена обнуляется или принимает неверное значение, хотя количество товара на складе еще есть. Давайте разберем по шагам, почему это происходит, и как правильно спроектировать архитектуру хранения данных.
Рассмотрим ситуацию, описанную в теме. Автор создал регистр накопления остатков СВХ_ТоварыНаКонсолидационномСкладе и добавил в него ресурс Цена. При поступлении 3 единиц товара по цене 26,48 в регистр записывается «Приход». При продаже 1 единицы по той же цене записывается «Расход».
Математика регистра накопления (тип «Остатки») работает по принципу простого сумматора. Когда мы запрашиваем остатки, платформа складывает все приходы и вычитает все расходы по каждому ресурсу в разрезе измерений. Проанализируем, что происходит с ресурсом Цена:
С точки зрения системы, это абсолютно логичное поведение. Регистр накопления предназначен для хранения аддитивных (суммируемых) величин. Количество и сумма — это экстенсивные показатели: если у нас есть 2 яблока и мы добавим еще 3, у нас станет 5 яблок. Цена — это интенсивная величина. Если цена одного яблока 10 рублей и цена другого 10 рублей, это не значит, что цена двух яблок — 20 рублей.
Важный вывод: Ресурсы регистра накопления остатков должны иметь физический или экономический смысл при сложении. Цена таким свойством не обладает.
Чтобы учет работал корректно, нам необходимо изменить состав ресурсов регистра. Вместо хранения цены напрямую, мы должны хранить Количество и Сумма. Разберем правильную структуру:
Номенклатура, Склад, Валюта. (Внимание: добавление Контрагента в складской регистр часто является ошибкой, так как товар на складе обычно «обезличен» относительно того, у кого он куплен).Количество, Сумма, ВесБрутто.В этом случае при расходе 1 единицы товара мы будем списывать не «цену», а «часть общей стоимости». Если пришло 3 шт. на сумму 79,44 (3 * 26,48), а ушло 1 шт. на сумму 26,48, то в остатке останется 2 шт. на сумму 52,96. Цена в любой момент может быть получена делением Сумма / Количество.
Проанализируем исправленный код формирования движений для документа прихода. Мы должны гарантировать, что в регистр записывается итоговая сумма строки, а не цена единицы.
// Регистр СВХ_ТоварыНаКонсолидационномСкладе: ПРИХОД
Движения.СВХ_ТоварыНаКонсолидационномСкладе.Записывать = Истина;
Для Каждого ТекСтрока Из Товары Цикл
Движение = Движения.СВХ_ТоварыНаКонсолидационномСкладе.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
Движение.Период = Дата;
// Заполняем измерения
Движение.Номенклатура = ТекСтрока.Номенклатура;
Движение.Склад = Склад;
Движение.Валюта = ТекСтрока.Валюта;
// Заполняем аддитивные ресурсы
Движение.Количество = ТекСтрока.Количество;
Движение.Сумма = ТекСтрока.Количество * ТекСтрока.Цена; // Или ТекСтрока.Сумма
Движение.ВесБрутто = ТекСтрока.ВесБрутто;
КонецЦикла;
Теперь рассмотрим формирование движений расхода. Здесь критически важно списывать сумму пропорционально количеству, чтобы в итоге (при нулевом остатке товара) сумма также закрылась в ноль. В сложных системах (как КА 2.4) для этого используется расчет себестоимости, но в простом варианте мы можем брать цену из документа:
// Регистр СВХ_ТоварыНаКонсолидационномСкладе: РАСХОД
Движения.СВХ_ТоварыНаКонсолидационномСкладе.Записывать = Истина;
Для Каждого ТекСтрока Из Товары Цикл
Движение = Движения.СВХ_ТоварыНаКонсолидационномСкладе.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Номенклатура = ТекСтрока.Номенклатура;
Движение.Склад = Склад;
Движение.Валюта = ТекСтрока.Валюта;
Движение.Количество = ТекСтрока.Количество;
// Списываем сумму, соответствующую расходу
Движение.Сумма = ТекСтрока.Количество * ТекСтрока.Цена;
Движение.ВесБрутто = ТекСтрока.ВесБрутто;
КонецЦикла;
Поскольку мы больше не храним цену в регистре как готовое число, нам нужно вычислять ее при получении остатков. В запросе к виртуальной таблице Остатки это выглядит следующим образом:
ВЫБРАТЬ
Остатки.Номенклатура,
Остатки.Склад,
Остатки.КоличествоОстаток,
Остатки.СуммаОстаток,
ВЫБОР
КОГДА Остатки.КоличествоОстаток <> 0
ТОГДА Остатки.СуммаОстаток / Остатки.КоличествоОстаток
ИНАЧЕ 0
КОНЕЦ КАК ЦенаСредняя
ИЗ
РегистрНакопления.СВХ_ТоварыНаКонсолидационномСкладе.Остатки(&Период, ) КАК Остатки
Такой подход обеспечивает корректное вычисление средней цены (средневзвешенной), даже если товар поступал партиями по разным ценам. Это стандарт индустрии 1С-разработки.
Выясним причину, если вам все же необходимо хранить именно "цену" как таковую (например, прайс-лист или последнюю цену закупки), а не остаток стоимости. В этом случае использование регистра накопления — ошибка архитектуры.
Для хранения неаддитивных характеристик, изменяющихся во времени, предназначены Регистры сведений (периодические). Разберем отличия:
Если ваша задача — знать, по какой цене товар должен списываться, создайте регистр сведений ЦеныНоменклатуры с измерениями Номенклатура и ВидЦены. При проведении документа расхода программа будет обращаться к этому регистру методом СрезПоследних(), получать актуальную цену и умножать её на количество для записи в регистр накопления.
Проанализировав ситуацию, мы видим, что корень проблемы — в смешивании понятий «остаток» и «свойство». Чтобы ваш учет был надежным, придерживайтесь следующих правил:
Контрагент, а списываете без него (или с другим контрагентом), по регистру «зависнет» положительный остаток по одному поставщику и отрицательный по другому.КА 2.4 складские остатки хранятся в регистре ТоварыНаСкладах (только количество), а стоимостная оценка — в регистре СебестоимостьТоваров. Разделение количественного и стоимостного учета позволяет системе работать быстрее и гибче.Применяя эти принципы, вы создадите архитектуру, которая не будет «разваливаться» при сложных операциях перемещения, корректировках цен или возвратах товаров.