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