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